Netty 4.0 新的特性及須要注意的地方

Netty 4.0 新的特性及須要注意的地方

這篇文章和你一塊兒過下Netty的主發行版本的一些顯著的改變和新特性,讓你在把你的應用程序轉換到新版本的時候有個概念。html

項目結構改變

Netty的包名從org.jboss.netty改成io.netty,由於咱們不在是JBoss.org的一部分了java

二進制JAR包被分爲了多個子模塊以便用戶可以從類路徑中去掉非必需的特性。當前的結構以下:git

模塊 描述
netty project parent
common utility and logging
buffer buffer API
transport channel API and its core implementations
transport-rtrx RTRX transport implementation
transport-sctp SCTP transport implementation
transport-udt UDT transport implementation
handler channel handlers
codec codec framework
codec-http HTTP, Web Sockets, SPDY, and RTSP codec
codec-socks Socks codec
example examples
all generates an all-in-one JAR
tarball generates a tarball distribution

全部的Netty的Jar(除了netty-all外)包如今都是OSGI的bundle,可以用在你喜歡的OSGI容器上。github

經常使用API的變化

  • 如今Netty裏的大部分操做都支持簡潔的方法鏈。
  • 不能配置的getter如今都沒有了get/is前綴 (如Channel.getRemoteAddress()→Channel.remoteAddress())

Buffer API變化

ChannelBuffer → ByteBuf

因爲上文所提到的結構上的變化,buffer API如今能夠做爲一個單獨的包被使用。爲此,ChannelBuffer這個類型名也不那麼講得通了,而應該變動爲ByteBuf。web

用來建立新buffer的功能類ChannelBuffers被拆分爲兩個功能類:Unpooled和BufUtil。就像這個名字所暗示的,4.0引入了一個新的池化的ByteBufs,它能夠經過ByteBuf的分配器(Allocator)的對應實現ByteBufAllocator來得到。bootstrap

大多數的buffer變成了動態的,具有可配置的最大容量

在3.x時期,buffer分爲固定和動態兩種類型。一個固定buffer的容量在建立以後就沒法改變,而動態buffer的容量在write*(譯者按:writeByte,writeInt,writeLong...)方法須要更多空間時自動擴容。api

從4.0開始,全部buffer都變成了動態的。可是,相對於以前的動態進行了優化。你能夠更容易也更安全的對一個buffer的容量進行擴大和縮小。之因此說它容易是由於有一個新的ByteBuf.capacity(int newCapacity)的方法。說它安全是由於你能夠設置一個容量的最大值,以防止容量沒有限制的增大。數組

1 // 不要再使用 dynamicBuffer() - 使用 buffer().
2 ByteBuf buf = ByteBuf.buffer();
3  
4 // 增長buffer的容量
5 buf.capacity(1024);
6 ...
7  
8 // 縮減buffer的容量 (最後的512個byte被丟棄)
9 buf.capacity(512);

惟一的例外是那些使用wrappedBuffer方法建立的,包裝(warp)了一個buffer或一個byte數組的buffer。你沒法擴大它的容量,由於這樣會使包裝一個已有buffer的目的是去意義——減小內存的複製。若是你想要在包裝了一個buffer以後改變它的容量,你應該從新建立一個擁有足夠容量的buffer,而後將你想要包裝的那個buffer的內容複製過來。promise

新接口: CompositeByteBuf

一個新的名叫CompositeByteBuf的接口爲組合buffer(composite buffer)的實現定義了多種高級的操做。一個用戶可使用組合buffer,以只比隨機訪問大一點的代價達到一個批量內存複製的目的。要建立一個新的組合buffer,能夠像之前同樣使用Unpooled.wrappedBuffer(... 譯者注:此處省略號應該是指省略方法參數,下同)或Unpooled.compositeBuffer(...)。緩存

可預知的NIO buffer轉型

在3.x中,ChannelBuffer.toByteBuffer()以及它的其餘變體所提供的約定並不那麼明確。用戶沒法肯定這些方法會返回一個擁有共享數據的視圖buffer仍是一個擁有獨立數據的經過複製獲得的buffer。4.0將toByteBuffer()替換爲ByteBuf.nioBufferCount(),nioBuffer(),以及nioBUffers()。若是調用nioBufferCount()返回0,用戶老是能夠經過調用copy().nioBuffer()來得到一個複製的buffer。

