zoukankan      html  css  js  c++  java
  • Netty入门

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

      网络协议

      编解码支持  

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

      各种通信协议支持

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

      粘包和拆包问题

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

      连接管理

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

      IO模型

        NIO模型,实现IO多路复用

      零拷贝

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

      内存管理

      线程模型

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

    一、Netty介绍

       官网:https://netty.io/

       学习代码: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.

      

  • 相关阅读:
    web服务器IIS 64位无法调用32位驱动问题
    asp.net临时文件的重定向
    手机归属地演示代码
    空气质量监测演示代码
    地图坐标服务
    车辆违章查询演示代码
    Python第二天
    python第七天
    python操作MongoDB
    Python第一天
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/14601959.html
Copyright © 2011-2022 走看看