惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
K
Kaspersky official blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
Cisco Talos Blog
Cisco Talos Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Cyberwarzone
Cyberwarzone
L
LINUX DO - 最新话题
PCI Perspectives
PCI Perspectives
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
N
News and Events Feed by Topic
V
Vulnerabilities – Threatpost
T
Troy Hunt's Blog
GbyAI
GbyAI
C
CERT Recently Published Vulnerability Notes
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
量子位
Scott Helme
Scott Helme
月光博客
月光博客
Attack and Defense Labs
Attack and Defense Labs
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
Project Zero
Project Zero
G
GRAHAM CLULEY
博客园 - 【当耐特】
Recorded Future
Recorded Future
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
小众软件
小众软件
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
S
Security @ Cisco Blogs
The Last Watchdog
The Last Watchdog
MongoDB | Blog
MongoDB | Blog
H
Hacker News: Front Page
Latest news
Latest news
P
Proofpoint News Feed

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
Netty保姆级全解析|技术背景+核心知识点+生产实战教程
佛祖让我来巡山 · 2026-04-24 · via 博客园_首页

大家好,上一篇博客我们搞懂了Netty的演进逻辑——从BIO的阻塞痛点,到NIO的复杂难用,Netty作为“NIO的优化者”应运而生,成为Java高性能网络编程的首选框架。

但只懂演进还不够,实际工作中,我们更需要知道:Netty到底是什么、核心技术点有哪些、生产环境中如何落地使用(避坑指南+完整代码)。这篇博客就主打“保姆级”,从0到1,把Netty的知识点讲透、代码写全、实战落地讲细,不管是新手入门,还是工作中需要快速上手Netty开发,跟着这篇走就能搞定,全程通俗易懂,不搞晦涩源码,只聚焦“有用、能用、好用”的核心内容。

温馨提示:本文适合有Java基础、了解简单网络编程(BIO/NIO)的开发者,全程无废话,每一个知识点都搭配代码演示,生产级实战部分直接照搬就能用,建议收藏备用~

一、Netty技术背景:为什么它能成为行业首选?

在讲具体技术之前,我们先搞明白:Netty到底解决了什么问题?为什么现在几乎所有高并发场景(IM、游戏、分布式框架)都在用它?结合上一篇的演进逻辑,我们再深入拆解Netty的技术背景,搞懂它的“存在价值”。

1. 行业痛点:原生NIO无法满足生产需求

上一篇我们提到,NIO解决了BIO“一个连接一个线程”的阻塞痛点,实现了“一个线程管理多个连接”,但原生NIO在生产环境中几乎无法直接使用,核心痛点有4个(再强调一遍,帮大家衔接记忆):

  • API复杂难用:Channel、Buffer、Selector的用法繁琐,缓冲区的flip()、rewind()等操作容易出错,开发成本极高;

  • 原生BUG频发:最著名的就是“NIO空轮询”(Selector.select()一直返回0,导致线程空转,消耗CPU),需要开发者自己手动规避;

  • 细节处理繁琐:粘包拆包、断线重连、心跳检测、编解码等基础功能,都需要开发者自己实现,重复造轮子且容易出问题;

  • 线程安全隐患:Buffer是非线程安全的,多线程操作时需要手动加锁,增加开发难度和安全风险。

简单说:原生NIO只解决了“高并发能不能用”的问题,没解决“生产环境好不好用”的问题。

2. Netty的诞生:对NIO的“极致封装与优化”

Netty是由JBoss团队开发的一款开源、异步事件驱动的高性能网络通信框架,基于Java NIO二次封装,核心目标就是“解决原生NIO的所有痛点,让高性能网络编程变得简单”。

Netty的发展历程也很有代表性,从2004年JBoss内部启动的Jboss Netty项目,到2008年开源发布第一个公开版本Netty 3.0,再到2012年重大里程碑版本Netty 4.0(全面重构优化),以及2016年的Netty 4.1(进一步优化性能、增加功能),Netty始终围绕“简化开发、提升性能、增强稳定性”迭代,最终成为行业标准。