對小字節序變動的支持

對小字節序的支持經歷了重大變化。在以前的版本中,一個用戶爲了獲得一個小字節序的buffer有兩種選擇:特別指定一個LittleEndianHeapChannelBufferFactory;用目標字節序將已存在的buffer包裝起來。4.0添加了一個新方法,ByteBuf.order(ByteOrder)。這個方法返回當前buffer對象的一個具備指定字節序的視圖buffer:

01 import io.netty.buffer.ByteBuf;
02 import io.netty.buffer.Unpooled;
03 import java.nio.ByteOrder;
04   
05 ByteBuf buf = Unpooled.buffer(4);
06 buf.setInt(01);
07 // 打印出 '00000001'
08 System.out.format("%08x%n", buf.getInt(0));
09   
10 ByteBuf leBuf = buf.order(ByteOrder.LITTLE_ENDIAN);
11 // 打印出 '01000000'
12 System.out.format("%08x%n", leBuf.getInt(0));
13   
14 assert buf != leBuf;
15 assert buf == buf.order(ByteOrder.BIG_ENDIAN);

Pooled ByteBuf

前面已經提到Netty引入了pooledByteBufinstances。這在不少方面都很實用,舉列以下:

  • 限制了GC壓力,這是由於使用unpooled ByteBufs會形成沉重的分配與再分配問題
  • Better handling of direct (native)ByteBuf更好的處理直接(本地)的ByteBuf
  • 一個ByteBuf 能夠被一個ByteBufAllocator包含.
01 public interface ByteBufAllocator {
02   
03     ByteBuf buffer();
04     ByteBuf buffer(int initialCapacity);
05     ByteBuf buffer(int initialCapacity, int maxCapacity);
06     ByteBuf heapBuffer();
07     ByteBuf heapBuffer(int initialCapacity);
08     ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
09     ByteBuf directBuffer();
10     ByteBuf directBuffer(int initialCapacity);
11     ByteBuf directBuffer(int initialCapacity, int maxCapacity);
12     ByteBuf ioBuffer();
13   
14     CompositeByteBuf compositeBuffer();
15     CompositeByteBuf compositeBuffer(int maxNumComponents);
16     CompositeByteBuf compositeHeapBuffer();
17     CompositeByteBuf compositeHeapBuffer(int maxNumComponents);
18     CompositeByteBuf compositeDirectBuffer();
19     CompositeByteBuf compositeDirectBuffer(int maxNumComponents);
20 }
要想從一個handler那裏獲取當前的 ByteBufAllocator,可使用ChannelHandlerContext.alloc()或Channel.alloc()方法:
1 Channel channel = ...;
2 ByteBuf buf = channel.alloc().buffer(512);
3 ....
4 channel.write(buf);
5   
6 ChannelHandlerContext ctx = ...
7 ByteBuf buf2 = ctx.alloc().buffer(512);
8 ....
9 channel.write(buf2)

一旦一個ByteBuf被寫入遠程節點,它會再次自動的釋放進入釋放到池(the pool)裏。

默認的ByteBufAllocator爲PooledByteBufAllocator.若是你不但願使用buffer pooling或使用你本身的allocator,你能夠運用Channel.config().setAllocator(..),以及一個可供選擇的 allocator,好比UnpooledByteBufAllocator。

Channel API的變化

在4.0中,許多io.netty.channel包中的類都經歷大量修改,所以文本上的簡單搜索-替換是沒法讓你基於3.x的程序遷移到4.0上。這個部分會嘗試將這些重大變動背後的思考過程展現出來,而不僅是簡單地做爲展現全部變動。

翻新後的ChannelHandler接口

Upstream → Inbound, Downstream → Outbound

對於初學者來講,術語'upstream'(譯者注:直譯爲向上遊,有點像TCP/IP協議棧中從下往上,從物理層最終到達應用層這麼一個流程)和'downstream'有點讓人迷惑。在4.0中,只要可能,都會使用'inbound'(譯者注:直譯爲開往內地的,相對於upstream確實更貼切,即指數據從外部網絡經歷層層filter到達咱們的處理邏輯)和'outbound'來替換他們。

