-
源代碼: github.com/lightningMa…html
-
NIO與OIO的區別java
- 解決線程資源有限
- 使用selector管理多個鏈接,減小建立線程的開銷。當有新鏈接進來時,將它註冊到一個selector上,批量監控是否有可讀的數據鏈接
- 解決線程切換效率低下
- 因爲NIO模型中線程數量大大下降,線程切換的開銷也變低了
- 解決IO讀寫面向流
- IO讀寫是面向流,意味着讀完以後流沒法再次讀取,須要本身緩存數據。而NIO是面向Buffer的,能夠隨意讀取Buffer中的任一字節,不須要本身緩存,只需移動指針。
-
NIO編程核心思想react
- NIO 模型中一般會有兩個線程,每一個線程綁定一個輪詢器 selector ,例如 serverSelector負責輪詢是否有新的鏈接,clientSelector負責輪詢鏈接是否有數據可讀
- 服務端監測到新的鏈接以後,再也不建立一個新的線程,而是直接將新鏈接綁定到clientSelector上,這樣就不用 IO 模型中 1w 個 while 循環在死等
- clientSelector被一個 while 死循環包裹着,若是在某一時刻有多條鏈接有數據可讀,那麼經過clientSelector.select(1)方法能夠輪詢出來,進而批量處理
- 數據的讀寫面向 Buffer
-
JAVA原始NIO相比Netty的缺點nginx
- JDK 的 NIO 編程模型不友好,ByteBuffer 的 Api 簡直反人類
- 對 NIO 編程來講,連簡單的自定義協議拆包都要你本身實現
- JDK 的 NIO 底層由 epoll 實現,該實現飽受詬病的空輪詢 bug 會致使 cpu 飆升 100%
- 項目龐大以後,自行實現的 NIO 很容易出現各種 bug,維護成本較高
-
服務端的啓動git
- ServerBootstrap
- NioEventLoopGroup -> boss/worker
- .channel -> NioServerSocketChannel
- .group
- .handler
- .childHandler
- ChannelInitializer
- childOption
- .option
- .bind
-
客戶端的啓動github
- Bootstrap
- channel -> NioSocketChannel
- option
- handler
- attr
- pipeline -> SocketChannel.pipeline
-
channelHandlerweb
- ChannelInboundHandlerAdapter 主要用於實現其接口 ChannelInboundHandler的全部方法,這樣咱們只須要重寫咱們感興趣的方法,不用實現handler中的每個方法
- ChannelOutboundHandlerAdapter 主要用於實現 ChannelOutboundHandler的全部方法
-
pipelineredis
- ByteToMessageDecoder 實現自定義解碼 不用關心ByteBuf的強轉和解碼結構的傳遞
- MessageToByteEncoder 實現自定義編碼 不用關係ByteBuf的建立,不用每次向對端寫Java對象都進行一次編碼
- SimpleChannelInboundHandler 實現每一種指令的處理,將類型轉換操做交給編解碼處理器,必須實現channelRead0來完成request/response的處理
-
粘包拆包編程
- Netty 自帶的拆包器
- FixedLengthFrameDecoder 固定長度的拆包器
- 若是你的應用層協議很是簡單,每一個數據包的長度都是固定的,好比 100,那麼只須要把這個拆包器加到 pipeline 中,Netty 會把一個個長度爲 100 的數據包 (ByteBuf) 傳遞到下一個 channelHandler。
- LineBasedFrameDecoder 行拆包器 - 從字面意思來看,發送端發送數據包的時候,每一個數據包之間以換行符做爲分隔,接收端經過 LineBasedFrameDecoder 將粘過的 ByteBuf 拆分紅一個個完整的應用層數據包。
- DelimiterBasedFrameDecoder 分隔符拆包器 - DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不過咱們能夠自定義分隔符。
- LengthFieldBasedFrameDecoder 基於長度域拆包器 《數據長度》 - 最後一種拆包器是最通用的一種拆包器,只要你的自定義協議中包含長度域字段,都可以使用這個拆包器來實現應用層拆包。
- LengthFieldBasedFrameDecoder 長度域
- new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 7, 4); 第一個參數指的是數據包的最大長度,第二個參數指的是長度域的偏移量,第三個參數指的是長度域的長度
- 須要在 pipeline 的最前面加上這個拆包器
- 拒絕非本協議鏈接
- 解碼器解碼時判斷協議標識,若是非本協議,則拒絕處理該報文
-
channelHandler lifeCycle緩存
- 開啓鏈接: handlerAdded -> channelRegister -> channelActive -> channelRead -> channelReadComplete
- handlerAdded 指的是當檢測到新鏈接以後,調用 ch.pipeline().addLast(new LifeCyCleTestHandler()); 以後的回調,表示在當前的 channel 中,已經成功添加了一個 handler 處理器。
- channelRegistered
- 這個回調方法,表示當前的 channel 的全部的邏輯處理已經和某個 NIO 線程創建了綁定關係,相似 socket.accept 到新的鏈接,而後建立一個線程來處理這條鏈接的讀寫,只不過 Netty 裏面是使用了線程池的方式, 只須要從線程池裏面去抓一個線程綁定在這個 channel 上便可。
- channelActive 當 channel 的全部的業務邏輯鏈準備完畢(也就是說 channel 的 pipeline 中已經添加完全部的 handler)以及綁定好一個 NIO 線程以後,這條鏈接算是真正激活了,接下來就會回調到此方法。
- channelRead
- channelReadComplete
- 關閉鏈接: channelInactive -> channelUnregistered -> handlerRemoved
- channelInactive 表面這條鏈接已經被關閉了,這條鏈接在 TCP 層面已經再也不是 ESTABLISH 狀態了
- channelUnregistered 既然鏈接已經被關閉,那麼與這條鏈接綁定的線程就不須要對這條鏈接負責了,這個回調就代表與這條鏈接對應的 NIO 線程移除掉對這條鏈接的處理
- handlerRemoved 咱們給這條鏈接上添加的全部的業務邏輯處理器都給移除掉
-
channelHandler lifeCycle應用舉例
- ChannelInitializer 的實現原理
- 仔細翻看一下咱們的服務端啓動代碼,咱們在給新鏈接定義 handler 的時候,其實只是經過 childHandler() 方法給新鏈接設置了一個 handler,這個 handler 就是 ChannelInitializer, 而在 ChannelInitializer 的 initChannel() 方法裏面,咱們經過拿到 channel 對應的 pipeline,而後往裏面塞 handler
- ChannelInitializer 其實就利用了 Netty 的 handler 生命週期中 channelRegistered() 與 handlerAdded() 兩個特性,咱們簡單翻一翻 ChannelInitializer 這個類的源代碼
- ChannelInitializer:initChannel、 handlerAdded、channelRegistered
- handlerAdded() 與 handlerRemoved() 這兩個方法一般能夠用在一些資源的申請和釋放
- channelActive() 與 channelInActive()
- 對咱們的應用程序來講,這兩個方法代表的含義是 TCP 鏈接的創建與釋放,一般咱們在這兩個回調裏面統計單機的鏈接數,channelActive() 被調用,鏈接數加一,channelInActive() 被調用,鏈接數減一
- 另外,咱們也能夠在 channelActive() 方法中,實現對客戶端鏈接 ip 黑白名單的過濾
- 統計用戶鏈接數、流量
- channelRead
- channelReadComplete
- 優化 ctx.channel().flush() 批量刷新
-
處理優化
- 共享handler
- 對於沒有成員變量的handler可使用單例模式,提升效率,避免建立不少小的對象
- @ChannelHandler.Sharable
- LoginRequestHandler is not a @Sharable handler, so can't be added or removed multiple times.
- 合併編解碼器
- extends MessageToMessageCodec<ByteBuf,Packet>
- 編碼器當outbound處理,解碼器當作inbound處理便可
- 壓縮 handler 合併平行 handler
- handler和handler之間無前後順序時,能夠放在一個map中,經過類型判斷處理
- 更改事件傳播源
- ctx.writeAndFlush() 替代 ctx.channel.writeAndFlush()
- 若是明確知道後面的操做只須要解碼便可,就能夠直接使用ctx.writeAndFlush
- 減小阻塞主線程的操做
- 統計處理時長
- 使用addListener監聽channel信息 -> ChannelFuture
-
心跳和空閒檢測
- 鏈接假死緣由
- 應用程序出現線程堵塞,沒法進行數據的讀寫。
- 客戶端或者服務端網絡相關的設備出現故障,好比網卡,機房故障。
- 公網丟包
- 服務端空閒檢測
- IdleStateHandler判斷是不是鏈接假死,channelIdle方法處理假死的鏈接,通常是手動關閉
- 客戶端定時發心跳
- heartbeat 定時發送
- 主要用於排除客戶端確實沒有數據傳輸的正常狀況
- 服務端回覆心跳與客戶端空閒檢測
-
netty-user-guide:netty.io/wiki/user-g…
-
netty-new-feature:netty.io/wiki/new-an…
-
netty-websocket-test-demo: github.com/netty/netty…
-
Netty源碼分析
-
拓展問題
- 集羣模式下多機器,netty 服務端如何工做?
- nginx負載均衡,redis存儲channel關係和ip,服務器之間互相轉發消息
- websocket-netty如何應用?
- netty-sockio redsion 如何使用?
- 使用netty實現消息推送,在生產環境須要處理那些常見問題?
- dubbo等RPC服務是如何使用netty的?