如今,Netty已经成为Java后端高并发场景的“标配”,像我们熟悉的RPC框架(Dubbo、gRPC)、消息队列(Kafka、RocketMQ)、搜索引擎(Elasticsearch)等,底层都用到了Netty作为网络通信的核心,学好Netty,不仅能独立开发高并发网络程序,还能更好地理解这些主流框架的底层原理。

3. Netty的核心优势(生产环境必选理由)

对比原生NIO和其他网络框架,Netty的优势非常突出,这也是它能成为行业首选的核心原因,用通俗的话总结5点,好记又实用:

  • 简单易用:封装了NIO的复杂API,提供了简洁的开发接口,新手也能快速上手,不用再手写Selector、Buffer的复杂逻辑;

  • 高性能:采用Reactor线程模型、内存池、零拷贝等优化技术,比原生NIO性能提升显著,能轻松支撑万级、十万级并发连接;

  • 稳定性强:修复了原生NIO的空轮询等BUG,底层优化了线程模型,自带故障恢复机制,生产环境运行稳定;

  • 功能完善:内置粘包拆包、编解码、断线重连、心跳检测等常用功能,不用重复造轮子,开发效率翻倍;

  • 扩展性好:支持自定义编解码器、自定义处理器,能轻松适配不同的业务场景(如IM聊天、游戏通信、分布式通信)。

二、Netty核心技术点:从基础到进阶,个个都是重点

这部分是Netty的核心,也是生产开发中必须掌握的知识点,我们按照“基础组件→核心机制→进阶特性”的顺序讲解,每个知识点都搭配通俗解释+简单代码演示,避免晦涩难懂,确保大家能理解、能记住、能运用。

1. Netty核心基础组件(必记!)

Netty的所有功能,都是基于以下4个核心组件实现的,就像盖房子的“砖瓦”,掌握它们,就能看懂Netty的核心逻辑。

(1)EventLoopGroup & EventLoop:Netty的“线程管理器”

通俗理解:EventLoopGroup是“线程池”,EventLoop是“单个线程”,负责管理Channel的生命周期(连接、读、写事件),是Netty高性能的核心。

核心作用:

  • EventLoopGroup:管理多个EventLoop,负责线程的创建、销毁和分配,分为两种(生产环境固定用法):

    • BossGroup(主线程组):只负责处理“客户端连接事件”,通常只创建1个线程(足够应对所有连接请求);

    • WorkerGroup(工作线程组):负责处理“客户端读写事件”,默认线程数是CPU核心数×2,可根据业务调整。

  • EventLoop:单个线程,绑定一个Selector(底层还是NIO的Selector),负责监听多个Channel的事件,一个EventLoop可以管理多个Channel,且一个Channel一旦绑定某个EventLoop,就会一直由这个EventLoop处理,避免线程切换的开销。

简单代码演示(核心用法,生产环境直接复用):

// 1. 创建BossGroup(主线程组,处理连接事件),指定1个线程
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 2. 创建WorkerGroup(工作线程组,处理读写事件),默认CPU核心数×2
EventLoopGroup workerGroup = new NioEventLoopGroup();

// 注意:使用完必须关闭,释放资源(通常放在finally中)
try {
    // 后续启动服务器的逻辑...
} finally {
    bossGroup.shutdownGracefully(); // 优雅关闭,释放资源
    workerGroup.shutdownGracefully();
}

(2)Channel:Netty的“通信通道”

通俗理解:Channel是客户端和服务器之间的“通信管道”,就像一根电线,负责数据的传输(读、写),相当于原生NIO的Channel,但Netty对其进行了封装,使用更简单。

核心特点:

  • 支持非阻塞:和NIO的Channel一样,Netty的Channel也是非阻塞的,不会因为等待数据而阻塞线程;

  • 支持双向通信:一个Channel既可以读数据,也可以写数据(客户端→服务器、服务器→客户端);

  • 常用实现类(生产环境固定用法):

    • NioServerSocketChannel:服务器端的Channel,负责监听客户端连接;

    • NioSocketChannel:客户端的Channel,负责和服务器通信。

简单代码演示(在服务器启动时配置):

// 服务器端配置Channel类型为NioServerSocketChannel
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class); // 服务器端Channel