新的ChannelHandler繼承層次

在3.x時代,ChannelHandler只是一個標記接口,而在ChannelUpstreamHandler、ChannelDownstreamHandler、LifeCycleAwareChannelHandler定義了具體的處理器方法。在Netty 4中,ChannelHandler將LifeCycleAwareChannelHandler接口和一堆實現輔助方法融合到了一塊兒,具體見代碼:

01 public interface ChannelHandler {
02   
03     void beforeAdd(ChannelHandlerContext ctx) throws Exception;
04     void afterAdd(ChannelHandlerContext ctx) throws Exception;
05     void beforeRemove(ChannelHandlerContext ctx) throws Exception;
06     void afterRemove(ChannelHandlerContext ctx) throws Exception;
07   
08     void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
09     void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
10     ...
11 }

下方的圖表描述了這個新的類型集成層次:

fixme(原文中尚未插入此圖)

事件對象從ChannelHandler中消失了

在3.x時代,全部的I/O操做都會建立一個新的ChannelEvent對象。對每一個讀或寫的操做,還會額外建立一個新的ChannelBuffer對象。因爲將資源管理和buffer的池化交給了JVM,這實際上極大地簡化了Netty的內部實現。可是,基於Netty開發的應用在高負載下運行時,有時會觀察到GC(Garbage Collection)的壓力增大或變化不定,這些問題的根源也來自於這裏。

4.0經過把事件對象替換爲直接與類型相對應(譯者注:原文爲strongly typed,可是我以爲直譯爲強類型不太容易理解)的方法調用,幾乎徹底避免了事件對象的建立。3.x中,有相似於handleUpstream()和handleDownstream()這種可以捕獲全部相關類型事件的處理器方法,4.0中你將不會再看到它們的身影了。全部的事件類型如今都有各自對應的處理器方法:

