如何使用Java WebSocket API实现客户端和服务器端的通信?_java websocket 服务端和客户端
WebSocket相关的方法主要涉及客户端和服务器端,以下是对其几个关键方法之间关联以及参数传递方式的介绍:
服务器端
Java中通常使用Jakarta WebSocket API(以前是Java EE的一部分,现在Jakarta EE 继续维护 )来实现WebSocket服务器端功能,常用注解有@ServerEndpoint
, 关键方法包括:
onOpen(Session session)
:当客户端与服务器成功建立WebSocket连接时,该方法会被调用。- 方法关联:它是连接建立后的第一个触发方法,为后续的数据交互等操作做准备。通过它可以获取到代表该连接的
Session
对象,后续对连接的操作,如发送消息等,都依赖于这个Session
。 - 参数传递:
Session
参数由WebSocket容器在连接建立时自动创建并传递,它包含了与该连接相关的各种信息,如远程地址、请求头、发送消息的方法等。例如,你可以通过session.getRemoteAddress()
获取客户端的远程地址。
- 方法关联:它是连接建立后的第一个触发方法,为后续的数据交互等操作做准备。通过它可以获取到代表该连接的
onMessage(String message, Session session)
:当服务器接收到客户端发送的文本消息时会调用此方法(如果处理二进制消息,有对应的onMessage(byte[] message, Session session)
方法 )。- 方法关联:它依赖于
onOpen
方法建立的连接,只有在连接成功建立后,才会接收并处理客户端发送的消息。在处理完消息后,也可以通过传入的Session
对象向客户端发送响应消息。 - 参数传递:
message
参数是客户端实际发送的文本内容,由WebSocket容器从网络传输数据中解析出来并传递。session
依然是代表该连接的会话对象,与onOpen
方法中的session
是同一个实例,用于在处理消息过程中操作连接,比如session.getBasicRemote().sendText(\"Response message\");
向客户端发送响应文本消息。
- 方法关联:它依赖于
onClose(Session session, CloseReason closeReason)
:当WebSocket连接关闭时调用,无论是正常关闭还是异常关闭。- 方法关联:它是连接生命周期的最后阶段,在连接关闭时触发,用于进行一些资源清理、状态更新等操作。
- 参数传递:
session
是即将关闭的连接会话对象。closeReason
由WebSocket容器根据连接关闭的情况生成并传递,包含了关闭的状态码和关闭原因描述,比如closeReason.getCloseCode().getCode()
可以获取关闭状态码,closeReason.getReasonPhrase()
获取关闭原因描述。
onError(Session session, Throwable throwable)
:当WebSocket连接在处理过程中发生错误时调用。- 方法关联:它与其他方法在异常情况下产生关联,一旦在消息处理、连接建立等过程中出现异常,就会触发此方法。可以在该方法中记录错误日志,根据情况关闭连接等。
- 参数传递:
session
是出现错误的连接会话对象。throwable
是具体的异常对象,包含了异常类型和堆栈信息等,通过throwable.printStackTrace()
可以打印出异常堆栈信息以便排查问题。
客户端
使用 StandardWebSocketClient
等类来实现WebSocket客户端功能,关键方法有:
connectToServer(Endpoint endpoint, URI uri)
:用于发起WebSocket连接。- 方法关联:它是客户端建立连接的入口方法,后续的消息接收、发送等操作都依赖于成功建立的连接。
- 参数传递:
endpoint
是一个实现了Endpoint
接口的类实例,包含了处理连接的回调方法,如onOpen
、onMessage
等。uri
是要连接的WebSocket服务器的地址,格式为ws://
或wss://
开头。例如:
WebSocketContainer container = ContainerProvider.getWebSocketContainer();MyEndpoint myEndpoint = new MyEndpoint();URI serverUri = new URI(\"ws://localhost:8080/your - websocket - path\");container.connectToServer(myEndpoint, serverUri);
其中 MyEndpoint
是自定义的实现了 Endpoint
接口的类。
sendText(String text)
(在Session
对象上调用 ):用于向服务器发送文本消息。- 方法关联:依赖于
connectToServer
方法成功建立的连接,只有在连接建立后才能发送消息。 - 参数传递:
text
是要发送的文本内容,由客户端程序指定,然后通过WebSocket协议发送到服务器端。
- 方法关联:依赖于
通过这些方法的相互协作,实现了WebSocket的全双工通信功能,不同方法之间通过 Session
等对象以及参数传递来协同工作。
以下是基于该API的详细实现步骤,包含服务器端和客户端的代码示例及核心原理说明。
一、服务器端实现(核心步骤)
服务器端需要定义一个WebSocket端点(Endpoint),用于处理客户端的连接、消息接收、连接关闭等事件。主要通过注解或继承Endpoint
类实现,推荐使用注解方式(更简洁)。
1. 依赖准备
若使用Maven,需添加Java EE WebSocket依赖(Java 7+支持,Java EE 7及以上内置):
<dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency>或<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency>
2. 定义WebSocket端点类
通过@ServerEndpoint
注解标记端点,并指定访问路径(如/chat
),同时实现核心事件方法:
import javax.websocket.*;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;@ServerEndpoint(\"/chat\") // 客户端连接的URL:ws://localhost:8080/项目名/chatpublic class ChatServer { // 存储所有连接的客户端会话(线程安全集合) private static CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>(); private Session session; // 当前客户端的会话 /** * 客户端连接成功时触发 */ @OnOpen public void onOpen(Session session) { this.session = session; sessions.add(session); // 将新会话加入集合 System.out.println(\"新客户端连接,当前在线:\" + sessions.size()); } /** * 收到客户端消息时触发 */ @OnMessage public void onMessage(String message, Session session) throws IOException { System.out.println(\"收到消息:\" + message + \"(来自会话:\" + session.getId() + \")\"); // 广播消息给所有在线客户端 for (Session s : sessions) { if (s.isOpen()) { // 确保会话未关闭 s.getBasicRemote().sendText(\"服务器转发:\" + message); // 同步发送消息 // s.getAsyncRemote().sendText(\"异步发送:\" + message); // 异步发送(非阻塞) } } } /** * 客户端连接关闭时触发 */ @OnClose public void onClose(Session session, CloseReason reason) { sessions.remove(session); // 从集合中移除会话 System.out.println(\"客户端断开连接,原因:\" + reason.getReasonPhrase() + \",当前在线:\" + sessions.size()); } /** * 通信发生错误时触发 */ @OnError public void onError(Session session, Throwable error) { System.out.println(\"发生错误:\"); error.printStackTrace(); }}
二、客户端实现(Java客户端)
Java客户端可通过WebSocketContainer
创建连接,与服务器端通信。需注意:客户端API在Java EE中也有定义,或可使用第三方库(如Tyrus,JSR 356的参考实现)。
1. 客户端依赖(若需独立运行)
若使用Tyrus作为客户端(适合非Web容器环境),添加Maven依赖:
<dependency> <groupId>org.glassfish.tyrus.bundles</groupId> <artifactId>tyrus-standalone-client</artifactId> <version>1.17</version></dependency>
2. 客户端代码实现
import javax.websocket.*;import java.net.URI;@ClientEndpoint // 标记为客户端端点public class ChatClient { private Session session; // 与服务器的会话 /** * 连接服务器 */ public void connect(String serverUri) throws Exception { WebSocketContainer container = ContainerProvider.getWebSocketContainer(); container.connectToServer(this, new URI(serverUri)); // 连接到服务器端点 } /** * 连接成功时触发 */ @OnOpen public void onOpen(Session session) { this.session = session; System.out.println(\"与服务器连接成功,会话ID:\" + session.getId()); try { session.getBasicRemote().sendText(\"客户端已连接\"); // 向服务器发送消息 } catch (IOException e) { e.printStackTrace(); } } /** * 收到服务器消息时触发 */ @OnMessage public void onMessage(String message, Session session) { System.out.println(\"收到服务器消息:\" + message); // 可在此处处理消息(如回复、业务逻辑) } /** * 连接关闭时触发 */ @OnClose public void onClose(Session session, CloseReason reason) { System.out.println(\"与服务器断开连接,原因:\" + reason.getReasonPhrase()); } /** * 发生错误时触发 */ @OnError public void onError(Session session, Throwable error) { System.out.println(\"客户端错误:\"); error.printStackTrace(); } /** * 向服务器发送消息 */ public void sendMessage(String message) throws IOException { if (session != null && session.isOpen()) { session.getBasicRemote().sendText(message); } } // 测试客户端 public static void main(String[] args) throws Exception { ChatClient client = new ChatClient(); client.connect(\"ws://localhost:8080/your-project/chat\"); // 服务器端点URL // 模拟发送消息 client.sendMessage(\"Hello, Server!\"); // 保持连接(实际应用中需根据业务处理) Thread.sleep(30000); }}
三、核心概念与参数传递说明
-
Session(会话)
- 服务器端和客户端均通过
Session
对象标识一个连接,包含连接的元数据(如ID、是否打开)。 - 消息发送通过
Session
的getBasicRemote()
(同步)或getAsyncRemote()
(异步)实现。
- 服务器端和客户端均通过
-
消息传递
- 服务器端
@OnMessage
方法接收客户端消息,参数message
为消息内容(支持String
、byte[]
、ByteBuffer
等类型)。 - 客户端通过
ws.send()
(JS)或session.getBasicRemote().sendText()
(Java)发送消息,服务器端接收后可转发给其他客户端(如示例中的广播逻辑)。
- 服务器端
-
生命周期方法关联
@OnOpen
→ 连接建立时触发,Session
对象在此处初始化并加入管理集合。@OnMessage
→ 依赖@OnOpen
中初始化的Session
进行消息转发。@OnClose
/@OnError
→ 清理Session
资源,确保集合中只保留活跃连接。
四、部署与测试
- 服务器端部署到支持WebSocket的容器(如Tomcat 8+、Jetty 9+),启动后访问
ws://localhost:8080/项目名/chat
。 - 运行Java客户端或浏览器客户端,输入消息即可实现双向通信。
五:WebsocketConfig.java配置
package com.yourcompany.knowledge.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;import org.springframework.boot.web.servlet.server.ServletWebServerFactory;import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configurationpublic class WebSocketConfig { /** * 让Spring管理@ServerEndpoint,实现WebSocket服务端自动注册 */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } @Bean public ServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(); }}
说明:
- 只要有这个配置,Spring Boot 会自动扫描并注册所有
@ServerEndpoint
注解的类为 WebSocket 服务端。 - 适用于内嵌 Tomcat、Jetty、Undertow 等主流 Spring Boot 容器。
- 不需要实现
WebSocketConfigurer
或加@EnableWebSocket
,也不需要注册 handler。
只需保证
- pom.xml 有
spring-boot-starter-websocket
依赖(你已补充)。 - WebSocketConfig 有
ServerEndpointExporter
Bean(如上)。 - 你的
@ServerEndpoint
类加了@Component
或被 Spring 扫描到。
如下的websocketConfig.java配置:
@Configuration@EnableWebSocket // 启用WebSocket支持public class WebSocketConfig implements WebSocketConfigurer { private final YourWebSocketHandler webSocketHandler; private final HandshakeInterceptor handshakeInterceptor; public WebSocketConfig(YourWebSocketHandler webSocketHandler, HandshakeInterceptor handshakeInterceptor) { this.webSocketHandler = webSocketHandler; this.handshakeInterceptor = handshakeInterceptor; } @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketHandler, \"/ws\") .setAllowedOrigins(\"*\") .addInterceptors(handshakeInterceptor); }}
@EnableWebSocket
(是 Spring WebSocket API 方式)。
你的这段代码是**Spring WebSocket(基于Spring WebSocket API)**的标准配置方式,和 @ServerEndpoint
方式属于两种不同的 WebSocket 服务端实现风格。它们各有优缺点,适用场景如下:
主要特性
- 使用
@EnableWebSocket
和实现WebSocketConfigurer
,通过 Java 配置注册 WebSocket 端点。 - 端点
/ws
由YourWebSocketHandler
处理(实现WebSocketHandler
接口)。 - 支持自定义握手拦截器(如 token 校验、用户认证等)。
- 支持灵活的跨域配置。
与 @ServerEndpoint
方式的对比
@ServerEndpoint
- 支持
@OnOpen
、@OnMessage
等注解- 适合与 Java EE 生态集成(如 JSR 356)
- 不易与 Spring Security、Session、依赖注入深度集成
WebSocketHandler
,更易与 Spring Security、Session、依赖注入集成- 支持拦截器、消息分发、STOMP 协议等高级特性
- 代码更灵活,适合复杂业务和权限控制
- 没有
@OnOpen
、@OnMessage
注解,需实现接口方法参考价值
- 如果需要更灵活的权限控制、与 Spring Security 深度集成、或需要 STOMP、SockJS 等高级特性,推荐用这种方式。
- 如果只需要简单的 WebSocket 服务端,
@ServerEndpoint
方式更简单直接。 - 两种方式不能混用同一个端点路径,但可以共存于同一个项目(不同路径)。
什么时候用这种配置方式?
- 需要拦截器(如 token 校验、用户认证)
- 需要和 Spring Security、Session、Bean 注入深度集成
- 需要 STOMP、消息广播、订阅等高级特性
- 需要更灵活的消息处理逻辑
总结
- 如果当前项目对权限、认证、消息分发有更高要求,建议采用这种方式
- 如果只需简单的 WebSocket 服务端,
@ServerEndpoint
方式即可。
如需将现有项目切换为 Spring WebSocket API 方式,或需要集成拦截器、权限校验等,Spring Boot 内嵌 Tomcat + @ServerEndpoint
或 Spring WebSocket 配置,已经支持 WebSocket 服务端功能。
- 只要 pom.xml 里有 spring-boot-starter-web(或 spring-boot-starter-websocket),就能让前端/客户端通过 ws:// 协议访问你的服务端接口。
- 只需要保证端口、防火墙、跨域等网络层面