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


    目录

    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
    <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.0modelVersion>
    <groupId>com.rockbb.testgroupId>
    <artifactId>ws-demo01artifactId>
    <packaging>jarpackaging>
    <version>1.0-SNAPSHOTversion>
    <name>WS: Demo 01name>
    <properties>
    <project.jdk.version>17project.jdk.version>
    <project.source.encoding>UTF-8project.source.encoding>
    <spring-boot.version>2.7.11spring-boot.version>
    properties>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-dependenciesartifactId>
    <version>${spring-boot.version}version>
    <type>pomtype>
    <scope>importscope>
    dependency>
    dependencies>
    dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
    dependency>
    <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-websocketartifactId>
    dependency>
    <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-messagingartifactId>
    dependency>
    <dependency>
    <groupId>org.java-websocketgroupId>
    <artifactId>Java-WebSocketartifactId>
    <version>1.5.3version>
    dependency>
    dependencies>
    <build>
    <finalName>ws-demo01finalName>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.pluginsgroupId>
    <artifactId>maven-compiler-pluginartifactId>
    <version>3.10.1version>
    <configuration>
    <source>${project.jdk.version}source>
    <target>${project.jdk.version}target>
    <encoding>${project.source.encoding}encoding>
    configuration>
    plugin>
    <plugin>
    <groupId>org.apache.maven.pluginsgroupId>
    <artifactId>maven-resources-pluginartifactId>
    <version>3.3.0version>
    <configuration>
    <encoding>${project.source.encoding}encoding>
    configuration>
    plugin>
    <plugin>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-maven-pluginartifactId>
    <version>${spring-boot.version}version>
    <executions>
    <execution>
    <goals>
    <goal>repackagegoal>
    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 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 路径, 观察通信的变化情况.

  • 相关阅读:
    Docker(12)CIG容器重量级监控系统
    数据结构第二课 -----线性表之顺序表
    硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战
    打造千万级流量秒杀第十九课 API 设计:如何使用 RESTFul 和 RPC 实现 API ?
    [英雄星球六月集训LeetCode解题日报] 第29日 分治
    深度学习应用篇-计算机视觉-OCR光学字符识别[7]:OCR综述、常用CRNN识别方法、DBNet、CTPN检测方法等、评估指标、应用场景
    java图片转pdf ,pdf 导出
    【网络编程】模仿Wireshark制作的抓包程序
    【linux】线程条件变量 信号量
    C++模板编程(7)---实际运用模板:模板追踪器(tracer)
  • 原文地址:https://www.cnblogs.com/milton/p/17489013.html