一、Netty简介

Netty是一款基于Java NIO的网络编程、高性能、异步事件驱动的网络应用框架。它的设计目标是提供简单易用、高性能、可扩展的网络编程框架。它极大地简化了网络编程,提供了许多强大的功能。

二、Netty主要特点

  1. 高并发:Netty使用异步的、非阻塞的I/O模型,通过事件驱动的方式处理网络操作,能够高效地处理并发连接和大量的并发请求。
  2. 高性能:Netty采用了一系列优化策略,如零拷贝技术、内存池和可定制的线程模型等,以提供出色的性能和吞吐量。
  3. 多协议支持:Netty提供了丰富的协议支持,包括常用的网络协议(如HTTP、WebSocket、TCP和UDP)以及自定义协议。具备灵活的编解码器和处理器,简化了协议的实现和交互。
  4. 可扩展和灵活:Netty的架构和组件设计具有高度的可扩展性和灵活性。它提供了一组可重用的组件,可以根据应用需求进行定制和扩展。
  5. 安全性:Netty提供了一系列的安全性功能,包括SSL/TLS支持、加密和身份验证等。
  6. 简单易用:Netty提供了简洁的API和丰富的文档,使得开发人员可以快速上手,快速开发高质量的网络应用程序。
  7. 跨平台:Netty可以在多种操作系统平台上运行,包括Windows、Linux、MacOS等。

三、Netty使用场景

  1. 高性能网络服务器
    Netty 非常适合构建高性能的网络服务器,如 Web 服务器、游戏服务器、即时通讯服务器等。
    典型场景:

    • Web 服务器:Netty 可以作为 HTTP 服务器,处理大量的并发请求。
    • 游戏服务器:Netty 支持 TCP/UDP 协议,适合构建实时性要求高的游戏服务器。
    • 即时通讯服务器:Netty 的高并发和低延迟特性非常适合构建即时通讯(IM)系统。

    优点:
    - 高并发:基于 NIO 的非阻塞模型,支持大量并发连接。
    - 低延迟:事件驱动模型减少了线程切换的开销。
    - 可扩展:支持自定义协议和编解码器。

  2. 分布式系统通信
    Netty 常用于分布式系统中的节点通信,如 RPC 框架、消息队列等。
    典型场景:

    • RPC 框架:Netty 可以作为 RPC 框架的底层通信组件,如 Dubbo、gRPC。
    • 消息队列:Netty 可以用于构建消息队列的客户端和服务器,如 Kafka、RocketMQ。

    优点:
    - 高性能:Netty 的零拷贝和内存池技术提高了通信效率。
    - 灵活性:支持多种协议(如 HTTP、WebSocket、自定义协议)。

  3. 实时数据传输
    Netty 适合构建实时数据传输系统,如实时日志收集、实时监控等。
    典型场景:

    • 实时日志收集:Netty 可以用于构建高效的日志收集系统,将日志实时传输到中央服务器。
    • 实时监控:Netty 可以用于构建监控系统,实时传输监控数据。

    优点:
    - 低延迟:适合实时性要求高的场景。
    - 高吞吐量:支持批量数据传输,提高吞吐量。

  4. 物联网(IoT)
    Netty 适合构建物联网设备与服务器之间的通信系统。
    典型场景:

    • 设备接入:Netty 可以用于构建设备接入服务器,处理大量设备的并发连接。
    • 协议适配:Netty 支持自定义协议,适合物联网中多样化的通信协议。

    优点:
    - 高并发:支持大量设备的并发连接。
    - 灵活性:支持多种协议和编解码器。

  5. 文件传输
    Netty 提供了零拷贝的文件传输功能,适合构建高效的文件传输系统。
    典型场景:

    • 大文件传输:Netty 可以用于构建大文件传输系统,支持断点续传和分块传输。
    • 分布式文件系统:Netty 可以用于构建分布式文件系统的通信组件。

    优点:
    - 高效传输:零拷贝技术减少了数据传输的开销。
    - 可靠性:支持断点续传和分块传输。

  6. HTTP/HTTPS 服务器和客户端
    Netty 支持 HTTP/HTTPS 协议,适合构建高性能的 Web 服务器和客户端。
    典型场景:

    • Web 服务器:Netty 可以用于构建高性能的 HTTP 服务器。
    • API 网关:Netty 可以用于构建 API 网关,处理大量的 HTTP 请求。

    优点:
    - 高性能:基于 NIO 的非阻塞模型,支持高并发。
    - 灵活性:支持 HTTP/2、WebSocket 等协议。

  7. WebSocket 服务器
    Netty 支持 WebSocket 协议,适合构建实时通信系统。
    典型场景:

    • 实时聊天:Netty 可以用于构建实时聊天系统,支持双向通信。
    • 实时数据推送:Netty 可以用于构建实时数据推送系统,如股票行情推送。

    优点:
    - 低延迟:适合实时性要求高的场景。
    - 双向通信:支持客户端和服务器之间的双向通信。

  8. SSL/TLS 加密通信
    Netty 支持 SSL/TLS 加密通信,适合构建安全的网络应用。
    典型场景:

    • 安全通信:Netty 可以用于构建安全的 HTTP/HTTPS 服务器和客户端。
    • 加密数据传输:Netty 可以用于构建加密数据传输系统,保护数据的机密性。

    优点:
    - 安全性:支持 SSL/TLS 加密通信。
    - 高性能:基于 NIO 的非阻塞模型,支持高并发。