// 客户端配置Channel类型为NioSocketChannel
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
         .channel(NioSocketChannel.class); // 客户端Channel

(3)ChannelPipeline & ChannelHandler:Netty的“业务处理器”

通俗理解:ChannelPipeline是“处理器链”,相当于一条“生产线”,ChannelHandler是“生产线上的工人”,负责处理Channel中的数据(编解码、业务逻辑、异常处理等)。

核心逻辑:

  • ChannelPipeline:每个Channel都会绑定一个ChannelPipeline,数据从Channel进入后,会依次经过Pipeline中的每个Handler,处理完成后再输出;

  • ChannelHandler:分为两种,是Netty业务开发的核心(重点掌握):

    • ChannelInboundHandler:处理“入站数据”(客户端→服务器的消息,如读取客户端发送的消息),常用实现类:ChannelInboundHandlerAdapter;

    • ChannelOutboundHandler:处理“出站数据”(服务器→客户端的消息,如向客户端发送回复),常用实现类:ChannelOutboundHandlerAdapter。

  • 核心用法:自定义Handler,重写对应方法,实现业务逻辑(如接收消息、发送消息、异常处理)。

完整代码演示(自定义Handler,生产环境可直接修改使用):

// 自定义入站处理器(处理客户端发送的消息)
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    // 1. 当客户端连接建立成功时触发(可选重写)
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端连接成功:" + ctx.channel().remoteAddress());
        // 向客户端发送连接成功的消息
        ctx.writeAndFlush("连接成功,欢迎使用Netty服务!");
    }

    // 2. 当收到客户端消息时触发(核心方法,必须重写)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // msg是客户端发送的消息(经过编解码后的数据)
        String message = (String) msg;
        System.out.println("收到客户端消息:" + message);

        // 业务逻辑处理(如查询数据库、调用接口等)
        String response = "服务器已收到消息:" + message;

        // 向客户端发送回复(出站数据)
        ctx.writeAndFlush(response);
    }

    // 3. 当读取消息完成时触发(可选重写)
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush(); // 刷新缓冲区,确保消息发送完成
    }

    // 4. 当发生异常时触发(必须重写,处理异常,避免程序崩溃)
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("发生异常:" + cause.getMessage());
        ctx.close(); // 关闭通道,释放资源
    }
}

// 配置Pipeline(在服务器/客户端启动时配置)
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // 获取Pipeline
        ChannelPipeline pipeline = ch.pipeline();
        // 向Pipeline中添加自定义Handler(顺序很重要,先入站后出站)
        pipeline.addLast(new MyInboundHandler());
    }
});

(4)Bootstrap & ServerBootstrap:Netty的“启动器”

通俗理解:启动器是Netty的“入口”,负责配置Netty的核心组件(EventLoopGroup、Channel、Pipeline等),简化启动流程,不用手动组装各个组件。

核心区别:

  • ServerBootstrap:服务器端启动器,用于启动服务器,需要配置BossGroup和WorkerGroup;

  • Bootstrap:客户端启动器,用于启动客户端,只需要配置一个EventLoopGroup。

核心配置(生产环境固定模板,直接复用):

// 服务器端启动器配置(完整模板)
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
        // 1. 配置线程组(BossGroup处理连接,WorkerGroup处理读写)
        .group(bossGroup, workerGroup)
        // 2. 配置服务器Channel类型
        .channel(NioServerSocketChannel.class)
        // 3. 配置服务器端的参数(可选,优化性能)
        .option(ChannelOption.SO_BACKLOG, 1024) // 连接队列大小,默认1024
        .option(ChannelOption.SO_REUSEADDR, true) // 允许端口复用
        // 4. 配置客户端Channel的参数(可选)
        .childOption(ChannelOption.SO_KEEPALIVE, true) // 开启TCP保活,防止连接断开
        .childOption(ChannelOption.TCP_NODELAY, true) // 禁用Nagle算法,减少延迟
        // 5. 配置Pipeline(添加处理器)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                // 添加自定义处理器(后续会补充编解码、粘包拆包处理器)
                pipeline.addLast(new MyInboundHandler());
            }
        });

