WebSocket 是一种在单个 TCP 连接上进行"全双工"(Full-Duplex)通信的协议。
为了让你更直观地理解,我们可以先用一个生活中的例子来对比 HTTP 和 WebSocket。
1. 通俗易懂的类比
HTTP (传统的 Web 通信)
-
像"发邮件"或"对讲机":
-
客户端(你)发起请求:"服务器,有新消息吗?"
-
服务器回复:"没有。"
-
连接断开。
-
过了一会儿,你又问:"现在有新消息吗?"
-
服务器:"有了,给你。"
-
特点:必须由客户端主动发起,服务器被动回复。每次都要重新建立连接,很麻烦。
-
WebSocket
-
像"打电话":
-
你打给服务器(握手),接通了。
-
电话线一直通着(长连接)。
-
你可以说话,服务器也可以随时插嘴告诉你新消息,不需要你一直问。
-
双方都可以同时说话。
-
特点:连接一旦建立,双方地位平等,服务器可以主动推送数据给客户端。
-
2. WebSocket 是什么?(技术定义)
WebSocket 是一种网络传输协议,位于 OSI 模型的应用层。
-
建立过程 :它借用了 HTTP 协议来"握手"。客户端发送一个特殊的 HTTP 请求(带有
Upgrade: websocket头),告诉服务器:"我们要不要升级成 WebSocket 协议聊天?"如果服务器同意,连接就会从 HTTP 升级为 WebSocket。 -
持久化:握手成功后,TCP 连接不会关闭,而是保持打开状态。
-
协议标识 :HTTP 是
http://,WebSocket 是ws://(加密版是wss://)。
3. 它有什么作用?(核心优势)
WebSocket 解决了 HTTP 协议在实时性方面的巨大缺陷。
在 WebSocket 出现之前,为了实现"即时通讯",开发者通常使用轮询 (Polling):客户端每隔几秒钟问一次服务器"有数据吗?"。这种方式不仅延迟高,而且浪费流量和服务器资源(因为大部分时间都在问"有没有",而答案是"没有")。
WebSocket 的核心作用:
-
服务器主动推送:服务器一旦产生新数据,可以立刻推送到客户端,无需客户端请求。
-
低延迟:省去了 HTTP 建立连接和发送臃肿头部的开销,数据传输极快。
-
减少网络开销:数据包头非常小,适合高频传输。
4. WebSocket vs HTTP 对比表
| 特性 | HTTP | WebSocket |
|---|---|---|
| 通信方向 | 单向(客户端请求 -> 服务器响应) | 双向(全双工,双方均可主动发送) |
| 连接状态 | 无状态,请求结束即断开 | 有状态,连接一直保持直到关闭 |
| 实时性 | 较差(依赖轮询) | 极高 |
| 开销 | 高(每次都要带完整的 HTTP 头) | 低(握手后,数据帧头部很小) |
| 适用场景 | 获取静态资源、普通的 API 调用 | 聊天、实时数据、游戏 |
5. 它的使用场景是什么?
只要场景中包含**"实时"、"即时"、"高频更新"**这些关键词,通常就是 WebSocket 的用武之地:
-
即时通讯 (IM) / 聊天室
- 微信网页版、Slack、客服聊天窗口。你需要立刻看到对方发来的消息。
-
实时数据大屏 / 股票基金
- 金融交易软件、加密货币价格走势。价格每毫秒都在变,不能等用户刷新。
-
多人在线游戏
- 《王者荣耀》、网页版 .io 游戏。你的操作需要立刻同步给其他玩家,延迟必须极低。
-
协同编辑
- 腾讯文档、Google Docs、Notion。当你看到光标在移动,或者别人输入的字实时出现在你屏幕上时,背后通常是 WebSocket。
-
体育赛事直播
- 文字直播比分更新、弹幕系统。
-
系统通知
- 你在浏览网页时,右上角突然弹出"您有一条新消息"。
总结
-
WebSocket 是为了实时通信而生的。
-
它让 Web 拥有了像桌面程序一样即时响应的能力。
-
它打破了"请求-响应"的传统模式,实现了服务器对浏览器的主动推送。
在 Java 中使用 WebSocket,核心在于理解它的生命周期。无论你使用原生 Java (JSR 356)、SSM 还是 Spring Boot,流程本质上是一样的,只是配置方式("粘合剂")不同。
为了让你最快上手,我们先看通用的工作流程 ,然后重点讲解目前最主流的 Spring Boot 写法,最后对比一下 SSM 的区别。
6. WebSocket 的核心工作流程
一个标准的 WebSocket 服务端通常包含 4 个生命周期事件。想象你在接电话:
-
连接建立 (Open):电话接通了。你需要保存这个连接(Session),以便后续能找到人。
-
收到消息 (Message):对方说话了。你处理消息,然后可能回复。
-
发生错误 (Error):信号不好或出错。记录日志。
-
连接关闭 (Close):挂断电话。你需要把这个连接从列表里移除。
7. Spring Boot 中的使用 (最推荐,最常用)
Spring Boot 极大地简化了配置。我们通常使用 注解式 (Annotation) 的方式,这种方式属于 Java 标准 API (JSR 356) 的风格,非常直观。
第一步:引入依赖 (pom.xml)
你需要引入 spring-boot-starter-websocket。
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
第二步:开启 WebSocket 支持 (配置类)
写一个配置类,向 Spring 容器注册 ServerEndpointExporter。它的作用是自动扫描并注册所有带有 @ServerEndpoint 注解的类。
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
第三步:编写核心业务逻辑 (ServerEndpoint)
这是最关键的部分。这就像写一个 Controller,但是处理的是长连接。
java
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ServerEndpoint: 将此类标记为 WebSocket 端点
* "/ws/{userId}": 客户端连接地址,例如 ws://localhost:8080/ws/1001
*/
@Component
@ServerEndpoint("/ws/{userId}")
public class WebSocketServer {
// 静态变量,用来记录当前在线连接数。应该设计为线程安全的。
private static ConcurrentHashMap<String, Session> webSocketMap = new ConcurrentHashMap<>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
private String userId;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
// 加入 Map 中,绑定 userId 和 session,方便后续点对点发送
webSocketMap.put(userId, session);
System.out.println("用户 " + userId + " 已连接,当前在线人数: " + webSocketMap.size());
//以此可以主动给客户端发一条消息
sendMessage("连接成功!欢迎你," + userId);
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到用户 " + userId + " 的消息: " + message);
// 这里可以写逻辑,比如回复消息,或者转发给别人
sendMessage("服务端已收到你的消息:" + message);
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
System.out.println("用户 " + userId + " 退出,当前在线人数: " + webSocketMap.size());
}
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("用户 " + userId + " 发生错误");
error.printStackTrace();
}
/**
* 自定义方法:实现服务器主动推送
*/
public void sendMessage(String message) {
try {
// getBasicRemote().sendText() 是同步发送
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
8. SSM (Spring MVC) 中的使用
在 SSM 这种较老的架构中,使用 @ServerEndpoint 也是可以的,但更常见的是使用 Spring 自己的接口 WebSocketHandler。这需要更多的配置。
主要区别:Spring Boot 是自动装配,SSM 需要手动在 XML 或 Java Config 中注册。
流程:
-
创建处理器 :创建一个类实现
WebSocketHandler接口(或者继承TextWebSocketHandler)。- 这里面没有
@OnOpen这种注解,而是要重写afterConnectionEstablished、handleTextMessage等方法。
- 这里面没有
-
创建拦截器 (可选) :实现
HandshakeInterceptor,用于在握手阶段拦截请求(比如做身份验证)。 -
配置映射:告诉 Spring,哪个 URL 对应哪个处理器。
SSM 配置示例 (Java Config 方式):
Java
@Configuration
@EnableWebSocket // 开启 WebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 将 /ws/myHandler 路径映射到 MyHandler 这个处理器
registry.addHandler(new MyHandler(), "/ws/myHandler")
.addInterceptors(new MyHandshakeInterceptor()) // 添加拦截器
.setAllowedOrigins("*"); // 允许跨域
}
}
总结 SSM vs Spring Boot:
核心逻辑代码(接收消息、发送消息)是一样的。区别在于 SSM 往往需要通过实现 Spring 的接口 (WebSocketHandler) 来写,且配置稍微繁琐;而 Spring Boot 推荐使用标准的 JSR 356 注解 (@ServerEndpoint),配合自动配置,代码更简洁。
9. Java WebSocket 常用语法速查
无论哪个框架,你主要打交道的对象就是 Session。
| 动作 | 代码 (基于 Session 对象) | 说明 |
|---|---|---|
| 发送文本 | session.getBasicRemote().sendText("Hello"); |
同步发送,最常用 |
| 发送对象 | session.getBasicRemote().sendObject(obj); |
需要配置编码器 |
| 获取参数 | @PathParam("id") String id |
在 @OnOpen 中获取 URL 路径参数 |
| 获取 ID | session.getId() |
获取当前连接的唯一标识 |
| 判断状态 | session.isOpen() |
发送前最好检查一下连接是否开着 |
| 关闭连接 | session.close() |
服务端主动断开 |
10. 如何测试?(简单的前端代码)
你可以直接在浏览器的控制台 (F12 -> Console) 或者写一个简单的 HTML 文件来测试你的 Java 后端:
JavaScript
// 1. 建立连接 (注意是 ws 协议)
var ws = new WebSocket("ws://localhost:8080/ws/1001");
// 2. 监听连接成功
ws.onopen = function() {
console.log("连接成功!");
ws.send("你好,我是客户端!"); // 发送消息
};
// 3. 监听收到消息
ws.onmessage = function(evt) {
console.log("收到服务端回复: " + evt.data);
};
// 4. 监听关闭
ws.onclose = function() {
console.log("连接关闭");
};
总结建议
-
如果你在写新项目 :直接使用 Spring Boot +
@ServerEndpoint注解。这是最简单、最符合直觉的方式。 -
如果你在维护 SSM 老项目 :查找项目中是否实现了
WebSocketHandler接口,那是 SSM 的标准做法。 -
进阶 :上述是"原生"WebSocket。如果你要做复杂的聊天室(群聊、点对点、订阅频道),Spring Boot 还封装了 STOMP 协议(基于 WebSocket 的子协议),功能更强大,但上手稍微难一点。