☰
Netty网络编程实战指南
2026/10/15 2:21:19 网站建设 项目流程

Netty 网络编程实战指南

Netty 是 Java 高并发网络编程的事实标准——Dubbo、RocketMQ、Elasticsearch、Sentinel 底层全是它。也是 CSDN Java 板块的热门话题。本文从 IO 模型讲起,覆盖 NIO、Netty 核心组件、EventLoop、ChannelHandler、编解码、粘包拆包、实战一个 RPC 雏形、常见坑。


一、为什么要 Netty

1.1 Java IO 演进

BIO(阻塞 IO):一个线程一个连接 → 万级连接就爆炸 NIO(非阻塞):多路复用,一个线程管万级连接 Netty:把 NIO 封装得优雅好用(Dubbo/RocketMQ/ES 都在用)

一句话:Netty 让 Java 开发者能写出高性能网络服务,而不用跟底层 NIO API 死磕。

1.2 Netty 优势

  • 性能高:NIO + 零拷贝 + 池化缓冲区;
  • 易用:Handler 链式处理,屏蔽了 NIO 复杂性;
  • 可靠:心跳、重连、断线重传有现成组件;
  • 生态:HTTP、WebSocket、RPC 协议都有实现。

二、NIO 基础(先懂原理)

2.1 三大组件

// Channel:连接(读写的通道)// Buffer:缓冲区(数据载体)// Selector:多路复用器(一个线程监听多个 Channel)
一个 Selector 监听多个 Channel: Channel1 可读 → 处理 Channel2 可写 → 处理 Channel3 有连接 → 处理

2.2 NIO vs BIO vs AIO

模型阻塞线程模型适用
BIO阻塞一连接一线程连接少
NIO非阻塞一线程多连接(Selector)高并发
AIO异步回调真异步(Linux 支持一般)

Netty 基于 NIO(多路复用)——就是操作系统篇讲过的 epoll。


三、Netty 核心组件

3.1 组件关系

Bootstrap(启动器) ├── EventLoopGroup(事件循环组,本质是线程池) │ └── EventLoop(一个线程 + 一个 Selector) ├── Channel(连接) ├── ChannelPipeline(Handler 链表) │ ├── 入站 Handler(解码/业务) │ └── 出站 Handler(编码) └── ChannelHandler(业务逻辑)

关键规则:一个 Channel 的所有操作由同一个 EventLoop 串行执行——所以 Handler 里不用加锁(无并发问题)。

3.2 服务端最小实现