// 客户端启动器配置(完整模板)
Bootstrap bootstrap = new Bootstrap();
bootstrap
        // 1. 配置线程组(只需要一个,处理所有事件)
        .group(group)
        // 2. 配置客户端Channel类型
        .channel(NioSocketChannel.class)
        // 3. 配置客户端参数(可选)
        .option(ChannelOption.SO_KEEPALIVE, true)
        .option(ChannelOption.TCP_NODELAY, true)
        // 4. 配置Pipeline
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new MyInboundHandler());
            }
        });

2. Netty核心机制(理解这些,才算真正懂Netty)

掌握了基础组件,我们再来看Netty的核心机制,这些机制是Netty高性能、高稳定性的关键,也是面试高频考点,同样用通俗的语言+代码演示讲解。

(1)Reactor线程模型:Netty高性能的核心秘密

通俗理解:Reactor线程模型是一种“事件驱动”模型,核心是“一个线程监听多个事件,有事件就处理,没事件就等待”,避免了线程阻塞和资源浪费,这也是Netty能支撑高并发的核心原因。

Netty采用的是“多线程Reactor模型”,具体分工如下(结合之前的EventLoopGroup讲解,好记):

  • MainReactor(BossGroup):由1个线程组成,专门监听“客户端连接事件”(OP_ACCEPT),一旦有客户端连接,就将连接交给SubReactor处理;

  • SubReactor(WorkerGroup):由多个线程组成,每个线程监听多个Channel的“读写事件”(OP_READ/OP_WRITE),一旦有数据可读/可写,就触发对应的Handler处理;

  • 业务线程池(可选):如果业务逻辑比较耗时(如查询数据库、复杂计算),可以单独创建业务线程池,将业务逻辑交给业务线程处理,避免阻塞SubReactor线程,提升并发能力。

简单示意图(帮助理解):

BossGroup(1个线程)→ 监听连接 → 分配连接给WorkerGroup → WorkerGroup(多个线程)→ 监听读写事件 → 处理业务逻辑

代码演示(添加业务线程池,生产环境推荐用法):

// 1. 创建业务线程池(核心线程数、最大线程数可根据业务调整)
ExecutorService businessPool = Executors.newFixedThreadPool(10);

// 2. 在自定义Handler中,将业务逻辑交给业务线程池处理
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String message = (String) msg;
        System.out.println("收到客户端消息:" + message);

        // 将业务逻辑交给业务线程池处理,避免阻塞EventLoop线程
        businessPool.submit(() -> {
            try {
                // 模拟耗时业务(如查询数据库、调用接口)
                Thread.sleep(100);
                String response = "服务器已处理消息:" + message;
                // 发送回复(注意:ctx是线程安全的,可以在其他线程中使用)
                ctx.writeAndFlush(response);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    // 关闭业务线程池(在服务器关闭时)
    public void closeBusinessPool() {
        businessPool.shutdown();
    }
}

(2)编解码机制:解决“数据传输格式”问题

通俗理解:客户端和服务器之间传输的数据,本质上是字节数组(byte[]),但我们开发中常用的是String、JavaBean等类型,编解码机制就是负责“字节数组 ↔ 业务数据”的转换。

Netty内置了多种编解码器,不用自己手动实现,生产环境直接复用,常用编解码器分为3类:

  • 字符串编解码器:StringDecoder(将字节数组→String)、StringEncoder(将String→字节数组);

  • Java对象编解码器:ObjectDecoder(将字节数组→Java对象)、ObjectEncoder(将Java对象→字节数组);

  • 自定义编解码器:如果内置编解码器满足不了需求(如自定义协议),可以实现MessageToByteEncoder和ByteToMessageDecoder,自定义编解码逻辑。

完整代码演示(字符串编解码+Java对象编解码,生产环境常用):

// 1. 字符串编解码(最常用,适用于简单文本传输)
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 添加字符串编解码器(顺序:先解码,后编码)
        pipeline.addLast(new StringDecoder()); // 入站解码:byte[] → String
        pipeline.addLast(new StringEncoder()); // 出站编码:String → byte[]
        // 添加自定义Handler
        pipeline.addLast(new MyInboundHandler());
    }
});

