编写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(); } } }