目录

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>
<!-- Global encoding -->
<project.jdk.version>17</project.jdk.version>
<project.source.encoding>UTF-8</project.source.encoding>
<!-- Global dependency versions -->
<spring-boot.version>2.7.11</spring-boot.version>
</properties> <dependencyManagement>
<dependencies>
<!-- Spring Boot 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 路径, 观察通信的变化情况.

Java Websocket 01: 原生模式 Websocket 基础通信的更多相关文章

  1. Practical Node.js (2018版) 第9章: 使用WebSocket建立实时程序,原生的WebSocket使用介绍,Socket.IO的基本使用介绍。

    Real-Time Apps with WebSocket, Socket.IO, and DerbyJS 实时程序的使用变得越来越广泛,如传统的交易,游戏,社交,开发工具DevOps tools, ...

  2. springboot整合websocket实现客户端与服务端通信

    定义  WebSocket是通过单个TCP连接提供全双工(双向通信)通信信道的计算机通信协议.此WebSocket API可在用户的浏览器和服务器之间进行双向通信.用户可以向服务器发送消息并接收事件驱 ...

  3. 076 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 01 Java面向对象导学

    076 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 01 Java面向对象导学 本文知识点:Java面向对象导学 说明:因为时间紧张,本人 ...

  4. 让ie6 7 8 9支持原生html5 websocket

      让ie6 7 8 9支持原生html5 websocket   从github上的 web-socket-js(socket.io好像也是用这个做的他们的flash替代传输方式)改过来的.不过值得 ...

  5. 基于Java API for WebSocket (JSR-356)的标准websocket客户端

    maven依赖 springboot <dependency> <groupId>org.springframework.boot</groupId> <ar ...

  6. # 095 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 03 封装总结 01 封装知识点总结

    095 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  7. 094 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 04 static关键字(续)

    094 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  8. 093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 03 static关键字(下)

    093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  9. 092 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 02 static关键字(中)

    092 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  10. 091 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 01 static关键字(上)

    091 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

随机推荐

  1. PyQt5学习 (2)--QWidget(上)

    描述:   1.所有可视控件的基类   2.是一个最简单的空白控件   3.控件时用户界面的最小元素:接收各种事件.绘制在桌面上,展示给用户看   4.每个控件都是矩形的,它们按Z轴顺序排序   5. ...

  2. day26:装饰器&面向对象当中的方法&property

    目录 1.装饰器 1.1 装饰器的基本用法 1.2 @符号的使用 1.3 装饰器的嵌套 1.4 用装饰器扩展带有参数的原函数 1.5 用装饰器扩展带有参数和返回值的原函数 1.6 用类装饰器扩展原函数 ...

  3. 【Java SE】集合

    1.java集合框架 使用Array存储对象有一定的弊端.java集合就是一种容器,动态地存储多个对象,存储主要是内存层面的存储,不涉及到持久化的存储(txt,avi,数据库). ①一旦初始化好,数组 ...

  4. 【Vue项目】商品汇前台(一)组件拆分+请求封装

    写在最前面 之前学完vue基础之后,一直想做一下前端的项目,但是自己写了一点前后端分离的项目发现自己前端写的是真的乱,所以趁着假期的空闲做一下尚硅谷商品汇的前后台项目,争取这周写完前台,然后这个月再写 ...

  5. nginx自定义负载均衡及根据cpu运行自定义负载均衡

    转载请注明出处: 1.nginx如何自定义负载均衡 在Nginx中,可以通过配置文件自定义负载均衡策略.具体步骤如下: 首先,在Nginx配置文件中定义一个upstream模块,并设置负载均衡策略和后 ...

  6. vue-element-admin 动态菜单改造

    vue-element-admin 动态菜单改造 vue-element-admin 是一款优秀后台前端解决方案,它基于 vue 和 element-ui实现.开源后台管理系统解决方案项目 Boot- ...

  7. OpenHarmony SystemUI开发记录

    背景介绍 最近学习OpenHarmony应用开发, SDK版本是3.2.9.2 Beta4,IDE版本是3.1.0.200.参考官方文档,做了个Demo应用,调试.运行非常顺利.启动应用后,状态栏和导 ...

  8. C++ Primer 5th 阅读笔记:入门指南

    学习方法 The way to learn a new programming language is to write programs. 学习一门新编程语言的方式是编写程序. 函数(Functio ...

  9. 【Ubuntu】2. 安装Ubuntu操作系统+VMware Tools

    接上一篇,这一部分介绍了操作系统的安装 开启虚拟机,首先由四个选项,选第一个正常安装即可,这里稍等片刻就可以进入安装选项 到了这一步的话可以在左侧设置系统语言,我的英语比较垃圾就选中文了,选择完之后点 ...

  10. 【Python基础】变量的作用域与global、nonlocal

    在 Python 中,变量的作用域指的是变量可以被访问的范围.Python 中的变量作用域有两种: 局部作用域:变量在函数函数内部的时候,只能在函数内部使用.这样的变量被称为局部变量. 如果在函数内部 ...