01 // 3.x時代:
02 void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception;
03 void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception;
04   
05 // 4.0:
06 void channelRegistered(ChannelHandlerContext ctx) throws Exception;
07 void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
08 void channelActive(ChannelHandlerContext ctx) throws Exception;
09 void channelInactive(ChannelHandlerContext ctx) throws Exception;
10 void inboundBufferUpdated(ChannelHandlerContext ctx) throws Exception;
11   
12 void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelFuture future) throws Exception;
13 void connect(
14         ChannelHandlerContext ctx, SocketAddress remoteAddress,
15         SocketAddress localAddress, ChannelFuture future) throws Exception;
16 void disconnect(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
17 void close(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
18 void deregister(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
19 void flush(ChannelHandlerContext ctx, ChannelFuture future) throws Exception;
20 void read(ChannelHandlerContext ctx);
21 void sendFile(ChannelHandlerContext ctx, FileRegion region, ChannelPromise promise) throws Exception;

ChannelHandlerContext類也被修改來反映上述提到的變化:

 

1 // Before:
2 ctx.sendUpstream(evt);
3   
4 // After:
5 ctx.fireInboundBufferUpdated();

全部這些變化意味着用戶沒法去擴展ChannelEvent這個已經不存在的接口了。那用戶要怎樣才能定義他或她本身的事件類型呢,就像IdleStateEvent?4.0中的ChannelHandler有一個處理器方法叫作userEventTriggered(),它就是被設計用來知足這種特殊的用戶需求。

Simplified channel state model

在3.x中,當一個新的Channel被建立並鏈接成功,至少三個ChannelStateEvent會被觸發:channelOpen、channelBound以及channelConnected。當一個Channel關閉,則對應channelDisconnected、channelUnbound以及channelClosed三個事件。

fixme

可是,觸發這麼多事件的意義並不那麼明顯。若是在一個Channel進入可讀或可寫的狀態時通知用戶,想來會更有幫助。

fixme

channelOpen、channelBound和channelConnected被合併爲channelActive。channelDisconnected、channelUnbound和channelClosed被合併爲channelInactive。相似的,Channel.isBound()和Channel.isConnected()也被合併爲了Channel.isActive()。

須要注意的是,channelRegistered和channelUnregistered這兩個事件與channelOpen和channelClosed具備的意義是不同的。它們(channelRegistered和channelUnregistered)是在支持Channel的動態註冊、註銷以及再註冊時被引入的,就像下圖所示:

fixme

每一個處理器的緩存

不像3.x那樣在每次讀操做都簡歷一個新堆裏的緩存來觸發上游的MessageEvent,4.0不會每次都建立新的 緩存。它直接從socket中讀取數據到由用戶的ChannelInboundByteHandler和ChannelInboundMessageHandler實現建立的入站緩存。

由於由上述處理器建立的入站緩存直到關聯的通道關閉前都會重用,因此在上面的GC和內存帶寬消耗都能保持較小。一樣,當接收到的數據被銷燬時用戶已經完成操做,codec的實現就變得更簡單和有效了

在建立出站緩存時也是差很少的(不會新建)。用戶的ChannelOutBoundBYteHandler和ChannelOutboundMessageHandler來操做。

不須要每條消息都有一個事件

4.0裏再也不有了messageReceived或writeRequested處理器方法。它們被inboundBufferUpdated和flush代替了。用戶的入隊一個或多個消息到一個入站(或出站)緩存同時會出發一個inboundBUfferUpdated(或flush)事件。

01 public void inboundBufferUpdated(ChannelHandlerContext ctx) {
02     Queue<MyMessage> in = ctx.inboundMessageBuffer();
03     Queue<MyNewMessage> out = ctx.nextInboundMessageBuffer();
04     for (;;) {
05         MyMessage m = in.poll();
06         if (m == null) {
07             break;
08         }
09         MyNewMessage decoded = decode(m);
10         out.add(decoded);
11     }
12     ctx.fireInboundBufferUpdated();
13 }
14  
15 public void flush(ChannelHandlerContext ctx, ChannelFuture future) {
16     Queue<MyNewMessage> in = ctx.outboundMessageBuffer();
17     Queue<MyMessage> out = ctx.nextOutboundMessageBuffer();
18     for (;;) {
19         MyNewMessage m = in.poll();
20         if (m == null) {
21             break;
22         }
23         MyMessage encoded = encode(m);
24         out.add(encoded);
25     }
26     ctx.flush(future);
27 }
做爲選擇,用戶可以在每一個單獨的入站(或出站)消息中觸發這樣的事件來模擬老的行爲,儘管相對新方法來講效率更低。

消息處理器 vs. 字節處理器

在3.x裏一個MessageEvent持有一個任意的對象。它可以是一個ChannelBuffer或是一個用戶自定義的對象,它們都是一樣對待的:

01 @Override
02 public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
03     Object msg = evt.getMessage();
04     if (msg instanceof ChannelBuffer) {
05         ChannelBuffer buf = (ChannelBuffer) msg;
06         ...
07     else {
08         MyMessage myMsg = (MyMessage) msg;
09         ...
10     }
11 }
在4.0裏,它們就分別對待了,由於一個處理器再也不處理一個獨立的消息,而是處理多種多樣的消息:
01 public void inboundBufferUpdated(ChannelHandlerContext ctx) {
02     if (ctx.hasInboundByteBuffer()) {
03         ByteBuf buf = ctx.inboundByteBuffer();
04         ...
05     else {
06         Queue<MyMessage> buf = ctx.inboundMessageBuffer();
07         for (;;) {
08             MyMessage msg = buf.poll();
09             if (buf == null) {
10                 break;
11             }
12             ...
13         }
14     }
15 }
你可能發現一個ServerChannel的處理器是一個入站緩存是Queue<Channel>的入站處理器是較爲有趣的。

處理器適配器

大多數用戶都發現建立和管理它的生命週期是繁瑣的,所以它支持用戶擴展預約義好的適配器類來使得更方便:

  • ChannelHandlerAdapter
  • ChannelStateHandlerAdapter
  • ChannelOperationHandlerAdapter
  • ChannelInboundMessageHandlerAdapter
  • ChannelInboundByteHandlerAdapter
  • ChannelOutboundMessageHandlerAdapter
  • ChannelOutboundByteHandlerAdapter

明智的和不易出錯的入站流量掛起

3.x有一個由Channel.setReadable(boolean)提供的不是很明顯的入站流量掛起機制。它引入了在ChannelHandler之間的複雜交互操做,同時處理器因爲不正確實現而很容易互相干擾。

4.0裏,新的名爲read()的出站操做增長了。若是你使用Channel.config().setAutoRead(false)來關閉默認的auto-read標誌,Netty不會讀入任何東西,直到你顯式地調用read()操做。一旦你發佈的read()操做完成,同時通道再次中止讀,一個名爲channelReadSuspended()的入站事件會觸發一遍你可以從新發布另外的read()操做。你一樣也能夠攔截read()操做來執行更多高級的流量控制。

暫停接收傳入的鏈接

在3.x裏,沒有方法讓一個用戶告訴Netty來廳子接收傳入鏈接,除非是阻塞I/O線程或者關閉服務器socket。在aotu-read標誌沒有設置的時候,4.0涉及到的read()操做就像一個普通的通道。

半關閉socket

TCP和SCTP容許用戶關閉一個socket的出站流量而不用徹底關閉它。這樣的socket被稱爲「半關閉socket」,同時用戶可以經過調用SocketChannel.shutdownOutput()方法來獲取一個半關閉socket。若是一個遠端關閉了出站通道,SocketChannel.read(..)會返回-1,這看上去並無和一個關閉了的連接有什麼區別。

3.x沒有shutdownOutput()操做。一樣,它老是在SocketChannel.read(..)返回-1的時候關閉連接。

要支持半關閉socket,4.0增長了SocketChannel.shutdownOutput()方法,同時用戶能設置「ALLOW_HALF_CLOSURE」的ChanneOption來阻止Netty在SocketChannel.read(..)返回-1的時候自動關閉連接。

靈活的I/O線程分配

在3.x裏,一個Channel是由ChannelFactory建立的,同時新建立的Channel會自動註冊到一個隱藏的I/O線程。4.0使用新的名爲EventLoopGroup的接口來替換ChannelFactory,它由一個或多個EventLoop來構成。一樣,一個新的Channel不會自動註冊到EventLoopGroup,但用戶能夠顯式調用EventLoopGroup.register()來註冊。

感謝這個變化(舉例來講,分離了ChannelFactory和I/O線程),用戶能夠註冊不一樣的Channel實現到同一個EventLoopGroup,或者同一個Channel實現到不一樣的EventLoopGroup。例如,你能夠運行一個NIO服務器socket,NIO UDP socket,以及虛擬機內部的通道在同一個I/O線程裏。在編寫一個須要最小延遲的代理服務器時這確實頗有用。

可以從一個已存在的jdk套接字上建立一個Channel

3.x沒提供方法從已存在的jdk套接字(如java.nio.channels.SocketChannel)建立一個新的通道。如今你能夠用4.0這樣作了。

取消註冊和從新註冊一個Channel從/到一個I/O線程

一旦一個新的Channel在3.x裏建立,它徹底綁定到一個單一的I/O線程上,直到它底層的socket關閉。在4.0裏,用戶可以從I/O線程裏取消註冊一個Channel來徹底控制它底層jdk套接字。例如,你可以利用Netty提供的高層次無阻塞I/O的優點來解決複雜的協議,而後取消註冊Channel而且切換到阻塞模式來在可能的最大吞吐量下傳輸一個文件。固然,它可以再次註冊已經取消了註冊的Channel。

01 java.nio.channels.FileChannel myFile = ...;
02 java.nio.channels.SocketChannel mySocket = java.nio.channels.SocketChannel.open();
03   
04 // Perform some blocking operation here.
05 ...
06   
07 // Netty takes over.
08 SocketChannel ch = new NioSocketChannel(mySocket);
09 EventLoopGroup group = ...;
10 group.register(ch);
11 ...
12   
13 // Deregister from Netty.
14 ch.deregister().sync();
15   
16 // Perform some blocking operation here.
17 mySocket.configureBlocking(false);
18 myFile.transferFrom(mySocket, ...);
19   
20 // Register back again to another event loop group.
21 EventLoopGroup anotherGroup = ...;
22 anotherGroup.register(ch);

調度任意的任務到一個I/O線程裏運行

當一個Channel被註冊到EventLoopGroup時,Channel其實是註冊到由EventLoopGroup管理EventLoop中的一個。EventLoop實現了java.utilconcurrent.ScheduledExecutorService接口。這意味着用戶能夠在一個用戶通道歸屬的I/O線程裏執行或調度一個任意的Runnable或Callable。隨着新的娘好定義的線程模型的到來(稍後會介紹),它變得極其容易地編寫一個線程安全的處理器。

01 public class MyHandler extends ChannelOutboundMessageHandlerAdapter {
02     ...
03     public void flush(ChannelHandlerContext ctx, ChannelFuture f) {
04         ...
05         ctx.flush(f);
06   
07         // Schedule a write timeout.
08         ctx.executor().schedule(new MyWriteTimeoutTask(), 30, TimeUnit.SECONDS);
09         ...
10     }
11 }
12   
13 public class Main {
14     public static void main(String[] args) throws Exception {
15         // Run an arbitrary task from an I/O thread.
16         Channel ch = ...;
17         ch.executor().execute(new Runnable() { ... });
18     }
19 }

簡化的關閉

releaseExternalResources()沒必要再用了。你能夠經過調用EventLoopGroup.shutdown()直接地關閉全部打開的鏈接同時使全部I/O線程中止,就像你使用java.util.concurrent.ExecutorService.shutdown()關閉你的線程池同樣。

類型安全的ChannelOptions

有兩個方法來配置Netty的Channel的socket參數。第一個是明確地調用ChannelConfig的setter,例如SocketChannelConfig.setTcpNoDelay(true)。這是最爲類型安全的方法。另一個是調用ChannelConfig.setOption()方法。有時候你不得不決定在運行時的時候socket要配置什麼選項,同時這個方法在這種狀況下有點不切實際。然而,在3.x裏它是容易出錯的,由於一個用戶必需用一對字符串和對象來指定選項。若是用戶調用了錯誤的選項名或者值,他或她將會趙宇到一個ClassCastException或指定的選項甚至可能會默默地忽略了。

4.0引入了名爲ChannelOption的新的類型,它提供了類型安全地訪問socket選項。

01 ChannelConfig cfg = ...;
02   
03 // Before:
04 cfg.setOption("tcpNoDelay"true);
05 cfg.setOption("tcpNoDelay"0);  // Runtime ClassCastException
06 cfg.setOption("tcpNoDelays"true); // Typo in the option name - ignored silently
07   
08 // After:
09 cfg.setOption(ChannelOption.TCP_NODELAY, true);
10 cfg.setOption(ChannelOption.TCP_NODELAY, 0); // Compile error

AttributeMap

在迴應用戶指令裏,你能夠附加任意的對象到Channel和ChannelHandlerContext。一個名爲AttributeMap的新接口被加入了,它被Channel和ChannelHandlerContext繼承。做爲替代,ChannelLocal和Channel.attachment被移除。這些屬性會在他們關聯的Channel被垃圾回收的同時回收。

01 public class MyHandler extends ChannelInboundMessageHandlerAdapter<MyMessage> {
02   
03     private static final AttributeKey<MyState> STATE =
04             new AttributeKey<MyState>("MyHandler.state");
05   
06     @Override
07     public void channelRegistered(ChannelHandlerContext ctx) {
08         ctx.attr(STATE).set(new MyState());
09         ctx.fireChannelRegistered();
10     }
11   
12     @Override
13     public void messageReceived(ChannelHandlerContext ctx, MyMessage msg) {
14         MyState state = ctx.attr(STATE).get();
15     }
16     ...
17 }

 

新的bootstrap API

bootstrap API已經重頭重寫,儘管它的目的仍是同樣;它執行須要使服務器或客戶端運行的典型步驟,一般能在樣板代碼裏找到。新的bootstrap一樣採起了流暢的接口。

01 public static void main(String[] args) throws Exception {
02     // Configure the server.
03     ServerBootstrap b = new ServerBootstrap();
04     try {
05         b.group(new NioEventLoopGroup(), new NioEventLoopGroup())
06          .channel(new NioServerSocketChannel())
07          .option(ChannelOption.SO_BACKLOG, 100)
08          .localAddress(8080)
09          .childOption(ChannelOption.TCP_NODELAY, true)
10          .childHandler(new ChannelInitializer<SocketChannel>() {
11              @Override
12              public void initChannel(SocketChannel ch) throws Exception {
13                  ch.pipeline().addLast(handler1, handler2, ...);
14              }
15          });
16   
17         // Start the server.
18         ChannelFuture f = b.bind().sync();
19   
20         // Wait until the server socket is closed.
21         f.channel().closeFuture().sync();
22     finally {
23         // Shut down all event loops to terminate all threads.
24         b.shutdown();
25     }
26 }

 

ChannelPipelineFactory → ChannelInitializer

和你在上面的例子注意到的同樣,ChannelPipelineFactory再也不存在了。而是由ChannelInitializer來替換,它給予了在Channel和ChannelPipeline的配置的更多控制。

請注意,你不能本身建立一個新的ChannelPipeline。經過觀察目前爲止的用例報告,Netty項目隊伍總結到讓用戶去建立本身的管道實現或者是繼承默認的實現是沒有好處的。所以,ChannelPipeline再也不讓用戶建立。ChannelPipeline由Channel自動建立。

ChannelFuture拆分爲ChannelFuture和ChannelPromise

ChannelFuture已經被拆分爲ChannelFuture和ChannelPromise了。這不只僅是讓異步操做裏的生產者和消費者間的約定更明顯,一樣也是得在使用從鏈中返回的ChannelFuture更加安全,由於ChannelFuture的狀態是不能改變的。

因爲這個編號,一些方法如今都採用ChannelPromiser而不是ChannelFuture來改變它的狀態。

良好定義的線程模型

在3.x裏並無良好設計的線程模型,儘管曾經要修復線程模型在3.5的不一致性。4.0定義的一個嚴格的線程模型來幫助用戶編寫ChannelHandler而沒必要擔憂太多關於線程安全的東西。

  • Netty將不會再同步地調用ChannelHandler的方法了,除非ChannelHandler由@Shareable註解。這不會理會處理器方法的相似——入站、操做、或者是生命週期時間處理器方法。
    • 用戶再也不須要同步入站或出站的事件處理器方法。
    • 4.0不容許加入加入一個ChannelHandler超過一次,除非它由@Sharable註解。
  • 每一個由Netty調用的ChannelHandler的方法之間的關係老是happens-before
    • 用戶不用定義一個volatile字段來保存處理器的狀態。
  • 用戶可以在他加入一個處理器到ChannelPipeline的時候指定EventExecutor。
    • 若是有指定,ChannelHandler的處理器方法老是由自動的EventExecutor來調用
    • 若是沒指定,處理器方法老是由它關聯的Channel註冊到的EventLoop來調用。
  • 聲明到一個處理器的EventExecutor和EventLoop老是單線程的。
    • 處理器方法老是由相同線程調用。
    • 若是指定了多線程的EventExecutor或EventLoop,線程中的一個會被選擇,而後選擇到的線程將會被使用,直到取消註冊。
    • 若是在相同管道里的兩個處理器聲明到不一樣的EventExecutor,它們會同時被調用。若是多個一個處理器去訪問共享數據,用戶須要注意線程安全,即便共享數據只能被相同管道里的處理器訪問。
  • 加入到ChannelFuture的ChannelFutureListener老是由關聯到future相關的Channel的EventLoop線程調用。

再也不有ExecutionHandler ——它包含到核內心

在你加入一個ChannelHandler到一個ChannelPipeline來告訴管道老是經過指定的EventExecutor調用加入的ChannelHander處理器的方法的時候,你能夠指定一個EventExecutor。

1 Channel ch = ...;
2 ChannelPipeline p = ch.pipeline();
3 EventExecutor e1 = new DefaultEventExecutor(16);
4 EventExecutor e2 = new DefaultEventExecutor(8);
5   
6 p.addLast(new MyProtocolCodec());
7 p.addLast(e1, new MyDatabaseAccessingHandler());
8 p.addLast(e2, new MyHardDiskAccessingHandler());

EventExecutor是EventLoop的超類,同時也繼承了ScheduledExecutorService。

fixme

編碼解碼器框架變化

在編碼解碼器框架裏有實質性的內部改變,由於4.0須要一個處理器來建立和管理它的緩存(看這篇文章的每一個處理器緩存部分。)然而,從用戶角度來看這些變化都不是很大的。

  • 核心編碼界面器類移到io.netty.handler.codec包裏。
  • FrameDecoder重命名爲ByteToMessageDecoder。
  • OneToOneEncoder和OneToOneDecoder由MessageToMessageEncoder和MessageToMessageDecoder替換。
  • decode(),decodeLast(),encode()的方法前面稍微改變了來支持泛型同時移除冗餘參數。

編碼解碼器嵌入器→ EmbeddedChannel

編碼解碼器嵌入器已經被 io.netty.channel.embedded.EmbeddedByteChannel和EmbeddedMessageChannel替換了。EmbeddedChannel容許用戶對任何包含編碼解碼器的管道進行單元測試。

HTTP編碼解碼器

HTTP解碼器如今在每一個HTTP消息中總生成多個消息對象:

1 1       * HttpRequest / HttpResponse
2 0 - n   * HttpContent
3 1       * LastHttpContent

要看更多的細節,請到轉到已更新了的HttpSnoopServer例子。若是你但願爲一個單一的HTTP消息處理多個消息,你能夠把HttpObjectAggregator放入管道里。HttpObjectAggregator會把多個消息轉換爲一個單一的FullHttpRequest或是FullHttpResponse。

傳輸實現的變化

下面是傳輸協議新加入的東西:

  • 使用NIO.2AsynchronousSocketChannel的AIO套接字傳輸實現。
  • OIO SCTP 傳輸實現
  • UDT 傳輸實現

用例學習:移植示例Factorial

這部分粗略地展現把示例Factorial從3.0移植到4.0的步驟。示例Factorial已經移植到4.0了,它放在io.netty.example.factorial包裏。請瀏覽示例的源代碼來看下每一處的變化。

移植服務端

  1. 重寫FactorialServer.run()方法來使用新的 bootstrap API。
    1. 再也不有ChannelFactory了。 由你本身去實例化NioEventLoop(一個是用來接收接入的連接,另外的就用來處理接收到的連接)。
  2. 從命名FactorialServerPipelineFactory爲FactorialServerInitializer。
    1. 讓它繼承ChannelInitializer<Channel>。
    2. 取代建立新的ChannelPipeline,經過Channel.pipeline()來獲取。
  3. 讓FactorialServerHandler繼承sChannelInboundMessageHandlerAdapter<BigInteger>。
    1. 用channelInactive()來替換channelDisconnected()。
    2. handleUpstream()不能再使用了。
  4. 讓BigIntegerDecoder繼承ByteToMessageDecoder<BigInteger>。
  5. 讓NumberEncoder繼承MessageToByteEncoder<Number>。
    1. encode()不在返回一個緩存了。由ByteToMessageDecoder來提供填充編碼好的數據到緩存裏。

移植客戶端

大部分和移植服務端差很少,但你要在你編寫一個潛在的大數據流時要多注意下。

  1. 重寫FactorialClient.run()方法來使用新的bootstrap API。
  2. 重命名FactorialClientPipelineFactory爲FactorialClientInitializer。
  3. 使FactorialClientHandler繼承ChannelInboundMessageHandlerAdapter<BigInteger>
    1. 在這一點,你發如今4.0裏沒有了Channel.isWritable()或者channelInterestChanged()。做爲代替,你本身來管理那些未決定的寫操做。新的sendNumbers()看起來以下:
    01 private void sendNumbers() {
    02     // Do not send more than 4096 numbers.
    03     boolean finished = false;
    04     MessageBuf<Object> out = ctx.nextOutboundMessageBuffer();
    05     while (out.size() < 4096) {
    06         if (i <= count) {
    07             out.add(Integer.valueOf(i));
    08             i ++;
    09         else {
    10             finished = true;
    11             break;
    12         }
    13     }
    14  
    15     ChannelFuture f = ctx.flush();
    16     if (!finished) {
    17         f.addListener(numberSender);
    18     }
    19 }
    20  
    21 private final ChannelFutureListener numberSender = new ChannelFutureListener() {
    22     @Override
    23     public void operationComplete(ChannelFuture future) throws Exception {
    24         if (future.isSuccess()) {
    25             sendNumbers();
    26         }
    27     }
    28 };
相關文章
相關標籤/搜索