publicclassNettyServer{publicstaticvoidmain(String[]args)throwsException{// 1. 创建线程组:boss 管连接,worker 管读写EventLoopGroupboss=newNioEventLoopGroup(1);EventLoopGroupworker=newNioEventLoopGroup();try{// 2. 启动器ServerBootstrapbootstrap=newServerBootstrap();bootstrap.group(boss,worker).channel(NioServerSocketChannel.class).childHandler(newChannelInitializer<SocketChannel>(){@OverrideprotectedvoidinitChannel(SocketChannelch){// 3. 添加 Handler(编解码 + 业务)ch.pipeline().addLast(newStringDecoder()).addLast(newStringEncoder()).addLast(newServerHandler());}});// 4. 绑定端口启动ChannelFuturefuture=bootstrap.bind(8080).sync();System.out.println("服务器启动: 8080");future.channel().closeFuture().sync();}finally{boss.shutdownGracefully();worker.shutdownGracefully();}}}// 业务 HandlerpublicclassServerHandlerextendsSimpleChannelInboundHandler<String>{@OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,Stringmsg){System.out.println("收到: "+msg);// 回复ctx.writeAndFlush("服务端回复: "+msg);}@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause){cause.printStackTrace();ctx.close();}}

3.3 客户端

publicclassNettyClient{publicstaticvoidmain(String[]args)throwsException{EventLoopGroupgroup=newNioEventLoopGroup();try{Bootstrapbootstrap=newBootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(newChannelInitializer<SocketChannel>(){@OverrideprotectedvoidinitChannel(SocketChannelch){ch.pipeline().addLast(newStringDecoder()).addLast(newStringEncoder()).addLast(newClientHandler());}});Channelchannel=bootstrap.connect("127.0.0.1",8080).sync().channel();channel.writeAndFlush("你好,Netty!");channel.closeFuture().sync();}finally{group.shutdownGracefully();}}}

四、EventLoop 与线程模型(面试重点)

4.1 单线程事件循环

EventLoop = 一个线程 + 一个 Selector + 一个任务队列 处理流程: 1. Selector 监听就绪事件 2. 处理 IO 事件(读/写) 3. 执行队列中的普通任务 4. 循环

4.2 为什么 Handler 不用加锁

一个 Channel 绑定一个固定 EventLoop——该 Channel 的所有操作(读、业务、写)都在同一线程串行执行。

这意味着:Handler 里直接改共享对象(只要只被一个 Channel 用)不需要 synchronized。

4.3 线程模型对比(面试常问)

模型说明
单线程模型一个 EventLoop 全干(简单,性能低)
多线程模型boss 一个线程,worker 多线程(最常用)
主从模型boss 多个线程(海量连接场景)

默认配置:

// boss 线程数:1(够了,只管 accept)// worker 线程数:默认 2 * CPU 核数EventLoopGroupworker=newNioEventLoopGroup();

五、粘包与拆包(必考)

5.1 问题

TCP 是字节流,没有消息边界:

发送方:ABC DEF 接收方可能收到:AB | CD | EF 或 ABCDEF(粘包/拆包)

5.2 解决方案

方案说明适用
固定长度每消息固定字节简单但浪费
分隔符按 \n 分割文本协议
长度字段头存长度二进制协议(主流)
自定义协议魔数+版本+长度RPC/游戏

5.3 长度字段解码器(主流方案)

// 协议格式:4字节长度 + 内容ch.pipeline().addLast(newLengthFieldBasedFrameDecoder(1024,// 最大帧长度0,// 长度字段偏移4,// 长度字段长度0,// 长度调整4// 跳过的字节数(剥离长度头)));ch.pipeline().addLast(newStringDecoder());

遇到"收到半截消息/多条消息黏一起"——第一反应就是加解码器。


六、编解码与自定义协议

6.1 序列化选择

// 常用:JSON(人可读)、Protobuf(性能)、JDK(不推荐)

6.2 自定义 RPC 请求

publicclassRpcRequest{privateintrequestId;privateStringmethodName;privateObject[]args;// getter/setter}// 编码器publicclassRpcEncoderextendsMessageToByteEncoder<RpcRequest>{@Overrideprotectedvoidencode(ChannelHandlerContextctx,RpcRequestmsg,ByteBufout)throwsException{// 1. 写魔数(协议标识)out.writeInt(0x12345678);// 2. 写长度byte[]body=JSON.toJSONBytes(msg);out.writeInt(body.length);// 3. 写内容out.writeBytes(body);}}// 解码器(配合 LengthFieldBasedFrameDecoder 拆包)publicclassRpcDecoderextendsByteToMessageDecoder{@Overrideprotectedvoiddecode(ChannelHandlerContextctx,ByteBufin,List<Object>out)throwsException{// 读魔数校验intmagic=in.readInt();if(magic!=0x12345678){ctx.close();return;}intlen=in.readInt();byte[]body=newbyte[len];in.readBytes(body);out.add(JSON.parseObject(body,RpcRequest.class));}}

零拷贝注意:协议里用ByteBuf,别在 Handler 里频繁toString()+getBytes()——性能杀手。


七、心跳与断线重连(生产必备)

7.1 服务端:IdleStateHandler

// 3 种空闲超时:读空闲/写空闲/读写空闲ch.pipeline().addLast(newIdleStateHandler(60,0,0,TimeUnit.SECONDS));// Handler 里处理publicclassHeartbeatHandlerextendsChannelInboundHandlerAdapter{@OverridepublicvoiduserEventTriggered(ChannelHandlerContextctx,Objectevt)throwsException{if(evtinstanceofIdleStateEvent){// 60 秒没读到数据 → 连接可能死了System.out.println("读空闲,关闭连接: "+ctx.channel().remoteAddress());ctx.close();}else{super.userEventTriggered(ctx,evt);}}}

7.2 客户端:定时心跳

// 客户端启动后定时发心跳channel.eventLoop().scheduleAtFixedRate(()->{if(channel.isActive()){channel.writeAndFlush(newHeartbeatPacket());}},5,5,TimeUnit.SECONDS);// 断线重连// 在 channelInactive 里延时重连@OverridepublicvoidchannelInactive(ChannelHandlerContextctx)throwsException{System.out.println("连接断开,5 秒后重连");ctx.channel().eventLoop().schedule(()->connect(),5,TimeUnit.SECONDS);}

八、实战:迷你 RPC 服务

// 服务端:注册服务 + 接收调用publicclassMiniRpcServer{privatefinalMap<String,Object>services=newHashMap<>();publicvoidregister(Stringname,Objectservice){services.put(name,service);}publicvoidstart(intport){EventLoopGroupboss=newNioEventLoopGroup(1);EventLoopGroupworker=newNioEventLoopGroup();try{newServerBootstrap().group(boss,worker).channel(NioServerSocketChannel.class).childHandler(newChannelInitializer<SocketChannel>(){@OverrideprotectedvoidinitChannel(SocketChannelch){ch.pipeline().addLast(newLengthFieldBasedFrameDecoder(1024,4,4)).addLast(newRpcDecoder()).addLast(newRpcEncoder()).addLast(newRpcServerHandler(services));}}).bind(port).sync().channel().closeFuture().sync();}catch(Exceptione){e.printStackTrace();}finally{boss.shutdownGracefully();worker.shutdownGracefully();}}}// 业务处理:反射调用publicclassRpcServerHandlerextendsSimpleChannelInboundHandler<RpcRequest>{privatefinalMap<String,Object>services;publicRpcServerHandler(Map<String,Object>services){this.services=services;}@OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,RpcRequestreq)throwsException{Objectservice=services.get(req.getServiceName());Methodmethod=service.getClass().getMethod(req.getMethodName(),req.getParameterTypes());Objectresult=method.invoke(service,req.getArgs());ctx.writeAndFlush(newRpcResponse(req.getRequestId(),result));}}

这就是 Dubbo 的迷你版原型:注册服务 + 协议编解码 + 反射调用。


九、常见坑速查

现象原因解决
消息半截/黏包没做拆包LengthFieldBasedFrameDecoder
Handler 里卡顿阻塞调用占住 EventLoop业务线程池异步处理
内存泄漏ByteBuf 没释放用完 release / 用 SimpleChannelInboundHandler
连接一直断没心跳/没重连IdleStateHandler + 重连逻辑
线程死等future.sync() 卡住检查事件循环死循环
客户端收不到编码/解码不匹配两端 pipeline 一致
高并发丢消息业务处理慢拆分 IO 线程和业务线程

本章小结

Netty 的核心心智:一个 Channel 一个 EventLoop 串行执行,Handler 链表处理消息。面试高频三件套:NIO 与多路复用原理、EventLoop 线程模型(为什么不用加锁)、粘包拆包解决方案。写代码的高频动作:加 LengthFieldBasedFrameDecoder、心跳 IdleStateHandler、业务逻辑丢线程池。

下一篇讲 Flutter——跨平台开发的性能王者。

(全文完)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询