您的位置:首页 > 财经 >

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

2023-06-18 22:29:37 来源:博客园

目录Java Websocket 01: 原生模式 Websocket 基础通信Java Websocket 02: 原生模式通过 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
    4.0.0    com.rockbb.test    ws-demo01    jar    1.0-SNAPSHOT    WS: Demo 01                    17        UTF-8                2.7.11                                                        org.springframework.boot                spring-boot-dependencies                ${spring-boot.version}                pom                import                                                org.springframework.boot            spring-boot-starter-web                            org.springframework.boot            spring-boot-starter-test            test                            org.springframework            spring-websocket                            org.springframework            spring-messaging                            org.java-websocket            Java-WebSocket            1.5.3                        ws-demo01                                    org.apache.maven.plugins                maven-compiler-plugin                3.10.1                                    ${project.jdk.version}                    ${project.jdk.version}                    ${project.source.encoding}                                                        org.apache.maven.plugins                maven-resources-plugin                3.3.0                                    ${project.source.encoding}                                                        org.springframework.boot                spring-boot-maven-plugin                ${spring-boot.version}                                                                                        repackage                                                                                    
application.yml

设置服务端口为 8763


(资料图)

server:  port: 8763  tomcat:    uri-encoding: UTF-8spring:  application:    name: ws-demo01
WsDemo01App.java将 @RestController 也合并到应用入口了. 和单独拆开做一个 Controller 类是一样的"/msg" 路径用于从 server 往 client 发送消息
@RestController@SpringBootApplicationpublic 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 服务

@Configurationpublic 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.

@OnOpenpublic 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 路径, 观察通信的变化情况.

标签:

参与评论