netty 4.x用户使用指南

引言html

问题
  如今咱们使用通用的应用程序或库来相互通讯。例如,咱们常用HTTP客户机从web服务器检索信息,并经过web服务调用远程过程调用。然而,通用协议或其实现有时不能很好地进行扩展。这就像咱们不使用通用HTTP服务器来交换巨大的文件、电子邮件消息和近乎实时的消息(如财务信息和多人游戏数据)同样。所须要的是一个高度优化的协议实现,专门用于一个特殊目的。例如,您可能但愿实现一个针对基于ajax的聊天应用程序、媒体流或大文件传输进行优化的HTTP服务器。您甚至能够设计和实现一个彻底根据您的须要量身定制的全新协议。另外一个不可避免的状况是,您必须处理遗留的专有协议,以确保与旧系统的互操做性。在这种状况下,重要的是在不牺牲结果应用程序的稳定性和性能的状况下,咱们能以多快的速度实现该协议。
 
解决方案
  Netty项目旨在提供异步事件驱动的网络应用程序框架和工具,以快速开发可维护的高性能·高可伸缩性协议服务器和客户端。
   换句话说,Netty是一个NIO客户端服务器框架,它支持协议服务器和客户端等网络应用程序的快速轻松开发。它极大地简化和简化了TCP和UDP套接字服务器开发等网络编程。
  “快速和简单”并不意味着最终的应用程序将遭受可维护性或性能问题的影响。Netty是根据从许多协议(如FTP、SMTP、HTTP以及各类基于二进制和文本的遗留协议)的实现中学到的经验精心设计的。所以,Netty成功地找到了一种无需妥协就能够轻松实现开发、性能、稳定性和灵活性的方法。
  一些用户可能已经发现了其余声称具备相同优点的网络应用程序框架,您可能想知道是什么使Netty与他们如此不一样。答案是它所创建的哲学。Netty旨在从一开始就为您提供最温馨的API和实现体验。它不是什么有形的东西,但你会意识到这种哲学将使你的生活更容易,当你读这本指南和玩Netty。
 
准备开始
  本章将介绍Netty的核心结构,并经过简单的示例让您快速入门。当你在这一章结束的时候,你将可以当即在Netty上编写一个客户端和一个服务器。
  若是您更喜欢学习自顶向下的方法,那么您可能但愿从第2章——体系结构概述开始,而后回到这里。
 
在开始以前
  运行本章示例的最低要求只有两个;Netty和JDK 1.6或更高版本的最新版本。最新版本的Netty可在项目下载页面得到。要下载正确版本的JDK,请参考您首选的JDK供应商的网站。
 
