WebSocket 是实现客户端与服务端全双工双向通信的关键技术,彻底解决了传统 HTTP 协议 "请求 - 响应" 模式下无法主动推送消息的问题。本文会从核心概念、工作原理到 Java 实战,帮你从零掌握 WebSocket 的使用。
一、先搞懂:WebSocket 是什么?
1.1 核心定义
WebSocket 是一种基于 TCP 的应用层协议,能在单个 TCP 连接上建立全双工通信(客户端和服务端可同时发消息,像打电话一样),而 HTTP 是半双工(客户端发请求,服务端才响应,像对讲机)。
1.2 为什么需要 WebSocket?
传统 HTTP 实现 "实时通信" 的痛点:
- 轮询:客户端频繁发请求问服务端 "有没有新消息",浪费带宽和服务器资源;
- 长轮询:服务端 hold 住请求直到有数据,仍有连接开销;
- WebSocket:一次握手建立连接,永久保持(除非主动断开),双向实时传数据,开销极低。
1.3 WebSocket 核心特点
- 基于 TCP,与 HTTP 兼容(握手阶段用 HTTP);
- 全双工通信,实时性强;
- 轻量级协议,数据帧格式简单;
- 支持跨域,可通过
CORS配置; - 常用场景:聊天系统、实时监控、弹幕、股票行情等。
二、WebSocket 工作流程(通俗版)
- 握手阶段 :客户端通过 HTTP 请求(带
Upgrade: websocket头)向服务端发起 "升级连接" 请求; - 建立连接:服务端同意升级后,HTTP 连接转为 WebSocket 长连接;
- 双向通信:连接建立后,客户端 / 服务端可随时发消息,无需重复握手;
- 断开连接:任意一方主动关闭,或连接超时 / 异常时断开。
三、Java 实现 WebSocket 实战(基于 Spring Boot)
Spring Boot 对 WebSocket 有成熟的封装,无需手动处理底层协议,是新手最易上手的方式。
3.1 环境准备
3.1.1 引入 Maven 依赖
在 pom.xml 中添加 WebSocket 核心依赖:
XML
<!-- Spring Boot WebSocket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<!-- 无需指定版本,Spring Boot 父工程已管理 -->
</dependency>
<!-- Web 依赖(用于测试页面) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.1.2 环境要求
- JDK 8 及以上;
- Spring Boot 2.x/3.x(本文以 3.2.x 为例);
- 任意 IDE(IDEA/Eclipse)。
3.2 核心代码实现
步骤 1:配置 WebSocket 开启
创建配置类,启用 WebSocket 并注册处理器:
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Configuration
@EnableWebSocket // 开启 WebSocket 支持
public class WebSocketConfig implements WebSocketConfigurer {
// 注入自定义的 WebSocket 处理器
private final MyWebSocketHandler myWebSocketHandler;
// 构造器注入(Spring 自动装配)
public WebSocketConfig(MyWebSocketHandler myWebSocketHandler) {
this.myWebSocketHandler = myWebSocketHandler;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册处理器:指定访问路径 + 允许跨域
registry.addHandler(myWebSocketHandler, "/ws/chat")
.setAllowedOrigins("*"); // 开发环境允许所有跨域,生产需指定具体域名
}
}
步骤 2:实现 WebSocket 处理器(核心)
处理器负责处理 "连接建立、接收消息、发送消息、连接关闭" 等核心逻辑:
java
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自定义 WebSocket 处理器
* 处理客户端的连接、消息收发、连接关闭
*/
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {
// 存储在线会话(ConcurrentHashMap 保证线程安全)
private static final ConcurrentHashMap<String, WebSocketSession> ONLINE_SESSIONS = new ConcurrentHashMap<>();
/**
* 连接建立成功触发
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 获取客户端唯一标识(这里用 sessionId,实际可替换为用户ID)
String sessionId = session.getId();
ONLINE_SESSIONS.put(sessionId, session);
System.out.println("客户端[" + sessionId + "]已连接,当前在线数:" + ONLINE_SESSIONS.size());
// 给客户端发送欢迎消息
sendMessageToSingle(session, "恭喜,WebSocket 连接成功!");
}
/**
* 接收客户端消息触发
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String sessionId = session.getId();
String receivedMsg = message.getPayload(); // 获取客户端发送的消息
System.out.println("收到客户端[" + sessionId + "]的消息:" + receivedMsg);
// 场景1:给发送者回显消息
sendMessageToSingle(session, "服务端已收到你的消息:" + receivedMsg);
// 场景2:广播消息(发给所有在线客户端)
broadcastMessage("客户端[" + sessionId + "]:" + receivedMsg);
}
/**
* 连接关闭触发
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String sessionId = session.getId();
ONLINE_SESSIONS.remove(sessionId);
System.out.println("客户端[" + sessionId + "]已断开连接,当前在线数:" + ONLINE_SESSIONS.size());
// 广播下线通知
broadcastMessage("客户端[" + sessionId + "]已离开");
}
/**
* 发生异常触发
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
String sessionId = session.getId();
System.out.println("客户端[" + sessionId + "]连接异常:" + exception.getMessage());
if (session.isOpen()) {
session.close(); // 异常时关闭连接
}
ONLINE_SESSIONS.remove(sessionId);
}
// ========== 工具方法:发送消息 ==========
/**
* 给单个客户端发消息
*/
private void sendMessageToSingle(WebSocketSession session, String msg) throws IOException {
if (session.isOpen()) { // 确保连接未关闭
session.sendMessage(new TextMessage(msg));
}
}
/**
* 广播消息(发给所有在线客户端)
*/
private void broadcastMessage(String msg) {
// 遍历所有在线会话,逐个发送
ONLINE_SESSIONS.forEach((sessionId, session) -> {
try {
sendMessageToSingle(session, msg);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
步骤 3:编写测试页面(前端)
在 resources/static 目录下创建 index.html(Spring Boot 会自动映射静态资源),用于测试 WebSocket 通信:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket 测试</title>
<style>
#msgBox { width: 400px; height: 300px; border: 1px solid #ccc; padding: 10px; overflow-y: auto; margin-bottom: 10px; }
#msgInput { width: 300px; padding: 5px; }
button { padding: 5px 10px; }
</style>
</head>
<body>
<div id="msgBox"></div>
<input type="text" id="msgInput" placeholder="请输入消息">
<button onclick="sendMsg()">发送消息</button>
<button onclick="closeConn()">关闭连接</button>
<script>
let ws = null;
// 1. 建立连接
function connect() {
// 判断浏览器是否支持 WebSocket
if (!window.WebSocket) {
alert("你的浏览器不支持 WebSocket!");
return;
}
// 连接服务端(ws://对应http,wss://对应https)
ws = new WebSocket("ws://localhost:8080/ws/chat");
// 连接成功回调
ws.onopen = function() {
appendMsg("WebSocket 连接成功!");
};
// 接收服务端消息回调
ws.onmessage = function(event) {
appendMsg("服务端:" + event.data);
};
// 连接关闭回调
ws.onclose = function() {
appendMsg("WebSocket 连接已关闭!");
};
// 连接异常回调
ws.onerror = function(error) {
appendMsg("连接异常:" + error);
};
}
// 2. 发送消息
function sendMsg() {
const input = document.getElementById("msgInput");
const msg = input.value.trim();
if (!msg || !ws) {
alert("请先连接且输入消息!");
return;
}
ws.send(msg); // 发送消息到服务端
appendMsg("我:" + msg);
input.value = ""; // 清空输入框
}
// 3. 关闭连接
function closeConn() {
if (ws) {
ws.close();
ws = null;
}
}
// 辅助:追加消息到页面
function appendMsg(msg) {
const msgBox = document.getElementById("msgBox");
msgBox.innerHTML += msg + "<br>";
// 滚动到底部
msgBox.scrollTop = msgBox.scrollHeight;
}
// 页面加载时自动连接
window.onload = connect;
</script>
</body>
</html>
3.3 运行测试
- 启动 Spring Boot 项目(默认端口 8080);
- 打开浏览器访问
http://localhost:8080/index.html; - 测试操作:
- 页面加载后自动建立连接,消息框显示 "WebSocket 连接成功";
- 输入消息点击 "发送",服务端接收后会回显 + 广播;
- 打开多个浏览器标签页,可看到广播消息(所有页面都能收到);
- 点击 "关闭连接",服务端会提示客户端断开。
四、核心知识点解析
4.1 关键类 / 接口
@EnableWebSocket:开启 Spring Boot 的 WebSocket 支持;WebSocketHandler:核心处理器接口,TextWebSocketHandler是其文本消息实现类;WebSocketSession:代表一个客户端连接会话,包含连接状态、发送消息等方法;ConcurrentHashMap:存储在线会话,保证多线程下的线程安全(WebSocket 是多线程处理)。
4.2 核心方法
| 方法 | 作用 |
|---|---|
afterConnectionEstablished |
客户端连接成功后触发 |
handleTextMessage |
接收客户端文本消息时触发 |
afterConnectionClosed |
客户端断开连接后触发 |
session.sendMessage() |
服务端给客户端发消息 |
4.3 常见问题与解决方案
-
跨域问题 :生产环境不要用
setAllowedOrigins("*"),需指定具体域名(如http://localhost:8080); -
连接断开 :可在前端加 "重连机制",监听
onclose后定时重试连接; -
消息大小限制 :默认 WebSocket 消息大小有限制,可在配置类中设置:
javaregistry.addHandler(...) .setAllowedOrigins("*") .setMessageSizeLimit(1024 * 1024); // 限制消息最大1MB
五、进阶方向
- 点对点通信 :结合用户认证(如 Token),将
sessionId替换为用户 ID,实现 "指定用户发消息"; - STOMP 协议:基于 WebSocket 的高级协议,支持 "订阅 - 发布" 模式(如按房间 / 主题收发消息);
- 分布式 WebSocket:集群环境下,需结合 Redis 发布订阅实现跨节点消息同步;
- 心跳检测:客户端 / 服务端定时发心跳包,检测无效连接并自动清理。
总结
- 核心概念:WebSocket 是全双工通信协议,通过一次握手建立长连接,解决 HTTP 实时通信痛点;
- Java 实现 :Spring Boot 中通过
@EnableWebSocket开启支持,自定义TextWebSocketHandler处理核心通信逻辑; - 关键要点 :会话管理需保证线程安全(用
ConcurrentHashMap),消息收发要判断连接状态,生产环境需处理跨域和重连。
通过本文的实战代码,你可以快速搭建一个基础的 WebSocket 通信系统,在此基础上扩展聊天、实时监控等业务功能。