Java Websocket 01: 原生模式 Websocket 基础通信

打印 上一主题 下一主题

主题 1781|帖子 1781|积分 5343

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录

Websocket 原生模式

原生模式下

  • 服务端通过 @ServerEndpoint 实现其对应的 @OnOpen, @OnClose, @OnMessage, @OnError 方法
  • 客户端创建 WebSocketClient 实现对应的 onOpen(), onClose(), onMessage(), onError()
演示项目

完整示例代码 https://github.com/MiltonLai/websocket-demos/tree/main/ws-demo01
目录结构
  1. │   pom.xml
  2. └───src
  3.     ├───main
  4.     │   ├───java
  5.     │   │   └───com
  6.     │   │       └───rockbb
  7.     │   │           └───test
  8.     │   │               └───wsdemo
  9.     │   │                       SocketServer.java
  10.     │   │                       WebSocketConfig.java
  11.     │   │                       WsDemo01App.java
  12.     │   └───resources
  13.     │           application.yml
  14.     └───test
  15.         └───java
  16.             └───com
  17.                 └───rockbb
  18.                     └───test
  19.                         └───wsdemo
  20.                                 SocketClient.java
复制代码
pom.xml


  • 可以用 JDK11, 也可以用 JDK17
  • 通过 Spring Boot plugin repackage, 生成 fat jar
  • 用 Java-WebSocket 作为 client 的 websocket 实现库, 当前最新版本为 1.5.3
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <modelVersion>4.0.0</modelVersion>
  6.     <groupId>com.rockbb.test</groupId>
  7.     <artifactId>ws-demo01</artifactId>
  8.     <packaging>jar</packaging>
  9.     <version>1.0-SNAPSHOT</version>
  10.     <name>WS: Demo 01</name>
  11.     <properties>
  12.         
  13.         <project.jdk.version>17</project.jdk.version>
  14.         <project.source.encoding>UTF-8</project.source.encoding>
  15.         
  16.         <spring-boot.version>2.7.11</spring-boot.version>
  17.     </properties>
  18.     <dependencyManagement>
  19.         <dependencies>
  20.             
  21.             <dependency>
  22.                 <groupId>org.springframework.boot</groupId>
  23.                 <artifactId>spring-boot-dependencies</artifactId>
  24.                 <version>${spring-boot.version}</version>
  25.                 <type>pom</type>
  26.                 <scope>import</scope>
  27.             </dependency>
  28.         </dependencies>
  29.     </dependencyManagement>
  30.     <dependencies>
  31.         <dependency>
  32.             <groupId>org.springframework.boot</groupId>
  33.             <artifactId>spring-boot-starter-web</artifactId>
  34.         </dependency>
  35.         <dependency>
  36.             <groupId>org.springframework.boot</groupId>
  37.             <artifactId>spring-boot-starter-test</artifactId>
  38.             <scope>test</scope>
  39.         </dependency>
  40.         <dependency>
  41.             <groupId>org.springframework</groupId>
  42.             <artifactId>spring-websocket</artifactId>
  43.         </dependency>
  44.         <dependency>
  45.             <groupId>org.springframework</groupId>
  46.             <artifactId>spring-messaging</artifactId>
  47.         </dependency>
  48.         <dependency>
  49.             <groupId>org.java-websocket</groupId>
  50.             <artifactId>Java-WebSocket</artifactId>
  51.             <version>1.5.3</version>
  52.         </dependency>
  53.     </dependencies>
  54.     <build>
  55.         <finalName>ws-demo01</finalName>
  56.         <plugins>
  57.             <plugin>
  58.                 <groupId>org.apache.maven.plugins</groupId>
  59.                 <artifactId>maven-compiler-plugin</artifactId>
  60.                 <version>3.10.1</version>
  61.                 <configuration>
  62.                     <source>${project.jdk.version}</source>
  63.                     <target>${project.jdk.version}</target>
  64.                     <encoding>${project.source.encoding}</encoding>
  65.                 </configuration>
  66.             </plugin>
  67.             <plugin>
  68.                 <groupId>org.apache.maven.plugins</groupId>
  69.                 <artifactId>maven-resources-plugin</artifactId>
  70.                 <version>3.3.0</version>
  71.                 <configuration>
  72.                     <encoding>${project.source.encoding}</encoding>
  73.                 </configuration>
  74.             </plugin>
  75.             <plugin>
  76.                 <groupId>org.springframework.boot</groupId>
  77.                 <artifactId>spring-boot-maven-plugin</artifactId>
  78.                 <version>${spring-boot.version}</version>
  79.                 <executions>
  80.                     <execution>
  81.                         <goals>
  82.                             <goal>repackage</goal>
  83.                         </goals>
  84.                     </execution>
  85.                 </executions>
  86.             </plugin>
  87.         </plugins>
  88.     </build>
  89. </project>
复制代码
application.yml

设置服务端口为 8763
  1. server:
  2.   port: 8763
  3.   tomcat:
  4.     uri-encoding: UTF-8
  5. spring:
  6.   application:
  7.     name: ws-demo01
