1. 引言
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它解决了传统 HTTP 协议「请求-响应」模式下服务器无法主动推送数据的问题。而 WebSocketServer 则是 Java 生态中基于 WebSocket 协议的服务端实现,广泛应用于实时聊天、在线游戏、股票行情推送、协同编辑等场景。
本文将从零开始,带你全面掌握 WebSocketServer 的使用方法,包括环境搭建、核心 API、消息收发、心跳保活、集群部署等实战内容。
1.1 依赖引入
以 Spring Boot 项目为例,在 pom.xml 中引入 WebSocket 相关依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
如果使用纯 Java EE 环境,则需要引入:
xml
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
说明:Spring Boot 的
spring-boot-starter-websocket底层基于 Spring 的WebSocketHandler体系,与 Java EE 的@ServerEndpoint注解方式是两套不同的 API。本文以 Spring 方式为主进行讲解。
1.2 创建 WebSocketServer 类
在 Spring 项目中,我们通过继承 TextWebSocketHandler 来实现服务端逻辑:
java
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.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WebSocketServer extends TextWebSocketHandler {
// 用于保存所有连接的会话,key 为 sessionId
private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
SESSIONS.put(session.getId(), session);
System.out.println("新连接加入:" + session.getId() + ",当前在线数:" + SESSIONS.size());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("收到消息:" + message.getPayload());
// 广播给所有客户端
for (WebSocketSession s : SESSIONS.values()) {
if (s.isOpen()) {
s.sendMessage(message);
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
SESSIONS.remove(session.getId());
System.out.println("连接关闭:" + session.getId() + ",当前在线数:" + SESSIONS.size());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
System.err.println("连接错误:" + session.getId() + ",错误信息:" + exception.getMessage());
}
}
2. 核心回调方法与生命周期
在 Spring 的 WebSocketHandler 体系中,核心在于四个回调方法,它们对应连接的不同阶段:
| 回调方法 | 触发时机 | 典型用途 |
|---|---|---|
afterConnectionEstablished |
客户端建立连接时 | 初始化资源、记录会话 |
handleTextMessage |
收到客户端文本消息时 | 处理业务逻辑、转发消息 |
afterConnectionClosed |
连接关闭时 | 释放资源、清理会话 |
handleTransportError |
发生异常时 | 记录日志、容错处理 |
2.1 会话对象 WebSocketSession
WebSocketSession 是 Spring WebSocket 连接的核心抽象,常用方法包括:
java
// 发送文本消息
session.sendMessage(new TextMessage("Hello"));
// 发送二进制消息
session.sendMessage(new BinaryMessage(ByteBuffer.wrap(data)));
// 获取会话属性(握手时通过拦截器写入)
Object userId = session.getAttributes().get("userId");
// 关闭连接
session.close();
2.2 注解方式与回调方式的区别
你可能会疑惑:为什么本文没有讲 @ServerEndpoint、@OnOpen、@OnMessage 这些注解?这是因为它们属于 Java EE(JSR-356) 的 WebSocket 规范,而 Spring 项目通常使用自己的 WebSocketHandler 体系(即上文继承 TextWebSocketHandler 的方式),两者是两套不同的 API,不能混用。
| 对比项 | Java EE 注解方式 | Spring 回调方式 |
|---|---|---|
| 核心注解/类 | @ServerEndpoint、@OnOpen、@OnMessage、@OnClose、@OnError |
继承 TextWebSocketHandler,重写回调方法 |
| 会话对象 | javax.websocket.Session |
org.springframework.web.socket.WebSocketSession |
| 注册方式 | 容器自动扫描 @ServerEndpoint |
通过 WebSocketConfigurer 手动注册 |
| 适用场景 | 纯 Java EE / 非 Spring 项目 | Spring / Spring Boot 项目 |
如果你在纯 Java EE 环境中使用注解方式,代码大致如下:
java
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/websocket")
public class AnnotationWebSocketServer {
@OnOpen
public void onOpen(Session session) {
System.out.println("新连接加入:" + session.getId());
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
session.getBasicRemote().sendText("收到:" + message);
}
@OnClose
public void onClose(Session session) {
System.out.println("连接关闭:" + session.getId());
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
}
提示:在 Spring Boot 项目中,如果确实想用注解方式,也可以引入
spring-boot-starter-websocket后直接使用@ServerEndpoint,但需要额外配置ServerEndpointExporter才能被容器识别。不过更推荐使用 Spring 原生的WebSocketHandler方式,便于与 Spring 生态(拦截器、鉴权、依赖注入)无缝集成。
3. 消息收发实战
3.1 点对点消息
java
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
// 解析目标用户 ID(可从消息体或 session 属性中获取)
String targetUserId = parseTargetUserId(message.getPayload());
WebSocketSession targetSession = findSessionByUserId(targetUserId);
if (targetSession != null && targetSession.isOpen()) {
targetSession.sendMessage(message);
}
}
3.2 广播消息
java
public static void broadcast(String message) {
TextMessage textMessage = new TextMessage(message);
for (WebSocketSession session : SESSIONS.values()) {
try {
if (session.isOpen()) {
session.sendMessage(textMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3 发送 JSON 消息
java
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
// 使用 Jackson 或 Gson 解析 JSON
JsonNode jsonNode = objectMapper.readTree(message.getPayload());
String type = jsonNode.get("type").asText();
if ("chat".equals(type)) {
// 构造响应 JSON
Map<String, Object> response = new HashMap<>();
response.put("type", "chat");
response.put("from", jsonNode.get("from").asText());
response.put("content", jsonNode.get("content").asText());
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
4. 心跳保活机制
WebSocket 连接可能因网络波动而断开,需要心跳机制来维持连接并检测异常:
java
@Override
public void afterConnectionEstablished(WebSocketSession session) {
SESSIONS.put(session.getId(), session);
// 启动心跳检测定时任务
startHeartbeat(session);
}
private void startHeartbeat(WebSocketSession session) {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
try {
if (session.isOpen()) {
session.sendMessage(new TextMessage("{\"type\":\"ping\"}"));
}
} catch (IOException e) {
// 发送失败,说明连接已断开
try {
session.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}, 0, 30, TimeUnit.SECONDS);
}
客户端收到 ping 后应回复 pong,服务端在 handleTextMessage 中处理:
java
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
if ("pong".equals(message.getPayload())) {
// 更新最后活跃时间
lastActiveTime.put(session.getId(), System.currentTimeMillis());
}
}
5. 配置与部署
5.1 Spring Boot 配置
在 Spring Boot 中,通过实现 WebSocketConfigurer 注册 WebSocket 处理器:
java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketServer(), "/websocket")
.addInterceptors(new WebSocketInterceptor())
.setAllowedOrigins("*");
}
}
5.2 拦截器配置
java
public class WebSocketInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
// 从请求中获取 token 进行鉴权
String token = request.getHeaders().getFirst("token");
if (token == null || !validateToken(token)) {
return false;
}
attributes.put("userId", getUserIdFromToken(token));
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
// 握手完成后的处理
}
}
6. 常见问题与优化
6.1 连接数限制
默认情况下,Tomcat 对 WebSocket 连接数有限制,可通过配置调整:
properties
# application.properties
server.tomcat.max-threads=200
server.tomcat.max-connections=10000
6.2 消息大小限制
properties
# 设置最大消息大小(字节)
server.tomcat.websocket.max-text-message-size=1048576
server.tomcat.websocket.max-binary-message-size=1048576
6.3 集群部署
单机部署无法支撑大规模连接,集群部署时需要引入消息中间件(如 Redis Pub/Sub、RabbitMQ)实现跨节点消息转发:
java
public class ClusterMessageHandler {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void sendToAll(String message) {
// 发布到 Redis 频道
redisTemplate.convertAndSend("websocket:channel", message);
}
@EventListener
public void onMessage(RedisMessageEvent event) {
// 收到其他节点的消息,广播给本机客户端
broadcast(event.getMessage());
}
}
8. 总结
本文详细介绍了 WebSocketServer 的使用方法,从环境搭建、核心注解、消息收发到心跳保活、集群部署,覆盖了实际开发中的主要场景。掌握这些内容后,你就能构建出稳定可靠的实时通信应用。
在实际项目中,还需要根据业务场景进行更多优化,比如消息压缩、断线重连、消息持久化等。希望本文能为你提供有价值的参考。