编写Discard服务
  世界上最简单的协议不是“你好,世界!”,而是 DISCARD。它是一种协议,在没有任何响应的状况下丢弃任何接收到的数据。
  要实现抛弃协议,唯一须要作的就是忽略全部接收到的数据。让咱们直接从处理程序实现开始,它处理Netty生成的I/O事件。
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,它是ChannelInboundHandler的一个实现。ChannelInboundHandler提供能够覆盖的各类事件处理程序方法。如今,只须要扩展ChannelInboundHandlerAdapter,而不是本身实现处理程序接口就足够了。
       二、咱们在这里重写channelRead()事件处理程序方法。当从客户机接收到新数据时,将使用接收到的消息调用此方法。在本例中,接收到的消息类型是ByteBuf。
       三、要实现丢弃协议,处理程序必须忽略接收到的消息。ByteBuf是一个引用计数对象,必须经过release()方法显式地释放它。请记住,处理程序有责任释放传递给处理程序的任何引用计数对象。一般,channelRead()处理程序方法是这样实现的:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { try { // Do something with msg } finally { ReferenceCountUtil.release(msg); } }
  四、异常捕获()事件处理程序方法在Netty因为I/O错误或处理程序实现因为在处理事件时抛出异常而引起异常时被一次性调用。在大多数状况下,应该对捕获的异常进行日志记录,并关闭其关联的通道,尽管此方法的实现可能因您但愿如何处理异常状况而有所不一样。例如,您可能但愿在关闭链接以前发送带有错误代码的响应消息。
  到目前为止还不错。咱们已经实现了废弃服务器的前一半。如今剩下的是编写main()方法,该方法使用DiscardServerHandler启动服务器。
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) 该对象至关于Socket中使用一个线程专门用户监听一个socket端口,而后将监听到的socket对象传入另外一对象 EventLoopGroup workerGroup = new NioEventLoopGroup();// 该对象至关于Socket中对于每一个socket链接都都单独开辟了一个线程进行数据解析出处理 try { ServerBootstrap b = new ServerBootstrap(); // (2)  b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // (3) .childHandler(new ChannelInitializer<SocketChannel>() { // (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; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8080; } new DiscardServer(port).run(); } }

 注:一、NioEventLoopGroup是一个处理I/O操做的多线程事件循环。Netty为不一样类型的传输提供了各类EventLoopGroup实现。在本例中,咱们正在实现一个服务器端应用程序,所以将使用两个NioEventLoopGroup。第一个,一般被称为“老板”,接受进入的链接。第二个一般称为“worker”,在boss接受链接并将接受的链接注册给worker时,它将处理已接受链接的流量。使用多少线程以及如何将它们映射到建立的通道取决于EventLoopGroup实现,甚至能够经过构造函数进行配置。java

        二、ServerBootstrap是一个设置服务器的助手类。您能够直接使用Channel设置服务器。可是,请注意,这是一个冗长的过程,在大多数状况下不须要这样作。
        三、在这里,咱们指定使用NioServerSocketChannel类,该类用于实例化一个新通道以接受传入链接。
        四、这里指定的处理程序将老是由新接受的通道进行计算。ChannelInitializer是一个用于帮助用户配置新通道的特殊处理程序。您极可能但愿经过添加一些处理程序(如DiscardServerHandler)来实现网络应用程序来配置新通道的ChannelPipeline。随着应用程序变得复杂,您可能会向管道中添加更多的处理程序,并最终将这个匿名类提取到顶级类中。
        五、您还能够设置特定于通道实现的参数。咱们正在编写一个TCP/IP服务器,因此咱们能够设置套接字选项,如tcpNoDelay和keepAlive。请参考ChannelOption的apidocs以及特定的ChannelConfig实现,以得到关于支持的ChannelOptions的概述。
        六、你注意到option()和childOption()吗?option()用于接受传入链接的NioServerSocketChannel。childOption()是父ServerChannel接受的通道,在本例中是NioServerSocketChannel
        七、咱们准备好了。剩下的就是绑定到端口并启动服务器。在这里,咱们绑定到机器中全部NICs(网络接口卡)的端口8080。如今,您能够根据须要屡次调用bind()方法(使用不一样的绑定地址)。
  恭喜你!你刚刚在Netty上完成了你的第一个服务器。
 
查看接收到的数据
  如今咱们已经编写了第一个服务器,咱们须要测试它是否真正工做。测试它的最简单方法是使用telnet命令。例如,您能够在命令行中输入telnet localhost 8080并输入一些内容。可是,咱们能够说服务器工做得很好吗?咱们没法真正知道这一点,由于它是一个丢弃服务器。你不会获得任何回应。为了证实它确实有效,让咱们修改服务器来打印它收到的内容。
咱们已经知道,每当接收到数据时都会调用channelRead()方法。让咱们将一些代码放入DiscardServerHandler的channelRead()方法中:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ByteBuf) msg; try { while (in.isReadable()) { // (1) System.out.print((char) in.readByte()); System.out.flush(); } } finally { ReferenceCountUtil.release(msg); // (2)  } }
注: 一、这个低效的循环实际上能够简化为:System.out.println(in.toString(io.net .util. charsetutil.us_ascii))
        二、或者,您能够在这里执行in.release()。
 
问题编写ECHO服务
  到目前为止,咱们一直在使用数据,而没有作出任何响应。然而,服务器一般应该响应请求。让咱们学习如何经过实现ECHO协议向客户端写入响应消息,其中全部接收到的数据都被发回。
  与咱们在前几节中实现的Discards服务器的惟一区别是,它将接收到的数据发送回服务器,而不是将接收到的数据打印到控制台。所以,再次修改channelRead()方法就足够了: 
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.write(msg); // (1) ctx.flush(); // (2) }
注: 一、ChannelHandlerContext对象提供各类操做,使您可以触发各类I/O事件和操做。在这里,咱们调用write(Object)以逐字记录接收到的消息。请注意,咱们没有释放接收到的消息,这与咱们在丢弃示例中所作的不一样。这是由于当它被写在网上时,Netty会为你释放它。
        二、ctx.write(Object) 不会将消息写到线路上。它在内部进行缓冲,而后经过ctx.flush()将其冲到电线上。或者,为了简洁起见,能够调用ctx.writeAndFlush(msg)。

若是您再次运行telnet命令,您将看到服务器返回您发送给它的任何内容。git

echo服务器的完整源代码位于发行版的io.net .example.echo包中。github

编写一个时间服务
  本节中要实现的协议是TIME协议。与前面的示例不一样的是,它发送一个包含32位整数的消息,而不接收任何请求,而且在消息发送后关闭链接。在本例中,您将学习如何构造和发送消息,以及如何在完成时关闭链接。由于咱们将忽略任何接收到的数据,可是在链接创建后当即发送消息,因此此次咱们不能使用channelRead()方法。相反,咱们应该重写channelActive()方法。下面是实现:
package io.netty.example.time;

public class TimeServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(final ChannelHandlerContext ctx) { // (1) final ByteBuf time = ctx.alloc().buffer(4); // (2) time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L)); final ChannelFuture f = ctx.writeAndFlush(time); // (3) f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { assert f == future; ctx.close(); } }); // (4)  } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
注: 一、如前所述,将在创建链接并准备生成通讯量时调用channelActive()方法。让咱们写一个32位整数,表示这个方法中的当前时间。
        二、要发送新消息,咱们须要分配一个包含消息的新缓冲区。咱们要写一个32位整数,所以咱们须要一个ByteBuf,它的容量至少是4字节。经过ChannelHandlerContext.alloc()获取当前的ByteBufAllocator,并分配一个新的缓冲区。

        三、像往常同样,咱们编写构造好的消息。web

             可是等等,抛硬币在哪里?在使用NIO发送消息以前,咱们不是曾经调用java.nio.ByteBuffer.flip()吗?ByteBuf没有这样的方法,由于它有两个指针;一个用于读操做,另外一个用于写操做。当您向ByteBuf写入内容时,写入器索引会增长,而读取器索引不会改变。阅读器索引和写入器索引分别表示消息开始和结束的位置。ajax

              相反,NIO缓冲区没有提供一种干净的方法来肯定消息内容在哪里开始和结束,而不调用flip方法。当您忘记翻转缓冲区时,您将遇到麻烦,由于不会发送任何或不正确的数据。在Netty中不会发生这样的错误,由于对于不一样的操做类型,咱们有不一样的指针。当你习惯了它,你会发现它让你的生活变得更容易——一个没有翻转的生活!
编程

              要注意的另外一点是ChannelHandlerContext.write()(和writeAndFlush())方法返回ChannelFuture。ChannelFuture表示还没有发生的I/O操做。这意味着,因为Netty中的全部操做都是异步的,所以可能尚未执行任何请求的操做。例如,如下代码可能会在发送消息以前关闭链接:bootstrap

Channel ch = ...;
ch.writeAndFlush(message);
ch.close();
  所以,您须要在ChannelFuture完成以后调用close()方法,这个方法由write()方法返回,当写操做完成时,它会通知它的侦听器。请注意,close()也可能不会当即关闭链接,它将返回ChannelFuture。
        四、那么,当写请求完成时,咱们如何获得通知?这就像在返回的通道将来中添加一个ChannelFutureListener同样简单。在这里,咱们建立了一个新的匿名通道futurelistener,它在操做完成时关闭通道。
              或者,您可使用预约义的侦听器简化代码:
f.addListener(ChannelFutureListener.CLOSE);

  要测试咱们的时间服务器是否按预期工做,您可使用UNIX rdate命令:api

$ rdate -o <port> -p <host>
  其中<port>是main()方法中指定的端口号,<host>一般是本地主机。
 
编写一个时间客户端
  与Discard服务器和ECHO服务器不一样,咱们须要一个时间协议客户机,由于人不能将32位二进制数据转换为日历上的日期。在本节中,咱们将讨论如何确保服务器正确工做,以及如何使用Netty编写客户机。
  在Netty中,服务器和客户机之间最大也是惟一的区别是使用了不一样的引导和通道实现。请查看如下代码:
package io.netty.example.time;

public class TimeClient { public static void main(String[] args) throws Exception { String host = args[0]; int port = Integer.parseInt(args[1]); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); // (1) b.group(workerGroup); // (2) b.channel(NioSocketChannel.class); // (3) b.option(ChannelOption.SO_KEEPALIVE, true); // (4) b.handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); // Start the client. ChannelFuture f = b.connect(host, port).sync(); // (5) // Wait until the connection is closed.  f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); } } }
注:一、Bootstrap相似于ServerBootstrap,只是它用于非服务器通道,好比客户端通道或无链接通道。
    二、若是只指定一个EventLoopGroup,它将做为boss组和工做者组使用。可是,老板员工并不用于客户端
    三、与NioServerSocketChannel不一样,NioSocketChannel用于建立客户端通道。
    四、注意,这里咱们不像使用ServerBootstrap那样使用childOption(),由于客户端SocketChannel没有父节点。
    五、咱们应该调用connect()方法,而不是bind()方法。
如您所见,它与服务器端代码并无什么不一样。那么ChannelHandler实现呢?它应该从服务器接收一个32位的整数,将其转换为人类可读的格式,打印翻译后的时间,并关闭链接:
package io.netty.example.time;

import java.util.Date; public class TimeClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf m = (ByteBuf) msg; // (1) try { long currentTimeMillis = (m.readUnsignedInt() - 2208988800L) * 1000L; System.out.println(new Date(currentTimeMillis)); ctx.close(); } finally { m.release(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
注:一、在TCP/IP中,Netty将从对等点发送的数据读入ByteBuf。
  它看起来很是简单,与服务器端示例没有任何不一样。然而,这个处理程序有时会拒绝启动IndexOutOfBoundsException。咱们将在下一节讨论为何会发生这种状况。
 
处理基于流的传输
  套接字缓冲区的一个小警告
  在基于流的传输(如TCP/IP)中,接收到的数据存储在套接字接收缓冲区中。不幸的是,基于流的传输的缓冲区不是包的队列,而是字节的队列。这意味着,即便您将两个消息做为两个独立的信息包发送,操做系统也不会将它们视为两个消息,而只是一堆字节。所以,不能保证您所阅读的内容就是您的远程对等者所写的内容。例如,假设一个操做系统的
TCP/IP栈接收了三个包:
   因为基于流的协议的通常特性,在您的应用程序中颇有可能以如下片断形式阅读它们:
  所以,不管接收部分是服务器端仍是客户端,都应该将接收到的数据碎片整理成应用程序逻辑能够容易理解的一个或多个有意义的帧,应用程序逻辑能够很容易地理解这些帧。对于上面的例子,接收到的数据应该像下面这样构造:
第一个解决方案:
  如今让咱们回到 TIME客户端示例。咱们在这里遇到一样的问题。32位整数是很是少许的数据,而且不太可能常常被分段。然而,问题在于它多是碎片化的,而且随着流量的增长,碎片化的可能性将增长。
  简单的解决方案是建立一个内部累积缓冲区,并等待全部4个字节都被接收到内部缓冲区。如下是 TimeClientHandler修复此问题的修改实现:
package io.netty.example.time;

import java.util.Date; public class TimeClientHandler extends ChannelInboundHandlerAdapter { private ByteBuf buf; @Override public void handlerAdded(ChannelHandlerContext ctx) { buf = ctx.alloc().buffer(4); // (1)  } @Override public void handlerRemoved(ChannelHandlerContext ctx) { buf.release(); // (1) buf = null; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf m = (ByteBuf) msg; buf.writeBytes(m); // (2)  m.release(); if (buf.readableBytes() >= 4) { // (3) long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L) * 1000L; System.out.println(new Date(currentTimeMillis)); ctx.close(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
注:一、ChannelHandler有两个生命周期监听器方法:handlerAdded()和handlerRemoved()。您能够执行任意(de)初始化任务,只要它不会长时间阻塞。
    二、首先,应将全部收到的数据累积到buf。
    三、而后,处理程序必须检查buf是否有足够的数据,在此示例中为4个字节,而后继续执行实际的业务逻辑。不然,channelRead()当更多数据到达时,Netty将再次调用该方法,最终将累计全部4个字节。
 
第二种解决方案
  虽然第一个解决方案已经解决了 TIME客户端的问题,但修改后的处理程序看起来并不干净。想象一个更复杂的协议,它由多个字段组成,例如可变长度字段。您的 ChannelInboundHandler实施将很快变得没法维护。
  您可能已经注意到,您能够 ChannelHandler为a 添加多个 ChannelPipeline,所以,您能够将一个单片拆分 ChannelHandler为多个模块化,以下降应用程序的复杂性。例如,您能够拆分 TimeClientHandler为两个处理程序:
  • TimeDecoder 它涉及碎片问题,以及
  • 最初的简单版本TimeClientHandler
  幸运的是,Netty提供了一个可扩展的类,能够帮助您编写第一个开箱即用的类:
package io.netty.example.time;

public class TimeDecoder extends ByteToMessageDecoder { // (1)
 @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2) if (in.readableBytes() < 4) { return; // (3)  } out.add(in.readBytes(4)); // (4)  } }
注:一、ByteToMessageDecoder是一种实现ChannelInboundHandler,能够很容易地处理碎片问题。
       二、ByteToMessageDecoderdecode()每当收到新数据时,都会使用内部维护的累积缓冲区调用该方法。
       三、decode()能够决定不向累积缓冲区中没有足够数据的地方添加任何内容。当接收到更多数据时,ByteToMessageDecoder将再次调用decode()。
      四、若是decode()向out添加一个对象,则表示解码器成功解码一条消息。ByteToMessageDecoder将丢弃累积缓冲区的读取部分。请记住,您不须要解码多个消息。ByteToMessageDecoder将继续调用decode()方法,直到它没有向out添加任何内容为止。
  如今咱们有另外一个处理程序插入到ChannelPipeline中,咱们应该修改TimeClient中的ChannelInitializer实现:
b.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeDecoder(), new TimeClientHandler()); } });

  若是你是一个喜欢冒险的人,你可能想试试ReplayingDecoder,这将解码器变得更加简单。不过,您须要参考API参考以得到更多信息。promise

public class TimeDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode( ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { out.add(in.readBytes(4)); } }
  另外,Netty提供了开箱即用的解码器,它使您可以很是容易地实现大多数协议,并帮助您避免最终出现难以维护的单块处理程序实现。更详细的例子请参考如下包:   
用POJO代替ByteBuf
  到目前为止,咱们所审查的全部示例都使用了 ByteBuf做为协议消息的主要数据结构。在本节中,咱们将改进 TIME协议客户端和服务器示例以使用POJO而不是 ByteBuf
在你的 ChannelHandler中使用POJO的优点是显而易见的; 经过分离 ByteBuf从处理程序中提取信息的代码,您的处理程序变得更易于维护和重用。在 TIME客户端和服务器示例中,咱们只读取一个32位整数,这不是 ByteBuf直接使用的主要问题。可是,您会发如今实现真实世界协议时必须进行分离。
  首先,让咱们定义一个名为的新类型 UnixTime
package io.netty.example.time;

import java.util.Date; public class UnixTime { private final long value; public UnixTime() { this(System.currentTimeMillis() / 1000L + 2208988800L); } public UnixTime(long value) { this.value = value; } public long value() { return value; } @Override public String toString() { return new Date((value() - 2208988800L) * 1000L).toString(); } }

  咱们如今能够修改它TimeDecoder来产生一个UnixTime而不是一个ByteBuf

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { if (in.readableBytes() < 4) { return; } out.add(new UnixTime(in.readUnsignedInt())); }

  使用更新的解码器,TimeClientHandler再也不使用ByteBuf

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { UnixTime m = (UnixTime) msg; System.out.println(m); ctx.close(); }

  更简单,更优雅,对吧?能够在服务器端应用相同的技术。咱们TimeServerHandler此次更新第一次:

@Override
public void channelActive(ChannelHandlerContext ctx) { ChannelFuture f = ctx.writeAndFlush(new UnixTime()); f.addListener(ChannelFutureListener.CLOSE); }

  如今,惟一缺乏的部分是一个编码器,它的实现ChannelOutboundHandler将一个UnixTime转换为一个ByteBuf。它比编写解码器简单得多,由于编码消息时无需处理数据包碎片和汇编。

package io.netty.example.time;

public class TimeEncoder extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { UnixTime m = (UnixTime) msg; ByteBuf encoded = ctx.alloc().buffer(4); encoded.writeInt((int)m.value()); ctx.write(encoded, promise); // (1)  } }
注:一、这一行中有不少重要的事情。
            首先,咱们按原样传递原始文件ChannelPromise,以便当编码数据实际写入线路时,Netty将其标记为成功或失败。
            第二,咱们没有调用ctx.flush()。有一个单独的处理程序方法void flush(ChannelHandlerContext ctx),用于覆盖flush()操做。
  为了进一步简化,您可使用 MessageToByteEncoder
public class TimeEncoder extends MessageToByteEncoder<UnixTime> {
    @Override
    protected void encode(ChannelHandlerContext ctx, UnixTime msg, ByteBuf out) { out.writeInt((int)msg.value()); } }
  最后一个任务是在TimeServerHandler以前将一个TimeEncoder插入到服务器端ChannelPipeline中,这只是一个简单的练习。
 
关闭你的应用程序
  关闭一个Netty应用程序一般与关闭经过shutdowndowns()建立的全部EventLoopGroups同样简单。当EventLoopGroup彻底终止而且属于该组的全部通道都已关闭时通知您,它返回一个 Future
 
概要
  在本章中,咱们快速浏览了Netty,并演示了如何在Netty上编写完整的网络应用程序。
  在接下来的章节中有关于Netty的更多详细信息。咱们还鼓励您查看 io.netty.example包中的Netty示例。
  另请注意,社区始终在等待您的问题和想法,以帮助您并根据您的反馈不断改进Netty及其文档。
相关文章
相关标签/搜索