持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的9天,点击查看活动详情
背景
在 SpringBoot 项目中,SpringBoot 内置的 Tomcat 服务器一般占有的端口是8080端口,而要在该项目中运行 netty 服务器肯定不能占用8080端口,需要在该项目中再开启另外一个端口,作为 netty 服务器的访问端口。
而netty内部使用的是 nio,通过 Selector 管理多个 Channel 实现多路复用,但如果在开启 SpringBoot 项目的过程中,netty服务器先于Tomcat服务器开启同时没有产生事件让selector去执行,就会造成 netty 服务器卡住 Tomcat服务器,即Tomcat服务器不能被启动。
整合
环境
Netty的依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
SpringBoot版本
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
启动Netty服务器
- 在项目中创建一个 Netty 包,实现 WebSocketServer
@Slf4j
@Component
public class WebSocketServer {
/**
* 端口号
*/
@Value("${netty.websocketPort}")
private int port;
/**
* 线程组1 接收 accept事件
*/
private EventLoopGroup bossGroup = new NioEventLoopGroup();
/**
* 线程组 2 接收 读写事件
*/
private EventLoopGroup workerGroup = new NioEventLoopGroup();
/**
* 通信channel
*/
private Channel channel;
/**
* 异步开启 netty 服务器
*/
@Async
public void start() {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.handler(new LoggingHandler(LogLevel.DEBUG))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
// http编解码器
nioSocketChannel.pipeline().addLast(new HttpServerCodec());
// 添加块处理器
nioSocketChannel.pipeline().addLast(new ChunkedWriteHandler());
/**
* http 数据在传输过程中是分段的,HttpObjectAggregator 可以将多段进行聚合
* 这就是为什么,当浏览器发送大量请求的时候,会产生多次 http 请求
*/
nioSocketChannel.pipeline().addLast(new HttpObjectAggregator(8192));
/**
* 1. 对应 websocket,它的数据是以帧的形式传递
* 2. 浏览器请求时,ws://localhost:7000/hello 表示请求的uri
* 3. WebSocketServerProtocolHandler 将 http协议升级为 ws 协议
*/
nioSocketChannel.pipeline().addLast(new WebSocketServerProtocolHandler("/hello"));
// 自定义的handler,处理业务逻辑
nioSocketChannel.pipeline().addLast(new WebSocketServerHandler());
}
});
try {
// 开启服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
// 获取到channel
channel = channelFuture.channel();
log.info("服务器已经启动,{}", channel.localAddress());
// 等待关闭服务器
channel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 优雅关闭服务器
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public void stop() {
log.info("Netty WebSocket服务器已关闭");
// 优雅的关闭EventLoopGroup,释放所有的资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
基础的 netty 配置这里就不详细说了,具体要注意的是 start 方法要加上 @Async 注解,表示异步开启netty服务器, 这样就不会有netty服务器阻塞Tomcat服务器的情况
使用@Async要同时在启动类中加入@EnableAsync 才会生效
- 在config包下创建 WebSocketConfig 文件
@Component
public class WebSocketConfig {
@Value("${netty.startWithSpring}")
private boolean autoStartWebsocket;
@Resource
private WebSocketServer webSocketServer;
/**
* 在创建完所有的 bean 后自动调用
*/
@PostConstruct
public void start() {
if (autoStartWebsocket) {
webSocketServer.start();
}
}
/**
* 在销毁所有的 bean 之前调用
*/
@PreDestroy
public void close() {
if (autoStartWebsocket) {
webSocketServer.stop();
}
}
}
这里的 start 方法加上了 @PostConstruct 注解,表示在创建完所有 bean 之后调用该方法,因为要先创建 WebSocketServer 然后再调用 WebSocketServer 的 start 方法,就可以异步创建 Netty 服务器
基于Netty 实现 WebSocket
还是之前的代码WebSocketServer中的代码
- 要加入 http 编解码器,因为 websocket 协议是基于 http 的
nioSocketChannel.pipeline().addLast(new HttpServerCodec());
- 添加 块处理器
nioSocketChannel.pipeline().addLast(new ChunkedWriteHandler());
- 添加 HttpObjectAggregator
- http 数据在传输过程中是分段的,HttpObjectAggregator 可以将多段进行聚合
nioSocketChannel.pipeline().addLast(new HttpObjectAggregator(8192));
- 添加 WebSocketServerProtocolHandler 处理器
- 对应 websocket,它的数据是以帧的形式传递
- 浏览器请求时,ws://localhost:7000/hello 表示请求的uri
- WebSocketServerProtocolHandler 将 http协议升级为 ws 协议
nioSocketChannel.pipeline().addLast(new WebSocketServerProtocolHandler("/hello"));
- 最后添加自己的业务处理 handler 即可
最后就可以在SpringBoot项目中启动Netty服务器