世上最簡單的協(xié)議不是'Hello, World!' 而是 DISCARD(丟棄)。這個協(xié)議將會丟掉任何收到的數(shù)據(jù),而不響應。
為了實現(xiàn) DISCARD 協(xié)議,你只需忽略所有收到的數(shù)據(jù)。讓我們從 handler (處理器)的實現(xiàn)開始,handler 是由 Netty 生成用來處理 I/O 事件的。
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* 處理服務端 channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
// 默默地丟棄收到的數(shù)據(jù)
((ByteBuf) msg).release(); // (3)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// 當出現(xiàn)異常就關閉連接
cause.printStackTrace();
ctx.close();
}
}
1.DiscardServerHandler 繼承自 ChannelInboundHandlerAdapter,這個類實現(xiàn)了 ChannelInboundHandler接口,ChannelInboundHandler 提供了許多事件處理的接口方法,然后你可以覆蓋這些方法?,F(xiàn)在僅僅只需要繼承 ChannelInboundHandlerAdapter 類而不是你自己去實現(xiàn)接口方法。
2.這里我們覆蓋了 chanelRead() 事件處理方法。每當從客戶端收到新的數(shù)據(jù)時,這個方法會在收到消息時被調用,這個例子中,收到的消息的類型是 ByteBuf
3.為了實現(xiàn) DISCARD 協(xié)議,處理器不得不忽略所有接受到的消息。ByteBuf 是一個引用計數(shù)對象,這個對象必須顯示地調用 release() 方法來釋放。請記住處理器的職責是釋放所有傳遞到處理器的引用計數(shù)對象。通常,channelRead() 方法的實現(xiàn)就像下面的這段代碼:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}
4.exceptionCaught() 事件處理方法是當出現(xiàn) Throwable 對象才會被調用,即當 Netty 由于 IO 錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應該被記錄下來并且把關聯(lián)的 channel 給關閉掉。然而這個方法的處理方式會在遇到不同異常的情況下有不同的實現(xiàn),比如你可能想在關閉連接之前發(fā)送一個錯誤碼的響應消息。
目前為止一切都還不錯,我們已經(jīng)實現(xiàn)了 DISCARD 服務器的一半功能,剩下的需要編寫一個 main() 方法來啟動服務端的 DiscardServerHandler。
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;
/**
* 丟棄任何進入的數(shù)據(jù)
*/
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)
// 綁定端口,開始接收進來的連接
ChannelFuture f = b.bind(port).sync(); // (7)
// 等待服務器 socket 關閉 。
// 在這個例子中,這不會發(fā)生,但你可以優(yōu)雅地關閉你的服務器。
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();
}
}
1.NioEventLoopGroup 是用來處理I/O操作的多線程事件循環(huán)器,Netty 提供了許多不同的 EventLoopGroup 的實現(xiàn)用來處理不同的傳輸。在這個例子中我們實現(xiàn)了一個服務端的應用,因此會有2個 NioEventLoopGroup 會被使用。第一個經(jīng)常被叫做‘boss’,用來接收進來的連接。第二個經(jīng)常被叫做‘worker’,用來處理已經(jīng)被接收的連接,一旦‘boss’接收到連接,就會把連接信息注冊到‘worker’上。如何知道多少個線程已經(jīng)被使用,如何映射到已經(jīng)創(chuàng)建的 Channel上都需要依賴于 EventLoopGroup 的實現(xiàn),并且可以通過構造函數(shù)來配置他們的關系。
2.ServerBootstrap 是一個啟動 NIO 服務的輔助啟動類。你可以在這個服務中直接使用 Channel,但是這會是一個復雜的處理過程,在很多情況下你并不需要這樣做。
3.這里我們指定使用 NioServerSocketChannel 類來舉例說明一個新的 Channel 如何接收進來的連接。
4.這里的事件處理類經(jīng)常會被用來處理一個最近的已經(jīng)接收的 Channel。ChannelInitializer 是一個特殊的處理類,他的目的是幫助使用者配置一個新的 Channel。也許你想通過增加一些處理類比如DiscardServerHandler 來配置一個新的 Channel 或者其對應的ChannelPipeline 來實現(xiàn)你的網(wǎng)絡程序。當你的程序變的復雜時,可能你會增加更多的處理類到 pipline 上,然后提取這些匿名類到最頂層的類上。
5.你可以設置這里指定的 Channel 實現(xiàn)的配置參數(shù)。我們正在寫一個TCP/IP 的服務端,因此我們被允許設置 socket 的參數(shù)選項比如tcpNoDelay 和 keepAlive。請參考 ChannelOption 和詳細的 ChannelConfig 實現(xiàn)的接口文檔以此可以對ChannelOption 的有一個大概的認識。
6.你關注過 option() 和 childOption() 嗎?option() 是提供給NioServerSocketChannel 用來接收進來的連接。childOption() 是提供給由父管道 ServerChannel 接收到的連接,在這個例子中也是 NioServerSocketChannel。
7.我們繼續(xù),剩下的就是綁定端口然后啟動服務。這里我們在機器上綁定了機器所有網(wǎng)卡上的 8080 端口。當然現(xiàn)在你可以多次調用 bind() 方法(基于不同綁定地址)。
恭喜!你已經(jīng)熟練地完成了第一個基于 Netty 的服務端程序。
更多建議: