SpringBoot2.x使用websocket

young 812 2021-10-18

netty-websocket-spring-boot-starter gitee地址

环境

SpringBoot 2.1.4
Java8

maven坐标

<dependency>
<groupId>org.yeauty</groupId>
<artifactId>netty-websocket-spring-boot-starter</artifactId>
<version>0.9.5</version>
</dependency>

使用

在端点类上加上@ServerEndpoint注解,并在相应的方法上加上@BeforeHandshake@OnOpen@OnClose@OnError@OnMessage@OnBinary@OnEvent注解

官方样例

@ServerEndpoint(path = "/ws/{arg}")
public class MyWebSocket {
@BeforeHandshake
public void handshake(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap,
@PathVariable String arg, @PathVariable Map pathMap){
session.setSubprotocols("stomp");
if (!req.equals("ok")){
System.out.println("Authentication failed!");
session.close();
}
}
@OnOpen
public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap,
@PathVariable String arg, @PathVariable Map pathMap){
System.out.println("new connection");
System.out.println(req);
}
@OnClose
public void onClose(Session session) throws IOException {
System.out.println("one connection closed");
}
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
}
@OnMessage
public void onMessage(Session session, String message) {
System.out.println(message);
session.sendText("Hello Netty!");
}
@OnBinary
public void onBinary(Session session, byte[] bytes) {
for (byte b : bytes) {
System.out.println(b);
}
session.sendBinary(bytes);
}
@OnEvent
public void onEvent(Session session, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
switch (idleStateEvent.state()) {
case READER_IDLE:
System.out.println("read idle");
break;
case WRITER_IDLE:
System.out.println("write idle");
break;
case ALL_IDLE:
System.out.println("all idle");
break;
default:
break;
}
}
}
}

注解说明

@ServerEndpoint

当ServerEndpointExporter类通过Spring配置进行声明并被使用,它将会去扫描带有@ServerEndpoint注解的类 被注解的类将被注册成为一个WebSocket端点 所有的配置项都在这个注解的属性中 ( 如:@ServerEndpoint("/ws") )

@BeforeHandshake

当有新的连接进入时,对该方法进行回调 注入参数的类型:Session、HttpHeaders...

@OnOpen

当有新的WebSocket连接完成时,对该方法进行回调 注入参数的类型:Session、HttpHeaders...

@OnClose

当有WebSocket连接关闭时,对该方法进行回调 注入参数的类型:Session

@OnError

当有WebSocket抛出异常时,对该方法进行回调 注入参数的类型:Session、Throwable

@OnMessage

当接收到字符串消息时,对该方法进行回调 注入参数的类型:Session、String

@OnBinary

当接收到二进制消息时,对该方法进行回调 注入参数的类型:Session、byte[]

@OnEvent

当接收到Netty的事件时,对该方法进行回调 注入参数的类型:Session、Object

配置说明

属性默认值说明
path"/"WebSocket的path,也可以用value来设置
host"0.0.0.0"WebSocket的host,"0.0.0.0"即是所有本地地址
port80WebSocket绑定端口号。如果为0,则使用随机端口(端口获取可见 多端点服务)
bossLoopGroupThreads0bossEventLoopGroup的线程数
workerLoopGroupThreads0workerEventLoopGroup的线程数
useCompressionHandlerfalse是否添加WebSocketServerCompressionHandler到pipeline
optionConnectTimeoutMillis30000与Netty的ChannelOption.CONNECT_TIMEOUT_MILLIS一致
optionSoBacklog128与Netty的ChannelOption.SO_BACKLOG一致
childOptionWriteSpinCount16与Netty的ChannelOption.WRITE_SPIN_COUNT一致
childOptionWriteBufferHighWaterMark64*1024与Netty的ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK一致,但实际上是使用ChannelOption.WRITE_BUFFER_WATER_MARK
childOptionWriteBufferLowWaterMark32*1024与Netty的ChannelOption.WRITE_BUFFER_LOW_WATER_MARK一致,但实际上是使用 ChannelOption.WRITE_BUFFER_WATER_MARK
childOptionSoRcvbuf-1(即未设置)与Netty的ChannelOption.SO_RCVBUF一致
childOptionSoSndbuf-1(即未设置)与Netty的ChannelOption.SO_SNDBUF一致
childOptionTcpNodelaytrue与Netty的ChannelOption.TCP_NODELAY一致
childOptionSoKeepalivefalse与Netty的ChannelOption.SO_KEEPALIVE一致
childOptionSoLinger-1与Netty的ChannelOption.SO_LINGER一致
childOptionAllowHalfClosurefalse与Netty的ChannelOption.ALLOW_HALF_CLOSURE一致
readerIdleTimeSeconds0IdleStateHandler中的readerIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
writerIdleTimeSeconds0IdleStateHandler中的writerIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
allIdleTimeSeconds0IdleStateHandler中的allIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
maxFramePayloadLength65536最大允许帧载荷长度

通过application.properties进行配置

所有参数皆可使用${...}占位符获取application.properties中的配置

首先在@ServerEndpoint注解的属性中使用${...}占位符

@ServerEndpoint(host = "${ws.host}",port = "${ws.port}")
public class MyWebSocket {
...
}

接下来即可在application.properties中配置

ws.host=0.0.0.0
ws.port=80

多端点服务

  • 快速启动的基础上,在多个需要成为端点的类上使用@ServerEndpoint@Component注解即可
  • 可通过ServerEndpointExporter.getInetSocketAddressSet()获取所有端点的地址
  • 当地址不同时(即host不同或port不同),使用不同的ServerBootstrap实例
  • 当地址相同,路径(path)不同时,使用同一个ServerBootstrap实例
  • 当多个端点服务的port为0时,将使用同一个随机的端口号
  • 当多个端点的port和path相同时,host不能设为"0.0.0.0",因为"0.0.0.0"意味着绑定所有的host

注入Spring管理的Bean

参考:https://blog.csdn.net/m0_37202351/article/details/86255132

@ServerEndpoint(path ="/testSocket")
public class TestSocket {
// 这里使用静态,让 service 属于类
private static SomeService someService;
// 注入的时候,给类的 service 注入
@Autowired
public void setSomeService(SomeService someService) {
TestSocket.someService = someService;
}
}

初始化:项目启动时,spring 工厂会创建 websocket 的单例对象(此时注解合法,spring 就会为 TestSocket类的属性 SomeService进行注入,并创建一个单例对象,spring 并不知道 websocket 的特殊意义,只是该类的注解合法,便会进行操作,与其他 controller 进行的操作一模一样),因此 chatService 不是 null。

接通时:当websocket接通时,,后台(不管是tomcat 还是java)会根据 TestSocket类创建一个新的 TestSocket对象,保存与用户的连接。因为someService是属于类的,所以也不是 null