在SpringBoot中整合使用Netty框架的詳細(xì)教程
Netty是一個(gè)非常優(yōu)秀的Socket框架。如果需要在SpringBoot開發(fā)的app中,提供Socket服務(wù),那么Netty是不錯(cuò)的選擇。
Netty與SpringBoot的整合,我想無非就是要整合幾個(gè)地方
- 讓netty跟springboot生命周期保持一致,同生共死
- 讓netty能用上ioc中的Bean
- 讓netty能讀取到全局的配置
整合Netty,提供WebSocket服務(wù)
這里演示一個(gè)案例,在SpringBoot中使用Netty提供一個(gè)Websocket服務(wù)。
servlet容器本身提供了websocket的實(shí)現(xiàn),但這里用netty的實(shí)現(xiàn) :sparkling_heart:
添加依賴
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency>
是的,不用聲明版本號(hào)。因?yàn)?spring-boot-dependencies 中已經(jīng)聲明了最新的netty依賴。
通過yaml配置基本的屬性
server: port: 80 logging: level: root: DEBUG management: endpoints: web: exposure: include: "*" endpoint: shutdown: enabled: true netty: websocket: # Websocket服務(wù)端口 port: 1024 # 綁定的網(wǎng)卡 ip: 0.0.0.0 # 消息幀最大體積 max-frame-size: 10240 # URI路徑 path: /channel
App使用了, actuator ,并且開啟暴露了 shutdown 端點(diǎn),可以讓SpringBoot App優(yōu)雅的停機(jī)。 在這里通過 netty.websocket.* 配置 websocket服務(wù)相關(guān)的配置。
通過 ApplicationRunner 啟動(dòng)Websocket服務(wù)
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服務(wù)
* @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的端點(diǎn)地址,響應(yīng)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 服務(wù)啟動(dòng),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 服務(wù)停止");
}
}
NettyBootsrapRunner 實(shí)現(xiàn)了 ApplicationRunner, ApplicationListener<ContextClosedEvent> , ApplicationContextAware 接口。
這樣一來, NettyBootsrapRunner 可以在App的啟動(dòng)和關(guān)閉時(shí)執(zhí)行Websocket服務(wù)的啟動(dòng)和關(guān)閉。而且通過 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;
// 業(yè)務(wù)層處理數(shù)據(jù)
this.discardService.discard(textWebSocketFrame.text());
// 響應(yīng)客戶端
ctx.channel().writeAndFlush(new TextWebSocketFrame("我收到了你的消息:" + System.currentTimeMillis()));
} else {
// 不接受文本以外的數(shù)據(jù)幀類型
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("鏈接創(chuàng)建:{}", ctx.channel().remoteAddress());
}
}
handler已經(jīng)是一個(gè)IOC管理的Bean,可以自由的使用依賴注入等Spring帶來的快捷功能。由于是單例存在,所有的鏈接都使用同一個(gè)hander,所以盡量不要保存任何實(shí)例變量。
這個(gè)Handler處理完畢客戶端的消息后,給客戶端會(huì)響應(yīng)一條: "我收到了你的消息:" + System.currentTimeMillis() 的消息
為了演示在Handler中使用業(yè)務(wù)層,這里假裝注入了一個(gè) DiscardService 服務(wù)。它的邏輯很簡單,就是丟棄消息
public void discard (String message) {
LOGGER.info("丟棄消息:{}", message);
}
演示
啟動(dòng)客戶端
<!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>
鏈接創(chuàng)建后就給服務(wù)端發(fā)送一條消息: Hello
關(guān)閉服務(wù)端
使用 PostMan 請(qǐng)求服務(wù)器的停機(jī)端點(diǎn)
日志
客戶端日志
服務(wù)端日志
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 服務(wù)啟動(dòng),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 : 鏈接創(chuàng)建:/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 服務(wù)停止
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會(huì)在SpringBoot App啟動(dòng)后啟動(dòng),App停止后關(guān)閉,可以正常的對(duì)外提供服務(wù) 并且Handler交給IOC管理可以注入Service,完成業(yè)務(wù)處理。
總結(jié)
到此這篇關(guān)于在SpringBoot中整合使用Netty框架的文章就介紹到這了,更多相關(guān)SpringBoot整合Netty框架內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+WebSocket+Netty實(shí)現(xiàn)消息推送的示例代碼
- SpringBoot整合Netty心跳機(jī)制過程詳解
- SpringBoot集成netty實(shí)現(xiàn)websocket通信功能
- springboot之springboot與netty整合方案
- SpringBoot整合Netty+Websocket實(shí)現(xiàn)消息推送的示例代碼
- SpringBoot使用Netty實(shí)現(xiàn)遠(yuǎn)程調(diào)用的示例
- SpringBoot整合Netty服務(wù)端的實(shí)現(xiàn)示例
- springboot整合netty過程詳解
- springboot整合netty實(shí)現(xiàn)心跳檢測(cè)和自動(dòng)重連
- SpringBoot項(xiàng)目整合Netty啟動(dòng)失敗的常見錯(cuò)誤總結(jié)
相關(guān)文章
Java虛擬機(jī)如何運(yùn)行Java字節(jié)碼
這篇文章主要介紹了Java虛擬機(jī)如何運(yùn)行Java字節(jié)碼的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
Java隨機(jī)數(shù)的5種獲得方法(非常詳細(xì)!)
這篇文章主要給大家介紹了關(guān)于Java隨機(jī)數(shù)的5種獲得方法,在實(shí)際開發(fā)中產(chǎn)生隨機(jī)數(shù)的使用是很普遍的,所以在程序中進(jìn)行產(chǎn)生隨機(jī)數(shù)操作很重要,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
解決springboot mapper注入報(bào)紅問題
這篇文章主要介紹了解決springboot mapper注入報(bào)紅問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Java處理科學(xué)計(jì)數(shù)法數(shù)字方式
這篇文章主要介紹了Java處理科學(xué)計(jì)數(shù)法數(shù)字方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
java中關(guān)于移位運(yùn)算符的demo與總結(jié)(推薦)
下面小編就為大家?guī)硪黄猨ava中關(guān)于移位運(yùn)算符的demo與總結(jié)(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-05-05
Java操作mongodb增刪改查的基本操作實(shí)戰(zhàn)指南
MongoDB是一個(gè)基于分布式文件存儲(chǔ)的數(shù)據(jù)庫,由c++語言編寫,旨在為WEB應(yīng)用提供可擴(kuò)展的高性能數(shù)據(jù)存儲(chǔ)解決方案,下面這篇文章主要給大家介紹了關(guān)于Java操作mongodb增刪改查的基本操作實(shí)戰(zhàn)指南,需要的朋友可以參考下2023-05-05
Plugin ‘org.springframework.boot:spring-boot-maven-plug
這篇文章給大家介紹了Plugin ‘org.springframework.boot:spring-boot-maven-plugin:‘ not found的解決方案,親測(cè)可用,文中給出了兩種解決方法,需要的朋友可以參考下2024-01-01

