概覽:
Netty 是什么睁枕?
為什么要用 Netty挎塌?
Netty 應(yīng)用場(chǎng)景了解么?
Netty 核心組件有哪些车酣?分別有什么作用曲稼?
EventloopGroup 了解么?和 EventLoop 啥關(guān)系?
Bootstrap 和 ServerBootstrap 了解么?
NioEventLoopGroup 默認(rèn)的構(gòu)造函數(shù)會(huì)起多少線程湖员?
Netty 線程模型了解么贫悄?
Netty 服務(wù)端和客戶端的啟動(dòng)過程了解么?
Netty 長(zhǎng)連接娘摔、心跳機(jī)制了解么窄坦?
Netty 的零拷貝了解么?
Netty 是什么凳寺?
- Netty 是一個(gè) 基于 NIO 的 client-server(客戶端服務(wù)器)框架鸭津,使用它可以快速簡(jiǎn)單地開發(fā)網(wǎng)絡(luò)應(yīng)用程序。
- 它極大地簡(jiǎn)化并優(yōu)化了 TCP 和 UDP 套接字服務(wù)器等網(wǎng)絡(luò)編程,并且性能以及安全性等很多方面甚至都要更好肠缨。
- 支持多種協(xié)議 如 FTP逆趋,SMTP,HTTP 以及各種二進(jìn)制和基于文本的傳統(tǒng)協(xié)議晒奕。
用官方的總結(jié)就是:Netty 成功地找到了一種在不妥協(xié)可維護(hù)性和性能的情況下實(shí)現(xiàn)易于開發(fā)闻书,性能,穩(wěn)定性和靈活性的方法吴汪。
除了上面介紹的之外惠窄,很多開源項(xiàng)目比如我們常用的 Dubbo蒸眠、RocketMQ漾橙、Elasticsearch、gRPC 等等都用到了 Netty楞卡。
為什么要用 Netty霜运?
統(tǒng)一的 API,支持多種傳輸類型蒋腮,阻塞和非阻塞的淘捡。
簡(jiǎn)單而強(qiáng)大的線程模型。
自帶編解碼器解決 TCP 粘包/拆包問題池摧。
自帶各種協(xié)議棧焦除。
真正的無(wú)連接數(shù)據(jù)包套接字支持。
比直接使用 Java 核心 API 有更高的吞吐量作彤、更低的延遲膘魄、更低的資源消耗和更少的內(nèi)存復(fù)制乌逐。
安全性不錯(cuò),有完整的 SSL/TLS 以及 StartTLS 支持创葡。
社區(qū)活躍
成熟穩(wěn)定浙踢,經(jīng)歷了大型項(xiàng)目的使用和考驗(yàn),而且很多開源項(xiàng)目都使用到了 Netty灿渴, 比如我們經(jīng)常接觸的 Dubbo洛波、RocketMQ 等等。
Netty 應(yīng)用場(chǎng)景了解么骚露?
作為 RPC 框架的網(wǎng)絡(luò)通信工具 :我們?cè)诜植际较到y(tǒng)中蹬挤,不同服務(wù)節(jié)點(diǎn)之間經(jīng)常需要相互調(diào)用,這個(gè)時(shí)候就需要 RPC 框架了棘幸。不同服務(wù)節(jié)點(diǎn)之間的通信是如何做的呢闻伶?可以使用 Netty 來做。比如我調(diào)用另外一個(gè)節(jié)點(diǎn)的方法的話够话,至少是要讓對(duì)方知道我調(diào)用的是哪個(gè)類中的哪個(gè)方法以及相關(guān)參數(shù)吧蓝翰!
實(shí)現(xiàn)一個(gè)自己的 HTTP 服務(wù)器 :通過 Netty 我們可以自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 HTTP 服務(wù)器,這個(gè)大家應(yīng)該不陌生女嘲。說到 HTTP 服務(wù)器的話畜份,作為 Java 后端開發(fā),我們一般使用 Tomcat 比較多欣尼。一個(gè)最基本的 HTTP 服務(wù)器可要以處理常見的 HTTP Method 的請(qǐng)求爆雹,比如 POST 請(qǐng)求、GET 請(qǐng)求等等愕鼓。
實(shí)現(xiàn)一個(gè)即時(shí)通訊系統(tǒng) :使用 Netty 我們可以實(shí)現(xiàn)一個(gè)可以聊天類似微信的即時(shí)通訊系統(tǒng)钙态,這方面的開源項(xiàng)目還蠻多的,可以自行去 Github 找一找菇晃。
實(shí)現(xiàn)消息推送系統(tǒng) :市面上有很多消息推送系統(tǒng)都是基于 Netty 來做的册倒。
Netty 核心組件有哪些?分別有什么作用磺送?
1. Channel
Channel 接口是 Netty 對(duì)網(wǎng)絡(luò)操作抽象類驻子,它除了包括基本的 I/O 操作,如 bind()估灿、connect()崇呵、read()、write() 等馅袁。
比較常用的Channel接口實(shí)現(xiàn)類是NioServerSocketChannel(服務(wù)端)和NioSocketChannel(客戶端)域慷,這兩個(gè) Channel 可以和 BIO 編程模型中的ServerSocket以及Socket兩個(gè)概念對(duì)應(yīng)上。Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 類的復(fù)雜性犹褒。
2. EventLoop
這么說吧兄纺!EventLoop(事件循環(huán))接口可以說是 Netty 中最核心的概念了!
《Netty 實(shí)戰(zhàn)》這本書是這樣介紹它的:
“EventLoop 定義了 Netty 的核心抽象化漆,用于處理連接的生命周期中所發(fā)生的事件估脆。
[圖片上傳失敗...(image-e19871-1614249023328)]
是不是很難理解?說實(shí)話座云,我學(xué)習(xí) Netty 的時(shí)候看到這句話是沒太能理解的疙赠。
說白了,EventLoop 的主要作用實(shí)際就是負(fù)責(zé)監(jiān)聽網(wǎng)絡(luò)事件并調(diào)用事件處理器進(jìn)行相關(guān) I/O 操作的處理朦拖。
那 Channel 和 EventLoop 直接有啥聯(lián)系呢圃阳?
Channel 為 Netty 網(wǎng)絡(luò)操作(讀寫等操作)抽象類,EventLoop 負(fù)責(zé)處理注冊(cè)到其上的Channel 處理 I/O 操作璧帝,兩者配合參與 I/O 操作捍岳。
3. ChannelFuture
Netty 是異步非阻塞的,所有的 I/O 操作都為異步的睬隶。
因此锣夹,我們不能立刻得到操作是否執(zhí)行成功,但是苏潜,你可以通過 ChannelFuture 接口的 addListener() 方法注冊(cè)一個(gè) ChannelFutureListener银萍,當(dāng)操作執(zhí)行成功或者失敗時(shí),監(jiān)聽就會(huì)自動(dòng)觸發(fā)返回結(jié)果恤左。
并且贴唇,你還可以通過ChannelFuture 的 channel() 方法獲取關(guān)聯(lián)的Channel
public interface ChannelFuture extends Future<Void> { Channel channel(); ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1); ...... ChannelFuture sync() throws InterruptedException;}
另外,我們還可以通過 ChannelFuture 接口的 sync()方法讓異步的操作變成同步的飞袋。
4. ChannelHandler 和 ChannelPipeline
下面這段代碼使用過 Netty 的小伙伴應(yīng)該不會(huì)陌生戳气,我們指定了序列化編解碼器以及自定義的 ChannelHandler 處理消息。
b.group(eventLoopGroup) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class)); ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class)); ch.pipeline().addLast(new KryoClientHandler()); } });
ChannelHandler 是消息的具體處理器巧鸭。他負(fù)責(zé)處理讀寫操作瓶您、客戶端連接等事情。
ChannelPipeline 為 ChannelHandler 的鏈蹄皱,提供了一個(gè)容器并定義了用于沿著鏈傳播入站和出站事件流的 API 览闰。當(dāng) Channel 被創(chuàng)建時(shí),它會(huì)被自動(dòng)地分配到它專屬的 ChannelPipeline巷折。
我們可以在 ChannelPipeline 上通過 addLast() 方法添加一個(gè)或者多個(gè)ChannelHandler ,因?yàn)橐粋€(gè)數(shù)據(jù)或者事件可能會(huì)被多個(gè) Handler 處理崖咨。當(dāng)一個(gè) ChannelHandler 處理完之后就將數(shù)據(jù)交給下一個(gè) ChannelHandler 锻拘。
EventloopGroup 了解么?和 EventLoop 啥關(guān)系?
EventLoopGroup 包含多個(gè) EventLoop(每一個(gè) EventLoop 通常內(nèi)部包含一個(gè)線程),上面我們已經(jīng)說了 EventLoop 的主要作用實(shí)際就是負(fù)責(zé)監(jiān)聽網(wǎng)絡(luò)事件并調(diào)用事件處理器進(jìn)行相關(guān) I/O 操作的處理。
并且 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理署拟,即 Thread 和 EventLoop 屬于 1 : 1 的關(guān)系婉宰,從而保證線程安全。
上圖是一個(gè)服務(wù)端對(duì) EventLoopGroup 使用的大致模塊圖推穷,其中 Boss EventloopGroup 用于接收連接心包,Worker EventloopGroup 用于具體的處理(消息的讀寫以及其他邏輯處理)。
從上圖可以看出:當(dāng)客戶端通過 connect 方法連接服務(wù)端時(shí)馒铃,bossGroup 處理客戶端連接請(qǐng)求蟹腾。當(dāng)客戶端處理完成后,會(huì)將這個(gè)連接提交給 workerGroup 來處理区宇,然后 workerGroup 負(fù)責(zé)處理其 IO 相關(guān)操作娃殖。
Bootstrap 和 ServerBootstrap 了解么?
EventLoopGroup group = new NioEventLoopGroup(); try { //創(chuàng)建客戶端啟動(dòng)引導(dǎo)/輔助類:Bootstrap Bootstrap b = new Bootstrap(); //指定線程模型 b.group(group). ...... // 嘗試建立連接 ChannelFuture f = b.connect(host, port).sync(); f.channel().closeFuture().sync(); } finally { // 優(yōu)雅關(guān)閉相關(guān)線程組資源 group.shutdownGracefully(); }
ServerBootstrap 客戶端的啟動(dòng)引導(dǎo)類/輔助類议谷,具體使用方法如下:
// 1.bossGroup 用于接收連接炉爆,workerGroup 用于具體的處理 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //2.創(chuàng)建服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); //3.給引導(dǎo)類配置兩大線程組,確定了線程模型 b.group(bossGroup, workerGroup). ...... // 6.綁定端口 ChannelFuture f = b.bind(port).sync(); // 等待連接關(guān)閉 f.channel().closeFuture().sync(); } finally { //7.優(yōu)雅關(guān)閉相關(guān)線程組資源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
從上面的示例中,我們可以看出:
Bootstrap 通常使用 connet() 方法連接到遠(yuǎn)程的主機(jī)和端口卧晓,作為一個(gè) Netty TCP 協(xié)議通信中的客戶端芬首。另外拜马,Bootstrap 也可以通過 bind() 方法綁定本地的一個(gè)端口唐片,作為 UDP 協(xié)議通信中的一端鲤拿。ServerBootstrap通常使用 bind() 方法綁定本地的端口上鸡捐,然后等待客戶端的連接励烦。Bootstrap 只需要配置一個(gè)線程組— EventLoopGroup ,而 ServerBootstrap需要配置兩個(gè)線程組— EventLoopGroup 姜骡,一個(gè)用于接收連接庶艾,一個(gè)用于具體的處理魔种。
NioEventLoopGroup 默認(rèn)的構(gòu)造函數(shù)會(huì)起多少線程掸屡?
回顧我們?cè)谏厦鎸懙姆?wù)器端的代碼:
// 1.bossGroup 用于接收連接封寞,workerGroup 用于具體的處理EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();
為了搞清楚NioEventLoopGroup 默認(rèn)的構(gòu)造函數(shù) 到底創(chuàng)建了多少個(gè)線程,我們來看一下它的源碼仅财。
/** * 無(wú)參構(gòu)造函數(shù)狈究。 * nThreads:0 / public NioEventLoopGroup() { //調(diào)用下一個(gè)構(gòu)造方法 this(0); } /* * Executor:null / public NioEventLoopGroup(int nThreads) { //繼續(xù)調(diào)用下一個(gè)構(gòu)造方法 this(nThreads, (Executor) null); } //中間省略部分構(gòu)造函數(shù) /* * RejectedExecutionHandler():RejectedExecutionHandlers.reject() */ public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,final SelectStrategyFactory selectStrategyFactory) { //開始調(diào)用父類的構(gòu)造函數(shù) super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject()); }
一直向下走下去的話,你會(huì)發(fā)現(xiàn)在 MultithreadEventLoopGroup 類中有相關(guān)的指定線程數(shù)的代碼盏求,如下:
// 從1抖锥,系統(tǒng)屬性,CPU核心數(shù)2 這三個(gè)值中取出一個(gè)最大的 //可以得出 DEFAULT_EVENT_LOOP_THREADS 的值為CPU核心數(shù)2 private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); // 被調(diào)用的父類構(gòu)造函數(shù)碎罚,NioEventLoopGroup 默認(rèn)的構(gòu)造函數(shù)會(huì)起多少線程的秘密所在 // 當(dāng)指定的線程數(shù)nThreads為0時(shí)磅废,使用默認(rèn)的線程數(shù)DEFAULT_EVENT_LOOP_THREADS protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) { super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args); }
綜上,我們發(fā)現(xiàn) NioEventLoopGroup 默認(rèn)的構(gòu)造函數(shù)實(shí)際會(huì)起的線程數(shù)為 CPU核心數(shù)*2荆烈。
另外拯勉,如果你繼續(xù)深入下去看構(gòu)造函數(shù)的話竟趾,你會(huì)發(fā)現(xiàn)每個(gè)NioEventLoopGroup對(duì)象內(nèi)部都會(huì)分配一組NioEventLoop,其大小是 nThreads, 這樣就構(gòu)成了一個(gè)線程池宫峦, 一個(gè)NIOEventLoop 和一個(gè)線程相對(duì)應(yīng)岔帽,這和我們上面說的 EventloopGroup 和 EventLoop關(guān)系這部分內(nèi)容相對(duì)應(yīng)。
Netty 線程模型了解么导绷?
“Reactor 模式基于事件驅(qū)動(dòng)犀勒,采用多路復(fù)用將事件分發(fā)給相應(yīng)的 Handler 處理,非常適合處理海量 IO 的場(chǎng)景妥曲。
在 Netty 主要靠 NioEventLoopGroup 線程池來實(shí)現(xiàn)具體的線程模型的 贾费。
我們實(shí)現(xiàn)服務(wù)端的時(shí)候,一般會(huì)初始化兩個(gè)線程組:
bossGroup :接收連接逾一。workerGroup :負(fù)責(zé)具體的處理铸本,交由對(duì)應(yīng)的 Handler 處理。下面我們來詳細(xì)看一下 Netty 中的線程模型吧遵堵!
1.單線程模型:
一個(gè)線程需要執(zhí)行處理所有的 accept箱玷、read、decode陌宿、process锡足、encode、send 事件壳坪。對(duì)于高負(fù)載舶得、高并發(fā),并且對(duì)性能要求比較高的場(chǎng)景不適用爽蝴。
對(duì)應(yīng)到 Netty 代碼是下面這樣的
“使用 NioEventLoopGroup 類的無(wú)參構(gòu)造函數(shù)設(shè)置線程數(shù)量的默認(rèn)值就是 CPU 核心數(shù) *2沐批。
//1.eventGroup既用于處理客戶端連接,又負(fù)責(zé)具體的處理蝎亚。 EventLoopGroup eventGroup = new NioEventLoopGroup(1); //2.創(chuàng)建服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); boobtstrap.group(eventGroup, eventGroup) //......
2.多線程模型
一個(gè) Acceptor 線程只負(fù)責(zé)監(jiān)聽客戶端的連接九孩,一個(gè) NIO 線程池負(fù)責(zé)具體處理:accept、read发框、decode躺彬、process、encode梅惯、send 事件宪拥。滿足絕大部分應(yīng)用場(chǎng)景,并發(fā)連接量不大的時(shí)候沒啥問題铣减,但是遇到并發(fā)連接大的時(shí)候就可能會(huì)出現(xiàn)問題她君,成為性能瓶頸。
對(duì)應(yīng)到 Netty 代碼是下面這樣的:
// 1.bossGroup 用于接收連接徙歼,workerGroup 用于具體的處理EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try { //2.創(chuàng)建服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); //3.給引導(dǎo)類配置兩大線程組,確定了線程模型 b.group(bossGroup, workerGroup) //......
3.主從多線程模型
從一個(gè) 主線程 NIO 線程池中選擇一個(gè)線程作為 Acceptor 線程犁河,綁定監(jiān)聽端口鳖枕,接收客戶端連接的連接魄梯,其他線程負(fù)責(zé)后續(xù)的接入認(rèn)證等工作桨螺。連接建立完成后,Sub NIO 線程池負(fù)責(zé)具體處理 I/O 讀寫酿秸。如果多線程模型無(wú)法滿足你的需求的時(shí)候灭翔,可以考慮使用主從多線程模型 。
// 1.bossGroup 用于接收連接辣苏,workerGroup 用于具體的處理EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try { //2.創(chuàng)建服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); //3.給引導(dǎo)類配置兩大線程組,確定了線程模型 b.group(bossGroup, workerGroup) //......
Netty 服務(wù)端和客戶端的啟動(dòng)過程了解么肝箱?
服務(wù)端
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //2.創(chuàng)建服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); //3.給引導(dǎo)類配置兩大線程組,確定了線程模型 b.group(bossGroup, workerGroup) // (非必備)打印日志 .handler(new LoggingHandler(LogLevel.INFO)) // 4.指定 IO 模型 .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); //5.可以自定義客戶端消息的業(yè)務(wù)處理邏輯 p.addLast(new HelloServerHandler()); } }); // 6.綁定端口,調(diào)用 sync 方法阻塞知道綁定完成 ChannelFuture f = b.bind(port).sync(); // 7.阻塞等待直到服務(wù)器Channel關(guān)閉(closeFuture()方法獲取Channel 的CloseFuture對(duì)象,然后調(diào)用sync()方法) f.channel().closeFuture().sync(); } finally { //8.優(yōu)雅關(guān)閉相關(guān)線程組資源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
簡(jiǎn)單解析一下服務(wù)端的創(chuàng)建過程具體是怎樣的:
- 首先你創(chuàng)建了兩個(gè) NioEventLoopGroup 對(duì)象實(shí)例:bossGroup 和 workerGroup稀蟋。
bossGroup : 用于處理客戶端的 TCP 連接請(qǐng)求煌张。workerGroup :負(fù)責(zé)每一條連接的具體讀寫數(shù)據(jù)的處理邏輯,真正負(fù)責(zé) I/O 讀寫操作退客,交由對(duì)應(yīng)的 Handler 處理骏融。舉個(gè)例子:我們把公司的老板當(dāng)做 bossGroup,員工當(dāng)做 workerGroup萌狂,bossGroup 在外面接完活之后档玻,扔給 workerGroup 去處理。一般情況下我們會(huì)指定 bossGroup 的 線程數(shù)為 1(并發(fā)連接量不大的時(shí)候) 茫藏,workGroup 的線程數(shù)量為 CPU 核心數(shù) *2 误趴。另外,根據(jù)源碼來看务傲,使用 NioEventLoopGroup 類的無(wú)參構(gòu)造函數(shù)設(shè)置線程數(shù)量的默認(rèn)值就是 CPU 核心數(shù) *2 凉当。
接下來 我們創(chuàng)建了一個(gè)服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap,這個(gè)類將引導(dǎo)我們進(jìn)行服務(wù)端的啟動(dòng)工作售葡。
通過 .group() 方法給引導(dǎo)類 ServerBootstrap 配置兩大線程組看杭,確定了線程模型。
通過下面的代碼天通,我們實(shí)際配置的是多線程模型泊窘,這個(gè)在上面提到過。
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();
- 通過channel()方法給引導(dǎo)類 ServerBootstrap指定了 IO 模型為NIO
NioServerSocketChannel :指定服務(wù)端的 IO 模型為 NIO像寒,與 BIO 編程模型中的ServerSocket對(duì)應(yīng)NioSocketChannel : 指定客戶端的 IO 模型為 NIO烘豹, 與 BIO 編程模型中的Socket對(duì)應(yīng)5.通過 .childHandler()給引導(dǎo)類創(chuàng)建一個(gè)ChannelInitializer ,然后制定了服務(wù)端消息的業(yè)務(wù)處理邏輯 HelloServerHandler 對(duì)象6.調(diào)用 ServerBootstrap 類的 bind()方法綁定端口客戶端
1.創(chuàng)建一個(gè) NioEventLoopGroup 對(duì)象實(shí)例 EventLoopGroup group = new NioEventLoopGroup(); try { //2.創(chuàng)建客戶端啟動(dòng)引導(dǎo)/輔助類:Bootstrap Bootstrap b = new Bootstrap(); //3.指定線程組 b.group(group) //4.指定 IO 模型 .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); // 5.這里可以自定義消息的業(yè)務(wù)處理邏輯 p.addLast(new HelloClientHandler(message)); } }); // 6.嘗試建立連接 ChannelFuture f = b.connect(host, port).sync(); // 7.等待連接關(guān)閉(阻塞诺祸,直到Channel關(guān)閉) f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); }
繼續(xù)分析一下客戶端的創(chuàng)建流程:
創(chuàng)建一個(gè) NioEventLoopGroup 對(duì)象實(shí)例
創(chuàng)建客戶端啟動(dòng)的引導(dǎo)類是 Bootstrap
通過 .group() 方法給引導(dǎo)類 Bootstrap 配置一個(gè)線程組
通過channel()方法給引導(dǎo)類 Bootstrap指定了 IO 模型為NIO
通過 .childHandler()給引導(dǎo)類創(chuàng)建一個(gè)ChannelInitializer 携悯,然后制定了客戶端消息的業(yè)務(wù)處理邏輯 HelloClientHandler 對(duì)象
調(diào)用 Bootstrap 類的 connect()方法進(jìn)行連接,這個(gè)方法需要指定兩個(gè)參數(shù):
inetHost : ip 地址inetPort : 端口號(hào)public ChannelFuture connect(String inetHost, int inetPort) { return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort)); } public ChannelFuture connect(SocketAddress remoteAddress) { ObjectUtil.checkNotNull(remoteAddress, "remoteAddress"); this.validate(); return this.doResolveAndConnect(remoteAddress, this.config.localAddress()); }
connect 方法返回的是一個(gè) Future 類型的對(duì)象
public interface ChannelFuture extends Future<Void> { ......}
也就是說這個(gè)方是異步的筷笨,我們通過 addListener 方法可以監(jiān)聽到連接是否成功憔鬼,進(jìn)而打印出連接信息龟劲。具體做法很簡(jiǎn)單,只需要對(duì)代碼進(jìn)行以下改動(dòng):
ChannelFuture f = b.connect(host, port).addListener(future -> { if (future.isSuccess()) { System.out.println("連接成功!"); } else { System.err.println("連接失敗!"); }}).sync();
什么是 TCP 粘包/拆包?有什么解決辦法呢轴或?
TCP 粘包/拆包 就是你基于 TCP 發(fā)送數(shù)據(jù)的時(shí)候昌跌,出現(xiàn)了多個(gè)字符串“粘”在了一起或者一個(gè)字符串被“拆”開的問題。比如你多次發(fā)送:“你好,你真帥罢昭恪蚕愤!哥哥!”饺蚊,但是客戶端接收到的可能是下面這樣的:
那有什么解決辦法呢?
1.使用 Netty 自帶的解碼器
LineBasedFrameDecoder : 發(fā)送端發(fā)送數(shù)據(jù)包的時(shí)候萍诱,每個(gè)數(shù)據(jù)包之間以換行符作為分隔,LineBasedFrameDecoder 的工作原理是它依次遍歷 ByteBuf 中的可讀字節(jié)污呼,判斷是否有換行符裕坊,然后進(jìn)行相應(yīng)的截取。DelimiterBasedFrameDecoder : 可以自定義分隔符解碼器燕酷,LineBasedFrameDecoder 實(shí)際上是一種特殊的 DelimiterBasedFrameDecoder 解碼器籍凝。FixedLengthFrameDecoder: 固定長(zhǎng)度解碼器,它能夠按照指定的長(zhǎng)度對(duì)消息進(jìn)行相應(yīng)的拆包悟狱。LengthFieldBasedFrameDecoder:2.自定義序列化編解碼器
在 Java 中自帶的有實(shí)現(xiàn) Serializable 接口來實(shí)現(xiàn)序列化静浴,但由于它性能、安全性等原因一般情況下是不會(huì)被使用到的挤渐。
通常情況下苹享,我們使用 Protostuff、Hessian2浴麻、json 序列方式比較多得问,另外還有一些序列化性能非常好的序列化方式也是很好的選擇:
專門針對(duì) Java 語(yǔ)言的:Kryo,F(xiàn)ST 等等跨語(yǔ)言的:Protostuff(基于 protobuf 發(fā)展而來)软免,ProtoBuf宫纬,Thrift,Avro膏萧,MsgPack 等等“由于篇幅問題漓骚,這部分內(nèi)容會(huì)在后續(xù)的文章中詳細(xì)分析介紹~~~
Netty 長(zhǎng)連接、心跳機(jī)制了解么榛泛?
TCP 長(zhǎng)連接和短連接了解么蝌蹂?
我們知道 TCP 在進(jìn)行讀寫之前,server 與 client 之間必須提前建立一個(gè)連接曹锨。建立連接的過程孤个,需要我們常說的三次握手,釋放/關(guān)閉連接的話需要四次揮手沛简。這個(gè)過程是比較消耗網(wǎng)絡(luò)資源并且有時(shí)間延遲的齐鲤。
所謂斥废,短連接說的就是 server 端 與 client 端建立連接之后,讀寫完成之后就關(guān)閉掉連接给郊,如果下一次再要互相發(fā)送消息牡肉,就要重新連接。短連接的有點(diǎn)很明顯丑罪,就是管理和實(shí)現(xiàn)都比較簡(jiǎn)單荚板,缺點(diǎn)也很明顯凤壁,每一次的讀寫都要建立連接必然會(huì)帶來大量網(wǎng)絡(luò)資源的消耗吩屹,并且連接的建立也需要耗費(fèi)時(shí)間。
長(zhǎng)連接說的就是 client 向 server 雙方建立連接之后拧抖,即使 client 與 server 完成一次讀寫煤搜,它們之間的連接并不會(huì)主動(dòng)關(guān)閉,后續(xù)的讀寫操作會(huì)繼續(xù)使用這個(gè)連接唧席。長(zhǎng)連接的可以省去較多的 TCP 建立和關(guān)閉的操作擦盾,降低對(duì)網(wǎng)絡(luò)資源的依賴,節(jié)約時(shí)間淌哟。對(duì)于頻繁請(qǐng)求資源的客戶來說迹卢,非常適用長(zhǎng)連接。
為什么需要心跳機(jī)制徒仓?Netty 中心跳機(jī)制了解么腐碱?
在 TCP 保持長(zhǎng)連接的過程中,可能會(huì)出現(xiàn)斷網(wǎng)等網(wǎng)絡(luò)異常出現(xiàn)掉弛,異常發(fā)生的時(shí)候症见, client 與 server 之間如果沒有交互的話,它們是無(wú)法發(fā)現(xiàn)對(duì)方已經(jīng)掉線的殃饿。為了解決這個(gè)問題, 我們就需要引入 心跳機(jī)制谋作。
心跳機(jī)制的工作原理是: 在 client 與 server 之間在一定時(shí)間內(nèi)沒有數(shù)據(jù)交互時(shí), 即處于 idle 狀態(tài)時(shí), 客戶端或服務(wù)器就會(huì)發(fā)送一個(gè)特殊的數(shù)據(jù)包給對(duì)方, 當(dāng)接收方收到這個(gè)數(shù)據(jù)報(bào)文后, 也立即發(fā)送一個(gè)特殊的數(shù)據(jù)報(bào)文, 回應(yīng)發(fā)送方, 此即一個(gè) PING-PONG 交互。所以, 當(dāng)某一端收到心跳消息后, 就知道了對(duì)方仍然在線, 這就確保 TCP 連接的有效性.
TCP 實(shí)際上自帶的就有長(zhǎng)連接選項(xiàng)乎芳,本身是也有心跳包機(jī)制遵蚜,也就是 TCP 的選項(xiàng):SO_KEEPALIVE。但是奈惑,TCP 協(xié)議層面的長(zhǎng)連接靈活性不夠吭净。所以,一般情況下我們都是在應(yīng)用層協(xié)議上實(shí)現(xiàn)自定義心跳機(jī)制的携取,也就是在 Netty 層面通過編碼實(shí)現(xiàn)攒钳。通過 Netty 實(shí)現(xiàn)心跳機(jī)制的話,核心類是 IdleStateHandler 雷滋。
Netty 的零拷貝了解么不撑?
維基百科是這樣介紹零拷貝的:
“零復(fù)制(英語(yǔ):Zero-copy文兢;也譯零拷貝)技術(shù)是指計(jì)算機(jī)執(zhí)行操作時(shí),CPU 不需要先將數(shù)據(jù)從某處內(nèi)存復(fù)制到另一個(gè)特定區(qū)域焕檬。這種技術(shù)通常用于通過網(wǎng)絡(luò)傳輸文件時(shí)節(jié)省 CPU 周期和內(nèi)存帶寬姆坚。
在 OS 層面上的 Zero-copy 通常指避免在 用戶態(tài)(User-space) 與 內(nèi)核態(tài)(Kernel-space) 之間來回拷貝數(shù)據(jù)。而在 Netty 層面 实愚,零拷貝主要體現(xiàn)在對(duì)于數(shù)據(jù)操作的優(yōu)化兼呵。
Netty 中的零拷貝體現(xiàn)在以下幾個(gè)方面:
使用 Netty 提供的 CompositeByteBuf 類, 可以將多個(gè)ByteBuf 合并為一個(gè)邏輯上的 ByteBuf, 避免了各個(gè) ByteBuf 之間的拷貝。ByteBuf 支持 slice 操作, 因此可以將 ByteBuf 分解為多個(gè)共享同一個(gè)存儲(chǔ)區(qū)域的 ByteBuf, 避免了內(nèi)存的拷貝腊敲。通過 FileRegion 包裝的FileChannel.tranferTo 實(shí)現(xiàn)文件傳輸, 可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo) Channel, 避免了傳統(tǒng)通過循環(huán) write 方式導(dǎo)致的內(nèi)存拷貝問題.