該文章是Netty相關文章。目的是讓讀者可以快速的瞭解netty的相關知識以及開發方法。所以本文章在正式介紹Netty開發前先介紹了Netty的前置相關內容:線程模型,JavaNIO,零拷貝等。本文章以大綱框架的形式總體介紹了Netty,但願對讀者有些幫助。文中圖片多來自於百度網絡,若是有侵權,能夠聯繫我進行刪除。內容如有不當歡迎在評論區指出。
netty是由JBOSS提供的一個Java開源框架,是一個異步的,基於事件驅動的網絡應用框架,用以快速開發高性能,高可靠性的網絡IO程序.java
異步 I/O 與信號驅動 I/O 的區別在於,異步 I/O 的信號是通知應用進程 I/O 完成,而信號驅動 I/O 的信號是通知應用進程能夠開始 I/O。
Channel:是雙向的,既能夠用來進行讀操做,又能夠用來進行寫操做bootstrap
Buffer:它經過幾個變量來保存這個數據的當前位置狀態:設計模式
向Buffer中寫數據:api
從Buffer中讀取數據:數組
Buffer經常使用方法緩存
Selector:Selector一塊兒使用時,Channel必須處於非阻塞模式下。經過channel.register,將channel登記到Selector上,同時添加關注的事件(SelectionKey),經常使用方法以下:安全
缺點:服務器
1. 單進程所打開的FD是具備必定限制的, 2. 套接字比較多的時候,每次select()都要經過遍歷Socket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間 3. 每次都須要把fd集合從⽤用戶態拷貝到內核態,這個開銷在fd不少時會很⼤大
poll:本質上和select沒有區別,fd使用鏈表實現,沒有最大鏈接數的限制。網絡
缺點:多線程
epoll:
epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是「高速」模式。
**epoll底層原理**:調用epoll_create後,內核cache裏建了個紅黑樹用於存儲之後epoll_ctl傳來的socket,創建一個rdllist雙向鏈表,用於存儲準備就緒的事件。在epoll_wait調用時,僅僅觀察這個rdllist雙向鏈表裏有沒有數據便可。有數據就返回,沒有數據就阻塞。
對一個操做系統進程來講,它既有內核空間(與其餘進程共享),也有用戶空間(進程私有),它們都是處於虛擬地址空間中。進程沒法直接操做I/O設備,必須經過操做系統調用請求內核來協助完成I/O動做。將靜態文件展現給用戶須要先將靜態內容從磁盤中拷貝出來放到內存buf中,而後再將這個buf經過socket發給用戶
問題:經歷了4次copy過程,4次內核切換
1. 用戶態到內核態:調用read,文件copy到內核態內存 2. 內核態到用戶態:內核態內存數據copy到用戶態內存 3. 用戶態到內核態:調用writer:用戶態內存數據到內核態socket的buffer內存中 4. 最後內核模式下的socket模式下的buffer數據copy到網卡設備中傳送 5. 從內核態回到用戶態執行下一個循環
Linux:零拷貝技術消除傳輸數據在存儲器之間沒必要要的中間拷貝次數,減小了用戶進程地址空間和內核地址空間之間由於上下文切換而帶來的開銷。
常見零拷貝技術
零拷貝不只僅帶來更少的數據複製,還能帶來其餘的性能優點,例如更少的上下文切換,更少的 CPU 緩存僞共享以及無 CPU 校驗和計算。
Netty 對 JDK 自帶的 NIO 的 API 進行了封裝,解決了上述問題。
Reactor模式:是事件驅動的,多個併發輸入源。它有一個服務處理器,有多個請求處理器;這個服務處理器會同步的將輸入的客戶端請求事件多路複用的分發給相應的請求處理器。
單Reactor單線程:多路複用、事件分發和消息的處理都是在一個Reactor線程上完成。
* 優勢: * 模型簡單,實現方便 * 缺點: * 性能差:單線程沒法發揮多核性能, * 可靠性差:線程意外終止或死循環,則整個模塊不可用
單Reactor多線程
一個Reactor線程負責監聽服務端的鏈接請求和接收客戶端的TCP讀寫請求;NIO線程池負責消息的讀取、解碼、編碼和發送
優勢:能夠充分的利用多核cpu的處理能 缺點:Reactor處理全部事件的監聽和響應,在單線程運行,在高併發場景容易出現性能瓶頸.
主從 Reactor 多線程
MainReactor負責監聽服務端的鏈接請求,接收到客戶端的鏈接後,將SocketChannel從MainReactor上移除,從新註冊到SubReactor線程池的線程上。SubReactor處理I/O的讀寫操做,NIO線程池負責消息的讀取、解碼、編碼和發送。
NioEventLoopGroup:主要管理 eventLoop 的生命週期,能夠理解爲一個線程池,內部維護了一組線程,每一個線程(NioEventLoop)負責處理多個 Channel 上的事件,而一個 Channel 只對應於一個線程
ChannelHandler用於處理Channel對應的事件
示例代碼:
public class NettyServer { public static void main(String[] args) throws Exception { //bossGroup和workerGroup分別對應mainReactor和subReactor NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); NioEventLoopGroup workGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workGroup) //用來指定一個Channel工廠,mainReactor用來包裝SocketChannel. .channel(NioServerSocketChannel.class) //用於指定TCP相關的參數以及一些Netty自定義的參數 .option(ChannelOption.SO_BACKLOG, 100) //childHandler()用於指定subReactor中的處理器,相似的,handler()用於指定mainReactor的處理器 .childHandler(new ChannelInitializer<SocketChannel>() { //ChannelInitializer,它是一個特殊的Handler,功能是初始化多個Handler。完成初始化工做後,netty會將ChannelInitializer從Handler鏈上刪除。 @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); //addLast(Handler)方法中不指定線程池那麼將使用默認的subReacor即woker線程池執行處理器中的業務邏輯代碼。 pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new MyServerHandler()); } }); //sync() 同步阻塞直到bind成功 ChannelFuture f = bootstrap.bind(8888).sync(); //sync()同步阻塞直到netty工做結束 f.channel().closeFuture().sync(); } }
NioEventLoopGroup:
NioEventLoop
NioEventLoop 肩負着兩種任務:
ServerBootstrap是一個工具類,用來配置netty
channel():提供一個ChannelFactory來建立channel,不一樣協議的鏈接有不一樣的 Channel 類型與之對應,常見的Channel類型:
ChannelHandler下主要是兩個子接口
ChannelInboundHandler(入站): 處理輸入數據和Channel狀態類型改變。
ChannelOutboundHandler(出站): 處理輸出數據
ChannelPipeline 是一個 Handler 的集合,它負責處理和攔截 inbound 或者 outbound 的事件和操做,一個貫穿 Netty 的鏈。每一個新的通道Channel,Netty都會建立一個新的ChannelPipeline,並將器pipeline附加到channel中。DefaultChinnelPipeline它的Handel頭部和尾部的Handel是固定的,咱們所添加的Handel是添加在這個頭和尾以前的Handel。
ChannelHandlerContext:ChannelPipeline並非直接管理ChannelHandler,而是經過ChannelHandlerContext來間接管理。
網絡中都是以字節碼的數據形式來傳輸數據的,服務器編碼數據後發送到客戶端,客戶端須要對數據進行解碼
Netty提供了一些默認的編碼器:
StringEncoder:對字符串數據進行編碼
ObjectEncoder:對 Java 對象進行編碼
StringDecoder:對字符串數據進行解碼
ObjectDecoder:對 Java 對象進行解碼
抽象解碼器
ReplayingDecoder: 繼承ByteToMessageDecoder,不須要檢查緩衝區是否有足夠的字節,可是ReplayingDecoder速度略慢於ByteToMessageDecoder,同時不是全部的ByteBuf都支持。
UDP是基於幀的,包的首部有數據報文的長度.TCP是基於字節流,沒有邊界的。TCP的首部沒有表示數據長度的字段。
發生TCP粘包或拆包的緣由:
Netty 已經提供了編碼器用於解決粘包。
Netty徹底工做在用戶態的,Netty的零拷貝更多的對數據操做的優化。
Netty的零拷貝(或者說ByteBuf的複用)主要體如今如下幾個方面: