凌峰创科服务平台

Java如何实现WebSocket服务器端?

WebSocket 核心概念

在开始编码前,理解几个基本概念很重要:

Java如何实现WebSocket服务器端?-图1
(图片来源网络,侵删)
  • WebSocket 协议:一种在单个 TCP 连接上进行全双工通信的协议,它允许服务器主动向客户端推送数据,解决了传统 HTTP 协议只能由客户端发起请求的局限性。
  • 握手:WebSocket 连接的建立过程,客户端发送一个特殊的 HTTP 请求,包含 Upgrade: websocketConnection: Upgrade 头,服务器如果支持,会返回一个 101 Switching Protocols 响应,此后连接就升级为 WebSocket 连接,双方就可以自由地双向传输数据了。
  • 会话:一个 Session 对象代表一个活跃的 WebSocket 连接,它包含了连接信息、允许你发送消息、关闭连接等。
  • 端点:服务器端处理 WebSocket 逻辑的类,通常是一个 Java 类,使用注解来定义其行为。

主流 Java WebSocket 库

Java 生态中有几个成熟的 WebSocket 库,各有特点:

  1. Jakarta WebSocket (JSR-356 / Java EE / Jakarta EE)

    • 描述:这是 Java 官方的 WebSocket API 标准,如果你使用的是 Java EE (如 WildFly, Payara) 或 Jakarta EE (如 WildFly, Tomcat 10+) 应用服务器,它通常是内置支持的。
    • 优点:标准化,代码可移植性好,与 Jakarta EE/Jakarta EE 容器集成紧密。
    • 缺点:在独立的 Java 应用(如 Spring Boot)中需要额外引入依赖。
    • 注解@ServerEndpoint, @OnOpen, @OnClose, @OnMessage, @OnError
  2. Spring Framework WebSocket

    • 描述:Spring 框架提供的 WebSocket 支持,与 Spring MVC 和 Spring Boot 深度集成。
    • 优点:功能强大,配置灵活,与 Spring 生态无缝集成(如 Security, STOMP 消息代理),非常适合构建复杂的、企业级的应用。
    • 缺点:学习曲线相对陡峭,概念比标准 API 更多(如 WebSocketHandler, SockJS, STOMP)。
    • 使用方式:通常通过 @Configuration 类和 @EnableWebSocket 注解来配置。
  3. Netty

    Java如何实现WebSocket服务器端?-图2
    (图片来源网络,侵删)
    • 描述:一个异步事件驱动的网络应用框架,性能极高,你可以用它来构建自定义的 WebSocket 服务器。
    • 优点:性能卓越,功能极其灵活,可以构建定制化的网络协议。
    • 缺点:API 相对底层,使用复杂,不适合快速开发。

选择建议

  • 快速入门 & 简单应用:推荐 Jakarta WebSocket (JSR-356),注解清晰直观。
  • Spring Boot 项目:强烈推荐 Spring WebSocket,因为它能最好地融入 Spring 生态。
  • 高性能 & 定制化:考虑 Netty

本指南将重点讲解最常用的 Jakarta WebSocket (JSR-356)Spring WebSocket 两种方式。


实现方式一:使用 Jakarta WebSocket (JSR-356)

这种方式非常直接,适合独立的 Java 项目或运行在支持该标准的 Web 容器中。

步骤 1: 添加依赖

如果你使用 Maven (pom.xml):

Java如何实现WebSocket服务器端?-图3
(图片来源网络,侵删)
<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-web-api</artifactId>
    <version>10.0.0</version> <!-- 使用与你的服务器/环境匹配的版本 -->
    <scope>provided</scope> <!-- 如果部署在 Tomcat/WildFly 等容器中,通常不需要打包进去 -->
</dependency>

如果你使用 Gradle (build.gradle):

implementation 'jakarta.platform:jakarta.jakartaee-web-api:10.0.0' // providedCompile

注意:如果你想在 Spring Boot 中使用,并且不想依赖 provided 的容器 API,可以使用 jakarta.websocket-api 这个依赖。

步骤 2: 创建 WebSocket 端点类

创建一个 Java 类,并使用 @ServerEndpoint 注解来定义 WebSocket 端点。

import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@ServerEndpoint("/websocket/chat") // 定义 WebSocket 的访问路径
public class ChatEndpoint {
    // 使用一个静态的 Set 来存储所有活跃的会话,实现广播功能
    private static final Set<Session> chatroomUsers = Collections.synchronizedSet(new HashSet<>());
    // 当新的 WebSocket 连接建立时被调用
    @OnOpen
    public void onOpen(Session session) {
        chatroomUsers.add(session);
        System.out.println("新连接加入: " + session.getId());
        broadcast("用户 " + session.getId() + " 已加入聊天室,当前在线人数: " + chatroomUsers.size());
    }
    // 当收到客户端消息时被调用
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自 " + session.getId() + " 的消息: " + message);
        // 广播消息给所有用户
        broadcast("用户 " + session.getId() + ": " + message);
    }
    // 当连接关闭时被调用
    @OnClose
    public void onClose(Session session) {
        chatroomUsers.remove(session);
        System.out.println("连接关闭: " + session.getId());
        broadcast("用户 " + session.getId() + " 已离开聊天室,当前在线人数: " + chatroomUsers.size());
    }
    // 当连接发生错误时被调用
    @OnError
    public void onError(Throwable error, Session session) {
        System.out.println("连接发生错误: " + session.getId());
        error.printStackTrace();
        chatroomUsers.remove(session);
    }
    // 广播消息的辅助方法
    private void broadcast(String message) {
        chatroomUsers.forEach(session -> {
            try {
                // getBasicRemote() 是同步的,对于大量用户可能性能不佳
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
                // 如果发送失败,可能连接已断开,从集合中移除
                chatroomUsers.remove(session);
            }
        });
    }
}

步骤 3: 部署到服务器

将你的应用打包成 WAR 文件,然后部署到支持 WebSocket 的服务器上,如 Tomcat 9+WildFly


实现方式二:使用 Spring WebSocket

这种方式在 Spring Boot 项目中非常流行,提供了更高级的抽象和集成。

步骤 1: 添加依赖

对于 Spring Boot 3.x,spring-boot-starter-web 通常已经包含了所需的核心依赖,如果需要明确添加 WebSocket 支持:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

步骤 2: 配置 WebSocket

创建一个配置类来注册和启用 WebSocket。

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
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册一个自定义的 WebSocket 处理器
        // "/websocket/chat" 是 WebSocket 的访问路径
        // 允许所有来源的连接,生产环境需要设置 allowedOrigins
        registry.addHandler(new MyWebSocketHandler(), "/websocket/chat")
                .setAllowedOrigins("*"); // 允许跨域
    }
}

步骤 3: 创建 WebSocket 处理器

这个类继承自 TextWebSocketHandler,并重写核心方法。

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.Collections;
import java.util.HashSet;
import java.util.Set;
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {
    private static final Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
        System.out.println("新连接建立: " + session.getId());
        // 广播新用户加入的消息
        broadcast("用户 " + session.getId() + " 已加入聊天室,当前在线人数: " + sessions.size());
    }
    @Override
    protected void handleTextMessage
分享:
扫描分享到社交APP
上一篇
下一篇