# netty ## netty解决的问题 当前,http协议被广泛使用于client和server之间的通信。但是,部分场景下,http协议并不适用,例如传输大文件、e-mail消息、事实的经济或游戏数据。对于该类场景,http协议并无法很好的满足,此时需要一个高度优化的自定义协议实现,而通过netty可以及进行快速的协议实现。 netty提供了一个`异步的事件驱动的`网络应用框架,用于快速开发一个拥有高性能、高可拓展性的协议的server和client。 ## netty example ### Discard Server Example 如下是一个Discard Protocol的样例,其会对所有接收到的数据进行丢弃,并且不返回任何结果。handler方法会处理由netty产生的io事件: ```java package io.netty.example.discard; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * Handles a server-side channel. */ public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1) @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2) // Discard the received data silently. ((ByteBuf) msg).release(); // (3) } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4) // Close the connection when an exception is raised. cause.printStackTrace(); ctx.close(); } } ``` DiscardServerHandler继承了`ChannelInboundHandlerAdapter`,而`ChannelInboundHandlerAdapter`则是实现了`ChannelInboundHandler`。`ChannelInboundHandler`提供了各种不同的io事件处理方法,可以对这些方法进行覆盖。 在上述示例中,对chnnelRead方法进行了重写,channelRead方法将会在从客户端接收到数据时被调用,被调用时会把从客户端接收到的消息作为参数。在上述示例中,接收到的消息是ByteBuf类型。 > ByteBuf是一个引用计数对象,必须要通过调用`release`方法来显式释放。并且,`handler method有责任对传入的引用计数对象进行释放操作`。 通常,channelRead方法的重写按照如下形式: ```java @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { // Do something with msg } finally { // handler makes sure that msg passed has been released ReferenceCountUtil.release(msg); } } ``` 上述示例中除了重写channelRead方法外,还重写了`exceptionCaught`方法。exceptionCaught方法会在 ***netty由于io error抛出异常*** 或是 ***handler method实现在处理事件时抛出异常*** 的场景下被调用。 在通常情况下,被exceptionCaught方法捕获的异常,其异常信息应该被打印到日志中,且异常关联的channel应该被关闭。通过重写caughtException也可以自定义异常捕获后的实现,例如在关闭channel之前向client发送error code. ### netty应用程序实现示例 如下是netty实现的Discard Server示例: ```java package io.netty.example.discard; 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; /** * Discards any incoming data. */ 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() { // (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) // Bind and start to accept incoming connections. ChannelFuture f = b.bind(port).sync(); // (7) // Wait until the server socket is closed. // In this example, this does not happen, but you can do that to gracefully // shut down your server. f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (args.length > 0) { port = Integer.parseInt(args[0]); } new DiscardServer(port).run(); } } ``` ### 核心组件解析 #### NioEventLoopGroup `NioEventLoopGroup`是一个多线程的event loop,用于处理io操作。netty提供了不同的EventLoopGroup实现用于不同种类的传输。 在上述示例中,使用了两个NioEventLoopGroup: - boss:boss event loop用于接收incoming connections - worker:worker event loop用于处理accepted connection的通信 每当boss event loop接收到connection之后,其会将新的连接注册到worker event loop。 event group loop中拥有的线程数,以及线程如何与channel相关联(可能是每个线程对应一个channel、或是一个线程管理多个channel),由EventLoopGroup的实现来决定,并且可以通过构造器来进行配置。 #### ServerBootstrap ServerBoostrap是一个helper类,用于方便的构建一个netty server。 #### NioServerSocketChannel 在上述使用中,使用了NioServerSocketChannel来实例化一个channel,改channel用于接收incoming connection。 #### handler注册 在上述示例中,为accepted connection注册handler的示例如下所示: ```java .childHandler(new ChannelInitializer() { // (4) @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new DiscardServerHandler()); } }) ``` 在上述注册示例中,每个newly accepted channel都会调用DiscardServerHandler。 `ChannelInitializer`是一个特殊的handler,用于帮助用户为新channel配置ahndler。其initChannel为新channel的ChannelPipeline配置添加自定义的handler,从而实现自定义的消息处理逻辑。 #### 为Channel实现设置参数 通过option和childOption方法,可以为channel设置参数。由于netty server基于TCP/IP,故而可以指定socket option例如`tcpNoDelay`和`keepAlive`参数。 - option:option设置的是NioServerSocketCahnel,该channel用于接收incoming connection - childOption:childOption用于设置被server channel接收的channel,被接收的channel为SocketChannel #### 为Channel绑定port 在经历完上述配置之后,就可以为server绑定监听端口并启动了。