原文引自:github.com/netty/netty…

前言

问题

现在我们使用通用应用程序或库来相互通信。例如,我们经常使用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供应商的网站。

在阅读时,您可能对本章介绍的类有更多疑问。如果您想了解更多相关信息,请参阅API参考。为方便起见,本文档中的所有类名都链接到在线API参考。另外,请不要犹豫与Netty项目社区联系,如果有任何不正确的信息,语法和拼写错误,以及您是否有改进文档的好主意,请告诉我们。

编写丢弃服务器

世界上最简单的协议不是'Hello,World!'但是丢弃。它是一种在没有任何响应的情况下丢弃任何接收数据的协议。

要实现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)@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)// Discard the received data silently.((ByteBuf) msg).release(); // (3)}@Overridepublic 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。 要实现DISCARD协议,处理程序必须忽略收到的消息。 ByteBuf是一个引用计数对象,必须通过release()方法显式释放。 请记住,处理程序有责任释放传递给处理程序的任何引用计数对象。 通常,channelRead()处理程序方法的实现方式如下: @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { // Do something with msg } finally { ReferenceCountUtil.release(msg); } } 当由于I / O错误或由处理事件时抛出的异常引起的处理程序实现而由Netty引发异常时,使用Throwable调用exceptionCaught()事件处理程序方法。 在大多数情况下,应该记录捕获的异常并在此处关闭其关联的通道,尽管此方法的实现可能会有所不同,具体取决于您要处理异常情况的操作。 例如,您可能希望在关闭连接之前发送带有错误代码的响应消息。 到现在为止还挺好。 我们已经实现了DISCARD服务器的前半部分。 现在剩下的就是编写使用DiscardServerHandler启动服务器的main()方法。

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<SocketChannel>() { // (4)@Overridepublic 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是一个处理I / O操作的多线程事件循环。 Netty为不同类型的传输提供各种EventLoopGroup实现。我们在此示例中实现了服务器端应用程序,因此将使用两个NioEventLoopGroup。第一个,通常称为“老板”,接受传入连接。第二个,通常称为“工人”,一旦老板接受连接并将接受的连接注册到工作人员,就处理被接受连接的流量。使用了多少个线程以及它们如何映射到创建的Channels取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。 ServerBootstrap是一个设置服务器的帮助程序类。您可以直接使用Channel设置服务器。但请注意,这是一个繁琐的过程,在大多数情况下您不需要这样做。 在这里,我们指定使用NioServerSocketChannel类,该类用于实例化新Channel以接受传入连接。 此处指定的处理程序将始终由新接受的Channel评估。 ChannelInitializer是一个特殊的处理程序,旨在帮助用户配置新的Channel。您最有可能希望通过添加一些处理程序(如DiscardServerHandler)来配置新Channel的ChannelPipeline,以实现您的网络应用程序。随着应用程序变得复杂,您可能会向管道添加更多处理程序,并最终将此匿名类提取到顶级类中。 您还可以设置特定于Channel实现的参数。我们正在编写TCP / IP服务器,因此我们可以设置套接字选项,如tcpNoDelay和keepAlive。请参阅ChannelOption的apidocs和特定的ChannelConfig实现,以获得有关受支持的ChannelOptions的概述。 你注意到option()和childOption()吗? option()用于接受传入连接的NioServerSocketChannel。 childOption()用于父ServerChannel接受的Channels,在这种情况下是NioServerSocketChannel。 我们现在准备好了。剩下的就是绑定到端口并启动服务器。在这里,我们绑定到机器中所有NIC(网络接口卡)的端口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.netty.util.CharsetUtil.US_ASCII)) 或者,你可以在这里做in.release()。 如果再次运行telnet命令,您将看到服务器打印已收到的内容。

丢弃服务器的完整源代码位于发行版的io.netty.example.discard包中。

编写Echo服务器

到目前为止,我们一直在使用数据而没有响应。 但是,服务器通常应该响应请求。 让我们学习如何通过实现ECHO协议向客户端写入响应消息,其中任何接收的数据都被发回。

与我们在前面部分中实现的丢弃服务器的唯一区别在于,它将接收到的数据发回,而不是将接收到的数据打印到控制台。 因此,再次修改channelRead()方法就足够了:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {ctx.write(msg); // (1)ctx.flush(); // (2)
}
复制代码

ChannelHandlerContext对象提供各种操作,使您能够触发各种I / O事件和操作。在这里,我们调用write(Object)来逐字写入接收到的消息。请注意,我们没有发布收到的消息,这与我们在DISCARD示例中的操作不同。这是因为Netty在写入线路时会为您发布。 ctx.write(Object)不会将消息写入线路。它在内部缓冲,然后通过ctx.flush()刷新到线路。或者,您可以调用ctx.writeAndFlush(msg)以简洁起见。 如果再次运行telnet命令,您将看到服务器发送回发送给它的任何内容。

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

编写时间服务器

本节中要实现的协议是TIME协议。它与前面的示例的不同之处在于,它发送包含32位整数的消息,而不接收任何请求,并在发送消息后关闭连接。在此示例中,您将学习如何构造和发送消息,以及在完成时关闭连接。

因为我们将忽略任何接收的数据,但是一旦建立连接就发送消息,这次我们不能使用channelRead()方法。相反,我们应该覆盖channelActive()方法。以下是实施:

package io.netty.example.time;public class TimeServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic 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() {@Overridepublic void operationComplete(ChannelFuture future) {assert f == future;ctx.close();}}); // (4)}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}
复制代码