// 2. Java对象编解码(适用于传输JavaBean,如用户信息、订单信息)
// 第一步:定义JavaBean(必须实现Serializable接口)
public class User implements Serializable {
    private String username;
    private Integer age;
    // 构造方法、getter、setter、toString方法
    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }
    // getter/setter/toString省略...
}

// 第二步:配置对象编解码器
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 添加对象编解码器(注意:最大对象长度可调整,防止内存溢出)
        pipeline.addLast(new ObjectDecoder(1024 * 1024, ClassResolvers.cacheDisabled(null)));
        pipeline.addLast(new ObjectEncoder());
        // 添加自定义Handler
        pipeline.addLast(new MyObjectHandler());
    }
});

// 第三步:自定义Handler处理Java对象
public class MyObjectHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 接收客户端发送的User对象
        User user = (User) msg;
        System.out.println("收到客户端发送的用户信息:" + user);

        // 回复客户端一个User对象
        User responseUser = new User("服务器", 99);
        ctx.writeAndFlush(responseUser);
    }
}

(3)粘包拆包机制:解决“数据传输不完整”问题

这是生产环境中最容易遇到的问题之一,必须掌握!

通俗理解:TCP协议是“面向连接、流式传输”的,数据会被拆分成多个数据包发送,或者多个小数据包合并成一个大数据包发送,这就导致服务器接收数据时,可能出现“粘包”(多个消息粘在一起)或“拆包”(一个消息被拆成多个)的情况。

举例:客户端发送两个消息“Hello”和“Netty”,服务器可能收到“HelloNetty”(粘包),或者“Hel”和“loNetty”(拆包),如果不处理,会导致业务逻辑出错。

Netty内置了多种粘包拆包解决方案,不用自己手动处理,生产环境常用3种:

  • FixedLengthFrameDecoder:固定长度解码器,每个消息的长度固定,适用于消息长度固定的场景(如每个消息都是100字节);

  • DelimiterBasedFrameDecoder:分隔符解码器,用指定的分隔符(如“\n”“|”)分隔消息,适用于文本消息;

  • LengthFieldBasedFrameDecoder:长度字段解码器(最常用),在消息中添加一个“长度字段”,表示消息的实际长度,适用于所有场景(文本、对象、二进制数据)。

完整代码演示(最常用的LengthFieldBasedFrameDecoder,生产环境直接复用):

bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 配置粘包拆包解码器(核心配置)
        // 参数说明:
        // 1. maxFrameLength:最大帧长度(防止内存溢出),这里设为1024*1024字节
        // 2. lengthFieldOffset:长度字段的偏移量(从0开始)
        // 3. lengthFieldLength:长度字段的长度(4字节,表示int类型)
        // 4. lengthAdjustment:长度字段的调整值(通常为0)
        // 5. initialBytesToStrip:跳过的初始字节数(0,表示不跳过)
        pipeline.addLast(new LengthFieldBasedFrameDecoder(
                1024 * 1024, 0, 4, 0, 4
        ));
        // 配置长度字段编码器(和解码器对应,在消息前添加长度字段)
        pipeline.addLast(new LengthFieldPrepender(4));

        // 后续添加编解码器和自定义Handler
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());
        pipeline.addLast(new MyInboundHandler());
    }
});

说明:配置后,Netty会自动处理粘包拆包问题,我们在Handler中接收的消息,都是完整的单个消息,不用再手动拆分/合并。

(4)心跳机制:解决“连接断开未检测”问题

通俗理解:客户端和服务器之间的连接,可能因为网络波动、客户端异常退出等原因断开,但服务器无法及时检测到,会导致资源浪费(如占用Channel、线程)。心跳机制就是“定期发送心跳消息”,检测连接是否正常。

Netty内置了IdleStateHandler,用于实现心跳机制,核心逻辑:

  • 服务器端:定期检测客户端是否发送心跳消息,如果超过指定时间未收到,就关闭连接;

  • 客户端:定期向服务器发送心跳消息,告知服务器“我还在线”。

完整代码演示(服务器端+客户端心跳配置,生产环境直接复用):