四、Netty服务端工作架构流程

在这里插入图片描述
(1)服务端工作流程:

  1. 服务端初始化时会创建2个NioEventLoopGroup,BossGroup用于Accept连接事件处理并分发请求,WokerGroup用于处理读写事件和业务逻辑。
  2. 服务端启动时创建 ServerBootstrap 实例,并配置 EventLoopGroup、Channel 类型和处理器,通过调用 serverBootstrap.bind() 绑定服务器端口。
  3. BossGroup中的NioEventLoop不断轮询注册在其Selector上的ServerSocketChannel的 Accept 事件。
  4. 接收到Accept 事件后,再交由processSelectedKeys处理 accept 事件,与客户端建立连接,生成一个 NioSocketChannel,并将其注册到WokerGroup的某个 NioEventLoop 的 Selector 上。
  5. WokerGroup的 NioEventLoop 不断轮询注册在其Selector上的NioSocketChannel的 read/write 事件。
  6. 接收到read/write 事件后,再交由processSelectedKeys处理 read/write 事件,并调用ChannelPipeline中的相关的ChannelHandler 的channelRead()方法接收并处理客户端发送的数据。
  7. 处理完数据后,通过ChannelHandlerContext 的writeAndFlush()方法发送响应结果消息给客户端。

(2)客户端工作流程:

  1. 客户端初始化时会创建1个NioEventLoopGroup 用于处理客户端的I/O操作。
  2. 客户端启动时创建 Bootstrap 实例,并配置 EventLoopGroup、Channel 类型和处理器,通过调用 bootstrap.connect() 发起连接请求交给 NioEventLoopGroup 处理。
  3. NioEventLoopGroup 选择一个 NioEventLoop 来处理连接请求,并与服务端建立连接。
  4. 当客户端与服务端成功建立连接时,NioEventLoop 会创建一个 Channel 对象来表示连接,该对象维护了与连接相关的状态和属性。
  5. 再通过调用ChannelHandlerContext 的writeAndFlush()方法向服务端发送消息。
  6. 发送成功后,服务端接收到消息并返回处理结果时,ChannelHandler 的channelRead()方法能接收到服务端返回的响应结果消息。