如上所述,当建立连接并准备生成流量时,将调用channelActive()方法。让我们写一个32位整数来表示这个方法中的当前时间。

要发送新消息,我们需要分配一个包含消息的新缓冲区。我们要写一个32位整数,因此我们需要一个容量至少为4个字节的ByteBuf。通过ChannelHandlerContext.alloc()获取当前的ByteBufAllocator并分配一个新的缓冲区。

像往常一样,我们编写构造的消息。

但等等,翻转的地方在哪里?在NIO中发送消息之前,我们不习惯调用java.nio.ByteBuffer.flip()吗? ByteBuf没有这样的方法,因为它有两个指针;一个用于读操作,另一个用于写操作。当您在读取器索引未更改时向ByteBuf写入内容时,写入器索引会增加。 reader索引和writer索引分别表示消息的开始和结束位置。

相比之下,NIO缓冲区没有提供一种干净的方法来确定消息内容的开始和结束位置,而无需调用flip方法。当您忘记翻转缓冲区时,您将遇到麻烦,因为不会发送任何数据或不正确的数据。在Netty中不会发生这样的错误,因为我们对不同的操作类型有不同的指针。你会发现它让你的生活变得更加轻松,因为你已经习惯了 - 没有翻身的生活!

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

Channel ch = ...;
ch.writeAndFlush(message);
ch.close();
复制代码

因此,您需要在完成ChannelFuture之后调用close()方法,该方法由write()方法返回,并在写入操作完成时通知其侦听器。 请注意,close()也可能不会立即关闭连接,并返回ChannelFuture。

当写请求完成后我们如何得到通知? 这就像向返回的ChannelFuture添加ChannelFutureListener一样简单。 在这里,我们创建了一个新的匿名ChannelFutureListener,它在操作完成时关闭Channel。

或者,您可以使用预定义的侦听器简化代码:

f.addListener(ChannelFutureListener.CLOSE);
复制代码

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

$ rdate -o <port> -p <host>
复制代码

其中是您在main()方法中指定的端口号,通常是localhost。

编写时间客户端

与DISCARD和ECHO服务器不同,我们需要TIME协议的客户端,因为人类无法将32位二进制数据转换为日历上的日期。 在本节中,我们将讨论如何确保服务器正常工作并学习如何使用Netty编写客户端。

