1.netty的組件
1.1 Channel
Channel 是 NIO 基本的結構。官方解釋:它代表了一個用於連接到實體如硬件設備、文件、網絡套接字或程序組件,能夠執行一個或多個不同的 I/O 操作(例如讀或寫)的開放連接。
簡單的來說Channel就是通信的載體,通過客戶端和服務端通過它進行通信、讀寫數據。
生命週期狀態
- ChannelUnregistered:Channel已經被創建,但還未註冊到EventLoop
- ChannelRegistered :Channel已經被註冊到了EventLoop
- ChannelActive:Channel處於活動狀態(已經連接到遠程節點),可以收發數據了
- ChannelInactive :Channel沒有連接到遠程節點
方法
- eventLoop:返回Channel的EventLoop
- pipeline:返回Channel的ChannelPipeline
- isActive:Channel是否活動的
- write:將數據寫到遠程節點。這個數據將被傳遞給ChannelPipeline,並且排隊直到它被沖刷
- flush:將已寫的數據沖刷到底層傳輸,如一個Socket
- writeAndFlush:等同於調用write()並接着調用flush()
1.2 Callback
Callback就是回調,回調在java中很常見,通常是提供給另外一個線程或者方法,在適當的時候調用,比如前面的AIO中,爲了實現異步非阻塞。
Netty在內部使用了回調來處理事件,當一個事件發生時,通過回調讓事件處理器處理事件。netty中的處理事件的回調需要實現ChannelHandler接口。
1.3 Future
Future可以看作是一個異步操作的結果的佔位符,在操作完成後提供對結果的訪問,也可以在操作完成時通知應用程序。
JDK中的Future,需要我們主動去檢查操作是否完成,並不友好。netty提供了自己實現的Future-ChannelFuture,用於在執行異步操作完成時主動回調指定的方法,不用我們主動檢查操作是否完成。
ChannelFuture提供了幾種方法,使我們能夠註冊一個或者多個ChannelFutureListener實例。監聽器的回調方法operationComplete(),將會在對應的操作完成時被調用。如果出錯了,我們可以檢索產生的Throwable。
每個Netty的I/O 操作都將返回一個ChannelFuture。
1.4 Event
Netty使用不同的事件來通知我們狀態的改變或者是操作的狀態。這使得我們能夠基於已經發生的事件來觸發適當的動作。
1.5 ChannelHandler
Netty的ChannelHandler是各種事件處理的基本抽象。其實就是一個回調,用於執行對各種事件的響應。
ChannelHandler分爲:
- 入站(ChannelInboundHandler):數據時從遠程主機到用戶應用程序
- 出站(ChannelOutboundHandler):數據是從應用程序到遠程主機則是出站
入站事件從pipelin頭部開始傳遞,最後數據到達pipeline的尾部,此時所有處理結束。
出站事件從尾部開始傳遞,直到它到達頭部。
ChannelHandlerContext:
當ChannelHandler被添加到ChannelPipeline,它得到一個 ChannelHandlerContext。它代表一個ChannelHandler和ChannelPipeline之間的綁定,可以被用來獲得底層 Channel,它主要是用來寫出站數據。
Netty發送消息有兩種方式:
直接寫消息給Channel和寫 ChannelHandlerContext對象。區別是, Channel會導致消息從ChannelPipeline的尾部開始,而ChannelHandlerContext導致消息從 ChannelPipeline下一個處理器開始。
生命週期: - handlerAdded:ChannelHandler添加到ChannelPipeline中時被調用
- handlerRemoved:從ChannelPipeline中移除ChannelHandler時被調用
- exceptionCaught:當處理過程中在ChannelPipeline中有錯誤產生時被調用
ChannelInboundHandler生命週期:
- channelReadComplete:Channel讀操作完成時被調用
- channelRead:從Channel讀取數據時被調用
- ChannelWritabilityChanged:Channel的可寫狀態發生改變時被調用,確保寫操作不會完成得太快,避免發生OOM。可以通過Channel的isWritable()方法來檢測Channel的可寫性。
- userEventTriggered:當ChannelnboundHandler.fireUserEventTriggered()方法被調用時被調用。
ChannelOutboundHandler生命週期:
- bind:Channel綁定到本地地址時被調用
- connect:當請求將Channel 連接到遠程節點時被調用
- disconnect:當請求將Channel 從遠程節點斷開時被調用
- close:當請求關閉Channel 時被調用
- deregister:當請求將Channel從它的EventLoop 註銷時被調用
- read:當請求從Channel讀取更多的數據時被調用
- flush:當請求通過Channel將入隊數據沖刷到遠程節點時被調用
- write:當請求通過Channel將數據寫到遠程節點時被調用
幾乎所有的方法都將 ChannelPromise 作爲參數,一旦請求結束要通過 ChannelPipeline 轉發的時候,必須通知此參數。
ChannelPromise vs. ChannelFuture
ChannelPromise是特殊的ChannelFuture,允許你的ChannelPromise及其操作成功或失敗。所以任何時候調用例如 Channel.write(…) 一個新的ChannelPromise將會創建並且通過ChannelPipeline傳遞。這次寫操作本身將會返回ChannelFuture, 這樣只允許你得到一次操作完成的通知。Netty 本身使用 ChannelPromise 作爲返回的 ChannelFuture 的通知,事實上在大多數時候就是 ChannelPromise 自身。
1.6 EVENTLOOP、EventLoopGroup
EVENTLOOP類似Selector。EventLoop分配給每個Channel來處理所有的事件,包括
註冊感興趣的事件,調度事件到ChannelHandler,安排進一步行動。
EventLoop本身只有一個線程驅動,並且在EventLoop的生命週期內不會改變,也就是一個Channel的所有IO操作一直在一個線程內部完成。同時任務(Runnable 或者Callable)可以提交給EventLoop實,以立即執行或者調度執行。但是一個EventLoop可以處理多個Channel。
EventLoopGroup可以包含一個或者讀個EVENTLOOP。
1.7 BOOTSTRAP
Netty應用程序通過設置bootstrap(引導)類的開始,該類提供了一個用於應用程序網絡層配置的容器。
有兩種類型的引導:
之所以有兩種,因爲服務器和客戶端的網絡行爲還是不同的,服務端是監聽連接,客戶端是發起連接。
Bootstrap:
Bootstrap用於客戶端,用來連接遠程主機,有1個EventLoopGroup。
ServerBootstrap:
ServerBootstrap用於服務器,用來綁定本地端口,有2個EventLoopGroup(可以是一個實例),想想前面講的Reactor模式。
與ServerChannel相關EventLoopGroup分配一個EventLoop是負責創建Channels用於傳入的連接請求。一旦連接接受,第二個EventLoopGroup分配一個EventLoop給它的 Channel。
這時候是不是又在想Reactor模式還有個工作線程池在哪。可以指定一個 EventExecutorGroup,用於獲得EventExecutor。當添加ChannelHandler到ChannelPipeline。EventExecutor將執行所有的 ChannelHandler 的方法。這EventExecutor將從 I/O線程使用不同的線程,從而釋放EventLoop。這樣如果有阻塞操作,不會阻塞IO線程,否則影響性能。
1.8 CHANNELPIPELINE
ChannelPipeline就是ChannelHandler的容器,包含一個ChannelHandler鏈。每當一個新的Channel被創建了,都會建立一個新的 ChannelPipeline,並且這個新的ChannelPipeline還會綁定到Channel上。這個關聯是永久性的,Channel既不能附上另一個ChannelPipeline 也不能分離當前這個。
ChannelHandler加入ChannelPipeline:
實現了ChannelHandler的抽象ChannelInitializer。ChannelInitializer子類通過ServerBootstrap進行註冊。當它的方法initChannel()被調用時,這個對象將安裝自定義的ChannelHandler加入到pipeline。當這個操作完成時,ChannelInitializer子類則從ChannelPipeline 自動刪除自身。
方法:
- addFirst、addBefore、addAfter、addLast:將ChannelHandler加到ChannelPipeline
- remove:將ChannelHandler 從ChannelPipeline中移除
- replace:將ChannelPipeline中的ChannelHandler替換爲另一個
- get:通過類型或者名稱返回ChannelHandler
- context:返回和ChannelHandler綁定的ChannelHandlerContext
- names:返回ChannelPipeline中所有ChannelHandler 的名稱
1.9 ChannelHandlerContext
ChannelHandlerContext是ChannelHandler和ChannelPipeline之間的關聯,每當有ChannelHandler添加到ChannelPipeline,都會創建ChannelHandlerContext。ChannelHandlerContext用來管理它所關聯的ChannelHandler 和在同一個ChannelPipeline中的其他ChannelHandler 之間的交互。
ChannelHandlerContext上的方法,則將從當前所關聯的ChannelHandler開始傳遞。
方法:
- alloc:返回關聯的Channel所配置的ByteBufAllocator
- bind ;綁定到給定的SocketAddress
- channel:返回綁定到這個實例的Channel
- close:關閉Channel
- connect:連接給定的SocketAddress
- deregister:從之前分配的EventExecutor註銷
- disconnect:從遠程節點斷開
- executor:返回調度事件的EventExecutor
- fire***: 觸發對下一個ChanneHandler的相同方法
- handler:返回綁定到這個實例的ChannelHandler
- isRemoved:關聯的ChannelHandler 是否已經被從ChannelPipeline中移除
- name:返回這個實例的唯一名稱
- pipeline:返回關聯的ChannelPipeline
- read:將數據從Channel讀取到第一個入站緩衝區,如果讀取成功則觸發channelRead事件,並在最後一個消息被讀取完成後觸發channelReadComplete
1.10 ChannelOption
- ChannelOption.SO_BACKLOG:服務端處理客戶端連接請求是順序處理的,所以同一時間只能處理一個客戶端連接,多個客戶端來的時候,服務端將不能處理的客戶端連接請求放在隊列中等待處理,backlog參數指定了隊列的大小
- ChannelOption.SO_REUSEADDR:這個參數表示允許重複使用本地地址和端口,
- ChannelOption.SO_KEEPALIVE:當設置該選項以後,如果在兩小時內沒有數據的通信時,TCP會自動發送一個活動探測數據報文。
- ChannelOption.SO_SNDBUF:接收緩衝區的大小
- ChannelOption.SO_RCVBUF:發送緩衝區的大小
- ChannelOption.SO_LINGER:在Linux內當用戶調用close方法的時候,不一定保證會發生剩餘的數據。使用SO_LINGER可以阻塞close()的調用時間,直到數據完全發送
- ChannelOption.TCP_NODELAY:而該參數的作用就是禁止使用Nagle算法(將小的數據包組裝爲更大的幀然後進行發送),即時傳輸。相對應的是TCP_CORK,該選項是需要等到發送的數據量最大的時候,一次性發送數據,適用於文件傳輸。
1.11 ByteBuf
ByteBuf有三種類型:
堆緩衝區:
將數據存儲在堆。這種模式被稱爲支撐數組,它能在沒有使用池化的情況下提供快速的分配和釋放。可以由hasArray()來判斷檢查ByteBuf是否由數組支撐。
直接緩衝區:
直接緩衝區相對於基於堆的緩衝區,分配和釋放都較爲昂貴。它分配在堆區以外,通過免去中間交換的內存拷貝, 提升IO處理速度。
COMPOSITE BUFFER:
複合緩衝區就像一個列表,可以包含多個不同的ByteBuf,我們可以動態的添加和刪除其中的ByteBuf,hasArray()總是返回 false,因爲它可能既包含堆緩衝區,也包含直接緩衝區。
ByteBufAllocator :
Netty通過ByteBufAllocator分配ByteBuf實例。
- buffe:返回一個基於堆或者直接內存存儲的ByteBuf
- heapBuffer:返回一個基於堆內存存儲的ByteBuf
- directBuffer:返回一個基於直接內存存儲的ByteBuf
- compositeBuffer:返回一個複合緩衝區
- ioBuffer():當所運行的環境具有sun.misc.Unsafe支持時,返回基於直接內存存儲的ByteBuf,否則返回基於堆內存存儲的ByteBuf
ByteBufAllocator有兩種:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的實例以提高性能並最大限度地減少內存碎片。後者的實現不池化ByteBuf實例,每次它被調用時都會返回一個新的實例。
可丟棄字節
可丟棄字節的分段包含了已經被讀過的字節。通過調用discardReadBytes,可以丟棄它們並回收空間。這個分段的初始大小爲0,存儲在readerIndex 中,會隨着read 操作的執行而增加(get*操作不會移動readerIndex)。
緩衝區上調用discardReadBytes方法後,可丟棄字節分段中的空間已經變爲可寫的了。但是這將極有可能會導致內存複製,因爲可讀字節必須被移動到緩衝區的開始位置
引用計數 :
引用計數是一種通過在某個對象所持有的資源不再被其他對象引用時釋放該對象所持有的資源來優化內存使用和性能的技術。Netty4中爲ByteBuf引入了引用計數技術 ReferenceCounted。
1.12 官方實例
服務端:
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println(
"Usage: " + EchoServer.class.getSimpleName() +
" <port>");
return;
}
int port = Integer.parseInt(args[0]);
new EchoServer(port).start();
}
public void start() throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup(); //3
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoServerHandler());
}
});
ChannelFuture f = b.bind().sync();
System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
@Sharable
public class EchoServerHandler extends
ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,
Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
客戶端:
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println(
"Usage: " + EchoClient.class.getSimpleName() +
" <host> <port>");
return;
}
final String host = args[0];
final int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
}
@Sharable
public class EchoClientHandler extends
SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",
CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx,
ByteBuf in) {
System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}