// 1. 服务器端心跳配置
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 配置心跳检测(核心)
        // 参数说明:
        // 1. readerIdleTimeSeconds:读空闲时间(秒),超过该时间未收到客户端消息,触发心跳事件
        // 2. writerIdleTimeSeconds:写空闲时间(秒),超过该时间未向客户端发送消息,触发心跳事件
        // 3. allIdleTimeSeconds:总空闲时间(秒),超过该时间既没读也没写,触发心跳事件
        pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
        // 自定义心跳处理器,处理心跳事件
        pipeline.addLast(new HeartbeatHandler());

        // 后续添加粘包拆包、编解码、自定义Handler
        pipeline.addLast(new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());
        pipeline.addLast(new MyInboundHandler());
    }
});

// 自定义心跳处理器(服务器端)
public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 判断是否是心跳事件
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            // 如果是读空闲(超过30秒未收到客户端消息),关闭连接
            if (event.state() == IdleState.READER_IDLE) {
                System.out.println("客户端心跳超时,关闭连接:" + ctx.channel().remoteAddress());
                ctx.close(); // 关闭通道,释放资源
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}

// 2. 客户端心跳配置(定期发送心跳消息)
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 客户端心跳检测(可选,主要是定期发送心跳)
        pipeline.addLast(new IdleStateHandler(0, 10, 0, TimeUnit.SECONDS));
        pipeline.addLast(new ClientHeartbeatHandler());

        // 后续配置和服务器端一致
        pipeline.addLast(new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());
        pipeline.addLast(new MyClientHandler());
    }
});

// 客户端心跳处理器(定期发送心跳消息)
public class ClientHeartbeatHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            // 如果是写空闲(超过10秒未向服务器发送消息),发送心跳消息
            if (event.state() == IdleState.WRITER_IDLE) {
                System.out.println("客户端发送心跳消息");
                ctx.writeAndFlush("heartbeat"); // 心跳消息内容,可自定义
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}

3. Netty进阶特性(生产环境必备)

掌握了基础组件和核心机制,我们再补充几个生产环境中常用的进阶特性,这些特性能帮我们优化Netty性能、解决实际开发中的复杂问题。

(1)断线重连机制(客户端必备)

通俗理解:客户端和服务器之间的连接可能因为网络波动断开,断线重连机制就是“自动重新连接服务器”,避免手动重启客户端,提升系统可用性。

代码演示(客户端断线重连,生产环境直接复用):

public class NettyClient {
    private EventLoopGroup group;
    private String host;
    private int port;

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
        this.group = new NioEventLoopGroup();
    }

    // 启动客户端
    public void start() {
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            // 配置粘包拆包、编解码、心跳、Handler
                            pipeline.addLast(new IdleStateHandler(0, 10, 0, TimeUnit.SECONDS));
                            pipeline.addLast(new ClientHeartbeatHandler());
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4));
                            pipeline.addLast(new LengthFieldPrepender(4));
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new MyClientHandler());
                        }
                    });

            // 连接服务器,并添加连接监听(核心:断线重连)
            ChannelFuture future = bootstrap.connect(host, port).addListener(f -> {
                if (!f.isSuccess()) {
                    // 连接失败,1秒后重新连接
                    System.out.println("连接服务器失败,1秒后重新连接...");
                    group.schedule(() -> start(), 1, TimeUnit.SECONDS);
                } else {
                    System.out.println("连接服务器成功");
                }
            });

            // 等待连接关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        // 启动客户端,连接服务器(127.0.0.1:8888)
        new NettyClient("127.0.0.1", 8888).start();
    }
}

(2)Netty性能优化(生产环境必做)

Netty本身性能已经很高,但在生产环境中,我们还可以通过以下配置进一步优化,提升并发能力和稳定性,核心优化点如下(直接配置到启动器中):

