protoBuf

 

1. 簡介

Netty 是一個異步的,事件驅動的網絡編程框架和工具,使用Netty 可以快速開發出可維護的,高性能、高擴展能力的協議服務及其客戶端應用。

也就是說,Netty 是一個基於NIO的客戶,服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。Netty相當簡化和流線化了網絡應用的編程開發過程,例如,TCP和UDP的socket服務開發。

“快速”和“簡單”並不意味着會讓你的最終應用產生維護性或性能上的問題。Netty 是一個吸收了多種協議的實現經驗,這些協議包括FTP,SMPT,HTTP,各種二進制,文本協議,並經過相當精心設計的項目,最終,Netty成功的找到了一種方式,在保證易於開發的同時還保證了其應用的性能,穩定性和伸縮性。

2. 總體架構

wps_clip_image-31221

2.1. 豐富的緩衝數據結構

Netty使用自建的buffer API,而不是使用NIO的ByteBuffer來代表一個連續的字節序列。與ByteBuffer相比這種方式擁有明顯的優勢。Netty使用新的buffer類型ChannelBuffer,ChannelBuffer被設計爲一個可從底層解決ByteBuffer問題,並可滿足日常網絡應用開發需要的緩衝類型。這些很酷的特性包括:

l 如果需要,允許自定義緩衝類型。

l 通過內置複合緩衝類型實現透明的零拷貝。

l 提供開箱即用的動態緩衝類型,其容量可以根據需要擴充,類似於StringBuffer。

l 不再需要調用的flip()方法。

l 一般來說比ByteBuffer更快。

關於Netty buffer更多的信息,參考:http://docs.jboss.org/netty/3.2/api/org/jboss/netty/buffer/package-summary.html#package_description 。

2.2. 統一的異步 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的統一的異步I/O編程接口,這個編程接口抽象了所有點對點的通信操作。也就是說,如果你的應用是基於Netty的某一種傳輸實現,那麼同樣的,你的應用也可以運行在Netty的另一種傳輸實現上。Netty提供了幾種擁有相同編程接口的基本傳輸實現:

l NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),

l OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),

l OIO-based UDP/IP transport, and

l Local transport (See org.jboss.netty.channel.local).

切換不同的傳輸實現通常只需對代碼進行幾行的修改調整,例如選擇一個不同的ChannelFactory實現。
此外,你甚至可以利用那些還沒有寫入的新的傳輸協議,只需替換一些構造器的調用方法即可,例如串口通信。而且由於核心API具有高度的可擴展性,你還可以完成自己的傳輸實現。

2.3. 基於攔截鏈模式的事件模型

一個定義良好並具有擴展能力的事件模型是事件驅動開發的必要條件。Netty具有定義良好的I/O事件模型。由於嚴格的層次結構區分了不同的事件類型,因此Netty也允許你在不破壞現有代碼的情況下實現自己的事件類型。這是與其他框架相比另一個不同的地方。很多NIO框架沒有或者僅有有限的事件模型概念;在你試圖添加一個新的事件類型的時候常常需要修改已有的代碼,或者根本就不允許你進行這種擴展。
在一個ChannelPipeline內部一個ChannelEvent被一組ChannelHandler處理。這個管道是攔截過濾器模式的一種高級形式的實現,因此對於一個事件如何被處理以及管道內部處理器間的交互過程,你都將擁有絕對的控制力。例如,你可以定義一個從socket讀取到數據後的操作:

Java代碼

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); 

    } 

}

同時你也可以定義當處理器接收入到寫請求時的動作:

Java代碼

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部分。

2.4. 適用快速開發的高級組件

上述所提及的核心組件已經足夠實現各種類型的網絡應用,除此之外,Netty也提供了一系列的高級組件來加速你的開發過程。

2.4.1. Codec框架

從業務邏輯代碼中分離協議處理部分總是一個很不錯的想法。然而如果一切從零開始便會遭遇 到實現上的複雜性。你不得不處理分段的消息。一些協議是多層的(例如構建在其他低層協議之上的協議)。一些協議過於複雜以致難以在一臺主機(single state machine)上實現。

