Echo Server 將會將接受到的數(shù)據(jù)的拷貝發(fā)送給客戶端。因此,我們需要實現(xiàn) ChannelInboundHandler 接口,用來定義處理入站事件的方法。由于我們的應用很簡單,只需要繼承 ChannelInboundHandlerAdapter 就行了。這個類 提供了默認 ChannelInboundHandler 的實現(xiàn),所以只需要覆蓋下面的方法:
EchoServerHandler 代碼如下:
Listing 2.2 EchoServerHandler
@Sharable //1
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)); //2
ctx.write(in); //3
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)//4
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace(); //5
ctx.close(); //6
}
}
1.@Sharable
標識這類的實例之間可以在 channel 里面共享
2.日志消息輸出到控制臺
3.將所接收的消息返回給發(fā)送者。注意,這還沒有沖刷數(shù)據(jù)
4.沖刷所有待審消息到遠程節(jié)點。關閉通道后,操作完成
5.打印異常堆棧跟蹤
6.關閉通道
這種使用 ChannelHandler 的方式體現(xiàn)了關注點分離的設計原則,并簡化業(yè)務邏輯的迭代開發(fā)的要求。處理程序很簡單;它的每一個方法可以覆蓋到“hook(鉤子)”在活動周期適當?shù)狞c。很顯然,我們覆蓋 channelRead因為我們需要處理所有接收到的數(shù)據(jù)。
覆蓋 exceptionCaught 使我們能夠應對任何 Throwable 的子類型。在這種情況下我們記錄,并關閉所有可能處于未知狀態(tài)的連接。它通常是難以 從連接錯誤中恢復,所以干脆關閉遠程連接。當然,也有可能的情況是可以從錯誤中恢復的,所以可以用一個更復雜的措施來嘗試識別和處理 這樣的情況。
如果異常沒有被捕獲,會發(fā)生什么?
每個 Channel 都有一個關聯(lián)的 ChannelPipeline,它代表了 ChannelHandler 實例的鏈。適配器處理的實現(xiàn)只是將一個處理方法調用轉發(fā)到鏈中的下一個處理器。因此,如果一個 Netty 應用程序不覆蓋exceptionCaught ,那么這些錯誤將最終到達 ChannelPipeline,并且結束警告將被記錄。出于這個原因,你應該提供至少一個 實現(xiàn) exceptionCaught 的 ChannelHandler。
關鍵點要牢記:
了解到業(yè)務核心處理邏輯 EchoServerHandler 后,下面要引導服務器自身了。
Transport(傳輸)
在本節(jié)中,你會遇到“transport(傳輸)”一詞。在網(wǎng)絡的多層視圖協(xié)議里面,傳輸層提供了用于端至端或主機到主機的通信服務?;ヂ?lián)網(wǎng)通信的基礎是 TCP 傳輸。當我們使用術語“NIO transport”我們指的是一個傳輸?shù)膶崿F(xiàn),它是大多等同于 TCP ,除了一些由 Java NIO 的實現(xiàn)提供了服務器端的性能增強。Transport 詳細在第4章中討論。
Listing 2.3 EchoServer
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]); //1
new EchoServer(port).start(); //2
}
public void start() throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup(); //3
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group) //4
.channel(NioServerSocketChannel.class) //5
.localAddress(new InetSocketAddress(port)) //6
.childHandler(new ChannelInitializer<SocketChannel>() { //7
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoServerHandler());
}
});
ChannelFuture f = b.bind().sync(); //8
System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
f.channel().closeFuture().sync(); //9
} finally {
group.shutdownGracefully().sync(); //10
}
}
}
1.設置端口值(拋出一個 NumberFormatException 如果該端口參數(shù)的格式不正確)
2.呼叫服務器的 start() 方法
3.創(chuàng)建 EventLoopGroup
4.創(chuàng)建 ServerBootstrap
5.指定使用 NIO 的傳輸 Channel
6.設置 socket 地址使用所選的端口
7.添加 EchoServerHandler 到 Channel 的 ChannelPipeline
8.綁定的服務器;sync 等待服務器關閉
9.關閉 channel 和 塊,直到它被關閉
10.關機的 EventLoopGroup,釋放所有資源。
在這個例子中,代碼創(chuàng)建 ServerBootstrap 實例(步驟4)。由于我們使用在 NIO 傳輸,我們已指定 NioEventLoopGroup(3)接受和處理新連接,指定 NioServerSocketChannel(5)為信道類型。在此之后,我們設置本地地址是 InetSocketAddress 與所選擇的端口(6)如。服務器將綁定到此地址來監(jiān)聽新的連接請求。
第7步是關鍵:在這里我們使用一個特殊的類,ChannelInitializer 。當一個新的連接被接受,一個新的子 Channel 將被創(chuàng)建, ChannelInitializer 會添加我們EchoServerHandler 的實例到 Channel 的 ChannelPipeline。正如我們如前所述,如果有入站信息,這個處理器將被通知。
雖然 NIO 是可擴展性,但它的正確配置是不簡單的。特別是多線程,要正確處理也非易事。幸運的是,Netty 的設計封裝了大部分復雜性,尤其是通過抽象,例如 EventLoopGroup,SocketChannel 和 ChannelInitializer,其中每一個將在更詳細地在第3章中討論。
在步驟8,我們綁定的服務器,等待綁定完成。 (調用 sync() 的原因是當前線程阻塞)在第9步的應用程序將等待服務器 Channel 關閉(因為我們 在 Channel 的 CloseFuture 上調用 sync())?,F(xiàn)在,我們可以關閉下 EventLoopGroup 并釋放所有資源,包括所有創(chuàng)建的線程(10)。
NIO 用于在本實施例,因為它是目前最廣泛使用的傳輸,歸功于它的可擴展性和徹底的不同步。但不同的傳輸?shù)膶崿F(xiàn)是也是可能的。例如,如果本實施例中使用的 OIO 傳輸,我們將指定 OioServerSocketChannel 和 OioEventLoopGroup。 Netty 的架構,包括更關于傳輸信息,將包含在第4章。在此期間,讓我們回顧下在服務器上執(zhí)行,我們只研究重要步驟。
服務器的主代碼組件是
執(zhí)行后者所需的步驟是:
這樣服務器的初始化就完成了,并可以被使用。
更多建議: