WebSocket 详解与实践拓展
WebSocket 作为一种在 TCP 协议之上的应用层协议,解决了 HTTP 协议在实时通信场景中的局限性,为客户端与服务器之间提供了高效的双向全双工通信能力。下面将从核心特性、工作机制、代码实践及拓展应用等方面进行详细阐述。
WebSocket 核心特性
- 双向全双工通信
连接建立后,客户端和服务器可随时向对方发送数据,无需等待对方响应,通信效率远高于 HTTP 的请求 - 响应模式。
- 持久连接
一旦通过握手建立连接,会保持持久化状态,避免 HTTP 每次通信都需重新建立连接的开销。
- 协议对称性
连接建立后,客户端与服务器的通信地位平等,没有严格的 “请求方” 与 “响应方” 之分。
- 多客户端支持
单个服务器可同时接纳多个客户端连接,而客户端通常只连接一个服务器。
WebSocket 工作流程
1. 握手阶段(协议升级)
客户端通过 HTTP 请求发起握手,核心是请求将协议从 HTTP 升级为 WebSocket。
- 客户端请求头关键字段
Upgrade: websocket:声明要升级到 WebSocket 协议
Connection: Upgrade:配合 Upgrade 字段,确认协议升级意图
Sec-WebSocket-Key:随机字符串,用于服务器验证并生成 Sec-WebSocket-Accept
Sec-WebSocket-Version: 13:指定 WebSocket 协议版本(当前主流版本)
- 服务器响应头关键字段
HTTP/1.1 101 Switching Protocols:表示协议切换成功
Sec-WebSocket-Accept:由服务器通过 Sec-WebSocket-Key 计算生成,客户端会验证该值以确认握手成功
2. 数据传输阶段
握手成功后,双方通过 WebSocket 帧格式传输数据,支持文本、二进制等类型。传输过程中可随时发送消息,无需额外的请求头开销。
3. 连接关闭阶段
任意一方可主动关闭连接,关闭时会触发 onclose 事件,并可携带关闭原因(如正常关闭、协议错误等)。
WebSocket 端点与 URI
端点 URI 格式
WebSocket 端点的 URI 格式为:
1 2
| ws://host:port/path?query // 非加密连接 wss://host:port/path?query // 加密连接(基于 TLS/SSL)
|
ws/wss:分别对应非加密和加密的 WebSocket 协议
host:port:服务器地址和端口(默认端口与 HTTP 一致:ws 为 80,wss 为 443)
path?query:资源路径和查询参数,用于标识具体的 WebSocket 端点
前端实现(JavaScript)
通过 WebSocket 对象创建连接并处理通信,核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const ws = new WebSocket("ws://localhost:8080/echo");
ws.onopen = (event) => { console.log("连接已建立,可发送消息"); ws.send("Hello, Server!"); };
ws.onmessage = (event) => { console.log("收到服务器消息:", event.data); };
ws.onerror = (event) => { console.error("连接错误:", event); };
ws.onclose = (event) => { console.log(`连接关闭,代码:${event.code},原因:${event.reason}`); };
|
注意:send 方法需在 onopen 事件触发后调用,否则会失败。
后端实现(Java)
Java 中可通过 JSR-356 规范(javax.websocket API)实现 WebSocket 服务器,需引入依赖:
1 2 3 4 5 6
| <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency>
|
方式 1:编程式端点(继承 Endpoint)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException;
@ServerEndpoint("/test") public class TestWebSocket extends Endpoint {
@Override public void onOpen(Session session, EndpointConfig config) { System.out.println("新客户端连接,会话 ID:" + session.getId());
session.addMessageHandler(new MessageHandler.Whole<String>() { @Override public void onMessage(String message) { System.out.println("收到消息:" + message); try { session.getBasicRemote().sendText("服务器已收到:" + message); } catch (IOException e) { e.printStackTrace(); } } }); }
@Override public void onClose(Session session, CloseReason reason) { System.out.println("连接关闭,原因:" + reason.getReasonPhrase()); }
@Override public void onError(Session session, Throwable throwable) { System.err.println("连接错误:" + throwable.getMessage()); } }
|
方式 2:注解式端点(简化开发)
通过注解直接定义端点行为,无需继承 Endpoint:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException;
@ServerEndpoint("/echo") public class EchoEndpoint {
@OnMessage public void onMessage(Session session, String message) throws IOException { System.out.println("收到消息:" + message); session.getBasicRemote().sendText("回声:" + message); }
@OnOpen public void onOpen(Session session) { System.out.println("客户端连接:" + session.getId()); }
@OnClose public void onClose(Session session, CloseReason reason) { System.out.println("连接关闭:" + reason.getReasonPhrase()); }
@OnError public void onError(Session session, Throwable throwable) { System.err.println("错误:" + throwable.getMessage()); } }
|
WebSocket 与 HTTP 的对比
| 特性 |
WebSocket |
HTTP |
| 连接类型 |
持久连接 |
短连接(每次请求后关闭) |
| 通信方向 |
双向全双工 |
单向(请求 - 响应) |
| 协议头 |
握手阶段使用 HTTP 头,之后无 |
每次请求都带完整头信息 |
| 适用场景 |
实时通信(聊天、监控等) |
普通数据请求(页面加载等) |
| 连接建立开销 |
一次握手,长期复用 |
每次请求都需建立连接 |
常见应用场景
- 实时聊天系统:如网页版即时通讯工具,支持双方实时收发消息。
- 实时数据监控:如股票行情、服务器性能指标实时展示。
- 多人协作工具:如在线文档同时编辑,实时同步内容变更。
- 游戏实时交互:如网页多人游戏,同步玩家操作和状态