因此,一個好的網絡應用框架應該提供一種可擴展,可重用,可單元測試並且是多層的codec框架,爲用戶提供易維護的codec代碼。
Netty提供了一組構建在其核心模塊之上的codec實現,這些簡單的或者高級的codec實現幫你解決了大部分在你進行協議處理開發過程會遇到的問題,無論這些協議是簡單的還是複雜的,二進制的或是簡單文本的。

2.4.2. SSL / TLS 支持

不同於傳統阻塞式的I/O實現,在NIO模式下支持SSL功能是一個艱難的工作。你不能只是簡單的包裝一下流數據並進行加密或解密工作,你不得不藉助於 javax.net.ssl.SSLEngine,SSLEngine是一個有狀態的實現,其複雜性不亞於SSL自身。你必須管理所有可能的狀態,例如密 碼套件,密鑰協商(或重新協商),證書交換以及認證等。此外,與通常期望情況相反的是SSLEngine甚至不是一個絕對的線程安全實現。
在Netty內部,SslHandler 封裝了所有艱難的細節以及使用SSLEngine可能帶來的陷阱。你所做的僅是配置並將該SslHandler插入到你的ChannelPipeline中。同樣Netty也允許你實現像StartTlS 那樣所擁有的高級特性,這很容易。

2.4.3.  HTTP實現

HTTP無疑是互聯網上最受歡迎的協議,並且已經有了一些例如Servlet容器這樣的HTTP實現。因此,爲什麼Netty還要在其核心模塊之上構建一套HTTP實現?
與現有的HTTP實現相比Netty的HTTP實現是相當與衆不同的。在HTTP消息的低層交互過程中你將擁有絕對的控制力。這是因爲Netty的 HTTP實現只是一些HTTP codec和HTTP消息類的簡單組合,這裏不存在任何限制——例如那種被迫選擇的線程模型。你可以隨心所欲的編寫那種可以完全按照你期望的工作方式工作的客戶端或服務器端代碼。這包括線程模型,連接生命期,快編碼,以及所有HTTP協議允許你做的,所有的一切,你都將擁有絕對的控制力。
由於這種高度可定製化的特性,你可以開發一個非常高效的HTTP服務器,例如:

l 要求持久化鏈接以及服務器端推送技術的聊天服務(e.g. Comet )

l 需要保持鏈接直至整個文件下載完成的媒體流服務(e.g. 2小時長的電影)

l 需要上傳大文件並且沒有內存壓力的文件服務(e.g. 上傳1GB文件的請求)

l 支持大規模mash-up應用以及數以萬計連接的第三方web services異步處理平臺

2.4.4. Google Protocol Buffer 整合

Google Protocol Buffers 是快速實現一個高效的二進制協議的理想方案。通過使用 ProtobufEncoderProtobufDecoder,你可以把Google Protocol Buffers 編譯器(protoc)生成的消息類放入到Netty的codec實現中。請參考官方示例中的“LocalTime”示例,這個例子也同時顯示出開發一個由簡單協議定義的客戶及服務端是多麼的容易。

2.5. 總述

在這一章節,我們從功能特性的角度回顧了Netty的整體架構。Netty有一個簡單卻不失強大的架構。這個架構由三部分組成——緩衝(buffer), 通道(channel),事件模型(event model)——所有的高級特性都構建在這三個核心組件之上。一旦你理解了它們之間的工作原理,你便不難理解在本章簡要提及的更多高級特性。

3. 比較重要的幾個特性

3.1. buffer

org.jboss.netty.buffer包的接口及類的結構圖如下:

wps_clip_image-7320

該包核心的接口是ChannelBuffer和ChannelBufferFactory,下面予以簡要的介紹。

Netty使用ChannelBuffer來存儲並操作讀寫的網絡數據。ChannelBuffer除了提供和ByteBuffer類似的方法,還提供了 一些實用方法,具體可參考其API文檔。ChannelBuffer的實現類有多個,這裏列舉其中主要的幾個:

l HeapChannelBuffer:這是Netty讀網絡數據時默認使用的ChannelBuffer,這裏的Heap就是Java堆的意 思,因爲讀SocketChannel的數據是要經過ByteBuffer的,而ByteBuffer實際操作的就是個byte數組,所以 ChannelBuffer的內部就包含了一個byte數組,使得ByteBuffer和ChannelBuffer之間的轉換是零拷貝方式。根據網絡字節續的不同,HeapChannelBuffer又分爲BigEndianHeapChannelBuffer和 LittleEndianHeapChannelBuffer,默認使用的是BigEndianHeapChannelBuffer。Netty在讀網絡數據時使用的就是HeapChannelBuffer,HeapChannelBuffer是個大小固定的buffer,爲了不至於分配的Buffer的大小不太合適,Netty在分配Buffer時會參考上次請求需要的大小。