Netty中服务器和客户端之间最大和唯一的区别是使用了不同的Bootstrap和Channel实现。 请看下面的代码:

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>() {@Overridepublic 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组和worker组。 不过,老板工作者不会用于客户端。 NioSocketChannel用于创建客户端通道,而不是NioServerSocketChannel。 请注意,我们不像在ServerBootstrap中那样使用childOption(),因为客户端SocketChannel没有父服务器。 我们应该调用connect()方法而不是bind()方法。 如您所见,它与服务器端代码没有什么不同。 ChannelHandler实现怎么样? 它应该从服务器接收一个32位整数,将其转换为人类可读的格式,打印翻译的时间,并关闭连接:

package io.netty.example.time;import java.util.Date;public class TimeClientHandler extends ChannelInboundHandlerAdapter {@Overridepublic 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();}}@Overridepublic 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;@Overridepublic void handlerAdded(ChannelHandlerContext ctx) {buf = ctx.alloc().buffer(4); // (1)}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) {buf.release(); // (1)buf = null;}@Overridepublic 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();}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}
复制代码

ChannelHandler有两个生命周期监听器方法:handlerAdded()和handlerRemoved()。您可以执行任意(de)初始化任务,只要它不会长时间阻塞。 首先,所有收到的数据应累积到buf中。 然后,处理程序必须检查buf是否有足够的数据,在此示例中为4个字节,然后继续执行实际的业务逻辑。否则,当更多数据到达时,Netty将再次调用channelRead()方法,最终将累计所有4个字节。

第二种解决方案

虽然第一个解决方案已经解决了TIME客户端的问题,但修改后的处理程序看起来并不干净。想象一个更复杂的协议,它由多个字段组成,例如可变长度字段。您的ChannelInboundHandler实现将很快变得无法维护。

您可能已经注意到,可以向ChannelPipeline添加多个ChannelHandler,因此,您可以将一个单片ChannelHandler拆分为多个模块化ChannelHandler,以降低应用程序的复杂性。例如,您可以将TimeClientHandler拆分为两个处理程序:

TimeDecoder处理碎片问题,以及 最初的简单版本的TimeClientHandler。 幸运的是,Netty提供了一个可扩展的类,可以帮助您编写第一个开箱即用的类:

package io.netty.example.time;public class TimeDecoder extends ByteToMessageDecoder { // (1)@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2)if (in.readableBytes() < 4) {return; // (3)}out.add(in.readBytes(4)); // (4)}
}
复制代码

ByteToMessageDecoder是ChannelInboundHandler的一个实现,可以很容易地处理碎片问题。 每当收到新数据时,ByteToMessageDecoder都会使用内部维护的累积缓冲区调用decode()方法。 decode()可以决定在累积缓冲区中没有足够数据的地方添加任何内容。 当收到更多数据时,ByteToMessageDecoder将再次调用decode()。 如果decode()将对象添加到out,则意味着解码器成功解码了一条消息。 ByteToMessageDecoder将丢弃累积缓冲区的读取部分。 请记住,您不需要解码多条消息。 ByteToMessageDecoder将继续调用decode()方法,直到它不添加任何内容。 既然我们有另一个要插入ChannelPipeline的处理程序,我们应该修改TimeClient中的ChannelInitializer实现:

b.handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());}
});
复制代码

如果您是一个喜欢冒险的人,您可能想尝试ReplayingDecoder,它可以进一步简化解码器。 您需要参考API参考以获取更多信息。

public class TimeDecoder extends ReplayingDecoder<Void> {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {out.add(in.readBytes(4));}
}
复制代码

此外,Netty提供开箱即用的解码器,使您能够非常轻松地实现大多数协议,并帮助您避免最终导致单片不可维护的处理程序实现。有关更多详细示例,请参阅以下软件包:

io.netty.example.factorial用于二进制协议,和 io.netty.example.telnet用于基于文本行的协议。