五、Netty的核心组件

  1. Bootstrap:用于启动和配置网络应用程序配置类,ServerBootstrap用于服务端,Bootstrap用于客户端。
  2. NioEventLoopGroup:相当于一个事件循环组,这个组中含有多个事件循环,每个事件循环就是一个 NioEventLoop。在服务端会创建BoosEventLoopGroup 和WorkerEventLoopGroup两个NioEventLoopGroup实例。BossEventLoopGroup 只负责处理连接事件,WorkerEventLoopGroup则负责read/write 事件。客户端通常只有一个NioEventLoopGroup来发起连接处理I/O任务。
  3. NioEventLoop:表示一个不断循环的执行事件处理的线程,每个 NioEventLoop 都包含一个 Selector和一个TaskQueue,Selector用于监听注册在其上的 SocketChannel 上的I/O事件,如:read,write,accept,connect等;TaskQueue用于存放一些非I/O任务,如:register,bind等任务。
  4. Channel:是一种双向的连接通道,客户端与服务端之间的所有网络事件(数据传输、生命周期事件)都会通过 Channel 处理。不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应。常用的类型有:NioSocketChannel(客户端 TCP Socket 连接)、NioServerSocketChannel(服务器端 TCP Socket 连接)。
  5. ChannelHandler:是消息的具体处理器,负责处理输入输出数据的逻辑。可以接收入站事件(如数据接收)和出站事件(如数据发送),并执行相应的处理逻辑。
  6. ChannelHandlerContext:是 ChannelHandler 的上下文环境。包含与ChannelHandler相关联的各种信息,如Channel、EventLoop、ChannelPipeline等。ChannelHandlerContext还提供了丰富的方法,以便于ChannelHandler与其他组件进行交互。
  7. ChannelPipeline:是一个双向链表,拦截和处理事件的链式结构,主要负责管理ChannelHandler并协调它们的处理顺序。ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。
  8. ChannelFuture:主要用于接收异步I/O操作返回的执行结果。ChannelFuture提供了丰富的方法,用于检查操作的状态、添加监听器以便在操作完成时接收通知,并对操作的结果进行处理。

六、ChannelPipeline

ChannelPipeline 是一系列 ChannelHandler 的有序组合,每个 ChannelHandler 都可以处理特定类型的事件或数据。当一个事件(如连接、读取数据、写入数据等)发生时,ChannelPipeline 会按照添加的顺序依次调用相应的 ChannelHandler 来处理该事件。

  1. ChannelHandler 类型:

    • ‌入站处理器(Inbound Handler)‌:处理入站 I/O 事件,如连接建立、数据读取等。典型的入站处理器包括 ChannelInboundHandlerAdapter、ChannelInboundByteHandler 等。
    • 出站处理器(Outbound Handler)‌:处理出站 I/O 操作,如发送数据、关闭连接等。典型的出站处理器包括 ChannelOutboundHandlerAdapter、ChannelOutboundByteHandler 等。
  2. 添加 ChannelHandler 到 ChannelPipeline:

    ChannelPipeline pipeline = channel.pipeline();
    pipeline.addLast(new MyInboundHandler1());
    pipeline.addLast(new MyInboundHandler2());
    pipeline.addLast(new MyOutboundHandler1());
    pipeline.addLast(new MyOutboundHandler2());
    
  3. ChannelHandler 的行为
    每个 ChannelHandler 可以选择性地处理或修改事件,具体行为取决于其实现方式:

    • 处理事件:ChannelHandler 可以对事件进行处理,例如读取数据、修改数据、记录日志等。
    • 传递事件:ChannelHandler 可以选择将事件传递给下一个 ChannelHandler,或者终止事件的传播。
    • 修改事件:ChannelHandler 可以修改事件的内容或状态,例如修改数据、添加元数据等。
  4. 事件传播机制

    • 入站事件传播
      入站事件从 ChannelPipeline 的头部(Head)流向尾部(Tail),依次经过每个 InboundHandler。
      • 处理事件:InboundHandler 可以处理事件,例如读取数据、记录日志等。
      • 传递事件:调用 ctx.fireChannelRead(msg) 将事件传递给下一个 InboundHandler。
      • 终止传播:如果不调用 ctx.fireChannelRead(msg),事件传播将被终止。
    • 出站事件传播
      出站事件从 ChannelPipeline 的尾部(Tail)流向头部(Head),依次经过每个 OutboundHandler。
      • 处理事件:OutboundHandler 可以处理事件,例如修改数据、记录日志等。
      • 传递事件:调用 ctx.write(msg) 将事件传递给下一个 OutboundHandler。
      • 终止传播:如果不调用 ctx.write(msg),事件传播将被终止。
  5. ChannelHandler 的选择性处理:
    在 ChannelPipeline 中,每个 ChannelHandler 都可以选择性地处理或修改事件,以及是否将事件传递给下一个 ChannelHandler。具体行为取决于业务需求:

    • 处理事件:如果 ChannelHandler 对事件感兴趣,可以处理事件并决定是否传递事件。
    • 忽略事件:如果 ChannelHandler 对事件不感兴趣,可以直接传递事件。
    public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    	@Override
    	public void channelRead(ChannelHandlerContext ctx, Object msg) {
        	if (msg instanceof String) {
            	// 处理字符串数据
            	System.out.println("Received string: " + msg);
        	} else {
            	// 忽略其他类型的数据
            	ctx.fireChannelRead(msg);
        	}
    	}
    }
    

