马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
目录
Websocket 原生模式
原生模式下
- 服务端通过 @ServerEndpoint 实现其对应的 @OnOpen, @OnClose, @OnMessage, @OnError 方法
- 客户端创建 WebSocketClient 实现对应的 onOpen(), onClose(), onMessage(), onError()
演示项目
完整示例代码 https://github.com/MiltonLai/websocket-demos/tree/main/ws-demo01
目录结构
- │ pom.xml
- └───src
- ├───main
- │ ├───java
- │ │ └───com
- │ │ └───rockbb
- │ │ └───test
- │ │ └───wsdemo
- │ │ SocketServer.java
- │ │ WebSocketConfig.java
- │ │ WsDemo01App.java
- │ └───resources
- │ application.yml
- └───test
- └───java
- └───com
- └───rockbb
- └───test
- └───wsdemo
- SocketClient.java
复制代码 pom.xml
- 可以用 JDK11, 也可以用 JDK17
- 通过 Spring Boot plugin repackage, 生成 fat jar
- 用 Java-WebSocket 作为 client 的 websocket 实现库, 当前最新版本为 1.5.3
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.rockbb.test</groupId>
- <artifactId>ws-demo01</artifactId>
- <packaging>jar</packaging>
- <version>1.0-SNAPSHOT</version>
- <name>WS: Demo 01</name>
- <properties>
-
- <project.jdk.version>17</project.jdk.version>
- <project.source.encoding>UTF-8</project.source.encoding>
-
- <spring-boot.version>2.7.11</spring-boot.version>
- </properties>
- <dependencyManagement>
- <dependencies>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-dependencies</artifactId>
- <version>${spring-boot.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-websocket</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-messaging</artifactId>
- </dependency>
- <dependency>
- <groupId>org.java-websocket</groupId>
- <artifactId>Java-WebSocket</artifactId>
- <version>1.5.3</version>
- </dependency>
- </dependencies>
- <build>
- <finalName>ws-demo01</finalName>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>3.10.1</version>
- <configuration>
- <source>${project.jdk.version}</source>
- <target>${project.jdk.version}</target>
- <encoding>${project.source.encoding}</encoding>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-resources-plugin</artifactId>
- <version>3.3.0</version>
- <configuration>
- <encoding>${project.source.encoding}</encoding>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <version>${spring-boot.version}</version>
- <executions>
- <execution>
- <goals>
- <goal>repackage</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </project>
复制代码 application.yml
设置服务端口为 8763- server:
- port: 8763
- tomcat:
- uri-encoding: UTF-8
- spring:
- application:
- name: ws-demo01
复制代码 WsDemo01App.java
- 将 @RestController 也合并到应用入口了. 和单独拆开做一个 Controller 类是一样的
- '/msg' 路径用于从 server 往 client 发送消息
- @RestController
- @SpringBootApplication
- public class WsDemo01App {
- public static void main(String[] args) {
- SpringApplication.run(WsDemo01App.class, args);
- }
- @RequestMapping("/msg")
- public String sendMsg(String sessionId, String msg) throws IOException {
- Session session = SocketServer.getSession(sessionId);
- SocketServer.sendMessage(session, msg);
- return "send " + sessionId + " : " + msg;
- }
- }
复制代码 WebSocketConfig.java
必须显式声明 ServerEndpointExporter 这个 Bean 才能提供 websocket 服务- @Configuration
- public class WebSocketConfig {
- @Bean
- public ServerEndpointExporter initServerEndpointExporter(){
- return new ServerEndpointExporter();
- }
- }
复制代码 SocketServer.java
提供 websocket 服务的关键类. @ServerEndpoint 的作用类似于 RestController, 这里指定 client 访问的路径格式为 ws://host:port/websocket/server/[id],
当 client 访问使用不同的 id 时, 会对应产生不同的 SocketServer 实例- @Component
- @ServerEndpoint("/websocket/server/{sessionId}")
- public class SocketServer {
- private static final org.slf4j.Logger log = LoggerFactory.getLogger(SocketServer.class);
- private static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
- private String sessionId = "";
- @OnOpen
- public void onOpen(Session session, @PathParam("sessionId") String sessionId) {
- this.sessionId = sessionId;
- /* Old connection will be kicked by new connection */
- sessionMap.put(sessionId, session);
- /*
- * this: instance id. New instances will be created for each sessionId
- * sessionId: assigned from path variable
- * session.getId(): the actual session id (start from 0)
- */
- log.info("On open: this{} sessionId {}, actual {}", this, sessionId, session.getId());
- }
- @OnClose
- public void onClose() {
- sessionMap.remove(sessionId);
- log.info("On close: sessionId {}", sessionId);
- }
- @OnMessage
- public void onMessage(String message, Session session) {
- log.info("On message: sessionId {}, {}", session.getId(), message);
- }
- @OnError
- public void onError(Session session, Throwable error) {
- log.error("On error: sessionId {}, {}", session.getId(), error.getMessage());
- }
- public static void sendMessage(Session session, String message) throws IOException {
- session.getBasicRemote().sendText(message);
- }
- public static Session getSession(String sessionId){
- return sessionMap.get(sessionId);
- }
- }
复制代码 关于会话对象 Session
OnOpen 会注入一个 Session 参数, 这个是实际的 Websocket Session, 其 ID 是全局唯一的, 可以唯一确定一个客户端连接. 在当前版本的实现中, 这是一个从0开始自增的整数. 如果你需要实现例如单个用户登录多个会话, 在通信中, 将消息转发给同一个用户的多个会话, 就要小心记录这些 Session 的 ID.- @OnOpen
- public void onOpen(Session session, @PathParam("sessionId") String sessionId)
复制代码 关于会话意外关闭
在客户端意外停止后, 服务端会收到 OnError 消息, 可以通过这个消息管理已经关闭的会话
SocketClient.java
client 测试类, 连接后可以通过命令行向 server 发送消息- public class SocketClient {
- private static final org.slf4j.Logger log = LoggerFactory.getLogger(SocketClient.class);
- public static void main(String[] args) throws URISyntaxException {
- WebSocketClient wsClient = new WebSocketClient(
- new URI("ws://127.0.0.1:8763/websocket/server/10001")) {
- @Override
- public void onOpen(ServerHandshake serverHandshake) {
- log.info("On open: {}, {}", serverHandshake.getHttpStatus(), serverHandshake.getHttpStatusMessage());
- }
- @Override
- public void onMessage(String s) {
- log.info("On message: {}", s);
- }
- @Override
- public void onClose(int i, String s, boolean b) {
- log.info("On close: {}, {}, {}", i, s, b);
- }
- @Override
- public void onError(Exception e) {
- log.info("On error: {}", e.getMessage());
- }
- };
- wsClient.connect();
- log.info("Connecting...");
- while (!ReadyState.OPEN.equals(wsClient.getReadyState())) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- log.error(e.getMessage(), e);
- }
- }
- log.info("Connected");
- wsClient.send("hello");
- Scanner scanner = new Scanner(System.in);
- while (scanner.hasNext()) {
- String line = scanner.next();
- wsClient.send(line);
- }
- wsClient.close();
- }
- }
复制代码 代码的执行过程就是新建一个 WebSocketClient 并实现其处理消息的接口方法, 使用 10001 作为 sessionId 进行连接, 在连接成功后, 不断读取键盘输入 (System.in), 将输入的字符串发送给服务端.
运行示例
示例是一个普通的 Spring Boot jar项目, 可以通过mvn clean package进行编译, 再通过java -jar ws-demo01.jar运行, 启动后工作在8763端口
然后运行 SocketClient.java, 可以观察到服务端接收到的消息.
服务端可以通过浏览器访问 http://127.0.0.1:8763/msg?sessionId=10001&msg=123 向客户端发送消息.
结论
以上说明并演示了原生的 Websocket 实现方式, 可以尝试运行多个 SocketClient, 使用相同或不同的 server sessionId 路径, 观察通信的变化情况.
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |