我的编程空间,编程开发者的网络收藏夹
学习永远不晚

浅谈WebSocket协议在Web领域的应用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

浅谈WebSocket协议在Web领域的应用

Part 01、WebSocket是什么?  

WebSocket是一种在Web应用程序中提供双向通信的协议。它允许服务器主动向客户端推送数据,而不是像传统的HTTP请求-响应模式一样,客户端必须发送请求才能获取数据。WebSocket 最早是在HTML5中引入的,建立在HTTP协议之上,使用握手阶段来升级连接,然后通过保持连接的状态来实现实时通信。

与传统的HTTP协议相比,WebSocket具有以下优势:

⑴ 增强实时性

服务器可以随时主动给客户端下发数据,相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少。和传统的轮询比较,WebSocket也可以在短时间内更有效率地传递数据;

⑵ 维持连接状态

在一些需要身份认证的场景下,HTTP请求可能需要在每个请求都携带状态信息(服务器不记录每次的请求和响应信息),而WebSocket一次连接建立后就会保持住会话状态,这就使其成为一种有状态的协议,后续通信时就可以省略部分状态信息;

⑶ 更灵活的扩展支持

开发者可以对WebSocket自定义二进制帧,相对HTTP,可以更轻松地处理二进制内容,此外开发者也自行扩展协议、实现部分自定义的子协议;

⑷ 更好的压缩效果

WebSocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。

Part 02、 Websocket报文 

2.1 报文格式

- 第0个字节

0位(FIN):0表示报文没有结束,1表示报文结束。

1-3位(RSV1、RSV2、RSV3):保留字段,一般全部为0。也可用于扩展自己的协议。

4-7位(opcode):报文类型。0 代表一个继续帧,1代表文本帧,2代表二进制帧,8 代表连接关闭,9 代表ping,10代表pong。

- 第1个字节

8位(MASK):1表示需要掩码操作,0表示不需要。如果为1,数据帧的masking-key属性会存在一个值,接收方会利用这个值来进行解掩码操作,所有从客户端传输到服务器的数据帧的Mask都被设置为1。

9-15位(Payload len):表示Payload data的长度。如果值是0~125,则真实长度就是前7位表示;如果值是126,则真实长度就是后16位(Extended payload length 126);如果值是127,则真实长度就是后64位(Extended payload length 127)

2.2 报文样例

- ping帧,Opcode=9,Mask=1,Payload len=0,Masking-Key有内容

图片

- pong帧,Opcode=10,Mask=0,Payload len=0,Masking-Key为空

- 文本帧,Opcode=1,Mask=0,Payload len=84,Masking-Key为空

Part 03、  代码实现  

接下来,一起动手编写WebSocket服务端和客户端,这里提供一个java版的服务端demo和一个html版的客户端demo。

1、服务端demo是一个springboot项目

- 引用spring-boot-starter-websocket依赖



  org.springframework.boot
  spring-boot-starter-websocket

- session管理

public class WsSessionManager {
    public static ConcurrentHashMap SESSION_POOL = new ConcurrentHashMap<>();
    public static void add(String key, WebSocketSession session) {
        SESSION_POOL.put(key, session);
    }
    public static WebSocketSession remove(String key) {
        return SESSION_POOL.remove(key);
    }
    public static void removeAndClose(String key) {
        WebSocketSession session = remove(key);
        if (session != null) {
            try {
                session.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static WebSocketSession get(String key) {
        return SESSION_POOL.get(key);
    }
}

- 消息处理

@Component
@Slf4j
public class MyWsHandler extends AbstractWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("建立ws连接,sessionId:{}", session.getId());
        WsSessionManager.add(session.getId(), session);
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 获得客户端传来的消息
        String payload = message.getPayload();
        log.info("server 接收到消息, sessionId " + session.getId() + ", payload: " + payload);
        session.sendMessage(new TextMessage("server 发送给的消息 " + payload + ",发送时间:" + LocalDateTime.now().toString()));
    }
    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
        log.info("发送二进制消息, sessionId " + session.getId());
    }
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        log.error("异常处理, sessionId " + session.getId());

- WebSocket配置

@Configuration
@EnableWebSocket
public class WsServerConfig implements WebSocketConfigurer {
    @Autowired
    private MyWsHandler myWsHandler;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWsHandler, "myWs")
                //允许跨域
                .setAllowedOrigins("*");
    }
}

2、客户端demo是一个html网页