Netty入门

一个完善的底层通信框架需要具备哪些功能?

  网络协议

  编解码支持  

    网络通信都是字节流,因此需要提供编解码的支持

  各种通信协议支持

    每个团队涉及的通信协议可能不同,因此框架需要尽可能的支持常见的协议

  粘包和拆包问题

    支持分隔符切分,固定长度等

  连接管理

    连接资源是有限的,所以要保持合理的连接数,通过心跳机制、检查空闲连接功能等来管理连接

  IO模型

    NIO模型,实现IO多路复用

  零拷贝

    发送文件时使用零拷贝减少拷贝次数,提升性能

  内存管理

  线程模型

    Reactor模型,accept 线程与 reactor 线程(I/O线程)与业务处理线程的编排等等,在大流量下合理的线程模型会减少线程切换次数,提高性能

 

 

 

一、Netty介绍

   官网:https://netty.io/

   文档: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();
                }
            }
        }

    }

}
View Code

 

  客户端代码示例:

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();
                }
            }

        }
    }

}
View Code

 

  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);
        }
    }

}
View Code

 

  客户端代码示例:

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();
                }
            }
        }
    }
}
View Code

 

 

 

三、为什么使用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模型:

  

 

  server端包含1个Boss NioEventLoopGroup和1个Worker NioEventLoopGroup,NioEventLoopGroup相当于1个事件循环组,这个组里包含多个事件循环NioEventLoop,每个NioEventLoop包含1个selector和1个事件循环线程
  
 
  每个Boss NioEventLoop循环执行的任务包含3步:
    ①:轮询accept事件(从已完成连接(三次握手建立连接)队列中拿到连接进行处理)
    ②:处理accept I/O事件,与Client建立连接,生成NioSocketChannel,并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上
    ③:处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务,或者其它线程提交到该eventloop的任务
 
 
  每个Worker NioEventLoop循环执行的任务包含3步:
    ①:轮询read、write事件
    ②:处理I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发生时进行处理
    ③:处理任务队列中的任务,runAllTasks
 
 
 
 
  从服务端角度看整体架构:
    启动服务,监听某个端口
    接收到客户端的建立连接请求
    持续监听连接上的请求,有请求过来,则解码、解析,并根据请求数据的不同进行不同的逻辑或业务处理,然后将响应返回给客户端
    关闭连接
 
 

 

  示例:

    依赖:

        <!--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.

  

posted @ 2021-03-31 16:03  杨岂  阅读(136)  评论(0编辑  收藏  举报