Netty 网络框架
Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。Netty 提供高性能和可扩展性,让你可以自由地专注于你真正感兴趣的东西,你的独特的应用!
Netty 是一个高性能、异步事件驱动网络库,它提供了对 TCP、UDP 和文件传输的支持使(这里首先就要搞清楚异步的 NIO 框架是什么意思)用更高效的 socket 底层,对 selector 空轮询引起的 cpu 占用飙升在内部进行了处理,避免了直接使用 NIO 的陷阱,简化了 NIO 的处理方式。采用多种decoder/encoder 支持,(后面我们会举例说明)对 TCP 粘包/分包进行自动化处理(后面也会演示说明)可使用接受/处理线程池,提高连接效率,对重连、心跳检测的简单支持可配置 IO 线程数、TCP 参数,TCP 接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用 ByteBuf 通过引用计数器及时申请释放不再引用的对象,降低了 GC 频率。高效的 Reactor 线程模型,大量使用了 volitale、使用了CAS 和原子类、线程安全类的使用、读写锁的使用。
Netty 技术和方法的特点
设计
- 针对多种传输类型的统一接口 - 阻塞和非阻塞。
- 简单但更强大的线程模型。
- 真正的无连接的数据报套接字支持。
- 链接逻辑支持复用。
易用性
- 大量的 Javadoc 和 代码实例。
- 除了在 JDK 1.6 + 额外的限制。(一些特征是只支持在 Java 1.7 +。可选的功能可能 有额外的限制。)
性能
- 比核心 Java API 更好的吞吐量,较低的延时。
- 资源消耗更少,这个得益于共享池和重用。
- 减少内存拷贝。
健壮性
- 消除由于慢,快,或重载连接产生的 OutOfMemoryError。
- 消除经常发现在 NIO 在高速网络中的应用中的不公平的读/写。
安全
- 完整的 SSL / TLS 和 StartTLS 的支持。
- 运行在受限的环境例如 Applet 或 OSGI。
社区
- 发布的更早和更频繁。
- 社区驱动。
Netty 和 NIO 的关系
处理客户端连接的 EventLoopGroup 一般包含一个 NioEventLoop,NioEventLoop 即为一个 Selector(也是一个线程,负责 NIO),负责处理 NioServerSocketChannel 的状态监测,当有连接到来时,执行 accept(),新建一个 NioSocketChannel 负责与 Client 端的通信,NioSocketChannel 从 WorkerEventLoopGroup(NioEventLoop 数量根据配置生成)中选择一个 NioEventLoop register 进去,该 Channel 后续所有的 NIO 操作均由该 NioEventLoop 负责处理。
NioEventLoop 主要包含两部分操作:
- processSelectedKeys() :即 selector 功能。
- RunAllTasks():执行任务队列中的任务,主要是定时任务和外部工作线程添加的读写任务。
Netty 的优势
- a、对 epoll 空轮询引起的 cpu 占用飙升在内部进行了处理,避免了直接使用 NIO 的陷阱。
- b、简化了 NIO 的处理方式。
- c、采用多种 decoder/encoder 支持。
- d、对 TCP 粘包/分包进行自动化处理可使用接受/处理线程池。
- e、提高连接效率,对重连、心跳检测的简单支持。
- f、可配置 IO 线程数、TCP 参数,TCP 接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用 ByteBuf。
- g、通过引用计数器及时申请释放不再引用的对象,降低了 GC 频率。
- h、使用单线程串行化的方式,高效的 Reactor 线程模型。
- i、大量使用了 volitale、使用了 CAS 和原子类、线程安全类的使用、读写锁的使用。
Netty 的编程流程及编码实践
服务端代码
public class EchoServer {
public void run() throws Exception {
// 进行服务器端的启动处理
// 线程池是提升服务器性能的重要技术手段,利用定长的线程池可以保证核心线程的有效数量
// 在 Netty 之中线程池的实现分为两类:主线程池(接收客户端连接)、工作线程池(处理客户端连接)
EventLoopGroup bossGroup = new NioEventLoopGroup(10); // 创建接收线程池
EventLoopGroup workerGroup = new NioEventLoopGroup(20); // 创建工作线程池
System.out.println("服务器启动成功,监听端口为:" + HostInfo.PORT);
try {
// 创建一个服务器端的程序类进行 NIO 启动,同时可以设置 Channel
ServerBootstrap serverBootstrap = new ServerBootstrap(); // 服务器端
// 设置要使用的线程池以及当前的 Channel 类型
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class);
// 接收到信息之后需要进行处理,于是定义子处理器
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new EchoServerHandler()); // 追加了处理器
}
});
// 可以直接利用常亮进行 TCP 协议的相关配置
serverBootstrap.option(ChannelOption.SO_BACKLOG, 128);
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
// ChannelFuture 描述的时异步回调的处理操作
ChannelFuture future = serverBootstrap.bind(HostInfo.PORT).sync();
future.channel().closeFuture().sync();// 等待 Socket 被关闭
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 当客户端连接成功之后会进行此方法的调用,明确可以给客户端发送一些信息
byte data[] = "【服务器激活信息】连接通道已经创建,服务器开始进行响应交互。".getBytes();
// NIO是基于缓存的操作,所以Netty也提供有一系列的缓存类(封装了NIO中的Buffer)
ByteBuf buf = Unpooled.buffer(data.length); // Netty 自己定义的缓存类
buf.writeBytes(data); // 将数据写入到缓存之中
ctx.writeAndFlush(buf); // 强制性发送所有的数据
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
// 表示要进行数据信息的读取操作,对于读取操作完成后也可以直接回应
// 对于客户端发送来的数据信息,由于没有进行指定的数据类型,所以都统一按照 Object 进行接收
ByteBuf buf = (ByteBuf) msg; // 默认情况下的类型就是 ByteBuf 类型
// 在进行数据类型转换的过程之中还可以进行编码指定(NIO 的封装)
String inputData = buf.toString(CharsetUtil.UTF_8); // 将字节缓冲区的内容转为字符串
String echoData = "【ECHO】" + inputData; // 数据的回应处理
// exit 是客户端发送来的内容,可以理解为客户端的编码,而 quit 描述的是一个客户端的结束
if ("exit".equalsIgnoreCase(inputData)) {
// 进行沟通的端开
echoData = "quit"; // 结束当前交互
}
byte[] data = echoData.getBytes()