前言
「作者主頁」:雪碧有白泡泡
「個人網(wǎng)站」:雪碧的個人網(wǎng)站
ChatGPT體驗地址
IO
在Java基礎(chǔ)中,IO流是一個重要操作,先上八股
- BIO:傳統(tǒng)的IO,同步阻塞,一個連接一個線程。一般不怎么使用
- AIO:JDK7引入的,異步非阻塞IO
- NIO:JDK1.4之后新的API,是多路復(fù)用,允許你一次性處理多個連接,而不需要等待每個連接的完成。(NIO 多路復(fù)用的核心概念是 Selector(選擇器)和 Channel(通道)通過Channel、Buffer和Selector來進行數(shù)據(jù)傳輸和事件處理)
Netty
Netty是建立在NIO之上的一個框架,提供了更高級的抽象,如ChannelHandler和EventLoop,簡化了事件處理和網(wǎng)絡(luò)編程。
執(zhí)行流程如下圖
具有高性能,高可靠性,高可擴展性,還支持多種協(xié)議
我們以聊天流程為例
- 服務(wù)端啟動
- 客戶端啟動
- 客戶端啟動連接上的時候,告知服務(wù)端
- 服務(wù)端讀取到客戶端的信息后立即發(fā)送信息給客戶端
- 客戶端收到信息后也發(fā)送給服務(wù)端
1. 引入依賴
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.76.Final</version>
</dependency>
2. 服務(wù)端
@Slf4j
public class NettyServer {
private final static int PORT = 9012;
public static void main(String[] args) throws InterruptedException {
/**
* 包含childGroup,childHandler,config,繼承的父類AbstractBootstrap包括了parentGroup
* */
ServerBootstrap bootstrap = new ServerBootstrap();
/**
* EventLoopGroup用于處理所有ServerChannel和Channel的所有事件和IO
* */
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
/**
* 綁定兩個事件組
* */
bootstrap.group(parentGroup, childGroup)
/**
* 初始化socket,定義tcp連接的實例
* 內(nèi)部調(diào)用ReflectiveChannelFactory實現(xiàn)對NioServerSocketChannel實例化
* channelFactory是在AbstractBootstrap,也就是bootstrap的父類
* */
.channel(NioServerSocketChannel.class)
/**
* 添加處理器
* ChannelInitializer包括了Set<ChannelHandlerContext> initMap
*
* 這里比較有趣的事情就是使用被注冊的channel去初始化其他的channel,
* 等初始化結(jié)束后移除該channel
* 所以SocketChannel是一個工具,
*
* 在bind綁定端口的時候,進行初始化和注冊initAndRegister,
* 通過channel = channelFactory.newChannel()得到初始化channel
* init(channel)真正開始初始化,
* p = channel.pipeline()得到ChannelPipeline,
* p.addLast開始添加
* ch.eventLoop().execute將childHandler賦值并開啟一個任務(wù)setAutoRead
* 所以最后在監(jiān)聽讀取的時候?qū)凑障旅嫣砑拥腸hannel進行讀取
*
* ChannelInitializer繼承了ChannelInboundHandlerAdapter
* 間接繼承ChannelHandlerAdapter,ChannelInboundHandler,
* */
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/**
* ByteBuf和String之間的轉(zhuǎn)換
*
* Decoders解密
* pipeline.addLast("frameDecoder", new {@link LineBasedFrameDecoder}(80))
* pipeline.addLast("stringDecoder", new {@link StringDecoder}(CharsetUtil.UTF_8))
*
* Encoder加密
* pipeline.addLast("stringEncoder", new {@link StringEncoder}(CharsetUtil.UTF_8))
*
* 使用上面的加密解密后就可以直接讀取字符串
* void channelRead({@link ChannelHandlerContext} ctx, String msg) {
* ch.write("Did you say '" + msg + "'?\n")
* }
*
* */
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//自定義處理器
pipeline.addLast(new ServerHandler1());
}
});
ChannelFuture future = bootstrap.bind(PORT).sync();
log.info("服務(wù)器已啟動");
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
這段代碼實現(xiàn)了一個使用Netty框架的服務(wù)器端,它監(jiān)聽指定的端口并處理客戶端的連接請求。
- 創(chuàng)建一個
ServerBootstrap
實例,用于啟動服務(wù)器。- 創(chuàng)建兩個
EventLoopGroup
實例,parentGroup
用于處理服務(wù)器的連接請求,childGroup
用于處理客戶端的數(shù)據(jù)通信。- 綁定事件組到
ServerBootstrap
實例。- 指定使用的
NioServerSocketChannel
作為服務(wù)器套接字通道的實現(xiàn)類。- 添加處理器到
ChannelInitializer
中,該處理器負責(zé)初始化和配置新連接的SocketChannel
。- 在處理器中,通過
ChannelPipeline
添加了如下處理器:
StringDecoder
:處理傳入的字節(jié)數(shù)據(jù),并將其解碼為字符串。StringEncoder
:處理傳出的字符串?dāng)?shù)據(jù),并將其編碼為字節(jié)。ServerHandler1
:自定義的處理器,用于處理客戶端發(fā)送的消息。- 綁定服務(wù)器的端口號,啟動服務(wù)器。
- 等待服務(wù)器的關(guān)閉事件。
- 處理器
@Slf4j
public class ServerHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("Client Address ====== {},讀取的信息:{}", ctx.channel().remoteAddress(),msg);
ctx.channel().writeAndFlush("服務(wù)端writeAndFlush:我是服務(wù)端");
ctx.fireChannelActive();
//睡眠
TimeUnit.MILLISECONDS.sleep(500);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//打印異常
cause.printStackTrace();
//關(guān)閉Channel連接,并通知ChannelFuture,通常是出現(xiàn)異常或者是完成了操作
ctx.close();
}
}
4. 客戶端
@Slf4j
public class NettyClient {
private final static int PORT = 9012;
private final static String IP = "localhost";
public static void main(String[] args) throws InterruptedException {
/**
* 服務(wù)端是ServerBootstrap,客戶端是Bootstrap
* Bootstrap引導(dǎo)channel連接,UDP連接用bind方法,TCP連接用connect方法
* */
Bootstrap bootstrap = new Bootstrap();
/**
* 服務(wù)端是EventLoopGroup,客戶端是NioEventLoopGroup
* 這里創(chuàng)建默認0個線程,一個線程工廠,一個選擇器提供者
* */
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
bootstrap.group(eventLoopGroup)
/**
* 初始化socket,定義tcp連接的實例
* */
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/**
* 進行字符串的轉(zhuǎn)換
* */
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
/**
* 自定義處理器
* */
pipeline.addLast(new ClientHandler1());
}
});
ChannelFuture future = bootstrap.connect(IP, PORT).sync();
log.info("客戶端訪問");
future.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
這段代碼實現(xiàn)了一個使用Netty框架的客戶端,它連接到指定的服務(wù)器端并與服務(wù)器進行通信。
- 創(chuàng)建一個
Bootstrap
實例,用于啟動客戶端。- 創(chuàng)建一個
NioEventLoopGroup
實例,用于處理客戶端的事件和IO操作。- 綁定事件組到
Bootstrap
實例。- 指定使用的
NioSocketChannel
作為客戶端套接字通道的實現(xiàn)類。- 添加處理器到
ChannelInitializer
中,該處理器負責(zé)初始化和配置客戶端連接的SocketChannel
。- 在處理器中,通過
ChannelPipeline
添加了如下處理器:
StringDecoder
:處理傳入的字節(jié)數(shù)據(jù),并將其解碼為字符串。StringEncoder
:處理傳出的字符串?dāng)?shù)據(jù),并將其編碼為字節(jié)。ClientHandler1
:自定義的處理器,用于處理與服務(wù)器之間的通信。- 使用
Bootstrap
的connect()
方法連接到指定的服務(wù)器IP和端口。- 等待連接完成。
- 在連接成功后,打印日志信息。
- 等待客戶端的關(guān)閉事件。
- 處理器
@Slf4j
public class ClientHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("客戶端讀取的信息:{}", msg);
ctx.channel().writeAndFlush("客戶端writeAndFlush:我是客戶端");
TimeUnit.MILLISECONDS.sleep(5000);
}
/**
* 當(dāng)事件到達pipeline時候觸發(fā)
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().writeAndFlush("客戶端:開始聊天");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
//關(guān)閉Channel連接
ctx.close();
}
}
結(jié)果
服務(wù)端日志
Client Address ====== /127.0.0.1:63740,讀取的信息:客戶端:開始聊天
Client Address ====== /127.0.0.1:63740,讀取的信息:客戶端writeAndFlush:我是客戶端
Client Address ====== /127.0.0.1:63740,讀取的信息:客戶端writeAndFlush:我是客戶端
客戶端日志
客戶端讀取的信息:服務(wù)端writeAndFlush:我是服務(wù)端
客戶端讀取的信息:服務(wù)端writeAndFlush:我是服務(wù)端
總結(jié)
引導(dǎo)類-Bootstarp和ServerBootstrap
Bootstarp和ServerBootstrap被稱為引導(dǎo)類,使你的應(yīng)用程序和網(wǎng)絡(luò)層相隔離。類似java項目的啟動類。
連接-NioSocketChannel
客戶端和服務(wù)端的啟動都是采用配置的channel去連接處理器,這里服務(wù)端和客戶端是用NioSocketChannel
事件組-EventLoopGroup和NioEventLoopGroup
客戶端使用的是NioEventLoopGroup,服務(wù)端使用的是EventLoopGroup。 服務(wù)端和客戶端的引導(dǎo)類啟動后實現(xiàn)了配置的運行,客戶端和服務(wù)端的連接都是采用NioSocketChannel。 連接的流程:
- 客戶端創(chuàng)建一個channel
- channel對應(yīng)一個EventLoop,EventLoop存放到NioEventLoopGroup中
- 服務(wù)端監(jiān)聽到后,創(chuàng)建一個channel連接,channel對應(yīng)一個EventLoop,EventLoop存放到子的EventLoopGroup,父的事件組負責(zé)監(jiān)聽,一個事件對應(yīng)一個子事件組。
在這里可以認為父是boss監(jiān)聽組,子是工作組。- 當(dāng)客戶端發(fā)送信息的時候,先被父監(jiān)聽,然后將異步調(diào)用工作組。
- 消息會經(jīng)過事件組的所有處理器。
實際上服務(wù)端的事件組也可以使用NioEventLoopGroup。
注博主+三連(點贊、收藏、評論)
購買鏈接:https://item.jd.com/13836258.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-776965.html
到了這里,關(guān)于構(gòu)建異步高并發(fā)服務(wù)器:Netty與Spring Boot的完美結(jié)合的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!