用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;}@Overridepublic String toString() {return new Date((value() - 2208988800L) * 1000L).toString();}
}
We can now revise the TimeDecoder to produce a UnixTime instead of a 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 {@Overridepublic 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> {@Overrideprotected void encode(ChannelHandlerContext ctx, UnixTime msg, ByteBuf out) {out.writeInt((int)msg.value());}
}
复制代码

剩下的最后一个任务是在TimeServerHandler之前将TimeEncoder插入到服务器端的ChannelPipeline中,并将其作为一个简单的练习。

关闭您的应用程序

关闭Netty应用程序通常就像关闭通过shutdownGracefully()创建的所有EventLoopGroup一样简单。 它返回一个Future,当EventLoopGroup完全终止并且所有属于该组的Channel已经关闭时,它会通知您。

摘要

在本章中,我们快速浏览了Netty,并演示了如何在Netty上编写完整的网络应用程序。

在接下来的章节中有关于Netty的更多详细信息。 我们还建议您查看io.netty.example包中的Netty示例。

另请注意,社区始终在等待您的问题和想法,以帮助您并根据您的反馈不断改进Netty及其文档。

转载于:https://juejin.im/post/5cda5ac7f265da03867e72df

Netty 框架文档相关推荐

  1. MFC应用程序框架-文档/视结构

    MFC应用程序框架-文档/视结构 1.MFC单文档应用程序结构 2.文档对象 2.1基类CDocument 2.2在应用程序中使用文档类的典型步骤 3.视的对象 3.1基类CView 3.2常用的CV ...

  2. spring框架文档学习(包会)

    文章目录 spring简介 IOC控制反转 ioc概念 ioc使用 (1)导入依赖 (2)编写实体类 (3)编写配置文件 (3)创建容器从容器中获取对象并测试 ioc三种创建对象方式 (1)下标赋值 ...

  3. Spring框架文档(二 )

    @[TOC](文章目录) 原文英文链接: https://docs.spring.io/spring/docs/5.2.3.BUILD-SNAPSHOT/spring-framework-refere ...

  4. selenium浏览器自动化测试框架文档(修正版)

    写在最前面:目前自动化测试并不属于新鲜的事物,或者说自动化测试的各种方法论已经层出不穷,但是,能够在项目中持之以恒的实践自动化测试的团队,却依旧不是非常多.有的团队知道怎么做,做的还不够好:有的团队还 ...

  5. 后端开发常用框架文档及中文翻译

    https://www.docs4dev.com 包含 Spring 系列文档(Spring, Spring Boot, Spring Cloud, Spring Security, Spring S ...

  6. 深入框架文档学习---英语词根学习

    文章说明: 本人是码农一枚,进入新公司后发现大牛们学习新技术速度都很快~ 经观察我发现大牛们喜欢看官方文档(大部分框架是英文)这让我意识到了英语的重要性.看国外一手消息.各种官方文档.各种框架开发者博 ...

  7. php msf 环境要求,4.6 配置 · php-msf PHP微服务框架文档 · 看云

    # 4.6 配置 配置是框架的重要组成部分,MSF框架的配置组件采用了第三方的[hassankhan/config](https://github.com/hassankhan/config),它支持 ...

  8. 可以一键生成crud的php框架,一键生成CRUD - FastAdmin框架文档 - FastAdmin开发文档

    一键生成CRUD 最后更新时间:2021-01-05 19:54:10 在FastAdmin中可以快速的一键生成CRUD,其中包括控制器.模型.视图.验证器.语言包.JS. 准备工作 在数据库中创建一 ...

  9. jsp调用servlet_053 JSP+Servlet整合练习:拼饭后台管理系统框架文档

    项目名称: 拼饭后台管理系统 项目需求: 实现用户登录功能 实现用户退出功能 实现用户注册功能 功能分析: 用户登录: 根据用户名和密码查询客户信息.查到则登录成功,查不到则登录失败. 用户退出: 销 ...

最新文章

  1. 动手扩充FreeTextBox的功能
  2. 2021年春季学期-信号与系统-第十二次作业参考答案-第七小题
  3. [2018雅礼集训1-16]方阵
  4. 类型名称了解typename的双重意义
  5. djngo快速实现--使用Bootstrap
  6. 20应用统计考研复试要点(part16)--应用多元分析
  7. Android 系统(95)---Android build.prop参数详解
  8. linux 指针什么意思,在linux中获取指向结构设备指针的更简洁方法是什么?
  9. 倒计时 5 天!Apache Flink Meetup 7.10 北京站,Flink x TiDB 专场等你来!
  10. 阿里巴巴android图标素材网,阿里巴巴图标素材库
  11. 车牌识别算法实现及其代码实现之三:车牌识别
  12. [论文写作笔记] C2论文写作结构与思路 C6 让研究方法称为加分项
  13. 【ITool】excel导入导出工具
  14. SpringMVC中注解和非注解形式配置
  15. 为什么按序发射只有RAW冲突?
  16. html5在线预览xml,HTML5教程 5分钟了解XML
  17. Python PymySQl 下载安装配置
  18. Spine使用外部图片动画换肤
  19. LIUNX账户与安全
  20. 高效记忆/形象记忆(05)定位法

热门文章

  1. DNS原理总结及其解析过程详解
  2. vue2计算属性computed
  3. 记金山西山居实习生面试2019.05.06
  4. setDaemon实例详解
  5. Java—Maven的使用
  6. MySQL获取当前时间、年月、年月日
  7. FFmpeg入门详解之113:live555简介
  8. python自媒体混剪视频_做自媒体混剪视频,万次播放收益150元!混剪视频剪辑技巧分享...
  9. 创业公司LOGO设计不容凑合 ! LOGO设计网让品牌更出色
  10. 凤凰金融:“区块链+”如何改变金融服务?