SpringBoot使用Netty實(shí)現(xiàn)遠(yuǎn)程調(diào)用的示例
前言
眾所周知我們?cè)谶M(jìn)行網(wǎng)絡(luò)連接的時(shí)候,建立套接字連接是一個(gè)非常消耗性能的事情,特別是在分布式的情況下,用線程池去保持多個(gè)客戶端連接,是一種非常消耗線程的行為。那么我們?cè)撏ㄟ^(guò)什么技術(shù)去解決上述的問(wèn)題呢,那么就不得不提一個(gè)網(wǎng)絡(luò)連接的利器——Netty.
正文 Netty
Netty是一個(gè)NIO客戶端服務(wù)器框架:
- 它可快速輕松地開發(fā)網(wǎng)絡(luò)應(yīng)用程序,例如協(xié)議服務(wù)器和客戶端。
- 它極大地簡(jiǎn)化和簡(jiǎn)化了網(wǎng)絡(luò)編程,例如
TCP和UDP套接字服務(wù)器。
NIO是一種非阻塞IO ,它具有以下的特點(diǎn)
- 單線程可以連接多個(gè)客戶端。
- 選擇器可以實(shí)現(xiàn)但線程管理多個(gè)
Channel,新建的通道都要向選擇器注冊(cè)。 - 一個(gè)
SelectionKey鍵表示了一個(gè)特定的通道對(duì)象和一個(gè)特定的選擇器對(duì)象之間的注冊(cè)關(guān)系。 selector進(jìn)行select()操作可能會(huì)產(chǎn)生阻塞,但是可以設(shè)置阻塞時(shí)間,并且可以用wakeup()喚醒selector,所以NIO是非阻塞IO。
Netty模型selector模式
它相對(duì)普通NIO的在性能上有了提升,采用了:
NIO采用多線程的方式可以同時(shí)使用多個(gè)selector- 通過(guò)綁定多個(gè)端口的方式,使得一個(gè)
selector可以同時(shí)注冊(cè)多個(gè)ServerSocketServer - 單個(gè)線程下只能有一個(gè)
selector,用來(lái)實(shí)現(xiàn)Channel的匹配及復(fù)用

半包問(wèn)題
TCP/IP在發(fā)送消息的時(shí)候,可能會(huì)拆包,這就導(dǎo)致接收端無(wú)法知道什么時(shí)候收到的數(shù)據(jù)是一個(gè)完整的數(shù)據(jù)。在傳統(tǒng)的BIO中在讀取不到數(shù)據(jù)時(shí)會(huì)發(fā)生阻塞,但是NIO不會(huì)。為了解決NIO的半包問(wèn)題,Netty在Selector模型的基礎(chǔ)上,提出了reactor模式,從而解決客戶端請(qǐng)求在服務(wù)端不完整的問(wèn)題。
netty模型reactor模式
在selector的基礎(chǔ)上解決了半包問(wèn)題。

上圖,簡(jiǎn)單地可以描述為"boss接活,讓work干":manReactor用來(lái)接收請(qǐng)求(會(huì)與客戶端進(jìn)行握手驗(yàn)證),而subReactor用來(lái)處理請(qǐng)求(不與客戶端直接連接)。
SpringBoot使用Netty實(shí)現(xiàn)遠(yuǎn)程調(diào)用
maven依賴
<!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <optional>true</optional> </dependency> <!--netty--> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.17.Final</version> </dependency>
服務(wù)端部分
NettyServer.java:服務(wù)啟動(dòng)監(jiān)聽器
@Slf4j
public class NettyServer {
public void start() {
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8082);
//new 一個(gè)主線程組
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//new 一個(gè)工作線程組
EventLoopGroup workGroup = new NioEventLoopGroup(200);
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer())
.localAddress(socketAddress)
//設(shè)置隊(duì)列大小
.option(ChannelOption.SO_BACKLOG, 1024)
// 兩小時(shí)內(nèi)沒(méi)有數(shù)據(jù)的通信時(shí),TCP會(huì)自動(dòng)發(fā)送一個(gè)活動(dòng)探測(cè)數(shù)據(jù)報(bào)文
.childOption(ChannelOption.SO_KEEPALIVE, true);
//綁定端口,開始接收進(jìn)來(lái)的連接
try {
ChannelFuture future = bootstrap.bind(socketAddress).sync();
log.info("服務(wù)器啟動(dòng)開始監(jiān)聽端口: {}", socketAddress.getPort());
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("服務(wù)器開啟失敗", e);
} finally {
//關(guān)閉主線程組
bossGroup.shutdownGracefully();
//關(guān)閉工作線程組
workGroup.shutdownGracefully();
}
}
}
ServerChannelInitializer.java:netty服務(wù)初始化器
/**
* netty服務(wù)初始化器
**/
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//添加編解碼
socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(new NettyServerHandler());
}
}
NettyServerHandler.java:netty服務(wù)端處理器
/**
* netty服務(wù)端處理器
**/
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 客戶端連接會(huì)觸發(fā)
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("Channel active......");
}
/**
* 客戶端發(fā)消息會(huì)觸發(fā)
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("服務(wù)器收到消息: {}", msg.toString());
ctx.write("你也好哦");
ctx.flush();
}
/**
* 發(fā)生異常觸發(fā)
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
RpcServerApp.java:SpringBoot啟動(dòng)類
/**
* 啟動(dòng)類
*
*/
@Slf4j
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RpcServerApp extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(RpcServerApp.class);
}
/**
* 項(xiàng)目的啟動(dòng)方法
*
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(RpcServerApp.class, args);
//開啟Netty服務(wù)
NettyServer nettyServer =new NettyServer ();
nettyServer.start();
log.info("======服務(wù)已經(jīng)啟動(dòng)========");
}
}
客戶端部分
NettyClientUtil.java:NettyClient工具類
/**
* Netty客戶端
**/
@Slf4j
public class NettyClientUtil {
public static ResponseResult helloNetty(String msg) {
NettyClientHandler nettyClientHandler = new NettyClientHandler();
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap()
.group(group)
//該參數(shù)的作用就是禁止使用Nagle算法,使用于小數(shù)據(jù)即時(shí)傳輸
.option(ChannelOption.TCP_NODELAY, true)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("decoder", new StringDecoder());
socketChannel.pipeline().addLast("encoder", new StringEncoder());
socketChannel.pipeline().addLast(nettyClientHandler);
}
});
try {
ChannelFuture future = bootstrap.connect("127.0.0.1", 8082).sync();
log.info("客戶端發(fā)送成功....");
//發(fā)送消息
future.channel().writeAndFlush(msg);
// 等待連接被關(guān)閉
future.channel().closeFuture().sync();
return nettyClientHandler.getResponseResult();
} catch (Exception e) {
log.error("客戶端Netty失敗", e);
throw new BusinessException(CouponTypeEnum.OPERATE_ERROR);
} finally {
//以一種優(yōu)雅的方式進(jìn)行線程退出
group.shutdownGracefully();
}
}
}
NettyClientHandler.java:客戶端處理器
/**
* 客戶端處理器
**/
@Slf4j
@Setter
@Getter
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
private ResponseResult responseResult;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("客戶端Active .....");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("客戶端收到消息: {}", msg.toString());
this.responseResult = ResponseResult.success(msg.toString(), CouponTypeEnum.OPERATE_SUCCESS.getCouponTypeDesc());
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
驗(yàn)證
測(cè)試接口
@RestController
@Slf4j
public class UserController {
@PostMapping("/helloNetty")
@MethodLogPrint
public ResponseResult helloNetty(@RequestParam String msg) {
return NettyClientUtil.helloNetty(msg);
}
}
訪問(wèn)測(cè)試接口

