编写Echo服务器
所有的Netty服务器都需要以下两部分:
至少一个ChannelHandler_该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。
引导——这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的端口上。
1.ChannelHandler和业务逻辑
ChannelHandler:是一个接口族的父接口,它的实现负责接收并响应事件通知。在Netty应用程序中,所有的数据处理逻辑都包含在这些核心抽象的实现中。
因为Echo服务器会响应传入的消息,所以不它需要实现ChannelInboundHandler接口,用来定义响应入站事件的方法。
channelRead()——对于每个传入的消息都要调用;
channelReadComplete()——通知ChannelInboundHandler最后一次对channelRead()的调用时当前批量读取中的最后一条消息;
exceptionCaught()——在读取操作期间,有异常抛出时会调用。
该服务器的ChannelHandler实现是EchoServerHandler,如下:
//标识一个ChannelHandler可以被多个Channel安全地共享
@ChannelHandler.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){
//将未决消息(目前暂存于ChannelOutboundBuffer中的消息,在下一次调用flush()或者writeAndFlush()方法时将会尝试写出到套接字)冲刷到远程节点,并且关闭该Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
//打印异常栈跟踪
cause.printStackTrace();
//关闭该Channel
ctx.close();
}
}
ChannelInboundHandlerAdapter有一个直观的API,并且它的每个方法都可以被重写以挂钩到事件生命周期的恰当点上。因为需要处理所有接收到的数据,所以重写了channelRead()方法。
重写exceptionCaught()方法允许你对Throwable的任何子类型做出反应,在这里是记录异常并关闭连接。一个更加完善的应用程序也许会尝试从异常中恢复,但在这个场景下,只是通过简单地关闭连接来通知远程节点发生了错误。
如果不捕获异常,会发生什么呢:
每个Channel都拥有一个与之相关联的ChannelPipeline,其持有一个ChannelHandler的实例链。在默认情况下,ChannelHandler会把对它的方法的调用转发给链中的下一个ChannelHandler。因此,如果exceptionCaught()方法没有被该链的某处实现,那么所接收的异常将会被传递到ChannelPipeeline的尾端并被记录。为此,你的应用程序应该提供至少有一个实现了exceptionCaught()方法的ChannelHandler。
几个关键点:
针对不同类型的事件来调用ChannelHandler;
应用程序通过实现或者扩展ChannelHandler来挂钩带事件的生命周期,并且提供自定义的应用程序逻辑;
在架构上,ChannelHandler有助于保持业务逻辑与网络处理代码的分离。这简化了开发过程,因为代码必须不断地演化以响应不断变化的需求。
2.引导服务器
引导服务器具体涉及以下内容:
1.绑定到服务器将在其上监听并接受传入连接请求的接口;
2.配置Channel,以将有关的入站消息通知给EchoServerHandler实例。
传输:
在网络协议的标准多层视图中,传输层提供了端到端的或者主机到主机的通信服务。
因特网通信是建立在TCP传输之上的。除了一些由Java NIO实现提供的服务器端性能增强之外,NIO传输的大多数时候指的就是TCP传输。
以下展示了EchoServerHandler类的完整代码。
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.out.println("Usage:" + EchoServer.class.getSimpleName() + " <port>");
return;
}
//设置端口值(如果端口从参数的格式不正确,则抛出一个NumberFormatException)
int port = Integer.parseInt(args[0]);
//调用服务器的start()方法
new EchoServer(port).start();
}
private void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
//创建EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try{
//创建ServerBootStrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//指定所使用的NIO传输Channel
.channel(NioServerSocketChannel.class)
//使用指定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
//添加一个EchoServer到子Channel的ChannelPipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception{
//EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
channel.pipeline().addLast(serverHandler);
}
});
//异步地绑定服务器;调用sync()方法阻塞等待直到绑定完成
ChannelFuture future = b.bind().sync();
//获取Channel的CloseFuture,并且阻塞当前线程直到它完成
future.channel().closeFuture().sync();
} finally {
//关闭EventLoopGroup,释放所有的资源
group.shutdownGracefully().sync();
}
}
}