l DynamicChannelBuffer:相比於HeapChannelBuffer,DynamicChannelBuffer可動態自適應大小。對於在DecodeHandler中的寫數據操作,在數據大小未知的情況下,通常使用DynamicChannelBuffer。

l ByteBufferBackedChannelBuffer:這是directBuffer,直接封裝了ByteBuffer的 directBuffer。

對於讀寫網絡數據的buffer,分配策略有兩種:

l 通常出於簡單考慮,直接分配固定大小的buffer,缺點是,對一些應用來說這個大小限制有時是不合理的,並且如果buffer的上限很大也會有內存上的浪費。

l 針對固定大小的buffer缺點,就引入動態buffer。

Netty對buffer的處理策略是:讀請求數據時,Netty首先讀數據到新創建的固定大小的HeapChannelBuffer中,當HeapChannelBuffer滿或者沒有數據可讀時,調用handler來處理數據,這通常首先觸發的是用戶自定義的DecodeHandler,因爲handler對象是和ChannelSocket 綁定的,所以在DecodeHandler裏可以設置ChannelBuffer成員,當解析數據包發現數據不完整時就終止此次處理流程,等下次讀事件觸發時接着上次的數據繼續解析。就這個過程來說,和ChannelSocket綁定的DecodeHandler中的Buffer通常是動態的可重用 Buffer(DynamicChannelBuffer),而在NioWorker中讀ChannelSocket中的數據的buffer是臨時分配的 固定大小的HeapChannelBuffer,這個轉換過程是有個字節拷貝行爲的。

對ChannelBuffer的創建,Netty內部使用的是ChannelBufferFactory接口,具體的實現有 DirectChannelBufferFactory和HeapChannelBufferFactory。對於開發者創建 ChannelBuffer,可使用實用類ChannelBuffers中的工廠方法。

3.2. Channel

和Channel相關的接口及類結構圖如下:

wps_clip_image-13127

從該結構圖也可以看到,Channel主要提供的功能如下:

l 當前Channel的狀態信息,比如是打開還是關閉等。

l 通過ChannelConfig可以得到的Channel配置信息。

l Channel所支持的如read、write、bind、connect等IO操作。

l 得到處理該Channel的ChannelPipeline,既而可以調用其做和請求相關的IO操作。

在Channel實現方面,以通常使用的nio socket來說,Netty中的NioServerSocketChannel和NioSocketChannel分別封裝了java.nio中包含的 ServerSocketChannel和SocketChannel的功能。

3.3. ChannelEvent

如前所述,Netty是事件驅動的,其通過ChannelEvent來確定事件流的方向。一個ChannelEvent將被ChannelPipeline的一系列ChannelHandler處理。下面是和 ChannelEvent相關的接口及類圖:

wps_clip_image-30696

事件流有兩種,upstream event和downstream event。當服務器端接收到來自客戶端的消息時,這個事件連帶着消息就是一個“upstream event”,當服務器端發送消息或響應給客戶端時,這個事件連帶着相關的寫請求就是一個“downstream event”。注意,“upstream event”被ChannelPipeline中的ChannelHandler處理的順序是從第一個到最後一個,而“downstream event”正好相反。

對於使用者來說,在ChannelHandler實現類中會使用繼承於ChannelEvent的MessageEvent,調用其 getMessage()方法來獲得讀到的ChannelBuffer或被轉化的對象。

3.4. ChannelPipeline

Netty 在事件處理上,是通過ChannelPipeline來控制事件流,通過調用註冊其上的一系列ChannelHandler來處理事件,這也是典型的攔截器模式。下面是和ChannelPipeline相關的接口及類圖:

wps_clip_image-17069

