前言
上一篇我们从BIO/NIO/AIO底层原理讲清楚了:为什么企业清一色选用Netty。
理论吃透后,本篇直接落地实战,带大家从零搭建一套可直接运行的Netty服务端+客户端。
本文所有代码为企业标准模板代码,可直接复用在 IM、网关、RPC、长连接服务中,同时逐行拆解Netty启动核心流程,解决新手最大痛点:会抄代码、不懂流程、报错不会修。
全程极简无冗余,看完即可跑通、看懂、记住。
一、Netty版本选择与Maven依赖
1.1 版本选择原则
生产环境优先选择稳定长期维护版本,避免最新版踩坑、老旧版存在已知BUG,这里选用企业最常用的4.1.x 稳定版(兼容JDK8+,适配绝大多数项目)。
1.2 核心Maven依赖
只需引入一个核心依赖,包含所有Netty网络编程所需能力(编解码、线程模型、事件处理、内存管理等):
<!-- Netty核心依赖 --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.97.Final</version> </dependency>补充说明:
netty-all:全量包,适合学习、开发、快速搭建项目;
生产精细化依赖可按需拆分,学习阶段直接使用全量包即可;
该版本修复了空轮询BUG、内存泄漏等经典问题,稳定性拉满。
二、Netty核心组件快速回顾(衔接上篇)
正式编码前,快速回顾5个核心组件,所有Netty项目启动逻辑都围绕这5个类:
ServerBootstrap:服务端启动器,负责组装整个Netty服务、配置参数、绑定端口;
Bootstrap:客户端启动器,负责客户端连接配置、服务端连接发起;
EventLoopGroup:线程池,核心分为Boss线程(接收连接)、Worker线程(处理读写);
ChannelInitializer:通道初始化器,用于给每个连接绑定自定义处理器、编解码规则;
ChannelInboundHandlerAdapter:业务处理器,处理客户端连接、读取数据、异常处理、连接断开等事件。
三、实战编写:Netty服务端(可直接运行)
3.1 自定义服务端业务处理器
负责接收客户端消息、打印日志、响应客户端数据,重写核心生命周期方法:
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * Netty服务端自定义处理器 * 处理:连接建立、消息读取、异常、连接断开 */ public class NettyServerHandler extends ChannelInboundHandlerAdapter { /** * 客户端读取到数据时触发 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // msg即为客户端发送的数据,默认是ByteBuf类型 io.netty.buffer.ByteBuf byteBuf = (io.netty.buffer.ByteBuf) msg; // 将字节数据转为字符串 String content = byteBuf.toString(io.netty.util.CharsetUtil.UTF_8); System.out.println("服务端收到客户端消息:" + content); // 给客户端响应数据 ctx.writeAndFlush(io.netty.buffer.Unpooled.copiedBuffer("服务端已收到消息:" + content, io.netty.util.CharsetUtil.UTF_8)); } /** * 通道读取完成后触发 */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } /** * 异常捕获 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 打印异常信息 cause.printStackTrace(); // 关闭连接 ctx.close(); } }3.2 服务端启动核心类
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * Netty服务端自定义处理器 * 处理:连接建立、消息读取、异常、连接断开 */ public class NettyServerHandler extends ChannelInboundHandlerAdapter { /** * 客户端读取到数据时触发 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // msg即为客户端发送的数据,默认是ByteBuf类型 io.netty.buffer.ByteBuf byteBuf = (io.netty.buffer.ByteBuf) msg; // 将字节数据转为字符串 String content = byteBuf.toString(io.netty.util.CharsetUtil.UTF_8); System.out.println("服务端收到客户端消息:" + content); // 给客户端响应数据 ctx.writeAndFlush(io.netty.buffer.Unpooled.copiedBuffer("服务端已收到消息:" + content, io.netty.util.CharsetUtil.UTF_8)); } /** * 通道读取完成后触发 */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } /** * 异常捕获 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 打印异常信息 cause.printStackTrace(); // 关闭连接 ctx.close(); } }四、实战编写:Netty客户端(可直接运行)
4.1 自定义客户端业务处理器
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * Netty客户端自定义处理器 */ public class NettyClientHandler extends ChannelInboundHandlerAdapter { /** * 客户端连接服务端成功后触发 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // 连接成功后,向服务端发送消息 ctx.writeAndFlush(io.netty.buffer.Unpooled.copiedBuffer("Hello Netty!", io.netty.util.CharsetUtil.UTF_8)); } /** * 读取服务端响应消息 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { io.netty.buffer.ByteBuf byteBuf = (io.netty.buffer.ByteBuf) msg; String content = byteBuf.toString(io.netty.util.CharsetUtil.UTF_8); System.out.println("客户端收到服务端响应:" + content); } /** * 异常处理 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }4.2 客户端启动核心类
import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; /** * Netty客户端启动类 */ public class NettyClient { public static void main(String[] args) throws InterruptedException { // 客户端只需要一个线程池,处理连接和读写 EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try { // 客户端启动器 Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup) // 客户端NIO通道 .channel(NioSocketChannel.class) // 初始化处理器 .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new NettyClientHandler()); } }); // 连接服务端 ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync(); System.out.println("客户端成功连接Netty服务端"); // 监听关闭 channelFuture.channel().closeFuture().sync(); } finally { // 优雅关闭 eventLoopGroup.shutdownGracefully(); } } }五、运行测试 & 结果演示
5.1 运行顺序
先启动NettyServer,服务监听8888端口;
再启动NettyClient,自动连接服务端并发送消息。
5.2 控制台输出结果
服务端输出:
Netty服务端启动成功,监听端口:8888 服务端收到客户端消息:Hello Netty!客户端输出:
客户端成功连接Netty服务端 客户端收到服务端响应:服务端已收到消息:Hello Netty!至此,一套完整的Netty双向通信demo跑通!
六、逐行拆解:Netty核心启动流程(必懂)
很多人只会复制代码,不懂执行流程,这里拆解企业面试必考的Netty启动流程:
6.1 服务端核心流程
创建Boss、Worker 双线程池(主从Reactor雏形);
ServerBootstrap 组装线程池、通道类型、初始化处理器;
绑定端口启动服务,开始监听客户端连接;
Boss线程接收客户端连接,将连接分发到Worker线程;
Worker线程执行Pipeline中的Handler,处理读写、业务逻辑。
6.2 客户端核心流程
创建单线程池处理所有客户端操作;
Bootstrap配置通道与处理器;
主动发起TCP连接,连接成功触发
channelActive方法;完成数据发送与响应接收。
七、新手高频踩坑总结(避坑指南)
7.1 端口占用异常
问题:Bind to port 8888 failed: Address already in use
解决:关闭占用端口进程,或修改监听端口;重启项目时确保旧进程完全退出。
7.2 客户端连接超时
问题:无法连接服务端
排查:优先确认服务端是否启动、IP端口是否正确、防火墙是否拦截。
7.3 数据读取乱码/为空
原因:未指定编码、ByteBuf未正确转换
解决:统一使用Netty自带UTF_8编码工具类,避免原生字符串编码问题。
7.4 程序运行结束立即关闭
原理:closeFuture().sync()阻塞主线程,保证服务持续运行,缺一不可。
八、面试高频考点(本篇必背)
Netty服务端Boss线程和Worker线程的职责区别?
ServerBootstrap 和 Bootstrap 的区别?
ChannelInitializer 的作用是什么?
Netty服务端启动的完整流程是什么?
为什么Netty线程池要优雅关闭?
下篇预告
下一篇我们将深度拆解Netty核心组件与Pipeline责任链机制,彻底搞懂:数据在Netty中是如何流转的?入站/出站处理器执行顺序是什么?,为后续讲解TCP粘包拆包打下核心基础。