WebSocketServer 使用详解:从入门到实战

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 的使用方法,从环境搭建、核心注解、消息收发到心跳保活、集群部署,覆盖了实际开发中的主要场景。掌握这些内容后,你就能构建出稳定可靠的实时通信应用。

在实际项目中,还需要根据业务场景进行更多优化,比如消息压缩、断线重连、消息持久化等。希望本文能为你提供有价值的参考。

相关推荐
随便做点啥2 小时前
32卡64G-910B4-16后端集群部署报告
java·大数据·运维
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之字段验证与自定义验证注解实战
java·算法·spring
CodeStats2 小时前
【Java 类加载器】Java 类加载器完整体系深度拆解(上):从 JVM 启动到双亲委派模型
java·开发语言·jvm·classloader·双亲委派
带多刺的玫瑰2 小时前
Leecode#29刷题之两数相除
java·python·算法
LuTshoes3 小时前
spring ai 实战-MCP
java·人工智能·spring
CodeStats3 小时前
【Java类加载器】Java 类加载器完整体系深度拆解(下):自定义加载器实战与 SPI 破坏双亲委派(MySQL 驱动揭秘)
java·开发语言·jvm·classloader·双亲委派
dishugj3 小时前
操作系统四大特征|软考架构师考点梳理
java·linux·服务器
砚底藏山河3 小时前
并发与限频工程:把20只的2秒压到0.5秒不封号(魔码量化实战 #03)
java·开发语言·数据库·python·金融
ly76893 小时前
Spring WebFlux背压机制在生产环境的调优与陷阱
java·spring·microsoft·webflux·project reactor·背压·响应式流