事件循環所做的正如它的名字所說的。它運行在一個循環里,直到它的終止。這符合網絡框架的設計,因為他們需要在一個循環為一個特定的連接運行事件。這不是 Netty 發明新的東西;其他框架和實現已經這樣做了。
下面的清單顯示了典型的 EventLoop 邏輯。請注意這是為了更好的說明這個想法而不是單單展示 Netty 實現本身。
Listing 14.1 Execute task in EventLoop
~~~
while (!terminated) {
List<Runnable> readyEvents = blockUntilEventsReady(); //1
for (Runnable ev: readyEvents) {
ev.run(); //2
}
}
~~~
1. 阻塞直到事件可以運行
2. 循環所有事件,并運行他們
在 Netty 中使用 EventLoop 接口代表事件循環,EventLoop 是從EventExecutor 和 ScheduledExecutorService 擴展而來,所以可以將任務直接交給 EventLoop 執行。類關系圖如下:
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%2015.2%20EventLoop%20class%20hierarchy.jpg)
Figure 15.2 EventLoop class hierarchy
EventLoop 是完全由一個 Thread,從未改變。為了更合理利用資源,根據配置和可用的內核, Netty 可以使用多個 EventLoop。
*事件/任務執行順序*
*一個重要的細節關于事件和任務的執行順序是,事件/任務執行順序按照FIFO(先進先出)。這是必要的,因為否則事件不能按順序處理,所處理的字節將不能保證正確的順序。這將導致問題,所以這個不是所允許的設計。*
### [](https://github.com/waylau/essential-netty-in-action/blob/master/ADVANCED%20TOPICS/The%20EventLoop.md#netty-4-中的-io-和事件處理)Netty 4 中的 I/O 和事件處理
Netty 使用 I/O 事件,b被各種 I/O 操作運輸本身所觸發。 這些 I/O 操作,例如網絡 API 的一部分,由Java 和底層操作系統提供。
一個區別在于,一些操作(或者事件)是由 Netty 的本身的傳輸實現觸發的,一些是由用戶自己。例如讀事件通常是由傳輸本身在讀取一些數據時觸發。相比之下,寫事件通常是由用戶本身,例如,當調用 Channel.write(…)。
究竟需要做一次處理一個事件取決于事件的性質。經常會讀網絡棧的數據轉移到您的應用程序。有時它會在另一個方向做同樣的事情,例如,把數據從應用程序到網絡堆棧(內核)發送到它的遠端。但不限于這種類型的事務;重要的是,所使用的邏輯是通用的,靈活地處理各種各樣的用例。
I/O 和事件處理的一個重要的事情在 Netty 4,是每一個 I/O 操作和事件總是由 EventLoop 本身處理,以及分配給 EventLoop 的 Thread。
我們應該注意,Netty 不總是使用我們描述的線程模型(通過 EventLoop 抽象)。在下一節中,你會了解 Netty 3 中使用的線程模型。這將幫助你理解為什么現在用新的線程模型以及為什么使用取代了 Netty 3 中仍然使用的舊模式。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/ADVANCED%20TOPICS/The%20EventLoop.md#netty-3-中的-io-操作)Netty 3 中的 I/O 操作
在以前的版本中,線程模型是不同的。Netty 保證只將入站(以前稱為 upstream)事件在執行 I/O Thread 執行 (I/O Thread 現在在 Netty 4 叫 EventLoop )。所有的出站(以前稱為 downstream)事件被調用Thread 處理,這可能是 I/O Thread 也可以能是其他 Thread。 這聽起來像一個好主意,但原來是容易出錯,因為處理 ChannelHandler需要小心的出站事件同步,因為它沒有保證只有一個線程運行在同一時間。這可能會發生如果你觸發 downstream 事件同時在一個管道時;例如,您 調用 Channel.write(..) 在不同的線程。
除了需要負擔同步 ChannelHandler,這個線程模型的另一個問題是你可能需要去掉一個入站事件作為一個出站事件的結果,例如 Channel.write(..) 操作導致異常。在這種情況下,exceptionCaught 必須生成并拋出去。乍看之下這不像是一個問題,但我們知道, exceptionCaught 由入站事件涉及,會讓你知道問題出在哪里。問題是,事實上,你現在的情況是在調用 Thread 上執行,但 exceptionCaught 事件必須交給工作線程來執行,這樣上下文切換是必須的。
相比之下,Netty 4 新線程模型根本沒有這些問題,因為一切都在同一個EventLoop 在同一 Thread 中 執行。這消除了需要同步ChannelHandler ,并且使它更容易為用戶理解執行。
現在你知道 EventLoop 如何執行任務,它的時間來快速瀏覽下 Netty 的各種內部功能。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/ADVANCED%20TOPICS/The%20EventLoop.md#netty-線程模型的內部)Netty 線程模型的內部
Netty 的內部實現使其線程模型表現優異,它會檢查正在執行的 Thread 是否是已分配給實際 Channel (和 EventLoop),在 Channel 的生命周期內,EventLoop 負責處理所有的事件。
如果 Thread 是相同的 EventLoop 中的一個,討論的代碼塊被執行;如果線程不同,它安排一個任務并在一個內部隊列后執行。通常是通過EventLoop 的 Channel 只執行一次下一個事件,這允許直接從任何線程與通道交互,同時還確保所有的 ChannelHandler 是線程安全,不需要擔心并發訪問問題。
下圖顯示在 EventLoop 中調度任務執行邏輯,這適合 Netty 的線程模型:
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/15.5%20EventLoop%20execution%20logic%20flow.jpg)
1. 應在 EventLoop 中執行的任務
2. 任務傳遞到執行方法后,執行檢查來檢測調用線程是否是與分配給 EventLoop 是一樣的
3. 線程是一樣的,說明你在 EventLoop 里,這意味著可以直接執行的任務
4. 線程與 EventLoop 分配的不一樣。當 EventLoop 事件執行時,隊列的任務再次執行一次
15.5 EventLoop execution logic/flow
設計是非常重要的,以確保不要把任何長時間運行的任務放在執行隊列中,因為長時間運行的任務會阻止其他在相同線程上執行的任務。這多少會影響整個系統依賴于 EventLoop 實現用于特殊傳輸的實現。
傳輸之間的切換在你的代碼庫中可能沒有任何改變,重要的是:切勿阻塞 I/O 線程。如果你必須做阻塞調用(或執行需要長時間才能完成的任務),使用 EventExecutor。
下一節將講解一個在應用程序中經常使用的功能,就是調度執行任務(定期執行)。Java對這個需求提供了解決方案,但 Netty 提供了幾個更好的方案
- Introduction
- 開始
- Netty-異步和數據驅動
- Netty 介紹
- 構成部分
- 關于本書
- 第一個 Netty 應用
- 設置開發環境
- Netty 客戶端/服務端 總覽
- 寫一個 echo 服務器
- 寫一個 echo 客戶端
- 編譯和運行 Echo 服務器和客戶端
- 總結
- Netty 總覽
- Netty 快速入門
- Channel, Event 和 I/O
- 什么是 Bootstrapping 為什么要用
- ChannelHandler 和 ChannelPipeline
- 近距離觀察 ChannelHandler
- 總結
- 核心功能
- Transport(傳輸)
- 案例研究:Transport 的遷移
- Transport API
- 包含的 Transport
- Transport 使用情況
- 總結
- Buffer(緩沖)
- Buffer API
- ByteBuf - 字節數據的容器
- 字節級別的操作
- ByteBufHolder
- ByteBuf 分配
- 總結
- ChannelHandler 和 ChannelPipeline
- ChannelHandler 家族
- ChannelPipeline
- ChannelHandlerContext
- 總結
- Codec 框架
- 什么是 Codec
- Decoder(解碼器)
- Encoder(編碼器)
- 抽象 Codec(編解碼器)類
- 總結
- 提供了的 ChannelHandler 和 Codec
- 使用 SSL/TLS 加密 Netty 程序
- 構建 Netty HTTP/HTTPS 應用
- 空閑連接以及超時
- 解碼分隔符和基于長度的協議
- 編寫大型數據
- 序列化數據
- 總結
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- 引導
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- NETTY BY EXAMPLE
- 單元測試
- 總覽
- 測試 ChannelHandler
- 測試異常處理
- 總結
- WebSocket
- WebSocket 程序示例
- 添加 WebSocket 支持
- 測試程序
- 總結
- SPDY
- SPDY 背景
- 示例程序
- 實現
- 啟動 SpdyServer 并測試
- 總結
- 通過 UDP 廣播事件
- UDP 基礎
- UDP 廣播
- UDP 示例
- EventLog 的 POJO
- 寫廣播器
- 寫監視器
- 運行 LogEventBroadcaster 和 LogEventMonitor
- 總結
- 高級主題
- 實現自定義的編解碼器
- 編解碼器的范圍
- 實現 Memcached 編解碼器
- 了解 Memcached 二進制協議
- Netty 編碼器和解碼器
- 測試編解碼器
- EventLoop 和線程模型
- 線程模型的總覽
- EventLoop
- EventLoop
- I/O EventLoop/Thread 分配細節
- 總結
- 用例1:Droplr Firebase 和 Urban Airship
- 用例2:Facebook 和 Twitter