// 服务器端性能优化配置(完整模板)
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
        .group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        // 1. 优化连接队列大小(根据并发量调整,默认1024)
        .option(ChannelOption.SO_BACKLOG, 2048)
        // 2. 允许端口复用(重启服务器时,避免端口被占用)
        .option(ChannelOption.SO_REUSEADDR, true)
        // 3. 禁用Nagle算法,减少数据传输延迟(适用于低延迟场景)
        .childOption(ChannelOption.TCP_NODELAY, true)
        // 4. 开启TCP保活,检测无效连接
        .childOption(ChannelOption.SO_KEEPALIVE, true)
        // 5. 设置接收缓冲区大小(根据业务调整,默认由系统决定)
        .childOption(ChannelOption.SO_RCVBUF, 1024 * 16)
        // 6. 设置发送缓冲区大小
        .childOption(ChannelOption.SO_SNDBUF, 1024 * 16)
        // 7. 配置线程池参数(优化EventLoop线程)
        .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(8 * 1024, 16 * 1024))
        // 8. 配置Pipeline
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // 配置粘包拆包、编解码、心跳、Handler
                // ...(省略,和之前一致)
            }
        });

补充:线程数优化,WorkerGroup的线程数默认是CPU核心数×2,生产环境中可以根据业务调整,比如CPU核心数为8,线程数设置为16或32,避免线程过多导致切换开销,或线程过少导致并发不足。

三、Netty生产级保姆级实战教程:从搭建到部署,一步到位

这部分是重点!我们将搭建一个“生产级Netty服务”,包含完整的服务器端和客户端,整合前面讲的所有知识点(粘包拆包、编解码、心跳、断线重连、性能优化),代码完整可运行,直接照搬就能用于实际工作。

实战需求:搭建一个简单的“消息通信服务”,实现客户端向服务器发送消息,服务器接收消息并回复,支持断线重连、心跳检测、粘包拆包,确保高并发、高稳定。

1. 环境准备(生产环境常用配置)

  • JDK版本:JDK 8及以上(Netty 4.1.x版本推荐JDK 8);

  • Netty版本:4.1.90.Final(稳定版,生产环境首选);

  • 构建工具:Maven(管理依赖,简化配置);

  • 开发工具:IDEA(推荐,方便调试)。

Maven依赖配置(pom.xml,直接复制):

<!-- Netty核心依赖(包含所有常用组件) -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.90.Final</version>
</dependency>

<!-- 日志依赖(可选,用于打印Netty日志,方便调试) -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>

2. 服务器端完整代码(生产级,可直接运行)

整合了:性能优化、粘包拆包、编解码、心跳检测、业务线程池、异常处理,注释详细,可直接修改业务逻辑。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Netty生产级服务器端
 */
public class NettyProductionServer {
    // 服务器端口(可配置在配置文件中,生产环境推荐)
    private static final int PORT = 8888;
    // 业务线程池(核心线程数,根据CPU核心数调整)
    private static final ExecutorService BUSINESS_POOL = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);

    public static void main(String[] args) {
        // 1. 创建线程组(BossGroup处理连接,WorkerGroup处理读写)
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 2. 创建服务器启动器,配置核心参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    // 性能优化配置
                    .option(ChannelOption.SO_BACKLOG, 2048)
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.SO_RCVBUF, 1024 * 16)
                    .childOption(ChannelOption.SO_SNDBUF, 1024 * 16)
                    // 配置Pipeline(核心)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            // 1. 心跳检测(30秒未收到客户端消息,关闭连接)
                            pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
                            pipeline.addLast(new HeartbeatHandler());

                            // 2. 粘包拆包处理(长度字段解码器)
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 4, 0, 4));
                            pipeline.addLast(new LengthFieldPrepender(4));

                            // 3. 字符串编解码
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());

                            // 4. 自定义业务Handler
                            pipeline.addLast(new BusinessHandler());
                        }
                    });

            // 3. 绑定端口,启动服务器(非阻塞)
            ChannelFuture future = bootstrap.bind(PORT).sync();
            System.out.println("Netty生产级服务器启动成功,端口:" + PORT);

            // 4. 等待服务器关闭(阻塞)
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 5. 优雅关闭线程组和业务线程池,释放资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            BUSINESS_POOL.shutdown();
        }
    }

    /**
     * 心跳处理器(处理心跳超时事件)
     */
    static class HeartbeatHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) evt;
                if (event.state() == IdleState.READER_IDLE) {
                    System.out.println("客户端心跳超时,关闭连接:" + ctx.channel().remoteAddress());
                    ctx.close();
                }
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }
    }

    /**
     * 业务处理器(处理客户端消息,核心业务逻辑)
     */
    static class BusinessHandler extends ChannelInboundHandlerAdapter {
        // 客户端连接建立成功时触发
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("客户端连接成功:" + ctx.channel().remoteAddress());
            ctx.writeAndFlush("连接成功,欢迎使用Netty消息服务!");
        }

        // 收到客户端消息时触发(核心方法)
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            String message = (String) msg;
            System.out.println("收到客户端[" + ctx.channel().remoteAddress() + "]消息:" + message);

            // 心跳消息处理(不执行业务逻辑,直接回复)
            if ("heartbeat".equals(message)) {
                ctx.writeAndFlush("heartbeat");
                return;
            }

            // 将业务逻辑交给业务线程池处理,避免阻塞EventLoop线程
            BUSINESS_POOL.submit(() -> {
                try {
                    // 模拟生产环境业务逻辑(如查询数据库、调用RPC接口)
                    Thread.sleep(50); // 模拟耗时操作
                    String response = "服务器已处理消息:" + message + "(处理时间:" + System.currentTimeMillis() + ")";
                    // 向客户端发送回复
                    ctx.writeAndFlush(response);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 业务处理异常,向客户端发送错误提示
                    ctx.writeAndFlush("消息处理失败,请重试!");
                }
            });
        }

        // 读取消息完成时触发
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }

        // 发生异常时触发(必须处理,避免程序崩溃)
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("客户端[" + ctx.channel().remoteAddress() + "]发生异常:" + cause.getMessage());
            ctx.close(); // 关闭通道,释放资源
        }

        // 客户端连接关闭时触发
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("客户端[" + ctx.channel().remoteAddress() + "]断开连接");
        }
    }
}