七、TCP 粘包与拆包

TCP 协议本身是一个面向流的协议,它并不保证消息边界,因此在网络传输过程中,多个小的数据包可能会被合并成一个大的数据包发送(粘包),或者一个大的数据包可能会被拆分成多个小的数据包发送(拆包)。TCP 协议本身是一个面向流的协议,它并不保证消息边界,因此在网络传输过程中,多个小的数据包可能会被合并成一个大的数据包发送(粘包),或者一个大的数据包可能会被拆分成多个小的数据包发送(拆包)。

  • 产生粘包和拆包的原因:

    1. ‌Nagle 算法‌:为了提高网络传输的效率,TCP 协议通常会使用 Nagle 算法将多个小的数据包合并成一个较大的数据块一起发送。
    2. ‌TCP 缓冲区‌:发送端和接收端都有 TCP 缓冲区,当数据写入缓冲区时,如果缓冲区满了或者达到了某个阈值,数据就会被发送出去,这可能导致一个大的数据包被拆分。
    3. ‌MTU 和 MSS‌:MTU(最大传输单元)和 MSS(最大报文段长度)限制了链路层和传输层一次可以发送的最大数据量,超过这个限制的数据包可能会被拆分。
  • 粘包和拆包的解决方案

    1. 使用自定义协议‌:在消息头中定义消息的长度,接收端根据消息的长度来划分数据包的边界。这种方法需要自定义协议,并使用 Netty 提供的编码器和解码器来处理消息的拆解与重组。
    2. 固定长度消息‌:如果所有消息的长度都是固定的,那么接收端可以直接按照固定长度来读取消息。这种方法简单但不够灵活。
    3. 使用分隔符‌:在每条消息的末尾添加特定的分隔符来标识消息的边界。例如,可以使用换行符、特殊字符等作为分隔符。Netty 提供了 DelimiterBasedFrameDecoder 来支持这种方式。
    4. 基于长度的帧解码器‌:使用 Netty 提供的 LengthFieldBasedFrameDecoder 来根据消息头中的长度字段来解码消息。这种方法比较灵活,可以处理不同长度的消息。

以下是一个使用自定义协议和 LengthFieldBasedFrameDecoder 来解决粘包和拆包问题的示例代码:

// 自定义协议类
public class MessageProtocol {
    private int length;
    private byte[] content;

    // 省略构造方法、getter 和 setter
}

// 编码器
public class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {
    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
        out.writeInt(msg.getLength()); // 写入消息长度
        out.writeBytes(msg.getContent()); // 写入消息内容
    }
}

// 解码器
public class MessageDecoder extends LengthFieldBasedFrameDecoder {
    public MessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        int length = in.readInt(); // 读取消息长度
        byte[] content = new byte[length];
        in.readBytes(content); // 读取消息内容
        return new MessageProtocol(length, content);
    }
}

// 管道配置
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new MessageDecoder(65535, 0, 4)); // 添加解码器
        pipeline.addLast(new MessageEncoder()); // 添加编码器
        pipeline.addLast(new MyServerHandler()); // 添加业务处理器
    }
}

// 业务处理器
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
        // 处理消息
        System.out.println("Received message: " + new String(msg.getContent()));
    }
}

八、使用实例

  1. 添加 Netty 依赖
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.97.Final</version>
</dependency>
  1. 编写Server服务端启动类
