Netty网络编程实战指南 Netty 网络编程实战指南Netty 是 Java 高并发网络编程的事实标准——Dubbo、RocketMQ、Elasticsearch、Sentinel 底层全是它。也是 CSDN Java 板块的热门话题。本文从 IO 模型讲起覆盖 NIO、Netty 核心组件、EventLoop、ChannelHandler、编解码、粘包拆包、实战一个 RPC 雏形、常见坑。一、为什么要 Netty1.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连接 ├── ChannelPipelineHandler 链表 │ ├── 入站 Handler解码/业务 │ └── 出站 Handler编码 └── ChannelHandler业务逻辑关键规则一个 Channel 的所有操作由同一个 EventLoop 串行执行——所以 Handler 里不用加锁无并发问题。3.2 服务端最小实现publicclassNettyServer{publicstaticvoidmain(String[]args)throwsException{// 1. 创建线程组boss 管连接worker 管读写EventLoopGroupbossnewNioEventLoopGroup(1);EventLoopGroupworkernewNioEventLoopGroup();try{// 2. 启动器ServerBootstrapbootstrapnewServerBootstrap();bootstrap.group(boss,worker).channel(NioServerSocketChannel.class).childHandler(newChannelInitializerSocketChannel(){OverrideprotectedvoidinitChannel(SocketChannelch){// 3. 添加 Handler编解码 业务ch.pipeline().addLast(newStringDecoder()).addLast(newStringEncoder()).addLast(newServerHandler());}});// 4. 绑定端口启动ChannelFuturefuturebootstrap.bind(8080).sync();System.out.println(服务器启动: 8080);future.channel().closeFuture().sync();}finally{boss.shutdownGracefully();worker.shutdownGracefully();}}}// 业务 HandlerpublicclassServerHandlerextendsSimpleChannelInboundHandlerString{OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,Stringmsg){System.out.println(收到: msg);// 回复ctx.writeAndFlush(服务端回复: msg);}OverridepublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause){cause.printStackTrace();ctx.close();}}3.3 客户端publicclassNettyClient{publicstaticvoidmain(String[]args)throwsException{EventLoopGroupgroupnewNioEventLoopGroup();try{BootstrapbootstrapnewBootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(newChannelInitializerSocketChannel(){OverrideprotectedvoidinitChannel(SocketChannelch){ch.pipeline().addLast(newStringDecoder()).addLast(newStringEncoder()).addLast(newClientHandler());}});Channelchannelbootstrap.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 核数EventLoopGroupworkernewNioEventLoopGroup();五、粘包与拆包必考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}// 编码器publicclassRpcEncoderextendsMessageToByteEncoderRpcRequest{Overrideprotectedvoidencode(ChannelHandlerContextctx,RpcRequestmsg,ByteBufout)throwsException{// 1. 写魔数协议标识out.writeInt(0x12345678);// 2. 写长度byte[]bodyJSON.toJSONBytes(msg);out.writeInt(body.length);// 3. 写内容out.writeBytes(body);}}// 解码器配合 LengthFieldBasedFrameDecoder 拆包publicclassRpcDecoderextendsByteToMessageDecoder{Overrideprotectedvoiddecode(ChannelHandlerContextctx,ByteBufin,ListObjectout)throwsException{// 读魔数校验intmagicin.readInt();if(magic!0x12345678){ctx.close();return;}intlenin.readInt();byte[]bodynewbyte[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{privatefinalMapString,ObjectservicesnewHashMap();publicvoidregister(Stringname,Objectservice){services.put(name,service);}publicvoidstart(intport){EventLoopGroupbossnewNioEventLoopGroup(1);EventLoopGroupworkernewNioEventLoopGroup();try{newServerBootstrap().group(boss,worker).channel(NioServerSocketChannel.class).childHandler(newChannelInitializerSocketChannel(){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();}}}// 业务处理反射调用publicclassRpcServerHandlerextendsSimpleChannelInboundHandlerRpcRequest{privatefinalMapString,Objectservices;publicRpcServerHandler(MapString,Objectservices){this.servicesservices;}OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,RpcRequestreq)throwsException{Objectserviceservices.get(req.getServiceName());Methodmethodservice.getClass().getMethod(req.getMethodName(),req.getParameterTypes());Objectresultmethod.invoke(service,req.getArgs());ctx.writeAndFlush(newRpcResponse(req.getRequestId(),result));}}这就是 Dubbo 的迷你版原型注册服务 协议编解码 反射调用。九、常见坑速查现象原因解决消息半截/黏包没做拆包LengthFieldBasedFrameDecoderHandler 里卡顿阻塞调用占住 EventLoop业务线程池异步处理内存泄漏ByteBuf 没释放用完 release / 用 SimpleChannelInboundHandler连接一直断没心跳/没重连IdleStateHandler 重连逻辑线程死等future.sync() 卡住检查事件循环死循环客户端收不到编码/解码不匹配两端 pipeline 一致高并发丢消息业务处理慢拆分 IO 线程和业务线程本章小结Netty 的核心心智一个 Channel 一个 EventLoop 串行执行Handler 链表处理消息。面试高频三件套NIO 与多路复用原理、EventLoop 线程模型为什么不用加锁、粘包拆包解决方案。写代码的高频动作加 LengthFieldBasedFrameDecoder、心跳 IdleStateHandler、业务逻辑丢线程池。下一篇讲 Flutter——跨平台开发的性能王者。全文完