# netty-websocket-spring-boot-starter
**Repository Path**: Yeauty/netty-websocket-spring-boot-starter
## Basic Information
- **Project Name**: netty-websocket-spring-boot-starter
- **Description**: 轻量级、高性能的WebSocket框架
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1149
- **Forks**: 369
- **Created**: 2018-09-24
- **Last Updated**: 2025-06-07
## Categories & Tags
**Categories**: web-dev-toolkits
**Tags**: None
## README
netty-websocket-spring-boot-starter [](http://www.apache.org/licenses/LICENSE-2.0.html)
===================================
[English Docs](https://github.com/YeautyYE/netty-websocket-spring-boot-starter/blob/master/README.md)
### 简介
本项目帮助你在spring-boot中使用Netty来开发WebSocket服务器,并像spring-websocket的注解开发一样简单
### 要求
- jdk版本为1.8或1.8+
### 快速开始
- 添加依赖:
```xml
org.yeauty
netty-websocket-spring-boot-starter
0.12.0
```
- 在端点类上加上`@ServerEndpoint`注解,并在相应的方法上加上`@BeforeHandshake`、`@OnOpen`、`@OnClose`、`@OnError`、`@OnMessage`、`@OnBinary`、`@OnEvent`注解,样例如下:
```java
@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 (!"ok".equals(req)){
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;
}
}
}
}
```
- 打开WebSocket客户端,连接到`ws://127.0.0.1:80/ws/xxx`
### 注解
###### @ServerEndpoint
> 当ServerEndpointExporter类通过Spring配置进行声明并被使用,它将会去扫描带有@ServerEndpoint注解的类
> 被注解的类将被注册成为一个WebSocket端点
> 所有的[配置项](#%E9%85%8D%E7%BD%AE)都在这个注解的属性中 ( 如:`@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"`即是所有本地地址
|port|80|WebSocket绑定端口号。如果为0,则使用随机端口(端口获取可见 [多端点服务](#%E5%A4%9A%E7%AB%AF%E7%82%B9%E6%9C%8D%E5%8A%A1))
|bossLoopGroupThreads|0|bossEventLoopGroup的线程数
|workerLoopGroupThreads|0|workerEventLoopGroup的线程数
|useCompressionHandler|false|是否添加WebSocketServerCompressionHandler到pipeline
|optionConnectTimeoutMillis|30000|与Netty的`ChannelOption.CONNECT_TIMEOUT_MILLIS`一致
|optionSoBacklog|128|与Netty的`ChannelOption.SO_BACKLOG`一致
|childOptionWriteSpinCount|16|与Netty的`ChannelOption.WRITE_SPIN_COUNT`一致
|childOptionWriteBufferHighWaterMark|64*1024|与Netty的`ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK`一致,但实际上是使用`ChannelOption.WRITE_BUFFER_WATER_MARK`
|childOptionWriteBufferLowWaterMark|32*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`一致
|childOptionTcpNodelay|true|与Netty的`ChannelOption.TCP_NODELAY`一致
|childOptionSoKeepalive|false|与Netty的`ChannelOption.SO_KEEPALIVE`一致
|childOptionSoLinger|-1|与Netty的`ChannelOption.SO_LINGER`一致
|childOptionAllowHalfClosure|false|与Netty的`ChannelOption.ALLOW_HALF_CLOSURE`一致
|readerIdleTimeSeconds|0|与`IdleStateHandler`中的`readerIdleTimeSeconds`一致,并且当它不为0时,将在`pipeline`中添加`IdleStateHandler`
|writerIdleTimeSeconds|0|与`IdleStateHandler`中的`writerIdleTimeSeconds`一致,并且当它不为0时,将在`pipeline`中添加`IdleStateHandler`
|allIdleTimeSeconds|0|与`IdleStateHandler`中的`allIdleTimeSeconds`一致,并且当它不为0时,将在`pipeline`中添加`IdleStateHandler`
|maxFramePayloadLength|65536|最大允许帧载荷长度
|useEventExecutorGroup|true|是否使用另一个线程池来执行耗时的同步业务逻辑
|eventExecutorGroupThreads|16|eventExecutorGroup的线程数
|sslKeyPassword|""(即未设置)|与spring-boot的`server.ssl.key-password`一致
|sslKeyStore|""(即未设置)|与spring-boot的`server.ssl.key-store`一致
|sslKeyStorePassword|""(即未设置)|与spring-boot的`server.ssl.key-store-password`一致
|sslKeyStoreType|""(即未设置)|与spring-boot的`server.ssl.key-store-type`一致
|sslTrustStore|""(即未设置)|与spring-boot的`server.ssl.trust-store`一致
|sslTrustStorePassword|""(即未设置)|与spring-boot的`server.ssl.trust-store-password`一致
|sslTrustStoreType|""(即未设置)|与spring-boot的`server.ssl.trust-store-type`一致
|corsOrigins|{}(即未设置)|与spring-boot的`@CrossOrigin#origins`一致
|corsAllowCredentials|""(即未设置)|与spring-boot的`@CrossOrigin#allowCredentials`一致
### 通过application.properties进行配置
> 所有参数皆可使用`${...}`占位符获取`application.properties`中的配置。如下:
- 首先在`@ServerEndpoint`注解的属性中使用`${...}`占位符
```java
@ServerEndpoint(host = "${ws.host}",port = "${ws.port}")
public class MyWebSocket {
...
}
```
- 接下来即可在`application.properties`中配置
```
ws.host=0.0.0.0
ws.port=80
```
### 自定义Favicon
配置favicon的方式与spring-boot中完全一致。只需将`favicon.ico`文件放到classpath的根目录下即可。如下:
```
src/
+- main/
+- java/
| +
+- resources/
+- favicon.ico
```
### 自定义错误页面
配置自定义错误页面的方式与spring-boot中完全一致。你可以添加一个 `/public/error` 目录,错误页面将会是该目录下的静态页面,错误页面的文件名必须是准确的错误状态或者是一串掩码,如下:
```
src/
+- main/
+- java/
| +
+- resources/
+- public/
+- error/
| +- 404.html
| +- 5xx.html
+-
```
### 多端点服务
- 在[快速启动](#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)的基础上,在多个需要成为端点的类上使用`@ServerEndpoint`、`@Component`注解即可
- 可通过`ServerEndpointExporter.getInetSocketAddressSet()`获取所有端点的地址
- 当地址不同时(即host不同或port不同),使用不同的`ServerBootstrap`实例
- 当地址相同,路径(path)不同时,使用同一个`ServerBootstrap`实例
- 当多个端点服务的port为0时,将使用同一个随机的端口号
- 当多个端点的port和path相同时,host不能设为`"0.0.0.0"`,因为`"0.0.0.0"`意味着绑定所有的host
---
### 更新日志
#### 0.8.0
- 自动装配
#### 0.9.0
- 通过`@PathVariable`支持RESTful风格中获取参数
- 通过`@RequestParam`实现请求中query的获取参数
- 移除原来的ParameterMap,用`@RequestParam MultiValueMap`代替
- 新增 `@BeforeHandshake` 注解,可在握手之前对连接进行关闭
- 在`@BeforeHandshake`事件中可设置子协议
- 去掉配置端点类上的 `@Component`
- 更新`Netty`版本到 `4.1.44.Final`
#### 0.9.1
- 修复bug:当使用`@RequestParam MultiValueMap`时获取的对象为null
- 更新`Netty`版本到 `4.1.45.Final`
#### 0.9.2
- 兼容 0.8.0 以下版本,可以手动装配`ServerEndpointExporter`对象
#### 0.9.3
- 修复bug:当没有 `@BeforeHandshake`时会出现空指针异常
#### 0.9.4
- 修复bug:当没有 `@BeforeHandshake`时 `OnOpen`中的`Session`为null.
#### 0.9.5
- 修复bug:`OnError`事件中的`Throwable`为null.
#### 0.10.0
- 修改`bossLoopGroupThreads`默认值为1
- 支持通过配置`useEventExecutorGroup`让同步且耗时的业务逻辑在EventExecutorGroup中执行,防止I/O线程被耗时的任务阻塞
- 支持SSL
- 支持跨域
- 更新`Netty`版本到 `4.1.59.Final`
#### 0.11.0
- 当`ServerEndpoint`类被cglib代理时(如aop增强),仍能正常运行
#### 0.12.0
- `@EnableWebSocket`增加`scanBasePackages`属性
- `@ServerEndpoint`不再依赖`@Component`
- 更新`Netty`版本到 `4.1.67.Final`