public class NettyServer {
	 /**
     * 创建服务端实例并绑定端口
     * @throws InterruptedException
     */
    public static void bind() throws InterruptedException {
        // 创建boss线程组,用于接收连接
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 创建worker线程组,用于处理连接上的I/O操作,含有子线程NioEventGroup个数为CPU核数大小的2倍
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // 创建ServerBootstrap实例,服务器启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();

            // 使用链式编程配置参数
            // 将boss线程组和worker线程组暂存到ServerBootstrap
            bootstrap.group(bossGroup, workerGroup);
            // 设置服务端Channel类型为NioServerSocketChannel作为通道实现
            bootstrap.channel(NioServerSocketChannel.class);

            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    // 添加ServerHandler到ChannelPipeline,对workerGroup的SocketChannel(客户端)设置处理器
                    pipeline.addLast(new NettyServerHandler());
                }
            });
            // 设置启动参数,初始化服务器连接队列大小。服务端处理客户端连接请求是顺序处理,一个时间内只能处理一个客户端请求
            // 当有多个客户端同时来请求时,未处理的请求先放入队列中
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            // 绑定端口并启动服务器,bind方法是异步的,sync方法是等待异步操作执行完成,返回ChannelFuture异步对象
            ChannelFuture channelFuture = bootstrap.bind(8888).sync();
            // 等待服务器关闭
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 优雅地关闭boss线程组
            bossGroup.shutdownGracefully();
            // 优雅地关闭worker线程组
            workerGroup.shutdownGracefully();
        }
    }
}
  1. 编写服务端处理器handler
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行 channelRegistered");
    }

    /**
     * 当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调
     * 用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行 channelUnregistered");
    }

    /**
     * 当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行 channelActive");
    }

    /**
     * 当 Channel 离开活动状态并且不再连接它的远程节点时被调用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行 channelInactive");
    }

    /**
     * 当从 Channel 读取数据时被调用
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        System.out.println("执行 channelRead");
        // 处理接收到的数据
        ByteBuf byteBuf = (ByteBuf) msg;
        try {
            // 将接收到的字节数据转换为字符串
            String message = byteBuf.toString(CharsetUtil.UTF_8);
            // 打印接收到的消息
            System.out.println("接收到客户端消息为: " + message);
            // 发送响应消息给客户端
            ctx.writeAndFlush(Unpooled.copiedBuffer("我是服务端,我收到你的消息啦~", CharsetUtil.UTF_8));
        } finally {
            // 释放ByteBuf资源
            ReferenceCountUtil.release(byteBuf);
        }

    }

    /**
     * 当 Channel 上的一个读操作完成时被调用,对通道的读取完成的事件或通知。当读取完成可通知发送方或其他的相关方,告诉他们接受方读取完成
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行 channelReadComplete");
    }

    /**
     * 当 ChannelnboundHandler.fireUserEventTriggered()方法被调用时被
     * 调用
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("执行 userEventTriggered");
    }

    /**
     * 当 Channel 的可写状态发生改变时被调用。可以通过调用 Channel 的 isWritable()方法
     * * 来检测 Channel 的可写性。与可写性相关的阈值可以通过
     * * Channel.config().setWriteHighWaterMark()和 Channel.config().setWriteLowWaterMark()方法来
     * * 设置
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行 channelWritabilityChanged");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("执行 exceptionCaught");
        // 异常处理
        cause.printStackTrace();
        ctx.close();
    }
}
  1. 编写客户端启动类
public class NettyClient {
    /**
     * 创建客户端实例并向服务端发送连接请求
     */
    public static void start() {

        // 创建EventLoopGroup,用于处理客户端的I/O操作
        EventLoopGroup groupThread = new NioEventLoopGroup();

        try {
            // 创建Bootstrap实例,客户端启动对象
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(groupThread);
            // 设置服务端Channel类型为NioSocketChannel作为通道实现
            bootstrap.channel(NioSocketChannel.class);
            // 设置客户端处理
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast(new NettyClientHandler());
                }
            });
            // 向服务端发送连接请求
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8888).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 优雅地关闭线程
            groupThread.shutdownGracefully();
        }
    }
}
  1. 编写客户端处理器handler
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 连接建立时的处理,发送请求消息给服务器
        ctx.writeAndFlush(Unpooled.copiedBuffer("我是客户端,连接建立成功!", CharsetUtil.UTF_8));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 处理接收到的数据
        ByteBuf byteBuf = (ByteBuf) msg;
        try {
            // 将接收到的字节数据转换为字符串
            String message = byteBuf.toString(CharsetUtil.UTF_8);
            // 打印接收到的消息
            System.out.println("收到服务端响应的消息为: " + message);

            // TODO: 对数据进行业务处理
        } finally {
            // 释放ByteBuf资源
            ReferenceCountUtil.release(byteBuf);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        cause.printStackTrace();
        ctx.close();
    }
}
推荐内容
阅读全文
AI总结
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