在SpringBoot中整合使用Netty框架的详细教程
作者:朱由检 发布时间:2023-03-26 23:59:53
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>io.netty</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.net.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 org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.springboot.netty.websocket.handler.WebsocketMessageHandler;
/**
* 初始化Netty服务
* @author Administrator
*/
@Component
public class NettyBootsrapRunner implements ApplicationRunner, ApplicationListener<ContextClosedEvent>, ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(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.group(bossGroup, workerGroup);
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.addLast(applicationContext.getBean(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 io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.springboot.netty.service.DiscardService;
/**
*
* @author Administrator
*
*/
@Sharable
@Component
public class WebsocketMessageHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
private static final Logger LOGGER = LoggerFactory.getLogger(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.discard(textWebSocketFrame.text());
// 响应客户端
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');
websocket.onmessage = e => {
console.log('收到消息:', e.data);
}
websocket.onclose = e => {
let {code, reason} = e;
console.log(`链接断开:code=$[code], reason=${reason}`);
}
websocket.onopen = () => {
console.log(`链接建立...`);
websocket.send('Hello');
}
websocket.onerror = e => {
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] org.xnio : XNIO version 3.8.0.Final
2020-06-22 17:08:22.752 INFO 9392 --- [ main] org.xnio.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.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 80 (http)
2020-06-22 17:08:22.931 INFO 9392 --- [ main] io.springboot.netty.NettyApplication : Started NettyApplication in 4.536 seconds (JVM running for 5.175)
2020-06-22 17:08:23.653 INFO 9392 --- [ main] i.s.n.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] i.s.n.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.s.netty.service.DiscardService : 丢弃消息:Hello
2020-06-22 17:08:33.688 INFO 9392 --- [ Thread-232] i.s.n.w.runner.NettyBootsrapRunner : websocket 服务停止
2020-06-22 17:08:33.691 INFO 9392 --- [ntLoopGroup-3-1] i.s.n.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 : Destroying Spring FrameworkServlet 'dispatcherServlet'
2020-06-22 17:08:33.708 INFO 9392 --- [ Thread-232] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Netty会在SpringBoot App启动后启动,App停止后关闭,可以正常的对外提供服务 并且Handler交给IOC管理可以注入Service,完成业务处理。
来源:https://juejin.im/post/5ef0786151882565b0119b00


猜你喜欢
- File类概述File类能新建、删除、重命名文件和目录,但不能访问文件内容本身,如果需要访问文件内容本身,则需要使用后续的输入/输出流。要在
- App中,有很大一部分场景是点击按钮,向服务端提交数据,由于网络请求需要时间,用户很可能会多次点击,造成数据重复提交,造成各种莫名其妙的问题
- SpringBoot的自动装配是拆箱即用的基础,也是微服务化的前提。这次主要的议题是,来看看它是怎么样实现的,我们透过源代码来把握自动装配的
- 我们有很多 Coding Style 或 代码规范。 但这一条可能会经常被我们所遗忘,就是我们 经常会在函数的参数里使用bool参数,这会大
- 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户授权:经过认证后判断当前用户是否有权限进行某个操作一、登录校验流程1、S
- 本文实例为大家分享了Android自定义view课表的具体代码,供大家参考,具体内容如下这里是模拟课表,数据写死了的,不过也可以通过抓包获取
- 前言在面对 生产者-消费者 的场景下, netcore 提供了一个新的命名空间 System.Threading.Channels 来帮助我
- 程序触发鼠标、键盘事件是C#程序设计中比较常见的功能,本文实例展示了C#中winform实现自动触发鼠标、键盘事件的方法,有不错的实用价值。
- 一、链表1.1 概述链表是真正动态的数据结构,最简单的动态数据结构,基本用于辅助组成其他数据结构。数据存储在“节点”(Node)中优点:真正
- StringString类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。这个
- RenderScript 介绍在开始之前,先看下 RenderScript 的官方介绍:RenderScript is a framewor
- 最近项目需要通过经纬度查询 具体的地址和区域名称,通过查询网络资源,发现提供的大多是得到具体的地址而对区域或城市名称的获取就不是很好把握;在
- 本文实例讲述了Java面向对象程序设计多态性。分享给大家供大家参考,具体如下:多态:具有表现多种形态的能力的特征(同一个实现接口,使用不同的
- 第一步:在d盘新建android文件夹,在此文件夹中再建三个子文件夹,Android Studio 、 Android_SDK、Androi
- 使用lombok插件的好处我们在java开发过程中,经常会有一些常规性的,重复性的工作。比如:根据成员变量生成get和set方法根据成员变量
- 本文实例为大家分享了android音乐播放器的具体代码,供大家参考,具体内容如下话不多说先上效果前言写这个音乐播放器实在是迫不得已。因为我们
- Json格式在后台服务中的重要性就不多说了,直入正题。首先引入pom文件,这里使用的是1.2.83版本<dependency>
- 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是
- 如何实现首先 * 是属于web这块的,那我们需要引入springboot web模块,具体版本在parent中<dependency&
- TransactionScope是.Net Framework 2.0滞后,新增了一个名称空间。它的用途是为数据库访问提供了一个“轻量级”[