netty-bio
: 阻塞型
網路通訊demo。
netty-nio
: 引入channel(通道)、buffer(緩衝區)、selector(選擇器)的概念,採用事件驅動的方式
,使用單個執行緒就可以監聽多個使用者端通道,改進bio模式下執行緒阻塞等待造成的資源浪費
。
netty-demo
: Netty小demo,認識Netty初體驗。
netty-groupchat
: 使用Netty編寫一個群聊系統。
netty-http
: Netty的HTTP呼叫demo。
netty-bytebuf
: Netty緩衝區使用demo。
netty-decoder
: Netty編解碼,handler呼叫鏈使用範例。
netty-idlestate
: Netty心跳包使用範例。
netty-sticking
: 自定義協定與handler,解決TCP傳輸粘包與拆包問題。
netty-rpc
: 使用Netty自定義實現RPC
通訊。
Demo地址:https://gitee.com/LHDAXIE/netty
netty-bio
模組模擬測試採用socket的bio
方式進行網路通訊。
blocking io
:同步並阻塞
,伺服器實現模式為一個連線一個執行緒,即使用者端有連線請求時伺服器就需要啟動一個執行緒進行處理,如果這個連線不做任何事情就會進入阻塞等待狀態,造成不必要的執行緒開銷。
適用於連線資料小且連線固定
的系統架構。
架構示意圖:
netty-nio
模組non-blocking io
:同步非阻塞
,在bio
的架構上進行改進,引入channel(通道)、buffer(緩衝區)、selector(選擇器)的概念,採用事件驅動的方式,使用單個執行緒就可以監聽多個使用者端通道,改進bio
模式下執行緒阻塞等待造成的資源浪費。
架構示意圖:
關鍵:select會根據不同的事件,在各個channel通道上進行切換。
本質上是一個可以讀寫資料(關鍵)的記憶體塊,nio
的讀取與寫入資料都必須是經過buffer的。
把通道看做流、把通道看做流、把通道看做流,重要的事情說三遍,會很好理解。 nio
引入的通道類似bio
中流的概念,不同之處在於:
通道可以同時進行讀寫操作,而流只能讀或者寫
通道可以實現非同步讀寫資料
通道可以從緩衝區讀資料,也可以寫資料到緩衝區(雙向的概念)
NIOFileOper01
: 本地檔案寫資料使用ByteBuffer
與FileChannel
,將「hello,李嘉圖」NIOFileOper01.txt
檔案中。
NIOFileOper02
: 本地檔案讀資料使用ByteBuffer
(緩衝) 和 FileChannel
(通道), 將 NIOFileOper01.txt
中的資料讀入到程式,並顯示在控制檯螢幕
NIOFileOper03
: 使用一個Buffer完成檔案讀取使用 FileChannel
(通道) 和 方法 read , write,完成檔案的拷貝
NIOFileCopy
:拷貝檔案 transferFrom
方法使用 FileChannel
(通道) 和 方法 transferFrom
,完成檔案的拷貝
核心:selector能夠檢測多個註冊的通道上是否有事件發生(多個channel以事件的方式可以註冊到同一個selector),如果有事件發生,便獲取事件然後針對每個事件進行相應的處理。 這樣就可以做到只使用一個單執行緒去管理多個通道。
只有在連線/通道真正有讀寫事件發生時,才會進行讀寫
,就大大地減少了系統開銷,並且不必為每個連線都建立一個執行緒,不用去維護多個執行緒。
原理圖:
說明:
當用戶端連線時,會通過ServerSocketChannel
得到SocketChannel
。
Selector進行監聽select方法,返回有事件發生的通道的個數。
將socketChannel
註冊到Selector上,register(),一個selector上可以註冊多個SocketChannel
。
註冊後返回一個selectionKey
,會和該selector關聯。
進一步得到各個selectionKey
(有事件發生)。
再通過selectionKey
反向獲取socketChannel
,方法channel()。
可以通過得到的channel,完成業務邏輯。
非同步的、基於事件驅動的網路應用程式框架,用以快速開發高效能、高可靠的網路IO程式。
有了NIO
為什麼還需要Netty?
不需要過於關注底層的邏輯,對下面的sdk等進行封裝,相當於簡化和流程化了NIO的開發過程
。spring
和springboot
的關係差不多。
因為 Netty 5
出現重大bug,已經被官網廢棄了,目前推薦使用的是Netty 4.x
的穩定版本。
模型特點:
採用阻塞IO模式獲取輸入的資料
每個連線都需要獨立的執行緒完成資料的輸入,業務處理,資料返回
問題分析:
當並行數很大,就會建立大量的執行緒,佔用很大系統資源
連線建立後,如果當前執行緒暫時沒有資料可讀,該執行緒會阻塞在read操作,造成執行緒資源浪費
I/O 複用結合執行緒池,就是 Reactor 模式基本設計思想。
Reactor在一個單獨的執行緒中執行,負責監聽和分發事件,分發給適當的處理程式來對IO事件作出反應。它像公司的電話接線員,接聽來自客戶的電話並將線路轉譯到適當的聯絡人。
優點:模型簡單,沒有多執行緒、程序通訊、競爭問題,全部都在一個執行緒中完成。
缺點:效能問題,只有一個執行緒,無法完全發揮多核CPU效能。Handler在處理某個連線上的業務時,整個程序無法處理其他連線事件,很容易導致效能瓶頸。
在上一代的問題上進行修改,Reactor主執行緒只負責響應事件,不做具體的業務處理,通過read讀取資料後,會分發給後面的worker執行緒池的某個執行緒處理業務。
優點:充分利用多核CPU的處理能力。
缺點:多執行緒資料共用和存取比較複雜,Reactor處理所有的事件監聽與響應
,在單執行緒執行,在高並行場景容易出現效能瓶頸。
針對單 Reactor 多執行緒模型中,Reactor 在單執行緒中執行,高並行場景下容易成為效能瓶頸,可以讓 Reactor 在多執行緒中執行。
Reactor主執行緒MainReactor
物件通過select監聽連線事件,收到事件後,通過Acceptor處理連線事件。當Acceptor處理連線事件後,MainReactor
將連線分配給 SubReactor
,SubReactor
將連線加入到連線佇列進行監聽,並建立Handler進行各種事件處理。
優點:父執行緒與子執行緒的資料互動簡單職責明確,父執行緒只需要接收新連線,子執行緒完成後續的業務處理,無需返回資料給主執行緒
。
缺點:程式設計複雜度較高。
單Reactor單執行緒,前臺接待員和服務員是同一個人,全程為客戶服務。
單Reactor多執行緒,1個前臺接待員,多個服務員,接待員只負責接待。
主從Reactor多執行緒,多個前臺接待員,多個服務生。
Netty抽象出兩組執行緒池,BossGroup
專門負責接收使用者端的連線,WorkerGroup
專門負責網路的讀寫。
每個worker nioEventLoop
處理業務時,會使用pipeline
(管道),pipeline
中包含了channel
,即通過pipeline
可以獲取到對應通道,管道中維護了很多的處理器。
基本介紹
非同步的概念和同步相對。當一個非同步過程呼叫發出後,呼叫者不能立刻得到結果。實際處理這個呼叫的元件完成後,通過狀態、通知和回撥來通知呼叫者。
Netty中的I/O操作是非同步的,包括Bind、Write、Connect等操作會簡單的返回一個 ChannelFuture
。
呼叫者不能立刻獲得結果,而是通過 Future-Listener
機制,使用者可以方便地主動獲取或者通過通知機制獲得I/O操作結果。
Netty的非同步模型是建立在future和callback(回撥)之上的。重點是future,它的核心思想:假設一個方法func
,計算過程可能非常耗時,等待func
返回顯然不合適。那麼在 呼叫func
的時候,立刻返回一個future,後續可以
通過future去監控方法func
的處理過程(即:Future-Listener機制)
ChannelFuture
是一個介面:Public interface ChannelFuture extends Future
可以新增監聽器,當監聽的事件發生時,就會通知到監聽器。
在使用Netty進行程式設計時,攔截操作和轉換出入站資料只需要你提供callback或利用future即可。這使得鏈式操作簡單、高效、並有利於編寫可重用、通用的程式碼。
當Future物件剛剛建立好時,處於非完成狀態,呼叫者可以通過返回的channelFuture
來獲取操作執行的狀態,註冊監聽函數來執行完成後的操作。
常見的操作:
- 通過 isDone 方法來判斷當前操作是否完成。
- 通過 isSuccess 方法來判斷已完成的當前操作是否成功。
- 通過 getCause 方法來獲取已完成的當前操作失敗的原因。
- 通過 isCancelled 方法來判斷已完成的當前操作是否被取消。
- 通過 addListener 方法來註冊監聽器,當操作已完成(isDone),將會通知指定的監聽器。
小結:相比於傳統阻塞I/O,執行I/O操作後執行緒會被阻塞住,直到操作完成。非同步處理的好處是不會造成執行緒阻塞,執行緒在I/O操作期間可以執行別的程式,在高並行情形下會 更穩定和更高的吞吐量。
Bootstrap意思是引導,一個Netty應用通常由一個Bootstrap開始,主要作用是設定整個Netty程式,串聯各個元件
,Netty中Bootstrap類是使用者端程式的啟動引導類, ServerBootstrap是伺服器啟動引導類。
常用方法:
- public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup):用於伺服器端,用來設定兩個EventLoop
- public B group(EventLoopGroup group):該方法用於使用者端,用來設定一個EventLoop
- public B channel(Class<? extends C> channelClass):該方法用來設定一個伺服器端的通道實現
- public B option(ChannelOption option, T value):用來給ServerChannel新增設定
- public ServerBootstrap childOption(ChannelOption childOption, T value):用來給接收的通道新增設定
- public ServerBootstrap childHandler(ChannelHandler childHandler):業務處理類,自定義handler
- public ChannelFuture bind(int inetPort):用於伺服器端,用來設定佔用的埠號
- public ChannelFuture connect(String inetHost, int inetPort):用於使用者端,用來連線伺服器端
Netty中所有的IO操作都是非同步的,不能立刻得知訊息是否被正確處理。但是可以過一會等它執行完成或者直接註冊一個監聽,具體的實現就是通過Future和ChannelFuture, 他們可以註冊一個監聽,當操作執行成功或失敗時監聽會自動觸發註冊的監聽事件
。
常用的方法:
- Channel channel():返回當前正在進行IO操作的通道
- ChannelFuture sync():等待非同步操作執行完畢
Netty網路通訊的元件,能夠用於執行網路 I/O 操作
。 通過 Channel 可獲得當前網路連線的通道的狀態。 通過 Channel 可獲得 網路連線的設定引數 (例如接收緩衝區大小)。 Channel 提供非同步的網路 I/O 操作(如建立連線,讀寫,繫結埠),非同步呼叫意味著任何 I/O 呼叫都將立即返回,並且不保證在呼叫結束時所請求的 I/O 操作已完成
呼叫立即返回一個 ChannelFuture
範例,通過註冊監聽器到ChannelFuture
上,可以 I/O 操作成功、失敗或取消時回撥通知呼叫方。 不同協定、不同的阻塞型別的連線都有不同的 Channel 型別與之對應,常用的 Channel 型別:
- NioSocketChannel,非同步的使用者端 TCP Socket 連線。
- NioServerSocketChannel,非同步的伺服器端 TCP Socket 連線。
- NioDatagramChannel,非同步的 UDP 連線。
- NioSctpChannel,非同步的使用者端 Sctp 連線。
- NioSctpServerChannel,非同步的 Sctp 伺服器端連線,這些通道涵蓋了 UDP 和 TCP 網路 IO 以及檔案 IO。
實際開發過程中,在拿到channel之後,做一個判斷,看是什麼連線,如(channel instanceof SocketChannel/DatagramChannel)
,就可以做不同的業務處理。
Netty基於Selector物件實現I/O多路複用,通過Selector一個執行緒可以監聽多個連線的Channel事件
。當向一個Selector中註冊Channel後, Selector內部的機制就可以自動不斷地查詢(Select)這些註冊的Channel是否有已就緒的I/O事件(例如可讀,可寫,網路連線完成等), 這樣程式就可以很簡單地使用一個執行緒高效地管理多個Channel。
ChannelHandler是一個介面,處理 I/O 事件或攔截 I/O 操作,並將其轉發到其 ChannelPipeline(業務處理鏈)中的下一個處理程式
。
ChannelHandler
及其實現類一覽圖:
- ChannelInboundHandler 用於處理入站 I/O 事件。
- ChannelOutboundHandler 用於處理出站 I/O 操作。
- ChannelInboundHandlerAdapter 用於處理入站 I/O 事件。
- ChannelOutboundHandlerAdapter 用於處理出站 I/O 操作。
- ChannelDuplexHandler 用於處理入站和出站事件。
ChannelPipeline
是一個 Handler 的集合,它負責處理和攔截 inbound 或者 outbound 的事件和操作,相當於一個貫穿 Netty 的鏈
。(也可以這樣理解:ChannelPipeline
是 儲存 ChannelHandler
的 List,用於處理或攔截 Channel 的入站事件和出站操作)。
ChannelPipeline
實現了一種高階形式的攔截過濾器模式,使使用者可以完全控制事件的處理方式,以及 Channel 中各個的 ChannelHandler
如何相互互動。
在 Netty 中每個 Channel 都有且僅有一個 ChannelPipeline
與之對應,它們的組成關係如下:
一個 Channel 包含了一個 ChannelPipeline
,而 ChannelPipeline
中又維護了一個由 ChannelHandlerContext
組成的雙向連結串列,並且每個ChannelHandlerContext
中又關聯著一個 ChannelHandler
。
入站事件和出站事件在一個雙向連結串列中,入站事件會從連結串列 head 往後傳遞到最後一個入站的 handler,出站事件會從連結串列 tail 往前傳遞到最前一個出站的 handler,兩種型別的 handler 互不干擾。
常用方法:
- ChannelPipeline addFirst(ChannelHandler... handlers),把一個業務處理類(handler)新增到鏈中的第一個位置。
- ChannelPipeline addLast(ChannelHandler... handlers),把一個業務處理類(handler)新增到鏈中的最後一個位置。
儲存Channel相關的所有上下文資訊,同時關聯一個ChannelHandler物件
。ChannelHandlerContext
中包含一個具體的事件處理器ChannelHandler
,同時ChannelHandlerContext
中也繫結了對應的pipeline和Channel的資訊,方便對ChannelHandler
進行呼叫。
常用方法:
- ChannelFuture close(): 關閉通道
- ChannelOutboundInvoker flush(): 重新整理
- ChannelFuture writeAndFlush(Object msg): 將資料寫到ChannelPipeline中當前ChannelHandler的下一個ChannelHandler開始處理。
ChannelOption.SO_BACKLOG
對應TCP/IP協定listen函數中的backlog引數,用來初始化伺服器可連線佇列大小。伺服器端處理使用者端連線請求時順序處理的,所以同一時間只能處理一個使用者端連線。 多個使用者端來的時候,伺服器將不能處理的使用者端連線請求放在佇列中等待處理,backlog引數指定了佇列的大小。
ChannelOption.SO_KEEPALIVE
一直保持連線活動狀態。
BoosEventLoopGroup
通常是一個單執行緒的EventLoop
,EventLoop
維護著一個註冊了ServerSocketChannel
的Selector範例,BossEventLoop
不斷輪詢將連線事件分離出來。
通常是OP_ACCEPT事件,然後將接收到的SocketChannel
交給WorkerEventLoopGroup
。
WorkerEventLoopGroup
會由next選擇其中一個EventLoop
來將這個SocketChannel
註冊到其維護的Selector並對其後續的IO事件進行處理。
常用方法:
- public NioEventLoopGroup(): 構造方法
- public Future<?> shutdownGracefully(): 斷開連線,關閉執行緒
Netty提供一個專門用來操作緩衝區(即Netty的資料容器)的工具類
。
常用方法如下:
public static ByteBuf copiedBuffer(CharSequence String, Charset charset):通過給定的資料和字元編碼返回一個ByteBuf物件(類似於NIO中的ByteBuffer)
Netty本身自帶的 ObjectDecoder
和ObjectEncoder
可以用來實現POJO
物件或各種業務物件的編碼和解碼,底層使用的仍然是Java序列化技術,而Java序列化技術本身效率就不高,存在如下問題:
無法跨語言
序列化後的體積太大,是二進位制的5倍多
序列化效能太低 引出新的解決方案:Google的Protobuf
。
程式碼範例:netty-decoder
模組
使用自定義的編碼器和解碼器來說明Netty的handler呼叫機制
使用者端傳送long -> 伺服器
伺服器傳送long -> 使用者端
結論:
不論解碼器handler還是編碼器handler接收的訊息型別必須與待處理的訊息型別一致,否則該handler不會被執行
。
在解碼器進行資料解碼時,需要判斷快取區(ByteBuf)
的資料是否足夠,否則接收到的結果會與期望的結果可能不一致。
ReplayingDecoder
擴充套件了ByteToMessageDecoder
類,使用這個類,我們不必呼叫readableBytes()
方法。引數S指定了使用者狀態管理的型別,其中Void代表不需要狀態管理。
ReplayingDecoder
使用方便,但它也有一些侷限性:
並不是所有的 ByteBuf
操作都被支援,如果呼叫了一個不被支援的方法,將會丟擲一個 UnsupportedOperationException
。
ReplayingDecoder
在某些情況下可能稍慢於 ByteToMessageDecoder
,例如網路緩慢並且訊息格式複雜時,訊息會被拆成了多個碎片,速度變慢。
TCP是面向連線的,面向流的,提供高可靠性服務。收發兩端(使用者端和伺服器端)都要有——成對的socket,因此,傳送端為了將多個傳送給接收端的包,更有效的傳送給對方, 使用了優化演演算法(Nagle
演演算法),將多次間隔較小且資料量小的資料,合併成一個大的資料塊,然後進行封包。這樣做雖然提高了效率,但是接收端就難於分辨出完整的封包了, 因為面向流的通訊是無訊息保護邊界的。
TCP粘包與拆包解決方案
使用 自定義協定 + 編解碼器 來解決
關鍵就是要解決 伺服器端每次讀取資料長度的問題,這個問題解決,就不會出現伺服器多讀或少讀資料的問題,從而避免TCP粘包、拆包。
程式碼範例:
要求使用者端傳送5個Message物件,使用者端每次傳送一個Message物件
伺服器端每次接收一個Message,分5次進行解碼,每讀取到一個Message,會回覆一個Message物件給使用者端
只有看過Netty原始碼,才能說是真的掌握了Netty框架。
判斷是否為 2 的 n 次方
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
原始碼解析:
Netty啟動過程原始碼剖析
Netty接受請求過程原始碼剖析
Pipeline Handler HandlerContext
建立原始碼剖析
ChannelPipeline
是如何排程handler的
Netty心跳(heartbeat)服務原始碼剖析
Netty核心元件EventLoop
原始碼剖析
handler中加入執行緒池和Context中新增執行緒池的原始碼剖析
RPC
(Remote Procedure call) - 遠端程式呼叫,是一個計算機通訊協定。該協定允許執行與一臺計算機的程式呼叫另一臺計算機的子程式,而程式設計師無需額外地為這個互動作用程式設計。
兩個或多個應用程式都分佈在不同的伺服器上,它們之間的呼叫都像是本地方法呼叫一樣。
常見的PRC框架有:阿里的Dubbo
、Google的gRPC
、Go語言的rpcx
,spring的Spring cloud。
RPC
的目標就是將 2-8 這些步驟都封裝起來,使用者無需關心這些細節,可以像呼叫本地方法一樣即可完成遠端服務呼叫。
需求說明:
Dubbo
底層使用了Netty作為網路通訊框架,要求用Netty實現一個簡單的RPC
框架
模仿Dubbo
,消費者和提供者約定介面和協定,消費者遠端呼叫提供者的服務,提供者返回一個字串,消費者列印提供者返回的資料
設計說明:
建立一個介面,定義抽象方法,用於消費者和提供者之間的約定。
建立一個提供者,該類需要監聽消費者請求,並按照約定返回資料。