Netty入门篇二:5分钟从零搭建Netty服务端与客户端(可直接运行,新手零踩坑)

Netty入门篇二:5分钟从零搭建Netty服务端与客户端(可直接运行,新手零踩坑)

前言

上一篇我们从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 运行顺序

  1. 先启动NettyServer,服务监听8888端口;

  2. 再启动NettyClient,自动连接服务端并发送消息。

5.2 控制台输出结果

服务端输出

Netty服务端启动成功,监听端口:8888 服务端收到客户端消息:Hello Netty!

客户端输出

客户端成功连接Netty服务端 客户端收到服务端响应:服务端已收到消息:Hello Netty!

至此,一套完整的Netty双向通信demo跑通!


六、逐行拆解:Netty核心启动流程(必懂)

很多人只会复制代码,不懂执行流程,这里拆解企业面试必考的Netty启动流程:

6.1 服务端核心流程

  1. 创建Boss、Worker 双线程池(主从Reactor雏形);

  2. ServerBootstrap 组装线程池、通道类型、初始化处理器;

  3. 绑定端口启动服务,开始监听客户端连接;

  4. Boss线程接收客户端连接,将连接分发到Worker线程;

  5. Worker线程执行Pipeline中的Handler,处理读写、业务逻辑。

6.2 客户端核心流程

  1. 创建单线程池处理所有客户端操作;

  2. Bootstrap配置通道与处理器;

  3. 主动发起TCP连接,连接成功触发channelActive方法;

  4. 完成数据发送与响应接收。


七、新手高频踩坑总结(避坑指南)

7.1 端口占用异常

问题:Bind to port 8888 failed: Address already in use

解决:关闭占用端口进程,或修改监听端口;重启项目时确保旧进程完全退出。

7.2 客户端连接超时

问题:无法连接服务端

排查:优先确认服务端是否启动、IP端口是否正确、防火墙是否拦截。

7.3 数据读取乱码/为空

原因:未指定编码、ByteBuf未正确转换

解决:统一使用Netty自带UTF_8编码工具类,避免原生字符串编码问题。

7.4 程序运行结束立即关闭

原理closeFuture().sync()阻塞主线程,保证服务持续运行,缺一不可。


八、面试高频考点(本篇必背)

  1. Netty服务端Boss线程和Worker线程的职责区别?

  2. ServerBootstrap 和 Bootstrap 的区别?

  3. ChannelInitializer 的作用是什么?

  4. Netty服务端启动的完整流程是什么?

  5. 为什么Netty线程池要优雅关闭?


下篇预告

下一篇我们将深度拆解Netty核心组件与Pipeline责任链机制,彻底搞懂:数据在Netty中是如何流转的?入站/出站处理器执行顺序是什么?,为后续讲解TCP粘包拆包打下核心基础。