复制代码
WsDemo01App.java


  • 将 @RestController 也合并到应用入口了. 和单独拆开做一个 Controller 类是一样的
  • '/msg' 路径用于从 server 往 client 发送消息
  1. @RestController
  2. @SpringBootApplication
  3. public class WsDemo01App {
  4.     public static void main(String[] args) {
  5.         SpringApplication.run(WsDemo01App.class, args);
  6.     }
  7.     @RequestMapping("/msg")
  8.     public String sendMsg(String sessionId, String msg) throws IOException {
  9.         Session session = SocketServer.getSession(sessionId);
  10.         SocketServer.sendMessage(session, msg);
  11.         return "send " + sessionId + " : " + msg;
  12.     }
  13. }
复制代码
WebSocketConfig.java

必须显式声明 ServerEndpointExporter 这个 Bean 才能提供 websocket 服务
  1. @Configuration
  2. public class WebSocketConfig {
  3.     @Bean
  4.     public ServerEndpointExporter initServerEndpointExporter(){
  5.         return new ServerEndpointExporter();
  6.     }
  7. }
复制代码
SocketServer.java

提供 websocket 服务的关键类. @ServerEndpoint 的作用类似于 RestController, 这里指定 client 访问的路径格式为 ws://host:port/websocket/server/[id],
当 client 访问使用不同的 id 时, 会对应产生不同的 SocketServer 实例
  1. @Component
  2. @ServerEndpoint("/websocket/server/{sessionId}")
  3. public class SocketServer {
  4.     private static final org.slf4j.Logger log = LoggerFactory.getLogger(SocketServer.class);
  5.     private static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
  6.     private String sessionId = "";
  7.     @OnOpen
  8.     public void onOpen(Session session, @PathParam("sessionId") String sessionId) {
  9.         this.sessionId = sessionId;
  10.         /* Old connection will be kicked by new connection */
  11.         sessionMap.put(sessionId, session);
  12.         /*
  13.          * this: instance id. New instances will be created for each sessionId
  14.          * sessionId: assigned from path variable
  15.          * session.getId(): the actual session id (start from 0)
  16.          */
  17.         log.info("On open: this{} sessionId {}, actual {}", this, sessionId, session.getId());
  18.     }
  19.     @OnClose
  20.     public void onClose() {
  21.         sessionMap.remove(sessionId);
  22.         log.info("On close: sessionId {}", sessionId);
  23.     }
  24.     @OnMessage
  25.     public void onMessage(String message, Session session) {
  26.         log.info("On message: sessionId {}, {}", session.getId(), message);
  27.     }
  28.     @OnError
  29.     public void onError(Session session, Throwable error) {
  30.         log.error("On error: sessionId {}, {}", session.getId(), error.getMessage());
  31.     }
  32.     public static void sendMessage(Session session, String message) throws IOException {
  33.         session.getBasicRemote().sendText(message);
  34.     }
  35.     public static Session getSession(String sessionId){
  36.         return sessionMap.get(sessionId);
  37.     }
  38. }
复制代码
关于会话对象 Session

OnOpen 会注入一个 Session 参数, 这个是实际的 Websocket Session, 其 ID 是全局唯一的, 可以唯一确定一个客户端连接. 在当前版本的实现中, 这是一个从0开始自增的整数. 如果你需要实现例如单个用户登录多个会话, 在通信中, 将消息转发给同一个用户的多个会话, 就要小心记录这些 Session 的 ID.
  1. @OnOpen
  2. public void onOpen(Session session, @PathParam("sessionId") String sessionId)
复制代码
关于会话意外关闭

在客户端意外停止后, 服务端会收到 OnError 消息, 可以通过这个消息管理已经关闭的会话
SocketClient.java

client 测试类, 连接后可以通过命令行向 server 发送消息
  1. public class SocketClient {
  2.     private static final org.slf4j.Logger log = LoggerFactory.getLogger(SocketClient.class);
  3.     public static void main(String[] args) throws URISyntaxException {
  4.         WebSocketClient wsClient = new WebSocketClient(
  5.                 new URI("ws://127.0.0.1:8763/websocket/server/10001")) {
  6.             @Override
  7.             public void onOpen(ServerHandshake serverHandshake) {
  8.                 log.info("On open: {}, {}", serverHandshake.getHttpStatus(), serverHandshake.getHttpStatusMessage());
  9.             }
  10.             @Override
  11.             public void onMessage(String s) {
  12.                 log.info("On message: {}", s);
  13.             }
  14.             @Override
  15.             public void onClose(int i, String s, boolean b) {
  16.                 log.info("On close: {}, {}, {}", i, s, b);
  17.             }
  18.             @Override
  19.             public void onError(Exception e) {
  20.                 log.info("On error: {}", e.getMessage());
  21.             }
  22.         };
  23.         wsClient.connect();
  24.         log.info("Connecting...");
  25.         while (!ReadyState.OPEN.equals(wsClient.getReadyState())) {
  26.             try {
  27.                 Thread.sleep(100);
  28.             } catch (InterruptedException e) {
  29.                 log.error(e.getMessage(), e);
  30.             }
  31.         }
  32.         log.info("Connected");
  33.         wsClient.send("hello");
  34.         Scanner scanner = new Scanner(System.in);
  35.         while (scanner.hasNext()) {
  36.             String line = scanner.next();
  37.             wsClient.send(line);
  38.         }
  39.         wsClient.close();
  40.     }
  41. }
复制代码
代码的执行过程就是新建一个 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 路径, 观察通信的变化情况.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表