引用:万字详解,带你彻底掌握 WebSocket 用法(至尊典藏版)写的不错
本文为笔者阅读上面博主文章的笔记,大家可以直接去阅读原文
黑马苍穹外卖的websocket代码
package com.sky;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching //开启缓存注解
@EnableScheduling //开启任务调度注解
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
一、 简介
1.1 什么是 WebSocket
WebSocket 是一种网络通信协议,设计用于在 Web 应用程序(客户端,通常是浏览器)和服务器之间建立全双工、低延迟、持久化的通信通道。与传统基于请求/响应模式的 HTTP 不同,WebSocket 在单个 TCP 连接上实现了双向实时数据流。该协议最初由 W3C 和 IETF 共同开发,其核心标准 RFC 6455 于 2011 年正式发布。
1.2 WebSocket 的优势和劣势
优势:
实时性: 建立连接后,数据可以在客户端和服务器之间即时、主动推送,无需客户端轮询(Polling),非常适合需要低延迟的应用(如聊天、实时游戏、金融行情)。
全双工通信: 支持同时双向数据传输。服务器可以主动向客户端推送消息,客户端也可以随时发送消息给服务器,通信效率更高。
降低网络开销:
减少 HTTP 请求头: 连接建立后,后续数据传输仅需携带少量协议开销(通常仅 2-14 字节的帧头),避免了 HTTP 请求/响应中庞大的头部信息。
避免轮询开销: 消除了客户端为获取更新而不断发送 HTTP 请求(及其头部)的需要。
持久化连接: 单个 TCP 连接在整个会话期间保持打开状态,避免了为每个消息建立新连接的开销(如 HTTP 短连接)。
支持二进制和文本数据: 原生支持高效传输二进制数据(如图片、音频、视频帧)和文本数据。
劣势:
兼容性要求: 需要浏览器和服务器同时支持 WebSocket 协议。虽然现代浏览器和主流后端框架/语言都普遍支持,但一些非常旧的浏览器或特定环境(如受限的嵌入式设备)可能不支持或支持不完善。
服务器资源消耗: 维护大量长时间存活的连接会消耗服务器的内存和 CPU 资源(连接状态管理、心跳保活等),对服务器架构(如连接管理、水平扩展)提出了更高要求。
复杂性增加: 相比简单的 HTTP 请求/响应,实现健壮、安全的 WebSocket 应用需要处理更多方面,如连接管理、错误处理、心跳机制、消息分帧、重连逻辑等。
代理和防火墙穿透: 某些配置严格的网络代理或防火墙可能阻止非 HTTP 流量(WebSocket 握手后升级为独立协议),需要特殊处理(如 WSS - WebSocket Secure 通常更容易穿透)。
安全考量:
未经请求的数据推送: 服务器主动推送的能力需要谨慎设计,确保只向授权的客户端发送数据,防止信息泄露或被恶意利用。
协议升级攻击: WebSocket 握手是基于 HTTP 的升级机制,需要防范相关的协议升级攻击。
跨域问题: 同源策略 (Same-Origin Policy) 同样适用于 WebSocket 连接,建立跨域连接需要服务器显式配置 CORS (通过
Origin
头验证) 或使用代理。
无状态协议上的状态管理: WebSocket 连接本身是有状态的(持久的),但构建在其上的应用协议通常需要自行管理应用层的会话状态。
二、 WebSocket 的基本概念
2.1 WebSocket 的协议
基础: WebSocket 是一个独立的、基于 TCP 的应用层协议(协议标识符
ws://
或加密的wss://
)。握手: 连接始于一个标准的 HTTP 升级请求。客户端发送一个包含
Upgrade: websocket
和Connection: Upgrade
等特定头部的 HTTP 请求。服务器响应一个状态码101 Switching Protocols
和相应的确认头部,完成协议升级。握手阶段协商协议版本、子协议 (可选)、扩展 (可选) 等。数据帧: 握手成功后,通信切换到 WebSocket 协议帧格式。数据被封装在帧 (Frames) 中传输,帧头较小(最小 2 字节),包含操作码 (Opcode - 标识帧类型:文本、二进制、控制帧等)、掩码 (客户端到服务器必须掩码)、负载长度等信息。这种轻量级帧结构是高效传输的关键。
双向通信: 连接建立后,客户端和服务器可以随时、独立地发送数据帧给对方,无需等待请求。支持文本帧 (UTF-8 编码) 和二进制帧。
控制帧: 除了承载应用数据的帧,协议定义了控制帧用于管理连接,如
Ping
(探测连接活性/心跳)、Pong
(响应 Ping)、Close
(优雅关闭连接)。
2.2 WebSocket 的生命周期
一个 WebSocket 连接的生命周期包含以下关键阶段:
连接建立阶段 (Connection Establishment / Opening Handshake):
客户端发起一个带有特定 WebSocket 头部的 HTTP
GET
请求(升级请求)。服务器验证请求,如果接受,则发送 HTTP
101 Switching Protocols
响应。TCP 连接成功升级为 WebSocket 连接。此阶段完成。
连接开放阶段 (Connection Open / Data Exchange):
连接已成功建立并处于活动状态。
客户端和服务器可以随时、双向地通过发送 WebSocket 数据帧(文本或二进制)进行应用数据的交换。
控制帧(如
Ping
/Pong
)也在此阶段用于连接保活和管理。
连接关闭阶段 (Connection Closing / Closing Handshake):
连接的关闭可以由任一方(客户端或服务器)主动发起,或由于底层网络故障被动触发。
主动关闭方发送一个
Close
控制帧,包含一个可选的关闭状态码和原因短语。接收方收到
Close
帧后,必须回应一个Close
帧作为确认(如果它尚未发送自己的Close
帧)。双方在发送并接收到
Close
帧后,会认为 WebSocket 连接已进入关闭流程,并准备关闭底层的 TCP 连接。
连接关闭完成阶段 (Connection Closed):
底层 TCP 连接被终止。
此时,WebSocket 连接已完全结束。任何尝试通过此连接发送或接收数据的操作都将失败或产生错误。
资源(如内存中的连接状态对象)应被释放。
Client->>Server: HTTP Upgrade 请求 (WebSocket 握手) Server-->>Client: 101 Switching Protocols (握手响应) Client->>Server: 双向实时数据传输 (文本/二进制) Server->>Client: 主动推送数据
在这个示意图中,客户端向服务器发送一个 WebSocket 握手请求,服务器响应一个握手响应,连接就被建立了。一旦连接建立,客户端和服务器就可以在连接上互相发送数据,直到其中一方发送一个关闭帧来关闭连接。在关闭帧被接收后,连接就会被关闭,WebSocket 连接关闭完成。
2.3 WebSocket的消息格式
字段 | 说明 |
---|---|
FIN (1bit) | 标记是否为完整消息 |
Opcode (4bit) | 消息类型(1=文本,2=二进制,8=关闭,9=Ping,10=Pong) |
Mask (1bit) | 是否加密(客户端→服务端必须为1) |
Payload Len | 数据长度(7bit/16bit/64bit) |
Masking Key | 加密密钥(当 Mask=1 时存在) |
Payload Data | 实际数据 |
2.4 核心 API
// 创建连接
const ws = new WebSocket("wss://api.example.com/ws");
// 事件监听
ws.onopen = () => console.log("Connected!");
ws.onmessage = (e) => console.log("Received:", e.data);
ws.onclose = () => console.log("Connection closed");
// 发送数据
ws.send("Hello Server!");
// 关闭连接
ws.close(1000, "Normal closure");
三、Java 中的 WebSocket 实践
3.1 Java WebSocket 服务端
依赖:
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
服务端代码:
@ServerEndpoint("/echo")
public class EchoServer {
@OnOpen
public void onOpen(Session session) {
System.out.println("Client connected: " + session.getId());
}
@OnMessage
public void onMessage(String msg, Session session) throws IOException {
System.out.println("Received: " + msg);
session.getBasicRemote().sendText("Echo: " + msg);
}
@OnClose
public void onClose(Session session) {
System.out.println("Connection closed");
}
}
3.2 Java WebSocket 客户端
@ClientEndpoint
public class EchoClient {
private Session session;
@OnOpen
public void onOpen(Session session) {
this.session = session;
}
public void connect(String url) throws Exception {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.connectToServer(this, new URI(url));
}
public void send(String msg) throws IOException {
session.getBasicRemote().sendText(msg);
}
}
3.3 Spring Boot 集成 WebSocket
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置类:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketHandler(), "/ws")
.setAllowedOrigins("*");
}
}
处理器:
public class WebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) {
System.out.println("New connection: " + session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
session.sendMessage(new TextMessage("Server: " + message.getPayload()));
}
}
四、消息格式详解
4.1 文本 vs 二进制消息
// 发送文本消息
session.getBasicRemote().sendText("Hello World!");
// 发送二进制消息
byte[] data = Files.readAllBytes(Paths.get("image.png"));
ByteBuffer buffer = ByteBuffer.wrap(data);
session.getBasicRemote().sendBinary(buffer);
4.2 Ping/Pong 心跳机制
// 服务端发送 Ping
ByteBuffer pingData = ByteBuffer.wrap(new byte[]{1, 2, 3});
session.getBasicRemote().sendPing(pingData);
// 客户端响应 Pong
@OnMessage
public void onPong(PongMessage pong) {
System.out.println("Received Pong: " + pong.getApplicationData());
}
4.3 关闭连接
// 正常关闭(状态码 1000)
session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Bye!"));
// 异常关闭处理
@OnClose
public void onClose(CloseReason reason) {
System.out.println("Closed: " + reason.getReasonPhrase());
}
五、性能优化策略
5.1 WebSocket vs HTTP 性能对比
指标 | WebSocket | HTTP 轮询 |
---|---|---|
连接开销 | 1 个 TCP 连接 | N 个 TCP 连接 |
头部开销 | 首次握手后无头部 | 每次请求携带头部 |
延迟 | 毫秒级 | 秒级 |
服务器压力 | 低(长连接) | 高(频繁建连) |
5.2 优化建议
压缩消息:对文本/二进制数据启用压缩
批处理:合并小消息为批量传输
CDN 加速:静态资源就近分发
负载均衡:Nginx 反向代理多节点
异步 I/O:避免阻塞线程(如 Netty 实现)
WebSocket 已成为实时通信领域的核心技术。本文从协议原理到代码实践,从性能优化到未来演进,系统性地解析了其完整技术栈。建议收藏本文作为工具手册,随时查阅关键知识点。如有疑问欢迎评论区交流!