服務(wù)端打印信息

客戶端打印信息

源碼
項(xiàng)目源碼可從的我的github中獲?。?a target="_blank" rel="external nofollow" >github源碼地址
到此這篇關(guān)于SpringBoot使用Netty實(shí)現(xiàn)遠(yuǎn)程調(diào)用的示例的文章就介紹到這了,更多相關(guān)SpringBoot Netty遠(yuǎn)程調(diào)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+WebSocket+Netty實(shí)現(xiàn)消息推送的示例代碼
- 在SpringBoot中整合使用Netty框架的詳細(xì)教程
- SpringBoot整合Netty心跳機(jī)制過(guò)程詳解
- SpringBoot集成netty實(shí)現(xiàn)websocket通信功能
- springboot之springboot與netty整合方案
- SpringBoot整合Netty+Websocket實(shí)現(xiàn)消息推送的示例代碼
- SpringBoot整合Netty服務(wù)端的實(shí)現(xiàn)示例
- springboot整合netty過(guò)程詳解
- springboot整合netty實(shí)現(xiàn)心跳檢測(cè)和自動(dòng)重連
- SpringBoot項(xiàng)目整合Netty啟動(dòng)失敗的常見錯(cuò)誤總結(jié)
相關(guān)文章
Java結(jié)構(gòu)型設(shè)計(jì)模式之裝飾模式詳解
裝飾模式(Decorator Pattern)允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有類的一個(gè)包裝。這種模式創(chuàng)建了一個(gè)裝飾類,用來(lái)包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能2023-03-03
springboot 整合EhCache實(shí)現(xiàn)單服務(wù)緩存的操作方法
這篇文章主要介紹了springboot 整合EhCache實(shí)現(xiàn)單服務(wù)緩存的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
利用Mybatis自定義排序規(guī)則實(shí)現(xiàn)復(fù)雜排序
本文主要介紹了利用Mybatis自定義排序規(guī)則實(shí)現(xiàn)復(fù)雜排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-09-09
Spring AspectJ 實(shí)現(xiàn)AOP的方法你了解嗎
這篇文章主要為大家介紹了Spring AspectJ 實(shí)現(xiàn)AOP的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01
Lombok使用@Tolerate實(shí)現(xiàn)沖突兼容問(wèn)題
這篇文章主要介紹了Lombok使用@Tolerate實(shí)現(xiàn)沖突兼容問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
Java調(diào)用dll文件的實(shí)現(xiàn)解析
這篇文章主要介紹了Java調(diào)用dll文件的實(shí)現(xiàn)解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Spring中的Eureka服務(wù)過(guò)期詳細(xì)解析
這篇文章主要介紹了Spring中的Eureka服務(wù)過(guò)期詳細(xì)解析,如果有一些服務(wù)過(guò)期了,或者宕機(jī)了,就不會(huì)調(diào)用shutdown()方法,也不會(huì)去發(fā)送請(qǐng)求下線服務(wù)實(shí)例,需要的朋友可以參考下2023-11-11

