官网:https://netty.io/
以下内容大部分来自Netty官网内容
一、现有问题
现在我们使用通用应用程序或库来彼此通信。例如,我们经常使用HTTP客户端库从web服务器检索信息,并通过web服务调用远程过程调用。然而,一个通用协议或它的实现有时并不能很好地扩展。这就像我们没有使用通用的HTTP服务器来交换巨大的文件、电子邮件消息和接近实时的消息(如财务信息和多人游戏数据)。需要的是一个高度优化的协议实现,专门用于特定的目的。例如,您可能想实现一个针对基于ajax的聊天应用程序、媒体流或大型文件传输进行优化的HTTP服务器。您甚至可能想设计和实现一个全新的协议,它可以根据您的需要进行精确定制。另一种不可避免的情况是,您必须处理遗留的专有协议,以确保与旧系统的互操作性。在这种情况下,重要的是在不牺牲应用程序的稳定性和性能的情况下,我们可以多快地实现该协议。
二、解决方案
Netty项目致力于提供一个异步事件驱动的网络应用框架和工具,用于快速开发可维护的高性能和高可伸缩性协议服务器和客户端。
换句话说,Netty是一个NIO客户端服务器框架,它支持快速而简单地开发网络应用程序,如协议服务器和客户端。它极大地简化和简化了网络编程,如TCP和UDP套接字服务器开发。
“快速和简单”并不意味着结果应用程序将受到可维护性或性能问题的影响。Netty是经过精心设计的,从许多协议(如FTP、SMTP、HTTP和各种基于二进制和文本的遗留协议)的实现中吸取了经验。因此,Netty成功地找到了一种方法,可以在不妥协的情况下实现开发的易用性、性能、稳定性和灵活性。
有些用户可能已经发现了其他声称具有相同优势的网络应用程序框架,您可能想知道是什么使Netty与它们如此不同。答案是它所建立的哲学。Netty从一开始就为您提供API和实现方面最舒适的体验。这不是一些有形的东西,但你会意识到,这种哲学将使你的生活更容易,当你阅读本指南和玩Netty。
三、编写一个丢弃服务器
世界上最简单的协议不是“你好,世界!”但丢弃。它是一种丢弃任何接收到的数据而不进行任何响应的协议。
要实现丢弃协议,您需要做的唯一一件事就是忽略所有接收到的数据。让我们直接从处理程序实现开始,它处理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();
}
}
实现丢弃服务服务器:
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)
@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();
}
}
上述代码中数字分别代表以下内容:
1.NioEventLoopGroup是一个处理I/O操作的多线程事件循环。Netty为不同类型的传输提供了各种EventLoopGroup实现。在本例中,我们将实现一个服务器端应用程序,因此将使用两个NioEventLoopGroup。第一个,通常称为“boss”,接受一个传入连接。第二个,通常称为“worker”,在老板接受连接并向worker注册已接受的连接后,处理已接受连接的流量。使用多少线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。
2.ServerBootstrap是一个设置服务器的助手类。您可以直接使用通道设置服务器。但是,请注意,这是一个乏味的过程,而且在大多数情况下不需要这样做。
3.这里,我们指定使用NioServerSocketChannel类,该类用于实例化一个新通道以接受传入连接。
4.这里指定的处理程序将始终由新接受的通道进行评估。ChannelInitializer是一个特殊的处理程序,用于帮助用户配置新通道。您很可能希望通过添加一些处理程序(如DiscardServerHandler)来配置新通道的ChannelPipeline,以实现网络应用程序。随着应用程序变得越来越复杂,您可能会向管道中添加更多的处理程序,并最终将这个匿名类提取到一个顶级类中。
5.您还可以设置特定于通道实现的参数。我们正在编写一个TCP/IP服务器,因此允许设置套接字选项,如tcpNoDelay和keepAlive。请参考ChannelOption的apidocs和特定的ChannelConfig实现,以获得有关受支持的ChannelOptions的概述。
6.您注意到option()和childOption()了吗?option()用于接受传入连接的NioServerSocketChannel。childOption()用于父服务器通道(在本例中为NioServerSocketChannel)接受的通道。
7.我们现在准备走了。剩下的工作就是绑定到端口并启动服务器。在这里,我们绑定到机器中所有nic(网络接口卡)的端口8080。现在可以任意多次调用bind()方法(使用不同的绑定地址)。
查看接收到的数据
@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)
}
}
1、他的低效循环实际上可以简化为:System.out.println(in.toString(io.netty.uti.charsetutil.us_ascii))
2、也可以在这里直接使用in.release()。
编写回显服务器
到目前为止,我们一直在使用数据而没有响应。然而,服务器通常应该响应请求。让我们学习如何通过实现ECHO协议向客户端编写响应消息,接收到的任何数据都会被发回。与我们在前几节中实现的丢弃服务器的惟一区别是,它将发送回接收到的数据,而不是将接收到的数据打印到控制台。因此,再次修改channelRead()方法就足够了:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg); //Read完直接写给客户端 (1)
ctx.flush();
// (2)可直接使用ctx.writeAndFlush(msg)
}
1、ChannelHandlerContext对象提供各种操作,使您能够触发各种I/O事件和操作。在这里,我们调用write(Object)来逐字写入接收到的消息。请注意,我们没有像在丢弃示例中那样释放接收到的消息。这是因为Netty在将其写入网络时为您释放了它。
2、write(Object)不会将消息写入网络。它在内部进行缓冲,然后通过ctx.flush()将其刷新到连接上。或者,为了简洁,您可以调用ctx.writeAndFlush(msg)。
待续。。