一文说透WebSocket协议(秒懂)
2025-05-16 08:20 阅读(39)

为避免同学们概念混淆,先声明一下,其实WebSocket和Socket之间是毫无关系的,就像北大青鸟和北大一样,大家不要被名字给带偏了。

WebSocket是一种建立在TCP底层连接上,使web客户端和服务器端可进行持续全双工通信的协议。

用大白话来说,WebSocket协议最大的特点是支持服务器端给客户端发送消息。

只需先通过HTTP协议进行握手并进行协议升级,即可让服务器端和客户端一直保持连接并实现通信,直到连接关闭。

如下图所示:


一定会有同学存在疑问,WebSocket协议所具备的“支持服务器端给客户端发送消息”的特点,具体适用场景是什么呢?

下面我们就来详细地讲解一下。

适用场景

对于这个问题,我们有必须逆向思考一下,WebSocket协议所适用的场景,必然是其他协议不适用的场景,这个协议就是HTTP。

由于HTTP协议是半双工模式,只能由客户端发起请求并由服务器端进行响应。

所以在线聊天、实时互动游戏、股票行情、物联网设备监控等业务场景下,只能通过客户端以轮询、长轮询的方式去服务器端获取最新数据。

股票行情场景,如下图所示:



这种方式所带来的问题有两点:

1、客户端频繁发送HTTP请求会带来网络开销,也会给服务器端带来负载压力;2、轮询间隔难以把控,间隔过短同样会带来问题(1)中提到的点,间隔过长会导致数据延迟。

而WebSocket协议只有在服务器端有事件发生的时候,才会第一时间给客户端发送消息,彻底杜绝了HTTP轮询所带来的网络开销、服务器负载和数据时延问题。


实现步骤

阶段一、客户端通过 HTTP 协议发送包含特殊头部的请求,触发协议升级:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13


Upgrade: websocket明确请求升级协议。

Sec-WebSocket-Key:客户端生成的随机字符串,用于安全验证。

Sec-WebSocket-Version:指定协议版本(RFC 6455 规定为 13)。


阶段二、服务器端进行响应确认,返回 101 Switching Protocols 响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=


Sec-WebSocket-Accept:服务器将客户端的 Sec-WebSocket-Key 与固定字符串拼接后,计算 SHA-1 哈希并进行 Base64 编码,生成验证令牌。


阶段三、此时 TCP 连接从 HTTP 升级为 WebSocket 协议,后续数据可通过二进制帧进行传输。

阶段四、数据传输,WebSocket是一种全双工通信协议,客户端与服务端可同时发送/接收数据,无需等待对方请求,数据帧是以二进制格式进行传输的。

如下图所示:


FIN (1 bit):标记是否为消息的最后一个分片。

Opcode (4 bits):定义数据类型(如文本 0x1、二进制 0x2、关闭连接 0x8、Ping 0x9、Pong 0xA)。

Mask (1 bit):客户端发送的数据需掩码处理(防止缓存污染攻击),服务端发送的数据无需掩码。

Payload Length (7 or 7+16 or 7+64 bits):帧内容的长度,支持最大 2^64-1 字节。

Masking-key(32 bits),掩码密钥,由上面的标志位 MASK 决定的,如果使用掩码就是 4 个字节的随机数,否则就不存在。

payload data 字段:这里存放的就是真正要传输的数据


阶段五、连接关闭,客户端或服务器端都可以发起关闭。

示例代码

前端代码:

<!DOCTYPE html>
<html>
<body>
  <input type="text" id="messageInput" placeholder="输入消息">
  <button onclick="sendMessage()">发送</button>
  <div id="messages"></div>
  <script>
    // 创建 WebSocket 连接
    const socket = new WebSocket('ws://localhost:8080/ws');
    // 连接打开时触发
    socket.addEventListener('open', () => {
      logMessage('连接已建立');
    });
    // 接收消息时触发
    socket.addEventListener('message', (event) => {
      logMessage('收到消息: ' + event.data);
    });
    // 连接关闭时触发
    socket.addEventListener('close', () => {
      logMessage('连接已关闭');
    });
    // 错误处理
    socket.addEventListener('error', (error) => {
      logMessage('连接错误: ' + error.message);
    });
    // 发送消息
    function sendMessage() {
      const message = document.getElementById('messageInput').value;
      socket.send(message);
      logMessage('发送消息: ' + message);
    }
    // 日志输出
    function logMessage(message) {
      const messagesDiv = document.getElementById('messages');
      const p = document.createElement('p');
      p.textContent = message;
      messagesDiv.appendChild(p);
    }
  </script>
</body>
</html>


我们通过 Spring WebSocket 来实现服务器端代码。


1、添加 Maven 依赖:


<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
  <version>2.7.14</version>
</dependency>


2、配置类启用 WebSocket:


import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyWebSocketHandler(), "/ws")
                .setAllowedOrigins("*");
    }
}


3、消息处理器实现:


import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class MyWebSocketHandler extends TextWebSocketHandler {
    private static final Set<WebSocketSession> sessions = 
        Collections.synchronizedSet(new HashSet<>());
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        sessions.add(session);
        log("新连接: " + session.getId());
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        String payload = message.getPayload();
        log("收到消息: " + payload);
        // 广播消息
        sessions.forEach(s -> {
            if (s.isOpen() && !s.equals(session)) {
                try {
                    s.sendMessage(new TextMessage("广播: " + payload));
                } catch (Exception e) {
                    log("发送消息失败: " + e.getMessage());
                }
            }
        });
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        sessions.remove(session);
        log("连接关闭: " + session.getId());
    }
    private void log(String message) {
        System.out.println("[MyWebSocketHandler] " + message);
    }
}


结语

在本文中,我们先是对WebSocket协议的概念进行了讲解,也对其适用场景、实现步骤进行描述,最后给出了实例代码,旨在帮助大家一站式熟悉WebSocket协议。


https://www.zuocode.com