所有的Netty服務器都需要一下兩部分:
①至少一個ChannelHandler——該組件實現了服務器對從客戶端接收的數據的處理,即它的業務邏輯。
②引導——這是配置服務器的啓動代碼。至少,它會將服務器綁定到連接請求的端口上。
因爲Echo服務器會影響傳入的消息,所以它需要ChannelInboundHandler接口,用來定義響應入站事件的方法。這個簡單的應用程序只需要用到少量的這些方法,所以繼承CHannelInboundHandlerAdapter類也就足夠了,它提供了ChannelInboundHandler的默認實現。
package netty.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
@Sharable //標示一個Channel-Handler可以被多個Channel安全的共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
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 {
//將未決消息沖刷到遠程節點,並且關閉該channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
ChannelInboundHandlerAdapter有一個直觀的API,並且它的每個方法都可以被重寫以掛鉤到事件生命週期的恰當點上。因爲需要處理所有接收到的數據,所以重寫了channelRead()方法。在這個服務器應用程序中,你將數據簡單地送給了遠程節點。
重寫exceptionCaught()允許你對Throwable的任何子類型做出反應。
如果不捕獲異常,會發生什麼呢?
每個channel都擁有一個與之相關聯的ChannelPipeline,其持有一個ChannelHandler的實例鏈。在默認情況下,ChannelHandler會把對它的方法的調用轉發給鏈接中的下一個ChannelHandler。因此,如果exceptionCaught()方法沒有被該鏈中的某處實現,那麼所接收的異常將會被傳遞到ChannelPipeline的尾端並被記錄。爲此,你的應用該程序應該提供至少有一個實現了exceptionCaught()方法的ChannelHandler。
引導服務器
①綁定到服務器將在其上監聽並接受傳入連接請求的端口;
②配置channel,並將有關的入站消息通知給EchoServerHandler實例。
package netty.server;
import java.net.InetSocketAddress;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
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;
}
//設置端口值(如果端口參數的格式不正確,則拋出一個NumberFormatException)
int port = Integer.parseInt(args[0]);
//調用服務器的start()方法
new EchoServer(port).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
//創建Event-LoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
//創建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//指定所使用的NIO傳輸Channel
.channel(NioServerSocketChannel.class)
//使用制定的端口設置套接字地址
.localAddress(new InetSocketAddress(port))
//添加一個EchoServer-Handler到子Channel的ChannelPipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//EchoServerHandler被標註爲@Shareable,所以我們可以總是使用同樣的實例
ch.pipeline().addLast(serverHandler);
}
});
//異步的綁定服務器;調用sync()方法阻塞等待直到綁定完成
ChannelFuture f = b.bind().sync();
//獲取Channel的CloseFuture,並且阻塞當前線程直到它完成
f.channel().closeFuture().sync();
}finally {
//關閉EventLoopGroup,釋放所有的資源
group.shutdownGracefully().sync();
}
}
}