什么是 WebSocket
客户端 A 和客户端 B 的消息传播需要借助服务器的中转 (原因是内网不能给另一个局域网的内网直接联通, 需要借助服务器的外网做 “中介”) (NAT 地址转换)
Http 协议 不支持实时通讯 (或者说不支持服务器主动推送数据给客户端)
TCP 本身是具有服务器推送数据这样的功能的, 但是 Http 把它搞丢了 (发明 Http 的大佬们当年所处的时候, 网页就是用来看看新闻, 也没想到后辈能把网页玩出花来)
于是有了 WebSocket 协议.(基于传输层的 TCP 实现) (和 Http 一样, 都是 应用层协议 )
Tomcat 和 Spring 都提供了 WebSocket API, 方便我们通过 WebSocket 协议实现服务器消息推送
Socket 和 WebSocket
Socket 是 OS (或者说传输层) 提供的一组用于网络编程的 API
WebSocket 是一个应用层协议
二者牛马不相及 (没关系)
硬要说有关系, 那就是 WebSocket 的内部实现用到了 Socket
帧格式 (Base Framing Protocol)
FIN : 表示本帧是否是消息的最后一帧, 是1否0
opcode : 解释 payload data 的类型
MASK : 表示 Payload data 是否经过掩码处理
Payload len: 有效载荷长度 ( [0, 125] – 只使用 Payload len空间, [126] – 表示使用 Payload len 和 Extension Payload len 空间, >[127, …] – 使用 Payload len 和 Extension Payload len 和 Extension payLoad len continue 空间) (即不同数值用不同空间来表示载荷的长度)
masking-key : 掩码内容, 当 MASK = 1 时被启用
payload data : 载荷的具体内容
WebSocket 握手过程
握手之前, 客户端和浏览器同为 Http 协议
握手开始, 客户端向服务器发起 Http 请求, 根据请求头里包含的信息, 请求服务器进行协议切换
( Connection: upgrade – 能不能换个协议啊)
( Upgrade: WebSocket – 换成 WebSocket 协议行不行啊)
服务器收到浏览器的请求, 并返回响应, 并切换协议格式
( Connection: upgrade – 能换协议啊)
( Upgrade: WebSocket – 换成 WebSocket 协议 ok 啊)
返回的协议内, 状态码为 101, 明确告诉客户端, 我已经切换协议了, 我们之后用 WebSocket 协议进行通信
WebSocket 和 Http 协议是什么关系?
没关系
Http 就是 工具人
客户端和服务器之间通过 Http 协议建立连接, 连接建立完成之后, 就再也用不到 Http 协议了
WebSocket 在 Spring 中的使用
前端
// 创建 websocket 实例, 或者说进行与服务器建立 WebSocket 连接 // let websocket = new WebSocket("ws://127.0.0.1:8080/WebSocketMessage"); // 操作本地 // let websocket = new WebSocket("ws://152.136.56.110:9090/WebSocketMessage"); //操作远程服务器 let websocket = new WebSocket("ws://" + location.host + "/WebSocketMessage"); //统一格式 websocket.onopen = function() { // 该方法在连接建立完成后, 被自动调用 console.log("websocket 连接成功!"); } websocket.onmessage = function(e) { // 该方法在收到消息时, 被自动调用 console.log("websocket 收到消息! " + e.data); // 处理消息响应 handleMessage(e.data); } websocket.onclose = function() { // 该方法在连接关闭后, 被自动调用 console.log("websocket 连接关闭!"); } websocket.onerror = function() { // 该方法在连接异常时, 被自动调用 console.log("websocket 连接异常!"); }
后端
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
首先是 创建 Handler 对象, 让其 继承 TextWebSocketHandler
然后将 Handler 注册到 实现 WebSocketConfigurer 接口 的 注册类中
对于 Handler 对象, 有四个常用的方法
afterConnectionEstablished : 该方法在 WebSocket 连接建立 之后, 被自动调用
handlerTextMessage : 该方法在 WebSocket 收到消息 的时候, 被自动调用
handlerTransportError : 该方法在连接 出现异常 的时候, 被自动调用
afterConnectionClosed : 该方法在连接 正常关闭 后, 被自动调用
@Component public class TestWebSocketAPI extends TextWebSocketHandler { @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 该方法会在 websocket 连接建立之后, 被自动调用 System.out.println("Test 连接成功!"); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 该方法会在 websocket 收到消息的时候, 被自动调用 System.out.println("Test 收到消息!" + message.toString()); // 这里可以直接用, 是因为在注册器中, 已经把 HttpSession 中的内容给放到 WebSocketSession 中了 User user = session.getAttribute().get("user"); // session 是个会话, 里面记录通信双方的信息 (session 中持有 websocket 的通信连接) session.sendMessage(message); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { // 这个方法实在 连接出现异常的时候, 被自动调用 System.out.println("Test 连接异常!"); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // 这个方法是在连接正常关闭后, 会被自动调用 System.out.println("Test 连接关闭!"); } }
对于注册器, 通过 registerWebSocketHandlers 方法的来注册 Handler (记得给注册器添加注解: @EnableWebSocket)
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private TestWebSocketAPI testWebSocketAPI; @Autowired private WebSocketAPI webSocketAPI; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // 通过本方法, 将创建好的 Handler 类给注册到具体路径上. // 此时浏览器可通过 请求路径, 调用到绑定的 Handler 类. registry.addHandler(testWebSocketAPI, "/test") // 通过注册这个特定的 HttpSession 拦截器, 可以把用户在 // HttpSession 中添加的 Attribute 键值对 // 往 WebSocketSession 中添加一份 .addInterceptors(new HttpSessionHandshakeInterceptor()); } }