WebSocket 是一种在单个 TCP 连接上实现全双工通信的协议,旨在解决传统 HTTP 协议在实时通信场景中的局限性。
1、消息推送方式
1.1 轮询(Polling)
- 特点:
- 客户端定期向服务器发送请求,询问是否有新消息。
- 实现简单,但效率较低,因为即使在没有新消息的情况下,客户端也会频繁发送请求,浪费网络资源。
- 适用场景:
- 对实时性要求不高的应用,或者作为临时解决方案。
1.2 长轮询(Long Polling)
- 特点:
- 客户端向服务器发送请求后,服务器会保持连接打开,直到有新消息或超时为止。
- 一旦有新消息,服务器立即响应并发送数据给客户端,然后客户端关闭连接并重新发起一个新的请求。
- 相比轮询,长轮询减少了客户端的请求次数,提高了效率。
- 适用场景:
- 需要一定实时性,但不想使用WebSocket等更复杂技术的场景。
1.3 WebSocket
- 特点:
- 提供了全双工的通信通道,允许服务器和客户端在任意时刻发起消息传递。
- 适用于实时通信和推送场景,如聊天应用、实时通知等。
- 支持文本和二进制数据格式,具有更好的性能和可扩展性。
- 适用场景:
- 需要高实时性和双向通信的应用,如在线游戏、实时协作工具等。
1.4 SSE(Server-Sent Events)
- 特点:
- 一种允许服务器向客户端推送实时数据的技术,但仅支持从服务器到客户端的单向通信。
- 客户端通过HTTP GET请求建立连接,服务器可以通过该连接持续向客户端发送事件流。
- 实现简单,适用于需要实时更新但不需要客户端响应的场景。
- 适用场景:
- 如股票价格更新、社交媒体通知等需要实时数据更新的应用。
2、与 HTTP 的对比
- HTTP 的局限性:
- 单向通信:客户端发起请求,服务器响应后连接关闭。
- 高开销:频繁的请求-响应模式导致头部重复传输。
- 延迟高:无法实现服务器主动推送数据(需依赖轮询或长轮询)。
- WebSocket 的优势:
- 全双工通信:客户端和服务器可同时发送数据。
- 低开销:连接持久化,数据帧头部仅 2~10 字节。
- 低延迟:建立连接后,数据实时传输。
3、工作原理
- 握手阶段(Handshake)
- 客户端发起请求:通过 HTTP 协议发送升级请求:
GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
- 服务器响应:返回状态码 101 Switching Protocols 完成协议升级:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- 客户端发起请求:通过 HTTP 协议发送升级请求:
- 数据传输
- 握手后,连接保持打开,双方通过数据帧(Frame)通信。
- 支持文本(UTF-8)和二进制数据格式。
- 通过 opcode 标识数据类型(如 0x1 表示文本,0x2 表示二进制)。
4、关键特性
- 持久连接:无需重复握手,减少延迟。
- 跨域支持:通过 Origin 头实现安全跨域通信。
- 心跳机制:通过 Ping/Pong 帧检测连接状态。
- 协议扩展:支持压缩(如 permessage-deflate)等扩展功能。
5、应用场景
- 实时通信:聊天应用(如 Slack)、在线客服。
- 高频数据更新:股票行情、实时游戏(如多人对战)。
- 协同工具:在线文档编辑(如 Google Docs)、远程控制。
- IoT 设备:传感器数据实时监控。
6、实现示例
Java原生代码实现websocket
- 依赖:
<dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.5.3</version> </dependency>
- 服务端
package com.example.helloword; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class ChatServer extends WebSocketServer { private static final int PORT = 8888; private final Set<WebSocket> connections = Collections.synchronizedSet(new HashSet<>()); public ChatServer() { super(new InetSocketAddress(PORT)); } @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { connections.add(conn); System.out.println("新客户端连接: " + conn.getRemoteSocketAddress()); broadcast("用户[" + conn.getRemoteSocketAddress() + "] 进入聊天室"); } @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { connections.remove(conn); System.out.println("客户端断开: " + conn.getRemoteSocketAddress()); broadcast("用户[" + conn.getRemoteSocketAddress() + "] 离开聊天室"); } @Override public void onMessage(WebSocket conn, String message) { System.out.println("收到消息: " + message); broadcast("用户[" + conn.getRemoteSocketAddress() + "]: " + message); } @Override public void onError(WebSocket conn, Exception ex) { System.err.println("发生错误: " + ex.getMessage()); } @Override public void onStart() { System.out.println("Server started!"); } public static void main(String[] args) { ChatServer server = new ChatServer(); server.start(); System.out.println("服务端已启动,监听端口: " + PORT); } }
- 客户端
package com.example.helloword; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.net.URI; import java.util.Scanner; public class ChatClient extends WebSocketClient { public ChatClient(URI serverUri) { super(serverUri); } @Override public void onOpen(ServerHandshake handshakedata) { System.out.println("已连接到服务端"); } @Override public void onMessage(String message) { System.out.println(message); // 接收服务端广播的消息 } @Override public void onClose(int code, String reason, boolean remote) { System.out.println("连接关闭: " + reason); } @Override public void onError(Exception ex) { System.err.println("发生错误: " + ex.getMessage()); } public static void main(String[] args) throws Exception { ChatClient client = new ChatClient(new URI("ws://localhost:8888")); client.connect(); // 读取控制台输入并发送消息 Scanner scanner = new Scanner(System.in); System.out.println("输入消息(输入 'exit' 退出):"); String message; while (!(message = scanner.nextLine()).equalsIgnoreCase("exit")) { client.send(message); } client.close(); } }
7、注意事项
安全性:
使用 wss://(WebSocket over TLS)加密通信。
验证 Origin 头防止跨站劫持(CSRF)。
兼容性:现代浏览器均支持,但需考虑旧版本回退方案(如长轮询)。
资源管理:长时间连接可能占用服务器资源,需合理设计连接生命周期。
总结
WebSocket 通过高效的双向通信机制,成为实时应用的理想选择。其设计在减少网络开销的同时,提供了灵活的扩展能力,适用于从即时通讯到物联网的广泛场景。开发者需结合具体需求权衡其优缺点,并注意安全与兼容性实践。