netty 相關(guān)問題

概覽:

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 是什么凳寺?

  1. Netty 是一個(gè) 基于 NIO 的 client-server(客戶端服務(wù)器)框架鸭津,使用它可以快速簡(jiǎn)單地開發(fā)網(wǎng)絡(luò)應(yīng)用程序。
  2. 它極大地簡(jiǎn)化并優(yōu)化了 TCP 和 UDP 套接字服務(wù)器等網(wǎng)絡(luò)編程,并且性能以及安全性等很多方面甚至都要更好肠缨。
  3. 支持多種協(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)建過程具體是怎樣的:

  1. 首先你創(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 凉当。

  1. 接下來 我們創(chuàng)建了一個(gè)服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap,這個(gè)類將引導(dǎo)我們進(jìn)行服務(wù)端的啟動(dòng)工作售葡。

  2. 通過 .group() 方法給引導(dǎo)類 ServerBootstrap 配置兩大線程組看杭,確定了線程模型。

通過下面的代碼天通,我們實(shí)際配置的是多線程模型泊窘,這個(gè)在上面提到過。

EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();

  1. 通過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)建流程:

  1. 創(chuàng)建一個(gè) NioEventLoopGroup 對(duì)象實(shí)例

  2. 創(chuàng)建客戶端啟動(dòng)的引導(dǎo)類是 Bootstrap

  3. 通過 .group() 方法給引導(dǎo)類 Bootstrap 配置一個(gè)線程組

  4. 通過channel()方法給引導(dǎo)類 Bootstrap指定了 IO 模型為NIO

  5. 通過 .childHandler()給引導(dǎo)類創(chuàng)建一個(gè)ChannelInitializer 携悯,然后制定了客戶端消息的業(yè)務(wù)處理邏輯 HelloClientHandler 對(duì)象

  6. 調(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)存拷貝問題.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末击喂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碰辅,更是在濱河造成了極大的恐慌懂昂,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件没宾,死亡現(xiàn)場(chǎng)離奇詭異凌彬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)循衰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門铲敛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人会钝,你說我怎么就攤上這事伐蒋。” “怎么了顽素?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵咽弦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我胁出,道長(zhǎng)型型,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任全蝶,我火速辦了婚禮闹蒜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抑淫。我一直安慰自己绷落,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布始苇。 她就那樣靜靜地躺著砌烁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上函喉,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天避归,我揣著相機(jī)與錄音,去河邊找鬼管呵。 笑死梳毙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捐下。 我是一名探鬼主播账锹,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼坷襟!你這毒婦竟也來了奸柬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啤握,失蹤者是張志新(化名)和其女友劉穎鸟缕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體排抬,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年授段,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蹲蒲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侵贵,死狀恐怖届搁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情窍育,我是刑警寧澤卡睦,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站漱抓,受9級(jí)特大地震影響表锻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乞娄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一瞬逊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仪或,春花似錦确镊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至到旦,卻和暖如春旨巷,著一層夾襖步出監(jiān)牢的瞬間廓块,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工契沫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留带猴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓懈万,卻偏偏與公主長(zhǎng)得像拴清,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子会通,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容