在SpringBoot中整合使⽤Netty框架的详细教程
Netty是⼀个⾮常优秀的Socket框架。如果需要在SpringBoot开发的app中,提供Socket服务,那么Netty是不错的选择。Netty与SpringBoot的整合,我想⽆⾮就是要整合⼏个地⽅
让netty跟springboot⽣命周期保持⼀致,同⽣共死
让netty能⽤上ioc中的Bean
让netty能读取到全局的配置
整合Netty,提供WebSocket服务
这⾥演⽰⼀个案例,在SpringBoot中使⽤Netty提供⼀个Websocket服务。
servlet容器本⾝提供了websocket的实现,但这⾥⽤netty的实现 :sparkling_heart:
添加依赖
<dependency>
<groupId>ioty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
是的,不⽤声明版本号。因为 spring-boot-dependencies 中已经声明了最新的netty依赖。
通过yaml配置基本的属性
server:
port: 80
logging:
level:
root: DEBUG
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
shutdown:
enabled: true
netty:
websocket:
# Websocket服务端⼝
port: 1024
# 绑定的⽹卡
ip: 0.0.0.0
# 消息帧最⼤体积
max-frame-size: 10240
# URI路径
path: /channel
App使⽤了, actuator ,并且开启暴露了shutdown端点,可以让SpringBoot App优雅的停机。在这⾥通过netty.websocket.*配置 websocket服务相关的配置。
通过 ApplicationRunner 启动Websocket服务
import java.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import t.ApplicationContext;
import t.ApplicationContextAware;
import t.ApplicationListener;
import t.event.ContextClosedEvent;
springboot框架是干嘛的import org.springframework.stereotype.Component;
import ioty.bootstrap.ServerBootstrap;
import ioty.channel.Channel;
import ioty.channel.ChannelFutureListener;
import ioty.channel.ChannelHandlerContext;
import ioty.channel.ChannelInboundHandlerAdapter;
import ioty.channel.ChannelInitializer;
import ioty.channel.ChannelPipeline;
import ioty.channel.EventLoopGroup;
import ioty.channel.nio.NioEventLoopGroup;
import ioty.channel.socket.SocketChannel;
import ioty.channel.socket.nio.NioServerSocketChannel;
import dec.http.DefaultFullHttpResponse;
import dec.http.FullHttpRequest;
import dec.http.HttpObjectAggregator;
import dec.http.HttpResponseStatus;
import dec.http.HttpServerCodec;
import dec.http.HttpVersion;
import dec.http.websocketx.WebSocketServerProtocolHandler;
import dec.sionspression.WebSocketServerCompressionHandler;
import ioty.handler.stream.ChunkedWriteHandler;
import io.springbootty.websocket.handler.WebsocketMessageHandler;
/**
* 初始化Netty服务
* @author Administrator
*/
@Component
public class NettyBootsrapRunner implements ApplicationRunner, ApplicationListener<ContextClosedEvent>, ApplicationContextAware { private static final Logger LOGGER = Logger(NettyBootsrapRunner.class);
@Value("${netty.websocket.port}")
private int port;
@Value("${netty.websocket.ip}")
private String ip;
@Value("${netty.websocket.path}")
private String path;
@Value("${netty.websocket.max-frame-size}")
private long maxFrameSize;
private ApplicationContext applicationContext;
private Channel serverChannel;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void run(ApplicationArguments args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.localAddress(new InetSocketAddress(this.ip, this.port));
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
String uri = fullHttpRequest.uri();
if (!uri.equals(path)) {
// 访问的路径不是 websocket的端点地址,响应404
ctx.channel().writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND))
.addListener(ChannelFutureListener.CLOSE);
return ;
}
}
super.channelRead(ctx, msg);
}
});
pipeline.addLast(new WebSocketServerCompressionHandler());
pipeline.addLast(new WebSocketServerProtocolHandler(path, null, true, maxFrameSize));
/**
* 从IOC中获取到Handler
*/
pipeline.Bean(WebsocketMessageHandler.class));
}
});
Channel channel = serverBootstrap.bind().sync().channel();
this.serverChannel = channel;
LOGGER.info("websocket 服务启动,ip={},port={}", this.ip, this.port);
channel.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public void onApplicationEvent(ContextClosedEvent event) {
if (this.serverChannel != null) {
this.serverChannel.close();
}
LOGGER.info("websocket 服务停⽌");
}
}
NettyBootsrapRunner实现了 ApplicationRunner, ApplicationListener<ContextClosedEvent> , ApplicationContextAware接⼝。
这样⼀来,NettyBootsrapRunner可以在App的启动和关闭时执⾏Websocket服务的启动和关闭。⽽且通过ApplicationContextAware 还能获取到ApplicationContext
通过IOC管理 Netty 的Handler
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ioty.channel.ChannelHandlerContext;
import ioty.channel.SimpleChannelInboundHandler;
import ioty.channel.ChannelFutureListener;
import ioty.channel.ChannelHandler.Sharable;
import dec.http.websocketx.TextWebSocketFrame;
import dec.http.websocketx.WebSocketCloseStatus;
import dec.http.websocketx.WebSocketFrame;
import io.springbootty.service.DiscardService;
/**
*
* @author Administrator
*
*/
@Sharable
@Component
public class WebsocketMessageHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
private static final Logger LOGGER = Logger(WebsocketMessageHandler.class);
@Autowired
DiscardService discardService;
@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception {
if (msg instanceof TextWebSocketFrame) {
TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) msg;
// 业务层处理数据
this.discardService.());
// 响应客户端
ctx.channel().writeAndFlush(new TextWebSocketFrame("我收到了你的消息:" + System.currentTimeMillis()));
} else {
// 不接受⽂本以外的数据帧类型
ctx.channel().writeAndFlush(WebSocketCloseStatus.INVALID_MESSAGE_TYPE).addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
LOGGER.info("链接断开:{}", ctx.channel().remoteAddress());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
LOGGER.info("链接创建:{}", ctx.channel().remoteAddress());
}
}
handler已经是⼀个IOC管理的Bean,可以⾃由的使⽤依赖注⼊等Spring带来的快捷功能。由于是单例存在,所有的链接都使⽤同⼀个hander,所以尽量不要保存任何实例变量。
这个Handler处理完毕客户端的消息后,给客户端会响应⼀条:"我收到了你的消息:" + System.currentTimeMillis()的消息
为了演⽰在Handler中使⽤业务层,这⾥假装注⼊了⼀个DiscardService服务。它的逻辑很简单,就是丢弃消息
public void discard (String message) {
LOGGER.info("丢弃消息:{}", message);
}
演⽰
启动客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Websocket</title>
</head>
<body>
</body>
<script type="text/javascript">
;(function(){
const websocket = new WebSocket('ws://localhost:1024/channel');
console.log('收到消息:', e.data);
}
let {code, reason} = e;
console.log(`链接断开:code=$[code], reason=${reason}`);
}
console.log(`链接建⽴...`);
websocket.send('Hello');
}
console.log('链接异常:', e);
}
})();
</script>
</html>
链接创建后就给服务端发送⼀条消息:Hello
关闭服务端
使⽤ PostMan 请求服务器的停机端点
⽇志
客户端⽇志
服务端⽇志
2020-06-22 17:08:22.728  INFO 9392 --- [          main] io.undertow                              : starting server: Undertow -
2.1.
3.Final
2020-06-22 17:08:22.740  INFO 9392 --- [          main] io                                : XNIO version 3.8.0.Final
2020-06-22 17:08:22.752  INFO 9392 --- [          main] io.nio                            : XNIO NIO Implementation Version 3.8.0.Final
2020-06-22 17:08:22.839  INFO 9392 --- [          main] org.jboss.threads                        : JBoss Threads version
3.1.0.Final
2020-06-22 17:08:22.913  INFO 9392 --- [          main] o.s.undertow.UndertowWebServer    : Undertow started on port(s) 80 (http)
2020-06-22 17:08:22.931  INFO 9392 --- [          main] io.springbootty.NettyApplication    : Started
NettyApplication in 4.536 seconds (JVM running for 5.175)
2020-06-22 17:08:23.653  INFO 9392 --- [          main] w.runner.NettyBootsrapRunner      : websocket 服务启动,ip=0.0.0.0,port=1024
2020-06-22 17:08:28.484  INFO 9392 --- [  XNIO-1 task-1] io.undertow.servlet                      : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-06-22 17:08:28.484  INFO 9392 --- [  XNIO-1 task-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-06-22 17:08:28.492  INFO 9392 --- [  XNIO-1 task-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms
2020-06-22 17:08:28.724  INFO 9392 --- [ntLoopGroup-3-1] w.handler.WebsocketMessageHandler  : 链接创建:/0:0:0:0:0:0:0:1:12093
2020-06-22 17:08:28.790  INFO 9392 --- [ntLoopGroup-3-1] i.sty.service.DiscardService        : 丢弃消息:Hello 2020-06-22 17:08:33.688  INFO 9392 --- [    Thread-232] w.runner.NettyBootsrapRunner      : websocket 服务停⽌
2020-06-22 17:08:33.691  INFO 9392 --- [ntLoopGroup-3-1] w.handler.WebsocketMessageHandler  : 链接断开:/0:0:0:0:0:0:0:1:12093
2020-06-22 17:08:33.699  INFO 9392 --- [    Thread-232] io.undertow                              : stopping server: Undertow - 2.1.3.Final
2020-06-22 17:08:33.704  INFO 9392 --- [    Thread-232] io.undertow.servlet                      : Destroyin
g Spring FrameworkServlet 'dispatcherServlet'
2020-06-22 17:08:33.708  INFO 9392 --- [    Thread-232] o.urrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。