在ChannelPipeline中,其可被註冊的ChannelHandler 既可以 是 ChannelUpstreamHandler 也可以是ChannelDownstreamHandler ,但事件在ChannelPipeline傳遞過程中只會調用匹配流的ChannelHandler。在事件流的過濾器鏈中,ChannelUpstreamHandler或ChannelDownstreamHandler既可以終止流程,也可以通過調用 ChannelHandlerContext.sendUpstream(ChannelEvent)或 ChannelHandlerContext.sendDownstream(ChannelEvent)將事件傳遞下去。下面是事件流處理的圖示:

wps_clip_image-16465

從上圖可見,upstream event是被Upstream Handler們自底向上逐個處理,downstream event是被Downstream Handler們自頂向下逐個處理,這裏的上下關係就是向ChannelPipeline裏添加Handler的先後順序關係。簡單的理解,upstream event是處理來自外部的請求的過程,而downstream event是處理向外發送請求的過程。

舉例來說,假設我們創建了以下的管道:

ChannelPipeline p = Channels.pipeline();

p.addLast("1", new UpstreamHandlerA());

p.addLast("2", new UpstreamHandlerB());

p.addLast("3", new DownstreamHandlerA());

p.addLast("4", new DownstreamHandlerB());

p.addLast("5", new UpstreamHandlerX());

其中類名以Upstream開頭的是一個upstream  handler,類名以Downstream開頭的是一個downstream handler。當一個upstream event發生的時候,處理的順序應該是1,2,3,4,5,而當一個downstream event發生的時候,處理的順序應該是5,4,3,2,1,在這個原則之上,ChannelPipeline會跳過一些handler:

l 3和4號handler沒有實現ChannelUpstreamHandler,因此實際上upstream event 被處理的順序爲:1,2,5。

l 1,2和5號handler沒有實現ChannelDownstreamHandler,因此實現上downstream event 被處理的順序爲4,3。

l 如果5號handler繼承了SimpleChannelHandlerSimpleChannelHandler實現了ChannelUpstreamHandlerChannelDownstreamHandler接口),那麼upstream event和downstream event被處理的順序就應該是125和543。

服務端處理請求的過程通常就是解碼請求、業務邏輯處理、編碼響應,構建的ChannelPipeline也就類似下面的代碼片斷:

ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
pipeline.addLast("handler", new MyBusinessLogicHandler());

其中,MyProtocolDecoder是ChannelUpstreamHandler類型,MyProtocolEncoder是 ChannelDownstreamHandler類型,MyBusinessLogicHandler既可以是 ChannelUpstreamHandler類型,也可兼ChannelDownstreamHandler類型,視其是服務端程序還是客戶端程序以及應用需要而定。

補充一點,Netty對抽象和實現做了很好的解耦。像org.jboss.netty.channel.socket包,定義了一些和socket處理相關的接口,而org.jboss.netty.channel.socket.nio、 org.jboss.netty.channel.socket.oio等包,則是和協議相關的實現。

3.5. codec framework

對於請求協議的編碼解碼,當然是可以按照協議格式自己操作ChannelBuffer中的字節數據。另一方面,Netty也做了幾個很實用的 codec helper,這裏給出簡單的介紹。

l FrameDecoder:FrameDecoder內部維護了一個 DynamicChannelBuffer成員來存儲接收到的數據,它就像個抽象模板,把整個解碼過程模板寫好了,其子類只需實現decode函數即可。 FrameDecoder的直接實現類有兩個:(1)DelimiterBasedFrameDecoder是基於分割符 (比如\r\n)的解碼器,可在構造函數中指定分割符。(2)LengthFieldBasedFrameDecoder是基於長度字段的解碼器。如果協 議 格式類似“內容長度”+內容、“固定頭”+“內容長度”+動態內容這樣的格式,就可以使用該解碼器,其使用方法在API DOC上詳盡的解釋。

l ReplayingDecoder: 它是FrameDecoder的一個變種子類,它相對於FrameDecoder是非阻塞解碼。也就是說,使用 FrameDecoder時需要考慮到讀到的數據有可能是不完整的,而使用ReplayingDecoder就可以假定讀到了全部的數據。

l ObjectEncoder 和ObjectDecoder:編碼解碼序列化的Java對象。

l HttpRequestEncoder和 HttpRequestDecoder:http協議處理。

下面來看使用FrameDecoder和ReplayingDecoder的兩個例子:

