Netty入门
一个完善的底层通信框架需要具备哪些功能?
网络协议
编解码支持
网络通信都是字节流,因此需要提供编解码的支持
各种通信协议支持
每个团队涉及的通信协议可能不同,因此框架需要尽可能的支持常见的协议
粘包和拆包问题
支持分隔符切分,固定长度等
连接管理
连接资源是有限的,所以要保持合理的连接数,通过心跳机制、检查空闲连接功能等来管理连接
IO模型
NIO模型,实现IO多路复用
零拷贝
发送文件时使用零拷贝减少拷贝次数,提升性能
内存管理
线程模型
Reactor模型,accept 线程与 reactor 线程(I/O线程)与业务处理线程的编排等等,在大流量下合理的线程模型会减少线程切换次数,提高性能
一、Netty介绍
文档:https://netty.io/wiki/index.html
学习代码:https://gitlab.com/yangyongjie/custom-netty
1、什么是Netty?
Netty是一个高性能、异步事件驱动的NIO框架(高性能Java网络通信的底层框架),用以快速开发高性能、高可靠的网络IO程序
Netty主要针对在TCP协议下,面向Clients端的高并发应用,或者 P2P 场景下的大量数据持续传输的应用。
Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景
2、Netty的应用场景
分布式服务的远程服务调用RPC框架,比如Dubbo就采用Netty框架做RPC通信组件
Netty作为高性能的基础通信组件,提供了TCP/UDP、HTTP等协议栈,并且能够定制和开发私有协议栈。
为什么Netty能够被广泛使用,先从了解IO模型开始。
二、IO模型
什么是IO模型?
简单理解就是用什么样的通道进行数据的发送和接收,并且很大程度上决定了程序通信的性能。
Java中支持的3种网络编程模型IO模式:
1)BIO:同步阻塞IO
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务端就需要启动一个线程进行处理。
适用于连接数较小且固定的业务,对服务器资源要求比较高,如果这个连接不做任何事情就会造成不必要的线程开销
2)NIO:同步非阻塞IO
服务器实现模式为一个线程处理多个请求(连接)。
基于Reactor模型,客户端和channel进行通信,channel可以进行读写操作,通过多路复用器selector来轮询注册到其上的channel,有就绪的IO请求就进行处理
缺点:
①:NIO的类库复杂,学习成本高,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
②:需要熟悉Java多线程编程。因为NIO涉及到Reactor模式
③:epoll bug,会导致Selector空轮询,最终CPU 占用率100%
Netty框架基于NIO实现
3)AIO:异步非阻塞IO
AIO引入异步通道的概念,采用了Proactor模式,简化了编程,有效的请求才启动线程。由操作系统完成后才通知服务器程序启动线程去处理。
一般应用于连接数较多且连接时间较长的应用。
1、BIO模型
阻塞IO
每次读写请求服务端都会创建一个线程去处理。
缺点:
每来一个连接都会创建一个线程,消耗CPU资源,即使使用线程池也不行,因为每个线程在处理连接accept和read地方会造成线程阻塞,浪费资源。
只适合于连接数少,并发度低的场景
服务端代码示例:
public class Server { public static void main(String[] args) { ServerSocket serverSocket = null; try { // 实例化服务端套接字 serverSocket = new ServerSocket(); // 绑定端口 serverSocket.bind(new InetSocketAddress(6666)); System.out.println("服务端已启动,端口号:6666"); // 不停等待客户端连接 while (!Thread.currentThread().isInterrupted()) { System.out.println("等待客户端连接..."); // 等待客户端连接,当没有客户端连接时,会阻塞 Socket socket = serverSocket.accept(); System.out.println("客户端:" + socket.getLocalAddress() + "连接成功"); // 每当有客户端连接进来,就启动一个线程进行处理 new Thread(new Runnable() { @Override public void run() { try { // 循环获取客户端的消息 while (!Thread.currentThread().isInterrupted() && !socket.isClosed()) { BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); byte[] bytes = new byte[1024]; // 当没有数据的时候,这个地方会阻塞,等待数据发送 int read = bufferedInputStream.read(bytes, 0, 1024); if (read > 0) { String result = new String(bytes, 0, read); System.out.println(">>> " + result); // 响应客户端收到数据 OutputStream outputStream = socket.getOutputStream(); outputStream.write("数据已收到,over".getBytes()); } } } catch (Exception e) { e.printStackTrace(); } } }).start(); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭服务端套接字 if (serverSocket != null) { try { serverSocket.close(); System.out.println("服务器已关闭"); } catch (IOException e) { e.printStackTrace(); } } } } }
客户端代码示例:
public class Client { public static void main(String[] args) { Socket socket = null; try { socket = new Socket("127.0.0.1", 6666); OutputStream outputStream = socket.getOutputStream(); outputStream.write("Hello,TCP,我来了".getBytes()); // 获取服务端的反馈 // 阻塞式方法 InputStream is = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = is.read(bytes); String s = new String(bytes, 0, len); System.out.println(s); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); System.out.println("客户端socket已关闭"); } catch (IOException e) { e.printStackTrace(); } } } } }
2、NIO模型
非阻塞IO
BIO模型主要问题在于等待连接及读数据时线程是阻塞的。因此NIO引入Selector就解决了线程阻塞的问题。
NIO三大核心:
Channel通道:客户端与服务端之间的双工连接通道。
所以在请求的过程中,客户端与服务端中间的channel就在不停地执行 连接、询问、断开 的过程。直到数据准备好,再通过channel传回来。
channel主要有4个类型:FileChannel(从文件读取)、DatagramChannel(读写UDP网络协议数据)、SocketChannel(读写TCP网络协议数据)、ServerSocketChannel(可以监听TCP连接)
Buffer缓冲区:客户端存放服务端信息的一个缓冲区容器,服务器如果把数据准备好了,就会通过Channel往buffer里面传。Buffer有7个类型,ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。
Selector选择器:服务端选择Channel的一个复用器。Selector有两个核心任务:监控数据是否准备好、应答channel。
NIO工作原理:
NIO是面向缓冲区编程的。它是将数据读取到缓冲区,需要时可在缓冲区前后移动
NIO工作模式——非阻塞模式:
Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能获得目前可用的数据,如果目前没有数据可用,就什么都不获取,而不是保持线程阻塞
NIO特点:
一个线程维护一个Selector,Selector维护多个Channel,当Channel有事件时,则该线程进行处理
BIO和NIO对比
1)BIO以流的方式处理数据,NIO以块的方式处理数据,块的方式处理数据比流的效率高
2)BIO是阻塞的,而NIO是非阻塞的
3)BIO是基于字节流和字符流进行操作,而NIO是基于channel和buffer进行操作,数据从通道读到缓冲区或者从缓冲区写到通道中,selector用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道
NIO缺点:
编程复杂,缓冲区Buffer要考虑读写指针切换。而Netty把它封装之后,进行优化并提供了一个易于操作的使用模式和接口,因此Netty就被广泛使用于通信框架
服务端代码示例:
public class Server { public static void main(String[] args) { ServerSocketChannel serverSocketChannel = null; Selector selector = null; try { // 1、创建一个ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2、获取绑定端口 serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 6666)); // 3、设置为非阻塞模式 serverSocketChannel.configureBlocking(false); // 4、获取Selector selector = Selector.open(); // 5、将ServerSocketChannel注册到selector上,并且设置selector对客户端Accept事件感兴趣 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 6、循环等待客户端连接 while (true) { // 超时等待1000ms获取就绪事件,当没有事件注册到selector时,继续下一次循环 if (selector.select(1000) == 0) { System.out.println("当前没有事件发生,继续下一次循环"); continue; } // 获取相关的SelectionKey集合 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectionKeys.iterator(); while (it.hasNext()) { SelectionKey selectionKey = it.next(); // 基于事件处理的handler handler(selectionKey); it.remove(); } } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭ServerSocketChannel if (serverSocketChannel != null) { try { serverSocketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } // 关闭Selector if (selector != null) { try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 基于事件处理的,根据key对应的通道发生的事件做相应的处理 * 有连接事件 * 读事件 * 写事件 * * @param selectionKey * @throws IOException */ private static void handler(SelectionKey selectionKey) throws IOException { // 如果是OP_ACCEPT事件,则表示有新的客户端连接 if (selectionKey.isAcceptable()) { // 获取为此key创建的channel ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel(); // 给客户端生成相应的Channel SocketChannel socketChannel = channel.accept(); // 将socketChannel设置为非阻塞 socketChannel.configureBlocking(false); System.out.println("客户端连接成功...生成socketChannel"); // 将当前的socketChannel注册到selector上, 关注事件:读, 同时给socketChannel关联一个Buffer socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(1024)); // 如果是读取事件 } else if (selectionKey.isReadable()) { // 通过key反向获取Channel SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); // 获取该channel关联的buffer ByteBuffer buffer = ByteBuffer.allocate(512); // 把当前channel数据读到buffer里面去 socketChannel.read(buffer); System.out.println("从客户端读取数据:" + new String(buffer.array())); // 往缓冲区写 ByteBuffer buffer1 = ByteBuffer.wrap("hello client".getBytes()); socketChannel.write(buffer1); selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); // 如果是写事件 } else if (selectionKey.isWritable()) { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); System.out.println("写事件"); selectionKey.interestOps(SelectionKey.OP_READ); } } }
客户端代码示例:
public class Client { public static void main(String[] args) { SocketChannel socketChannel = null; try { // 打开socket通道 socketChannel = SocketChannel.open(); // 设置为非阻塞模式 socketChannel.configureBlocking(false); // 服务器地址端口 InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666); // // 打开选择器 // Selector selector = Selector.open(); // // 注册连接服务端socket动作 // socketChannel.register(selector, SelectionKey.OP_CONNECT); // 连接服务器 if (!socketChannel.connect(inetSocketAddress)) { while (!socketChannel.finishConnect()) { System.out.println("连接需要时间,客户端不会被阻塞。。可以做其他事情"); } String str = "hello nio server"; ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(byteBuffer); } } catch (Exception e) { e.printStackTrace(); } finally { if (socketChannel != null) { try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
三、为什么使用Netty
为什么不直接用JDK NIO而是用Netty?
Netty做得更好,功能更多
1、做得更多
1)支持常用应用层协议(HTTP、编解码协议等 )
2)解决传输问题:粘包、半包现象
3)支持流量整形(比如定制化的流量控制,黑白名单等)
4)完善的断连、Idle等异常处理
2、做得更好
1)规避JDK NIO bug
epoll bug:异常唤醒(没有事件发生),selector空轮询,最终导致CPU100%
IP_TOS参数(IP包的优先级和QoS选项)使用时抛出异常
2)API更友好,更强大
JDK的NIO一些API不够友好,功能薄弱,例如ByteBuffer -> ByteBuf
除了NIO之外,也提供了其他一些增强:Threadlocal -> Netty's FastThreadLocal
3)隔离变化、屏蔽细节
隔离JDK NIO的实现变化:nio -> nio2(aio)
屏蔽 JDK NIO的实现细节
三、Netty架构设计和使用
Netty是一个异步的、基于事件驱动的高性能网络应用框架,它底层封装了NIO。
Netty与NIO服务端和客户端的区别:
Netty | NIO | |
服务端 | NioServerSocketChannel | ServerSocketChannel |
客户端 | NioSocketChannel | SocketChanel |
Netty架构图:
①、Core:绿色的部分Core核心模块,实际上就是提供了一些底层通用实现来供上层使用,从图中可以看出包含的核心有可扩展事件模型、通用通信 API、可零拷贝的 buffer
②、Protocol Support:橙色部分Protocol Support协议支持,包括Http协议、webSocket、SSL(安全套接字协议)、谷歌Protobuf协议(编解码协议)、zlib/gzip压缩与解压缩、Large File Transfer大文件传输等等
③、Transport Services:红色的部分Transport Services传输服务,表明Netty支持多种传输方式,例如 TCP 和 UDP 、HTTP隧道、虚拟机管道。我们可以很方便的切换各种传输方式,因为 Netty 都支持了
线程模型
基于主从Reactor多线程模型,它维护两个线程池,一个是处理Accept连接,另一个是处理读写事件。
Reactor模型:
示例:
依赖:
<!--netty 本质就是一个jar包--> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.58.Final</version> </dependency>
1)客户端启动类:
import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * 客户端启动类 */ public class MyClient { public static void main(String[] args) throws InterruptedException { NioEventLoopGroup eventExecutors = new NioEventLoopGroup(); try { //创建bootstrap对象,配置参数 Bootstrap bootstrap = new Bootstrap(); //设置线程组 bootstrap.group(eventExecutors) //设置客户端的通道实现类型 .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) //使用匿名内部类初始化通道 .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //添加客户端通道的处理器 ChannelPipeline p = ch.pipeline(); p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new MyClientHandler()); } }); System.out.println("客户端准备就绪..."); // 启动客户端,连接服务端 ChannelFuture cf = bootstrap.connect("127.0.0.1", 6666).sync(); //对通道关闭进行监听 cf.channel().closeFuture().sync(); } finally { //关闭线程组 eventExecutors.shutdownGracefully(); } } }
2)客户端处理器:
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; /** * 客户端处理器 */ public class MyClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //发送消息到服务端 ctx.writeAndFlush(Unpooled.copiedBuffer("呼叫,呼叫,服务器在吗", CharsetUtil.UTF_8)); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //接收服务端发送过来的消息 ByteBuf byteBuf = (ByteBuf) msg; System.out.println("收到服务端" + ctx.channel().remoteAddress() + "的消息:" + byteBuf.toString(CharsetUtil.UTF_8)); } }
3)服务端启动类
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * 服务端启动类 * ①:线程模型 * ②:IO模型 * ③:读写逻辑 * ④:绑定端口 */ public class MyServer { public static void main(String[] args) throws Exception { // 创建两个线程组 boosGroup、workerGroup // boosGroup用于Accept连接建立时间并分发请求,workGroup用于处理IO读写事件和业务逻辑 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { // 创建服务端的启动对象,设置参数 ServerBootstrap bootstrap = new ServerBootstrap(); // 线程模型,这里是Reactor主从多线程,设置两个线程组boosGroup和workerGroup bootstrap.group(bossGroup, workerGroup) // IO模型,这里是NIO,设置服务端通道实现类型 .channel(NioServerSocketChannel.class) // 设置线程队列得到连接个数 .option(ChannelOption.SO_BACKLOG, 128) // 设置保持活动连接状态 .childOption(ChannelOption.SO_KEEPALIVE, true) // 读写处理逻辑,使用匿名内部类的形式初始化通道对象 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline cp = socketChannel.pipeline(); // // 给workerGroup的EventLoop对应的管道设置处理器 cp.addLast(new LoggingHandler(LogLevel.INFO)); cp.addLast(new MyServerHandler()); } }); System.out.println("服务端已经准备就绪..."); // 绑定端口号,启动服务端 ChannelFuture channelFuture = bootstrap.bind(6666).sync(); // 对关闭通道进行监听 channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
4)服务端处理器:
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; /** * 服务端处理器 */ public class MyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { //获取客户端发送过来的消息 ByteBuf byteBuf = (ByteBuf) msg; System.out.println("收到客户端" + ctx.channel().remoteAddress() + "发送的消息:" + byteBuf.toString(CharsetUtil.UTF_8)); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { //发送消息给客户端 ctx.writeAndFlush(Unpooled.copiedBuffer("服务端已收到消息,over", CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 打印异常日志 //发生异常,关闭通道 ctx.close(); } }
先启动服务端,再启动客户端
客户端日志:
客户端准备就绪... 22:19:53.361 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xe7e338a8] REGISTERED 22:19:53.361 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xe7e338a8] CONNECT: /127.0.0.1:6666 22:19:53.361 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xe7e338a8, L:/127.0.0.1:10007 - R:/127.0.0.1:6666] ACTIVE 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@2b951e3d 22:19:53.392 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xe7e338a8, L:/127.0.0.1:10007 - R:/127.0.0.1:6666] WRITE: 33B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| e5 91 bc e5 8f ab ef bc 8c e5 91 bc e5 8f ab ef |................| |00000010| bc 8c e6 9c 8d e5 8a a1 e5 99 a8 e5 9c a8 e5 90 |................| |00000020| 97 |. | +--------+-------------------------------------------------+----------------+ 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8 22:19:53.392 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.delayedQueue.ratio: 8 22:19:53.408 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xe7e338a8, L:/127.0.0.1:10007 - R:/127.0.0.1:6666] FLUSH 22:19:53.424 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xe7e338a8, L:/127.0.0.1:10007 - R:/127.0.0.1:6666] READ: 31B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| e6 9c 8d e5 8a a1 e7 ab af e5 b7 b2 e6 94 b6 e5 |................| |00000010| 88 b0 e6 b6 88 e6 81 af ef bc 8c 6f 76 65 72 |...........over | +--------+-------------------------------------------------+----------------+ 收到服务端/127.0.0.1:6666的消息:服务端已收到消息,over 22:19:53.424 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xe7e338a8, L:/127.0.0.1:10007 - R:/127.0.0.1:6666] READ COMPLETE
服务端日志:
服务端已经准备就绪... 22:19:53.377 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xf4094332, L:/127.0.0.1:6666 - R:/127.0.0.1:10007] REGISTERED 22:19:53.377 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xf4094332, L:/127.0.0.1:6666 - R:/127.0.0.1:10007] ACTIVE 22:19:53.408 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096 22:19:53.408 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2 22:19:53.408 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16 22:19:53.408 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8 22:19:53.408 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.delayedQueue.ratio: 8 22:19:53.424 [nioEventLoopGroup-3-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true 22:19:53.424 [nioEventLoopGroup-3-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true 22:19:53.424 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@6eb3cfd6 22:19:53.424 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xf4094332, L:/127.0.0.1:6666 - R:/127.0.0.1:10007] READ: 33B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| e5 91 bc e5 8f ab ef bc 8c e5 91 bc e5 8f ab ef |................| |00000010| bc 8c e6 9c 8d e5 8a a1 e5 99 a8 e5 9c a8 e5 90 |................| |00000020| 97 |. | +--------+-------------------------------------------------+----------------+ 收到客户端/127.0.0.1:10007发送的消息:呼叫,呼叫,服务器在吗 22:19:53.424 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xf4094332, L:/127.0.0.1:6666 - R:/127.0.0.1:10007] READ COMPLETE 22:19:53.424 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xf4094332, L:/127.0.0.1:6666 - R:/127.0.0.1:10007] WRITE: 31B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| e6 9c 8d e5 8a a1 e7 ab af e5 b7 b2 e6 94 b6 e5 |................| |00000010| 88 b0 e6 b6 88 e6 81 af ef bc 8c 6f 76 65 72 |...........over | +--------+-------------------------------------------------+----------------+ 22:19:53.424 [nioEventLoopGroup-3-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xf4094332, L:/127.0.0.1:6666 - R:/127.0.0.1:10007] FLUSH
END.