前言
这个问题
现在我们使用通用应用程序或库相互通信。例如,我们经常使用一个HTTP客户端库从web服务器和检索信息通过web服务调用远程过程调用。然而,一个通用的协议或其实现有时不能很好地规模。就像我们不使用通用HTTP服务器交换巨大的文件,电子邮件和接近实时的信息,如财务信息和多人游戏数据。需要的是一个高度优化的协议实现为一个特殊目的。例如,您可能想要实现一个HTTP服务器优化基于ajax的聊天应用程序,流媒体,或大型文件传输。你甚至可能想设计和实现一个全新的协议正是适合你的需要。另一个不可避免的情况是当你不得不处理遗留专有协议,确保与旧系统的互操作性。在这种情况下重要的是实现该协议的速度能有多快,而不会牺牲最终的应用程序的稳定性和性能。
解决方案
网状的项目是为了提供一个异步事件驱动的网络应用程序框架和工具的迅速发展,可维护的高性能路高可伸缩性协议服务器和客户端。
换句话说,网状的NIO服务器端框架,它允许快速和容易的开发网络应用,如协议服务器和客户端。它极大简化了网络编程,如TCP和UDP套接字服务器开发。
“快捷”并不意味着生成的应用程序将遭受可维护性或性能问题。网状的设计精心的体验中获得很多协议的实现如FTP、SMTP、HTTP、和各种二进制和文本遗留协议。因此,网状的成功找到一个方法来实现易于开发、性能、稳定性和灵活性没有妥协。
一些用户可能已经发现了其他网络应用程序框架,它声称有相同的优势,你也许会想问是什么让网状的如此不同。答案是它是建立在哲学。网状的设计给你最舒适的体验的API和实现的第一天。它不是有形的东西但是你会意识到这种哲学会使你的生活更容易当你阅读本指南和网状的一起玩。
开始
本章围绕核心旅游结构的网状的简单的例子让你很快开始。您可以编写一个客户机和一个服务器上的网状的马上当你在这一章的结束。
如果你喜欢自上而下的方法在学习一些东西,你可能想从第二章开始,体系结构概述,回到这里。
在开始之前
最低要求运行的例子介绍了只有两章,最新版本的网状的JDK 1.6或以上。网状的可用的最新版本项目下载页面。下载正确的版本的JDK,请参阅您的首选JDK供应商的网站。
当你阅读的时候,你可能会有更多的关于本章中介绍的类的问题。请参考API参考每当你想知道更多关于他们。本文档中所有类名与在线API参考,为了您的方便。另外,请不要犹豫联系网状的社区项目并让我们知道如果有任何不正确的信息,错误的语法和错误,如果你有一个好主意来改进文档。
写一个丢弃服务器
世界上最简单的协议不是“你好,世界!但 DISCARD
。这是一个协议丢弃任何接收的数据没有任何反应。
来实现 DISCARD
协议,你唯一需要做的就是忽略所有接收的数据。让我们开始直接从处理程序实现,处理由网状的I / O事件。
packageio.netty.example.discard;
importio.netty.buffer.ByteBuf;
importio.netty.channel.ChannelHandlerContext;
importio.netty.channel.ChannelInboundHandlerAdapter;
/** * Handles a server-side channel.*/publicclassDiscardServerHandlerextendsChannelInboundHandlerAdapter { // (1)@OverridepublicvoidchannelRead(ChannelHandlerContextctx, Objectmsg) { // (2)// Discard the received data silently.
((ByteBuf) msg).release(); // (3)
}
@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx, Throwablecause) { // (4)// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
DiscardServerHandler
扩展ChannelInboundHandlerAdapter
,这是一个实现的ChannelInboundHandler
.ChannelInboundHandler
提供各种事件处理程序方法,您可以覆盖。现在,它只是足够的扩展ChannelInboundHandlerAdapter
而不是自己实现处理程序接口。- 我们覆盖
channelRead()
事件处理程序方法。调用这个方法时使用接收到的消息,只要从客户机接收到新的数据。在这个例子中,接收到的消息的类型ByteBuf
. - 来实现
DISCARD
协议处理程序必须忽略收到消息。ByteBuf
是一个采用引用计数的对象必须明确通过发布release()
方法。请记住,这是处理程序的责任释放任何采用引用计数对象传递到处理程序。通常情况下,channelRead()
处理程序方法实现如下:
@Overridepublicvoid channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}
- 的
exceptionCaught()
当调用事件处理程序方法,Throwable异常网状的提出是由于一个I / O错误或者由于异常处理程序实现在处理事件。在大多数情况下,发现异常应记录及其相关频道应该关闭这里,虽然这个方法可以不同的实现取决于你想做些什么来处理异常情况。例如,您可能想要发送一个响应消息关闭连接前一个错误代码。
目前为止一切都很顺利。我们已经实现了的前半部分 DISCARD
服务器。现在剩下的就是编写 main()
启动服务器的方法 DiscardServerHandler
.
packageio.netty.example.discard;
importio.netty.bootstrap.ServerBootstrap;
importio.netty.channel.ChannelFuture;
importio.netty.channel.ChannelInitializer;
importio.netty.channel.ChannelOption;
importio.netty.channel.EventLoopGroup;
importio.netty.channel.nio.NioEventLoopGroup;
importio.netty.channel.socket.SocketChannel;
importio.netty.channel.socket.nio.NioServerSocketChannel;
/** * Discards any incoming data.*/publicclassDiscardServer {
privateint port;
publicDiscardServer(intport) {
this.port = port;
}
publicvoidrun() throwsException {
EventLoopGroup bossGroup =newNioEventLoopGroup(); // (1)EventLoopGroup workerGroup =newNioEventLoopGroup();
try {
ServerBootstrap b =newServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(newChannelInitializer<SocketChannel>() { // (4)@OverridepublicvoidinitChannel(SocketChannelch) throwsException {
ch.pipeline().addLast(newDiscardServerHandler());
}
})
.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();
}
}
publicstaticvoidmain(String[] args) throwsException {
int port;
if (args.length >0) {
port =Integer.parseInt(args[0]);
} else {
port =8080;
}
newDiscardServer(port).run();
}
}
NioEventLoopGroup
是一个多线程事件循环处理I / O操作。网状的��供各种EventLoopGroup
实现不同的传输。我们正在实施一个服务器端应用程序在这个例子中,因此两个NioEventLoopGroup
就会被使用。第一个,通常被称为“老板”,接受传入的连接。第二个,通常被称为“工人”,处理接受的交通连接一旦老板接受连接,注册接受连接到工人。有多少线程创建使用和它们是如何映射到Channel
年代取决于EventLoopGroup
通过构造函数实现,甚至可能是可配置的。ServerBootstrap
是一个辅助类,设置了一个服务器。您可以设置服务器使用Channel
直接。然而,请注意,这是一个乏味的过程,你在大多数情况下不需要这样做。- 在这里,我们指定使用
NioServerSocketChannel
类用于实例化一个新Channel
接受传入的连接。 - 在这里指定的处理器将永远被新接受评估
Channel
。的ChannelInitializer
所定是一个特殊的处理程序,帮助用户配置一个新的吗Channel
。最有可能,你想配置ChannelPipeline
的新Channel
通过添加一些处理程序等DiscardServerHandler
实现你的网络应用程序。当应用程序变得复杂,很可能你会增加更多的管道处理程序和提取最终这个匿名类成一个顶级类。 - 你也可以设置特定的参数
Channel
实现。我们正在写一个TCP / IP服务器,所以我们可以设置套接字选项等tcpNoDelay
和keepAlive
。请参阅apidocsChannelOption
和具体的ChannelConfig
实现对支持概述ChannelOption
年代。 - 你有没有注意到
option()
和childOption()
?option()
是NioServerSocketChannel
接受传入的连接。childOption()
是Channel
被父母接受ServerChannel
,这是NioServerSocketChannel
在这种情况下。 - 我们准备走了。剩下的就是绑定到端口并启动服务器。在这里,我们绑定到端口
8080
所有网卡(网络接口卡)的机器。现在可以调用bind()
方法尽可能多次(使用不同的绑定地址。)
恭喜你!你刚刚完成你的第一个服务器上的网状的。
观察接收到的数据
现在我们所写的第一个服务器,我们需要测试,如果它确实有效。测试它的最简单方法是使用telnet命令。例如,您可以输入 telnet localhost 8080
在命令行和类型。
但是,我们能说服务器工作正常吗?我们无法知道,因为它是一个丢弃服务器。你不会得到任何回应。来证明它是真的工作了,让我们修改服务器打印已收到。
我们已经知道, channelRead()
方法被调用时收到的数据。让我们把一些代码 channelRead()
的方法 DiscardServerHandler
:
@Overridepublicvoid 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
协议,任何接收的数据发送回来。
唯一的区别从丢弃服务器我们实现了在前面的部分是,它发送接收到的数据,而不是将接收到的数据打印到控制台。因此,足够的再次修改 channelRead()
方法:
@Overridepublicvoid channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg); // (1)
ctx.flush(); // (2)
}
- 一个
ChannelHandlerContext
对象提供了各种操作,使您能够引发各种I / O事件和操作。在这里,我们调用write(Object)
写逐字的收到的消息。请注意,我们没有接收到的信息与我们的发布DISCARD
的例子。因为网状的写入时释放给你。 ctx.write(Object)
不会使消息写入。它内部缓冲,然后刷新的线ctx.flush()
。或者,你可以调用ctx.writeAndFlush(msg)
简洁。
如果再次运行telnet命令,您将看到该服务器发送回任何你已经发送给它。
回声的完整源代码服务器位于 io.netty.example.echo
包的分布。
写一个时间服务器
协议实现在本节 TIME
协议。它不同于前面的例子,它发送一条消息,其中包含一个32位整数,没有收到任何请求和关闭连接一旦消息被发送。在这个例子中,您将学习如何构建和发送消息,并关闭连接完成。
因为我们要忽略任何接收的数据但发送消息一旦建立连接,我们不能使用 channelRead()
这一次的方法。相反,我们应该覆盖 channelActive()
方法。下面是实现:
packageio.netty.example.time;
publicclassTimeServerHandlerextendsChannelInboundHandlerAdapter {
@OverridepublicvoidchannelActive(finalChannelHandlerContextctx) { // (1)finalByteBuf time = ctx.alloc().buffer(4); // (2)
time.writeInt((int) (System.currentTimeMillis() /1000L+2208988800L));
finalChannelFuture f = ctx.writeAndFlush(time); // (3)
f.addListener(newChannelFutureListener() {
@OverridepublicvoidoperationComplete(ChannelFuturefuture) {
assert f == future;
ctx.close();
}
}); // (4)
}
@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx, Throwablecause) {
cause.printStackTrace();
ctx.close();
}
}
-
解释说,
channelActive()
方法将调用当建立连接并准备产生流量。让我们写一个32位整数,表示当前时间在这个方法中。 -
送一个新的信息,我们需要分配一个新的缓冲它将包含消息。我们要写一个32位整数,因此我们需要一个
ByteBuf
是谁的能力至少4个字节。获取当前ByteBufAllocator
通过ChannelHandlerContext.alloc()
分配一个新的缓冲区。 -
像往常一样,我们写构造消息。
但是,等等,抛在哪里?没有我们用来调用
java.nio.ByteBuffer.flip()
在发送消息之前NIO吗?ByteBuf
没有这样一种方法,因为它有两个指针,一个用于读取操作,另一个用于写操作。作者指数增加当你写点东西ByteBuf
而读者指数并没有改变。读者索引和作者指数分别表示消息开始和结束的地方。相比之下,NIO缓冲不提供一个干净的方法找出消息内容没有调用翻转方法的开始和结束。你会有麻烦当你忘了翻转缓冲区因为没有或不正确的数据将被发送。这种错误不会发生在网状的,因为我们有不同的指针对不同操作类型。你会发现它让你的生活更容易,你要去适应它,人生没有翻出来!
另外要注意的一点是,
ChannelHandlerContext.write()
(和writeAndFlush()
)方法返回一个ChannelFuture
。一个ChannelFuture
代表了一个尚未发生的I / O操作。这意味着,任何请求的操作可能不是在网状的表现,因为所有操作都是异步的。例如,下面的代码可能会关闭连接之前发送消息:Channel ch =...; ch.writeAndFlush(message); ch.close();
因此,您需要调用
close()
方法后ChannelFuture
完成后,返回的是哪一个write()
方法,它通知侦听器在写操作完成。请注意,close()
也可能不会立即关闭连接,它返回一个ChannelFuture
. -
我们如何写请求完成时得到通知?这是简单地添加一个
ChannelFutureListener
对返回的ChannelFuture
。在这里,我们创建了一个新的匿名ChannelFutureListener
而关闭Channel
当操作完成。或者,您可以使用一个预定义的侦听器简化代码:
f.addListener(ChannelFutureListener.CLOSE);
测试如果我们的时间服务器按预期工作,您可以使用UNIX rdate
命令:
$ rdate -o <port> -p <host>
在哪里 <port>
是你指定的端口号呢 main()
方法和 <host>
通常是 localhost
.
写一次客户端
不像 DISCARD
和 ECHO
服务器,我们需要一个客户端 TIME
协议,因为人不能一个32位二进制数据转换成日期在日历上。在本节中,我们讨论如何确保服务器正常工作和学习如何编写一个客户端与网状的。
最大的和唯一的区别在网状的服务器和客户机之间是不同的 Bootstrap
和 Channel
实现使用。请看看下面的代码:
packageio.netty.example.time;
publicclassTimeClient {
publicstaticvoidmain(String[] args) throwsException {
String host = args[0];
int port =Integer.parseInt(args[1]);
EventLoopGroup workerGroup =newNioEventLoopGroup();
try {
Bootstrap b =newBootstrap(); // (1)
b.group(workerGroup); // (2)
b.channel(NioSocketChannel.class); // (3)
b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
b.handler(newChannelInitializer<SocketChannel>() {
@OverridepublicvoidinitChannel(SocketChannelch) throwsException {
ch.pipeline().addLast(newTimeClientHandler());
}
});
// 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
,它将被用于作为老板集团和工人。老板的工人不用于客户端。 - 而不是
NioServerSocketChannel
,NioSocketChannel
被用来创建一个客户端Channel
. - 请注意,我们不使用
childOption()
这与我们所做的ServerBootstrap
因为客户端SocketChannel
没有一个家长。 - 我们应该叫
connect()
方法,而不是bind()
方法。
正如你所看到的,它不是真的不同于服务器端代码。是怎样的 ChannelHandler
实现吗?它应该从服务器接收一个32位整数,将它转化为一个人类可读的格式,打印翻译时间,和关闭连接:
packageio.netty.example.time;
importjava.util.Date;
publicclassTimeClientHandlerextendsChannelInboundHandlerAdapter {
@OverridepublicvoidchannelRead(ChannelHandlerContextctx, Objectmsg) {
ByteBuf m = (ByteBuf) msg; // (1)try {
long currentTimeMillis = (m.readUnsignedInt() -2208988800L) *1000L;
System.out.println(newDate(currentTimeMillis));
ctx.close();
} finally {
m.release();
}
}
@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx, Throwablecause) {
cause.printStackTrace();
ctx.close();
}
}
- 在TCP / IP,网状的读取数据发送从窥视
ByteBuf
.
它看起来很简单,看起来并没有任何的改变从服务器端例子。然而,这个处理程序有时会拒绝工作提高一个 IndexOutOfBoundsException
。在下一节中我们将讨论为什么发生这种情况。
基于流的传输
一个小小的警告的套接字缓冲区
在TCP / IP等基于流的传输,接收的数据是存储在一个套接字接收缓冲区。不幸的是,基于流的缓冲区传输不是一个队列的数据包队列的字节数。这意味着,即使你作为两个独立的数据包发送两个消息,一个操作系统不会把他们当作两个消息但只是一串字节。因此,不能保证你读的是什么远程对等写道。例如,让我们假设的TCP / IP堆栈操作系统已收到三个包:
因为这个一个面向流的协议的一般属性,有很高的机会阅读他们在接下来的支离破碎的表单在您的应用程序:
因此,接收部分,不管它是服务器端或客户端,应该整理磁盘碎片接收的数据到一个或多个有意义的框架,可以很容易地理解应用程序逻辑。在上面的示例中,接收到的数据应该框架如下:
第一个解决方案
现在让我们回到 TIME
客户端示例。我们这里也有同样的问题。一个32位的整数是一个非常小的数据量,它不太可能是分散的。然而,问题是,它可以分散,和分裂的可能性将增加随着流量的增加。
简单的解决方案是创建一个内部累积缓冲区和等待,直到所有4个字节到内部缓冲区接收。下面是修改后的 TimeClientHandler
实现修复存在的问题:
packageio.netty.example.time;
importjava.util.Date;
publicclassTimeClientHandlerextendsChannelInboundHandlerAdapter {
privateByteBuf buf;
@OverridepublicvoidhandlerAdded(ChannelHandlerContextctx) {
buf = ctx.alloc().buffer(4); // (1)
}
@OverridepublicvoidhandlerRemoved(ChannelHandlerContextctx) {
buf.release(); // (1)
buf =null;
}
@OverridepublicvoidchannelRead(ChannelHandlerContextctx, Objectmsg) {
ByteBuf m = (ByteBuf) msg;
buf.writeBytes(m); // (2)
m.release();
if (buf.readableBytes() >=4) { // (3)long currentTimeMillis = (buf.readUnsignedInt() -2208988800L) *1000L;
System.out.println(newDate(currentTimeMillis));
ctx.close();
}
}
@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx, Throwablecause) {
cause.printStackTrace();
ctx.close();
}
}
- 一个
ChannelHandler
有两个生命周期侦听器方法:handlerAdded()
和handlerRemoved()
。您可以执行任意(de)初始化任务,只要它不阻碍了很长一段时间。 - 首先,所有接收的数据应该累积
buf
. - 然后,处理程序必须检查
buf
有足够的数据,4个字节在这个例子中,并进行实际的业务逻辑。否则,网状的调用channelRead()
方法又更多的数据到达时,最终都将累积4个字节。
第二个解决方案
虽然第一个解决方案解决的问题 TIME
客户,修改后的处理程序看起来不干净。想象一个更复杂的协议是由多个字段,比如可变长字段。你的 ChannelInboundHandler
实现将很快成为不可维护。
正如你可能已经注意到,您可以添加不止一个 ChannelHandler
到一个 ChannelPipeline
,因此,你可以分割一个铁板一块 ChannelHandler
为多个模块化的减少你的应用程序的复杂性。例如,您可以分裂 TimeClientHandler
为两个处理程序:
TimeDecoder
处理碎片问题,- 最初的简单版本
TimeClientHandler
.
幸运的是,网状的提供了一个可扩展的类可以帮助你写第一个开箱即用的:
packageio.netty.example.time;
publicclassTimeDecoderextendsByteToMessageDecoder { // (1)@Overrideprotectedvoiddecode(ChannelHandlerContextctx, ByteBufin, List<Object>out) { // (2)if (in.readableBytes() <4) {
return; // (3)
}
out.add(in.readBytes(4)); // (4)
}
}
ByteToMessageDecoder
是一个实现ChannelInboundHandler
这使得它很容易处理碎片问题。ByteToMessageDecoder
调用decode()
方法用一个内部维护累积缓冲区每当接收到新的数据。decode()
可以添加什么out
那里没有足够的累积缓冲区中的数据。ByteToMessageDecoder
将调用decode()
当有更多的数据。- 如果
decode()
增加了一个对象out
,这意味着解码器解码成功消息。ByteToMessageDecoder
将会丢弃阅读累积缓冲区的一部分。请记住,你不需要解码多个消息。ByteToMessageDecoder
将调用decode()
方法直到它毫无用处out
.
现在我们有了另一个处理程序来插入 ChannelPipeline
,我们应该修改 ChannelInitializer
实现的 TimeClient
:
b.handler(newChannelInitializer<SocketChannel>() {
@OverridepublicvoidinitChannel(SocketChannelch) throwsException {
ch.pipeline().addLast(newTimeDecoder(), newTimeClientHandler());
}
});
如果你是一个爱冒险的人,你可能会想尝试的 ReplayingDecoder
这简化了译码器。你需要查阅API参考的更多信息。
publicclassTimeDecoderextendsReplayingDecoder<Void> {
@Overrideprotectedvoiddecode(
ChannelHandlerContextctx, ByteBufin, List<Object>out) {
out.add(in.readBytes(4));
}
}
此外,网状的提供了开箱即用的解码器,使您可以实现大多数协议很容易和帮助你避免从最后一个单片不可维护的处理程序实现。请参考下面的包更详细的例子:
io.netty.example.factorial
一个二进制协议,io.netty.example.telnet
文本基于行的协议。
而不是在POJO ByteBuf
所有的例子我们回顾了迄今为止使用 ByteBuf
作为协议消息的主要数据结构。在本节中,我们将会改善 TIME
协议客户端和服务器的例子使用POJO代替 ByteBuf
.
在你使用POJO的优势 ChannelHandler
s是明显;分离处理程序变得更加可维护和可重用的代码中提取信息 ByteBuf
从处理程序。在 TIME
客户机和服务器的例子,我们读只有一个32位整数,它不是一个大问题 ByteBuf
直接。然而,你会发现有必要使分离你实现一个真正的全球协议。
首先,让我们定义一个新类型 UnixTime
.
packageio.netty.example.time;
importjava.util.Date;
publicclassUnixTime {
privatefinallong value;
publicUnixTime() {
this(System.currentTimeMillis() /1000L+2208988800L);
}
publicUnixTime(longvalue) {
this.value = value;
}
publiclongvalue() {
return value;
}
@OverridepublicStringtoString() {
returnnewDate((value() -2208988800L) *1000L).toString();
}
}
我们现在可以修改 TimeDecoder
产生一个 UnixTime
而不是 ByteBuf
.
@Overrideprotectedvoid decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (in.readableBytes() <4) {
return;
}
out.add(newUnixTime(in.readUnsignedInt()));
}
更新后的解码器, TimeClientHandler
不使用 ByteBuf
了:
@Overridepublicvoid channelRead(ChannelHandlerContext ctx, Object msg) {
UnixTime m = (UnixTime) msg;
System.out.println(m);
ctx.close();
}
更简单和优雅的,对吧?同样的技术可以应用于服务器端。让我们更新 TimeServerHandler
第一:
@Overridepublicvoid channelActive(ChannelHandlerContext ctx) {
ChannelFuture f = ctx.writeAndFlush(newUnixTime());
f.addListener(ChannelFutureListener.CLOSE);
}
现在,唯一缺少的功能是一个编码器,这是一个实现的 ChannelOutboundHandler
这意味着一个 UnixTime
回一个 ByteBuf
。比编写一个简单的译码器因为没有需要处理的数据包编码消息时碎片和组装。
packageio.netty.example.time;
publicclassTimeEncoderextendsChannelOutboundHandlerAdapter {
@Overridepublicvoidwrite(ChannelHandlerContextctx, Objectmsg, ChannelPromisepromise) {
UnixTime m = (UnixTime) msg;
ByteBuf encoded = ctx.alloc().buffer(4);
encoded.writeInt((int)m.value());
ctx.write(encoded, promise); // (1)
}
}
-
有很多重要的事情在这一行。
首先,我们通过原始
ChannelPromise
按原样,网状的将它标记为成功或失败当编码数据实际上是写入。第二,我们没有电话
ctx.flush()
。有一个单独的处理程序方法void flush(ChannelHandlerContext ctx)
这是计划的覆盖flush()
操作。
进一步简化,可以利用 MessageToByteEncoder
:
publicclassTimeEncoderextendsMessageToByteEncoder<UnixTime> {
@Overrideprotectedvoidencode(ChannelHandlerContextctx, UnixTimemsg, ByteBufout) {
out.writeInt((int)msg.value());
}
}
剩下的最后一项任务是插入一个 TimeEncoder
到 ChannelPipeline
之前在服务器端 TimeServerHandler
,这是作为一个微不足道的练习。
关闭应用程序
关闭一个网状的应用程序通常是关闭所有一样简单 EventLoopGroup
通过创建年代 shutdownGracefully()
。它返回一个 Future
时通知你 EventLoopGroup
已经完全终止,所有 Channel
年代,属于集团已被关闭。
总结
在这一章,我们有一个快速浏览的网状的演示如何编写一个完全工作在网状的网络应用程序。
有更详细的信息在接下来的章节中网状的。我们也鼓励你审查的网状的例子 io.netty.example
包中。
也请注意,社区总是等待你的问题和想法帮助你和精益求精的网状的根据你的反馈及其文档。