問題
現在我們使用通用的應用或者庫跟其他人進行通信。例如, 我們經常使用 HTTP 客戶端的庫從網站服務中獲取數據和通過網站服務調用遠程程序。然而, 一個通用的協議它有時實現起來不是很好。就像我們不使用通用的 HTTP 服務獲取大文件, 電子郵件消息, 和諸如財務信息和多人遊戲數據的實時消息。必須有一個高度優化的協議致力於一個特別的目的。例如, 你可能想實現一個基於 AJAX 優化的一個聊天應用, 流媒體,大的文件傳輸的 HTTP 服務。你甚至可以根據你需求設計和實現一個全新的協議。另外一個不可避免的情況是你必須處理一個已有的協議以確保在老系統中的互通性。在這種情況下,重要的是我們如何快速地實現該協議,同時不犧牲所得到的應用的穩定性和性能。
解決方案
Netty是一個努力提供異步事件驅動可維護的高效快速發展, 高擴展性協議服務器和客戶端的網絡應用框架和工具。
換句話說, Netty 是一個能夠快速和輕鬆開發的如協議服務器和客戶端的網絡應用 NIO 客戶端服務端框架。它大大簡化了網絡編程,如TCP和UDP套接字服務器的開發。
‘快速和簡單’不意味着應用會遭受可維護性和性能產生的問題。 Netty 已被精心設計通過大量如FTP、SMTP、HTTP以及各種二進制和文本協議中的經驗。因此, Netty 已經成功地找到一種方法沒有妥協的來實現容易開發,性能,穩定,靈活。
寫一個 Discard 服務
在世界上最簡單的協議不是’Hello, World!’ 而是DISCARD
。它是一個丟棄任何接收到數據沒有任何反應的協議。
爲了實現 DISCARD
的協議, 你只需要做的一件事情是忽略所有接收到的數據。讓我們從 Netty 的 handler 直接開始 實現 I/O 事件。
package io.netty.example.discard;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Handles a server-side channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
// Discard the received data silently.
((ByteBuf) msg).release(); // (3)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
DiscardServerHandler
繼承於ChannelInboundHandlerAdapter
,這是一個ChannelInboundHandler
的實現類。ChannelInboundHandler
提供了各種你可以重寫的方法。目前, 只需要繼承於ChannelInboundHandlerAdapter
而不是實現這個接口。我們在這裏重寫
channelRead()
方法。這個方法被用來接收消息, 無論何時來自客戶端的新數據都被接收。在這個例子, 接收到的消息類型是ByteBuf
。爲了實現
DISCARD
協議, 處理機已經忽略接收到的消息。ByteBuf
是一個必須通過release()
方法顯式釋放的引用計數對象。請記住通過處理機釋放任何引用的對象是處理機的職責。通常,channelRead()
方法的實現如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}
- Netty 由於一個 I/O 錯誤或者由於處理機實現類處理時拋出異常引起一個異常會調用
exceptionCaught()
方法。在大多數情況下, 這個被捕獲的異常應該被記錄下來並且在這裏關閉相關的通道, 雖然這種方法的實現可以是不同的,這取決於你想做什麼來處理一個特殊的情況。例如, 你可能想在錯誤代碼關閉連接之前發送一條響應的消息。
到目前爲止一切順利。我們一開始已經實現了半個 DISCARD
服務器。 剩下的就是寫 main()
方法開啓 DiscardServerHandler
這個服務。
package io.netty.example.discard;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Discards any incoming data.
*/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new DiscardServer(port).run();
}
}
NioEventLoopGroup
是一個多線程的處理機 I/O 操作事件輪詢。Netty 提供各種傳輸不同種類的EventLoopGroup
實現類。我們實現一個服務端應用的例子, 因此兩個NioEventLoopGroup
將被使用。第一個, 通常被稱爲 ‘boss’,
接受傳入的連接。第二個,通常被稱爲 ‘worker’, 處理所接受的連接的流量,一旦 boss 接受連接,並註冊接受的連接到 worker。多少線程被使用, 它們怎樣被映射到創建Channel
s ,取決於EventLoopGroup
的實現,甚至可通過構造器進行配置。ServerBootstrap
是一個設置服務器的輔助類。你可以直接使用Channel
來設置服務器。但是, 請注意一個繁瑣的過程,大多數情況下你不需要這樣做。這裏, 我們指定使用被實例化一個新的
Channel
來接受傳入的連接的NioServerSocketChannel
類。這裏指定的處理機將一直被一個新的
Channel
處理。這個ChannelInitializer
是一個特別有意幫助用戶配置新Channel
的 handler。