public class IntegerHeaderFrameDecoder extends FrameDecoder {
protected Object decode(ChannelHandlerContext ctx, Channel channel,
ChannelBuffer buf) throws Exception {
if (buf.readableBytes() < 4) {
return null;
}
buf.markReaderIndex();
int length = buf.readInt();
if (buf.readableBytes() < length) {
buf.resetReaderIndex();
return null;
}
return buf.readBytes(length);
}
}

而使用ReplayingDecoder的解碼片斷類似下面的,相對來說會簡化很多。

public class IntegerHeaderFrameDecoder2 extends ReplayingDecoder {
protected Object decode(ChannelHandlerContext ctx, Channel channel,
ChannelBuffer buf, VoidEnum state) throws Exception {
return buf.readBytes(buf.readInt());
}
}

就實現來說,當在ReplayingDecoder子類的decode函數中調用ChannelBuffer讀數據時,如果讀失敗,那麼ReplayingDecoder就會catch住其拋出的Error,然後ReplayingDecoder接手控制權,等待下一次讀到後續的數據後繼續decode。

4. 源代碼分析

4.1. org.jboss.netty.bootstrap

提供了一些輔助類用於實現典型的服務器和客戶端初始化。

4.2. org.jboss.netty.buffer

提供Netty中的byte buffer的抽象和實現。

4.3. org.jboss.netty.channel

channel核心API,包括異步和事件驅動等各種傳送接口。

4.4. org.jboss.netty.channel.group

channel group,幫助用戶維護channel列表。

4.5. org.jboss.netty.local

一種虛擬傳輸方式,允許同一個虛擬機上的兩個部分可以互相通信。

4.6. org.jboss.netty.socket

TCP、UDP接口,繼承了核心的channel API。

4.7. org.jboss.netty.socket.nio

基於nio的socket channel實現

4.8. org.jboss.netty.socket.oio

基於老io的socket channel實現

4.9. org.jboss.netty.socket.http

基於http的客戶端和相應的server端的實現,工作在有防火牆的情況

4.10. org.jboss.netty.container

各種容器的兼容

4.11. org.jboss.netty.container.microcontainer

JBoss Microcontainer集成接口

4.12. org.jboss.netty.container.osgi

OSGi framework集成接口

4.13. org.jboss.netty.container.spring

Spring framework集成接口

4.14. org.jboss.netty.handler

處理器

4.15. org.jboss.netty.handler.codec

編碼解碼器

4.16. org.jboss.netty.handler.execution

基於Executor的實現

4.17. org.jboss.netty.handler.queue

將event存入內部隊列的處理

4.18. org.jboss.netty.handler.ssl

基於SSLEngine的SSL以及TLS實現

4.19. org.jboss.netty.handler.stream

異步寫入大數據,不會產生OutOfMemory也不會花費很多內存

4.20. org.jboss.netty.handler.timeout

通過Timer來對讀寫超時或者閒置鏈接進行通知

4.21. org.jboss.netty.handler.codec.base64

Base64編碼

4.22. org.jboss.netty.handler.codec.compression

壓縮格式

4.23. org.jboss.netty.handler.codec.embedder

嵌入模式下編碼和解碼

4.24. org.jboss.netty.handler.codec.frame

評估流的數據的排列和內容

4.25. org.jboss.netty.handler.codec.http.websocket

websocket編碼和解碼

4.26. org.jboss.netty.handler.codec.http

http的編碼解碼以及類型信息

4.27. org.jboss.netty.handler.codec.oneone

對象到對象編碼解碼

4.28. org.jboss.netty.handler.codec.protobuf

Protocol Buffers的編碼解碼

4.29. org.jboss.netty.handler.codec.replay

在阻塞io中實現非阻塞解碼

4.30. org.jboss.netty.handler.codec.rtsp

RTSP的編碼解碼

4.31. org.jboss.netty.handler.codec.serialization

序列化對象到bytebuffer實現

4.32. org.jboss.netty.handler.codec.string

字符串編碼解碼,繼承oneone

4.33. org.jboss.netty.logging

根據不同的log framework實現的類

4.34. org.jboss.netty.util

Netty util類

4.35. org.jboss.netty.internal

Netty 內部util類,不被外部使用

5. 參考資料

Netty官網:http://www.jboss.org/netty

Netty文檔及範例:http://www.jboss.org/netty/documentation

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章