# 架構總覽
[TOC=2,2]

在本章中,我們將研究 Netty 提供的核心功能以及他們是如何構成一個完整的網絡應用開發堆棧頂部的核心。你閱讀本章時,請把這個圖記住。
## 豐富的緩沖實現
Netty 使用自建的 buffer API,而不是使用 NIO 的 [ByteBuffer](http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html?is-external=true) 來表示一個連續的字節序列。與 ByteBuffer 相比這種方式擁有明顯的優勢。Netty 使用新的 buffer 類型 [ByteBuf](http://netty.io/4.0/api/io/netty/buffer/ByteBuf.html),被設計為一個可從底層解決 ByteBuffer 問題,并可滿足日常網絡應用開發需要的緩沖類型。這些很酷的特性包括:
- 如果需要,允許使用自定義的緩沖類型。
- 復合緩沖類型中內置的透明的零拷貝實現。
- 開箱即用的動態緩沖類型,具有像 [StringBuffer](http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html?is-external=true) 一樣的動態緩沖能力。
- 不再需要調用的flip()方法。
- 正常情況下具有比 ByteBuffer 更快的響應速度。
更多信息請參考:[io.netty.buffer 包描述](http://netty.io/4.0/api/io/netty/buffer/package-summary.html#package_description)
### Extensibility 可擴展性
ByteBuf 具有豐富的操作集,可以快速的實現協議的優化。例如,ByteBuf 提供各種操作用于訪問無符號值和字符串,以及在緩沖區搜索一定的字節序列。你也可以擴展或包裝現有的緩沖類型用來提供方便的訪問。自定義緩沖式仍然實現自 ByteBuf 接口,而不是引入一個不兼容的類型
### Transparent Zero Copy 透明的零拷貝
舉一個網絡應用到極致的表現,你需要減少內存拷貝操作次數。你可能有一組緩沖區可以被組合以形成一個完整的消息。網絡提供了一種復合緩沖,允許你從現有的任意數的緩沖區創建一個新的緩沖區而無需沒有內存拷貝。例如,一個信息可以由兩部分組成;header 和 body。在一個模塊化的應用,當消息發送出去時,這兩部分可以由不同的模塊生產和裝配。
~~~
<pre> +--------+----------+
| header | body |
+--------+----------+
</pre>
~~~
如果你使用的是 ByteBuffer ,你必須要創建一個新的大緩存區用來拷貝這兩部分到這個新緩存區中。或者,你可以在 NiO做一個收集寫操作,但限制你將復合緩沖類型作為 ByteBuffer 的數組而不是一個單一的緩沖區,打破了抽象,并且引入了復雜的狀態管理。此外,如果你不從 NIO channel 讀或寫,它是沒有用的。
~~~
// 復合類型與組件類型不兼容。
ByteBuffer[] message = new ByteBuffer[] { header, body };
~~~
通過對比, ByteBuf 不會有警告,因為它是完全可擴展并有一個內置的復合緩沖區。
~~~
// 復合類型與組件類型是兼容的。
ByteBuf message = Unpooled.wrappedBuffer(header, body);
?
// 因此,你甚至可以通過混合復合類型與普通緩沖區來創建一個復合類型。
ByteBuf messageWithFooter = Unpooled.wrappedBuffer(message, footer);
?
// 由于復合類型仍是 ByteBuf,訪問其內容很容易,
//并且訪問方法的行為就像是訪問一個單獨的緩沖區,
//即使你想訪問的區域是跨多個組件。
//這里的無符號整數讀取位于 body 和 footer
messageWithFooter.getUnsignedInt(
messageWithFooter.readableBytes() - footer.readableBytes() - 1);
~~~
### Automatic Capacity Extension 自動容量擴展
許多協議定義可變長度的消息,這意味著沒有辦法確定消息的長度,直到你構建的消息。或者,在計算長度的精確值時,帶來了困難和不便。這就像當你建立一個字符串。你經常估計得到的字符串的長度,讓 StringBuffer 擴大了其本身的需求。
~~~
// 一種新的動態緩沖區被創建。在內部,實際緩沖區是被“懶”創建,從而避免潛在的浪費內存空間。
ByteBuf b = Unpooled.buffer(4);
?
// 當第一個執行寫嘗試,內部指定初始容量 4 的緩沖區被創建
b.writeByte('1');
?
b.writeByte('2');
b.writeByte('3');
b.writeByte('4');
?
// 當寫入的字節數超過初始容量 4 時,
//內部緩沖區自動分配具有較大的容量
b.writeByte('5');
~~~
### Better Performance 更好的性能
最頻繁使用的緩沖區 ByteBuf 的實現是一個非常薄的字節數組包裝器(比如,一個字節)。與 ByteBuffer 不同,它沒有復雜的邊界和索引檢查補償,因此對于 JVM 優化緩沖區的訪問更加簡單。更多復雜的緩沖區實現是用于拆分或者組合緩存,并且比 ByteBuffer 擁有更好的性能。
## I/O API 統一的異步 I/O API
傳統的 Java I/O API 在應對不同的傳輸協議時需要使用不同的類型和方法。例如:java.net.Socket 和 java.net.DatagramSocket 它們并不具有相同的超類型,因此,這就需要使用不同的調用方式執行 socket 操作。
這種模式上的不匹配使得在更換一個網絡應用的傳輸協議時變得繁雜和困難。由于(Java I/O API)缺乏協議間的移植性,當你試圖在不修改網絡傳輸層的前提下增加多種協議的支持,這時便會產生問題。并且理論上講,多種應用層協議可運行在多種傳輸層協議之上例如TCP/IP,UDP/IP,SCTP和串口通信。
讓這種情況變得更糟的是,Java 新的 I/O(NIO)API與原有的阻塞式的I/O(OIO)API 并不兼容,NIO.2(AIO)也是如此。由于所有的API無論是在其設計上還是性能上的特性都與彼此不同,在進入開發階段,你常常會被迫的選擇一種你需要的API。
例如,在用戶數較小的時候你可能會選擇使用傳統的 OIO(Old I/O) API,畢竟與 NIO 相比使用 OIO 將更加容易一些。然而,當你的業務呈指數增長并且服務器需要同時處理成千上萬的客戶連接時你便會遇到問題。這種情況下你可能會嘗試使用 NIO,但是復雜的 NIO Selector 編程接口又會耗費你大量時間并最終會阻礙你的快速開發。
Netty 有一個叫做 [Channel](http://netty.io/4.0/api/io/netty/channel/package-summary.html#package_description) 的統一的異步 I/O 編程接口,這個編程接口抽象了所有點對點的通信操作。也就是說,如果你的應用是基于 Netty 的某一種傳輸實現,那么同樣的,你的應用也可以運行在 Netty 的另一種傳輸實現上。Netty 提供了幾種擁有相同編程接口的基本傳輸實現:
- 基于 NIO 的 TCP/IP 傳輸 (見 io.netty.channel.nio),
- 基于 OIO 的 TCP/IP 傳輸 (見 io.netty.channel.oio),
- 基于 OIO 的 UDP/IP 傳輸, 和
- 本地傳輸 (見 io.netty.channel.local).
切換不同的傳輸實現通常只需對代碼進行幾行的修改調整,例如選擇一個不同的 [ChannelFactory](http://netty.io/4.0/api/io/netty/bootstrap/ChannelFactory.html) 實現。
此外,你甚至可以利用新的傳輸實現沒有寫入的優勢,只需替換一些構造器的調用方法即可,例如串口通信。而且由于核心 API 具有高度的可擴展性,你還可以完成自己的傳輸實現。
## 基于攔截鏈模式的事件模型
一個定義良好并具有擴展能力的事件模型是事件驅動開發的必要條件。Netty 具有定義良好的 I/O 事件模型。由于嚴格的層次結構區分了不同的事件類型,因此 Netty 也允許你在不破壞現有代碼的情況下實現自己的事件類型。這是與其他框架相比另一個不同的地方。很多 NIO 框架沒有或者僅有有限的事件模型概念;在你試圖添加一個新的事件類型的時候常常需要修改已有的代碼,或者根本就不允許你進行這種擴展。
在一個 [ChannelPipeline](http://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html) 內部一個 [ChannelEvent]() 被一組[ChannelHandler](http://netty.io/4.0/api/io/netty/channel/ChannelHandler.html) 處理。這個管道是 [Intercepting Filter (攔截過濾器)](http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html)模式的一種高級形式的實現,因此對于一個事件如何被處理以及管道內部處理器間的交互過程,你都將擁有絕對的控制力。例如,你可以定義一個從 socket 讀取到數據后的操作:
~~~
public class MyReadHandler implements SimpleChannelHandler {
public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
Object message = evt.getMessage();
// Do something with the received message.
...
?
// And forward the event to the next handler.
ctx.sendUpstream(evt);
}
}
~~~
同時你也可以定義一種操作響應其他處理器的寫操作請求:
~~~
public class MyWriteHandler implements SimpleChannelHandler {
public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) {
Object message = evt.getMessage();
// Do something with the message to be written.
...
?
// And forward the event to the next handler.
ctx.sendDownstream(evt);
}
}
~~~
有關事件模型的更多信息,請參考 API 文檔 ChannelEvent 和ChannelPipeline 部分。
## 適用快速開發的高級組件
上述所提及的核心組件已經足夠實現各種類型的網絡應用,除此之外,Netty 也提供了一系列的高級組件來加速你的開發過程。
### Codec 框架
就像“[使用POJO代替ChannelBuffer](#)”一節所展示的那樣,從業務邏輯代碼中分離協議處理部分總是一個很不錯的想法。然而如果一切從零開始便會遭遇到實現上的復雜性。你不得不處理分段的消息。一些協議是多層的(例如構建在其他低層協議之上的協議)。一些協議過于復雜以致難以在一臺獨立狀態機上實現。
因此,一個好的網絡應用框架應該提供一種可擴展,可重用,可單元測試并且是多層的 codec 框架,為用戶提供易維護的 codec 代碼。
Netty 提供了一組構建在其核心模塊之上的 codec 實現,這些簡單的或者高級的 codec 實現幫你解決了大部分在你進行協議處理開發過程會遇到的問題,無論這些協議是簡單的還是復雜的,二進制的或是簡單文本的。
### SSL / TLS 支持
不同于傳統阻塞式的 I/O 實現,在 NIO 模式下支持 SSL 功能是一個艱難的工作。你不能只是簡單的包裝一下流數據并進行加密或解密工作,你不得不借助于 javax.net.ssl.SSLEngine,SSLEngine 是一個有狀態的實現,其復雜性不亞于 SSL 自身。你必須管理所有可能的狀態,例如密碼套件,密鑰協商(或重新協商),證書交換以及認證等。此外,與通常期望情況相反的是 SSLEngine 甚至不是一個絕對的線程安全實現。
在 Netty 內部,[SslHandler](http://netty.io/4.0/api/io/netty/handler/ssl/SslHandler.html) 封裝了所有艱難的細節以及使用 SSLEngine 可 能帶來的陷阱。你所做的僅是配置并將該 SslHandler 插入到你的 [ChannelPipeline](http://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html) 中。同樣 Netty 也允許你實現像 [StartTlS](http://en.wikipedia.org/wiki/Starttls) 那樣所擁有的高級特性,這很容易。
### HTTP 實現
HTTP無 疑是互聯網上最受歡迎的協議,并且已經有了一些例如 Servlet 容器這樣的 HTTP 實現。因此,為什么 Netty 還要在其核心模塊之上構建一套 HTTP 實現?
與現有的 HTTP 實現相比 Netty 的 HTTP 實現是相當與眾不同的。在HTTP 消息的低層交互過程中你將擁有絕對的控制力。這是因為 Netty 的HTTP 實現只是一些 HTTP codec 和 HTTP 消息類的簡單組合,這里不存在任何限制——例如那種被迫選擇的線程模型。你可以隨心所欲的編寫那種可以完全按照你期望的工作方式工作的客戶端或服務器端代碼。這包括線程模型,連接生命期,快編碼,以及所有 HTTP 協議允許你做的,所有的一切,你都將擁有絕對的控制力。
由于這種高度可定制化的特性,你可以開發一個非常高效的HTTP服務器,例如:
- 要求持久化鏈接以及服務器端推送技術的聊天服務(如,[Comet](http://en.wikipedia.org/wiki/Comet_%28programming%29) )
- 需要保持鏈接直至整個文件下載完成的媒體流服務(如,2小時長的電影)
- 需要上傳大文件并且沒有內存壓力的文件服務(如,上傳1GB文件的請求)
- 支持大規模混合客戶端應用用于連接以萬計的第三方異步 web 服務。
### WebSockets 實現
[WebSockets](http://en.wikipedia.org/wiki/WebSockets) 允許雙向,全雙工通信信道,在 TCP socket 中。它被設計為允許一個 Web 瀏覽器和 Web 服務器之間通過數據流交互。
WebSocket 協議已經被 IETF 列為 [RFC 6455](http://tools.ietf.org/html/rfc6455)規范。
Netty 實現了 RFC 6455 和一些老版本的規范。請參閱[io.netty.handler.codec.http.websocketx](http://netty.io/4.0/api/io/netty/handler/codec/http/websocketx/package-frame.html)包和相關的[例子](http://static.netty.io/3.5/xref/org/jboss/netty/example/http/websocketx/server/package-summary.html)。
### Google Protocol Buffer 整合
[Google Protocol Buffers](http://code.google.com/apis/protocolbuffers/docs/overview.html) 是快速實現一個高效的二進制協議的理想方案。通過使用 [ProtobufEncoder](http://netty.io/4.0/api/io/netty/handler/codec/protobuf/ProtobufEncoder.html) 和 [ProtobufDecoder](http://netty.io/4.0/api/io/netty/handler/codec/protobuf/ProtobufDecoder.html),你可以把 Google Protocol Buffers 編譯器 (protoc) 生成的消息類放入到 Netty 的codec 實現中。請參考“[LocalTime](http://docs.jboss.org/netty/3.2/xref/org/jboss/netty/example/localtime/package-summary.html)”實例,這個例子也同時顯示出開發一個由簡單協議定義 的客戶及服務端是多么的容易。
*譯者注:翻譯版本的項目源碼見 [https://github.com/waylau/netty-4-user-guide-demos](https://github.com/waylau/netty-4-user-guide-demos)*
## 總結
在這一章節,我們從功能特性的角度回顧了 Netty 的整體架構。Netty 有一個簡單卻不失強大的架構。這個架構由三部分組成——緩沖(buffer),通道(channel),事件模型(event model)——所有的高級特性都構建在這三個核心組件之上。一旦你理解了它們之間的工作原理,你便不難理解在本章簡要提及的更多高級特性。
你可能對 Netty 的整體架構以及每一部分的工作原理仍舊存有疑問。如果是這樣,最好的方式是[告訴我們](http://netty.io/community.html) 應該如何改進這份指南。
*譯者注:對本翻譯有任何疑問,在[https://github.com/waylau/netty-4-user-guide/issues](https://github.com/waylau/netty-4-user-guide/issues)提問*