3. 客户端完整代码(生产级,可直接运行)

整合了:断线重连、心跳检测、粘包拆包、编解码、异常处理,支持从控制台输入消息发送给服务器。

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * Netty生产级客户端
 */
public class NettyProductionClient {
    // 服务器地址和端口(生产环境可配置在配置文件中)
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 8888;
    // 线程组
    private final EventLoopGroup group;

    public NettyProductionClient() {
        this.group = new NioEventLoopGroup();
    }

    // 启动客户端,支持断线重连
    public void start() {
        try {
            // 1. 创建客户端启动器,配置核心参数
            Bootstrap bootstrap = new Bootstrap();
            bootstrap
                    .group(group)
                    .channel(NioSocketChannel.class)
                    // 性能优化配置
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.SO_RCVBUF, 1024 * 16)
                    .option(ChannelOption.SO_SNDBUF, 1024 * 16)
                    // 配置Pipeline
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            // 1. 心跳检测(10秒未发送消息,发送心跳)
                            pipeline.addLast(new IdleStateHandler(0, 10, 0, TimeUnit.SECONDS));
                            pipeline.addLast(new ClientHeartbeatHandler());

                            // 2. 粘包拆包处理
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 4, 0, 4));
                            pipeline.addLast(new LengthFieldPrepender(4));

                            // 3. 字符串编解码
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());

                            // 4. 自定义客户端Handler
                            pipeline.addLast(new ClientBusinessHandler());
                        }
                    });

            // 2. 连接服务器,添加断线重连监听
            ChannelFuture future = bootstrap.connect(HOST, PORT).addListener(f -> {
                if (!f.isSuccess()) {
                    // 连接失败,1秒后重新连接
                    System.out.println("连接服务器失败(" + HOST + ":" + PORT + "),1秒后重新连接...");
                    group.schedule(this::start, 1, TimeUnit.SECONDS);
                } else {
                    System.out.println("连接服务器成功(" + HOST + ":" + PORT + "),可输入消息发送(输入exit退出)");
                    // 连接成功后,从控制台输入消息发送
                    sendMessage(future);
                }
            });

            // 3. 等待连接关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 优雅关闭线程组
            group.shutdownGracefully();
        }
    }

    // 从控制台输入消息,发送给服务器
    private void sendMessage(ChannelFuture future) {
        Scanner scanner = new Scanner(System.in);