
文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载本篇技术指南以 CodeGuide 开源仓库中 itstack-demo-netty 中级拓展篇二的完整案例为蓝本讲解如何在 Netty 4.1 服务端与客户端之间使用 Google Protocol BuffersProtobuf作为数据传输格式涵盖MsgInfo.proto协议定义、protoc 编译、ProtobufDecoder/ProtobufEncoder/ProtobufVarint32FrameDecoder/ProtobufVarint32LengthFieldPrepender四个编解码器的管道装配以及 Protobuf 与 JSON 的互转。读完本文你将掌握一套定义 proto → 编译生成类 → 服务端/客户端对称装配编解码器 → 二进制传输的完整实战方案并理解它与仓库中字符串解码、自定义编解码器、protostuff 等其他传输方案的差异与适用场景。一、为什么选择 Protobuf 作为 Netty 的数据传输格式在 Netty 数据传输过程中可以有很多选择比如字符串、JSON、XML、Java 对象。但为了保证传输的数据具备良好的通用性、方便的操作性和传输的高性能可以选择 Protobuf 作为数据传输格式。Protobuf 是 Google 提出的语言中立、平台中立、可扩展的序列化结构化数据机制——可以把它理解为更小、更快、更简单的 XML只需定义一次数据结构就可以用生成的源代码在多种数据流、多种语言之间轻松读写这些结构化数据。目前 Protobuf 可以支持 C、C#、Dart、Go、Java、Python 等语言也可以在 JS 里使用。在仓库的基础入门篇章中作者已经梳理过 Netty 处理半包粘包的常用解码器。参考 基础入门篇三《NettyServer字符串解码器》 的问答可以知道字符串格式下常用的解码器有三种——LineBasedFrameDecoder基于换行、DelimiterBasedFrameDecoder基于指定字符串、FixedLengthFrameDecoder基于字符串长度而在 String 之外protobuf 数据格式同样是 Netty 原生支持处理半包粘包的序列化方案之一这正是本案例存在的意义。本章节涉及的知识点有ProtobufDecoder、ProtobufEncoder、ProtobufVarint32FrameDecoder、ProtobufVarint32LengthFieldPrepender。其中前两个负责 Protobuf 消息对象与字节流之间的编解码后两个负责以 varint32 前缀承载消息长度从而在传输层划定消息边界、解决半包粘包问题。二、开发环境与工程结构环境要求jdk1.8jdk1.7 以下只能部分支持 nettyNetty4.1.36.Finalnetty3.x、4.x、5.x 每次的变化较大接口类名也随着变化protoc-3.5.0-win32用于编译 proto 文件protoc -I源地址 --java_out目标地址 源地址/xxx.proto源码中已经提供其他开发环境可以自行下载对应版本。需要说明的是本案例运行在 JDK 8 Netty 4.1.36 组合下若你使用的 JDK 或 Netty 版本不同需要注意接口与依赖版本的兼容性。工程结构itstack-demo-netty-2-02 └── src ├── main │ └── java │ └── org.itstack.demo.netty │ ├── client │ │ ├── MyChannelInitializer.java │ │ ├── MyClientHandler.java │ │ └── NettyClient.java │ ├── domain │ │ ├── MsgBody.java │ │ ├── MsgBodyOrBuilder.java │ │ └── MsgInfo.java │ ├── proto │ │ └── MsgInfo.proto │ ├── server │ │ ├── MyChannelInitializer.java │ │ ├── MyServerHandler.java │ │ └── NettyServer.java │ └── util │ └── MsgUtil.java │ └── test └── java └── org.itstack.demo.test └── ApiTest.javadomain包下的MsgBody.java、MsgBodyOrBuilder.java、MsgInfo.java都是由proto/MsgInfo.proto经过 protoc 编译自动生成的 Java 类不需要手写MsgBody即本案例的通信消息体。三、定义消息协议MsgInfo.proto通信双方服务端与客户端必须使用同一份协议定义才能保证编解码一致。本案例的协议文件位于src/main/java/org/itstack/demo/netty/proto/MsgInfo.proto内容如下syntax proto3; package org.itstack.demo.netty.domain; option java_package org.itstack.demo.netty.domain; option java_multiple_files true; option java_outer_classname MsgInfo; message MsgBody { string channelId 1; string msgInfo 2; }各配置项与字段说明配置 / 字段含义syntax proto3声明使用 proto3 语法相比 proto2 更简洁字段默认值由语言侧处理packageproto 文件的逻辑包名用于避免命名冲突java_package指定生成的 Java 类所在包名这里为org.itstack.demo.netty.domainjava_multiple_files true每个 message 生成独立的 Java 文件对应工程结构中的MsgBody.java、MsgBodyOrBuilder.java等java_outer_classname MsgInfo外层类名为MsgInfo与文件名对应message MsgBody定义一个消息类型包含channelId字段编号 1与msgInfo字段编号 2两个 string 字段字段编号 1、 2是 Protobuf 二进制编码中实际写入字节流的标识一旦发布使用后不宜随意变更。编译 proto 文件在 IDEA 的 Terminal 下执行编译命令命令格式为protoc -I源地址 --java_out目标地址 源地址/xxx.proto案例中的实际执行命令Windows 环境protoc-3.5.0-win32位于工程根目录的protoc-3.5.0-win32/bin下protoc.exe -IE:\itstack\GIT\itstack.org\itstack-demo-netty\itstack-demo-netty-2-02\src\main\java\org\itstack\demo\netty\proto --java_outE:\itstack\GIT\itstack.org\itstack-demo-netty\itstack-demo-netty-2-02\src\main\java MsgInfo.proto其中-I指定 proto 源文件所在目录--java_out指定生成 Java 代码的输出目录这里输出到src\main\java与java_package组合后最终落在org.itstack.demo.netty.domain包下。编译完成后即可在domain包中看到生成的MsgBody、MsgBodyOrBuilder、MsgInfo等类。四、服务端管道装配 Protobuf 编解码器server/MyChannelInitializer.java服务端在ChannelInitializer中按顺序向管道添加 4 个 Protobuf 相关的编解码器再添加业务处理器public class MyChannelInitializer extends ChannelInitializerSocketChannel { Override protected void initChannel(SocketChannel channel) { //protobuf 处理 channel.pipeline().addLast(new ProtobufVarint32FrameDecoder()); channel.pipeline().addLast(new ProtobufDecoder(MsgBody.getDefaultInstance())); channel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()); channel.pipeline().addLast(new ProtobufEncoder()); // 在管道中添加我们自己的接收数据实现方法 channel.pipeline().addLast(new MyServerHandler()); } }从这四个组件的分工看可以这样理解整条入站/出站链路ProtobufVarint32FrameDecoder入站负责按 varint32 长度前缀切割字节流把网络上连续的字节流还原成一个个完整的消息帧从源头处理半包、粘包问题ProtobufDecoder(MsgBody.getDefaultInstance())入站把切好的帧数据反序列化为MsgBody消息对象构造时传入的MsgBody.getDefaultInstance()是 Protobuf 解码器要求的默认实例用于获取消息的描述信息ProtobufVarint32LengthFieldPrepender出站与ProtobufVarint32FrameDecoder对称在发送的 Protobuf 二进制数据前补上 varint32 格式的长度前缀让对端能够据此切帧ProtobufEncoder出站把MsgBody消息对象序列化为字节流写出。由于ProtobufVarint32LengthFieldPrepender在出站方向、ProtobufVarint32FrameDecoder在入站方向各自生效因此只要客户端与服务端采用完全一致的装配顺序双端就能完成对称的加长度前缀 → 编码发送与解码接收 → 按长度切帧。server/NettyServer.javapublic class NettyServer { public static void main(String[] args) { new NettyServer().bing(7397); } private void bing(int port) { //配置服务端NIO线程组 EventLoopGroup parentGroup new NioEventLoopGroup(); //NioEventLoopGroup extends MultithreadEventLoopGroup Math.max(1, SystemPropertyUtil.getInt(io.netty.eventLoopThreads, NettyRuntime.availableProcessors() * 2)); EventLoopGroup childGroup new NioEventLoopGroup(); try { ServerBootstrap b new ServerBootstrap(); b.group(parentGroup, childGroup) .channel(NioServerSocketChannel.class) //非阻塞模式 .option(ChannelOption.SO_BACKLOG, 128) .childHandler(new MyChannelInitializer()); ChannelFuture f b.bind(port).sync(); System.out.println(itstack-demo-netty server start done.); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { childGroup.shutdownGracefully(); parentGroup.shutdownGracefully(); } } }服务端要点配置服务端 NIO 线程组parentGroup用于接受连接childGroup用于处理已建立连接的 I/ONioServerSocketChannel开启非阻塞模式ChannelOption.SO_BACKLOG, 128设置服务端可排队的连接数上限childHandler(new MyChannelInitializer())为每个新接入的客户端 SocketChannel 装配管道即上文包含 Protobuf 编解码器的管道端口固定为7397与客户端连接地址保持一致。server/MyServerHandler.javapublic class MyServerHandler extends ChannelInboundHandlerAdapter { /** * 当客户端主动链接服务端的链接后这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据 */ Override public void channelActive(ChannelHandlerContext ctx) throws Exception { SocketChannel channel (SocketChannel) ctx.channel(); System.out.println(链接报告开始); System.out.println(链接报告信息有一客户端链接到本服务端。channelId channel.id()); System.out.println(链接报告IP: channel.localAddress().getHostString()); System.out.println(链接报告Port: channel.localAddress().getPort()); System.out.println(链接报告完毕); //通知客户端链接建立成功 String str 通知客户端链接建立成功 new Date() channel.localAddress().getHostString() \r\n; ctx.writeAndFlush(MsgUtil.buildMsg(channel.id().toString(), str)); } /** * 当客户端主动断开服务端的链接后这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据 */ Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println(客户端断开链接 ctx.channel().localAddress().toString()); } Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //接收msg消息{与上一章节相比此处已经不需要自己进行解码} System.out.println(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()) 接收到消息类型 msg.getClass()); System.out.println(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()) 接收到消息内容 JsonFormat.printToString((MsgBody) msg)); } /** * 抓住异常当发生异常的时候可以做一些相应的处理比如打印日志、关闭链接 */ Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); System.out.println(异常信息\r\n cause.getMessage()); } }处理器的核心变化体现在channelRead入站的msg已经被管道中的解码器处理为MsgBody对象因此可以直接(MsgBody) msg强转并通过JsonFormat.printToString以 JSON 形式打印消息内容——与字符串传输章节相比这里已经不需要自己进行解码。五、客户端与服务端对称的编解码管线client/MyChannelInitializer.javapublic class MyChannelInitializer extends ChannelInitializerSocketChannel { Override protected void initChannel(SocketChannel channel) throws Exception { //protobuf 处理 channel.pipeline().addLast(new ProtobufVarint32FrameDecoder()); channel.pipeline().addLast(new ProtobufDecoder(MsgBody.getDefaultInstance())); channel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()); channel.pipeline().addLast(new ProtobufEncoder()); // 在管道中添加我们自己的接收数据实现方法 channel.pipeline().addLast(new MyClientHandler()); } }客户端管道与服务端完全一致这正是 Protobuf 通信成立的前提通信双方必须保持同样的半包粘包处理、编码解码处理与收发数据方式。这一点与仓库 基础入门篇八《NettyClient半包粘包处理、编码解码处理、收发数据方式》 中强调的原则一致只是把字符串解码器换成了 Protobuf 编解码器组合。client/NettyClient.javapublic class NettyClient { public static void main(String[] args) { new NettyClient().connect(127.0.0.1, 7397); } private void connect(String inetHost, int inetPort) { EventLoopGroup workerGroup new NioEventLoopGroup(); try { Bootstrap b new Bootstrap(); b.group(workerGroup); b.channel(NioSocketChannel.class); b.option(ChannelOption.AUTO_READ, true); b.handler(new MyChannelInitializer()); ChannelFuture f b.connect(inetHost, inetPort).sync(); System.out.println(itstack-demo-netty client start done.); f.channel().writeAndFlush(MsgUtil.buildMsg(f.channel().id().toString(),你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。)); f.channel().writeAndFlush(MsgUtil.buildMsg(f.channel().id().toString(),你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。)); f.channel().writeAndFlush(MsgUtil.buildMsg(f.channel().id().toString(),你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。)); f.channel().writeAndFlush(MsgUtil.buildMsg(f.channel().id().toString(),你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。)); f.channel().writeAndFlush(MsgUtil.buildMsg(f.channel().id().toString(),你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。)); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { workerGroup.shutdownGracefully(); } } }客户端要点使用Bootstrap引导NioSocketChannel非阻塞模式ChannelOption.AUTO_READ, true表示通道自动读取数据connect(127.0.0.1, 7397)与服务端bing(7397)的端口对应连接成功后连续writeAndFlush5 条由MsgUtil.buildMsg构建的MsgBody消息——这些消息经过出站方向的ProtobufVarint32LengthFieldPrepender与ProtobufEncoder处理后以带长度前缀的二进制形式发往服务端。client/MyClientHandler.javapublic class MyClientHandler extends ChannelInboundHandlerAdapter { /** * 当客户端主动链接服务端的链接后这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据 */ Override public void channelActive(ChannelHandlerContext ctx) throws Exception { SocketChannel channel (SocketChannel) ctx.channel(); System.out.println(链接报告开始); System.out.println(链接报告信息本客户端链接到服务端。channelId channel.id()); System.out.println(链接报告IP: channel.localAddress().getHostString()); System.out.println(链接报告Port: channel.localAddress().getPort()); System.out.println(链接报告完毕); //通知客户端链接建立成功 String str 通知服务端链接建立成功 new Date() channel.localAddress().getHostString(); ctx.writeAndFlush(MsgUtil.buildMsg(channel.id().toString(), str)); } /** * 当客户端主动断开服务端的链接后这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据 */ Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println(断开链接 ctx.channel().localAddress().toString()); } Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //接收msg消息{与上一章节相比此处已经不需要自己进行解码} System.out.println(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()) 接收到消息类型 msg.getClass()); System.out.println(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()) 接收到消息内容 JsonFormat.printToString((MsgBody) msg)); } /** * 抓住异常当发生异常的时候可以做一些相应的处理比如打印日志、关闭链接 */ Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); System.out.println(异常信息\r\n cause.getMessage()); } }客户端处理器与服务端处理器结构对称连接建立后立即通知服务端并携带本端channelId与连接信息channelRead中收到的同样已是MsgBody对象直接强转后以 JSON 打印。六、消息构建工具与 Protobuf/JSON 互转util/MsgUtil.java为了避免在业务代码里反复写 Builder 装配逻辑案例把构建 Protobuf 消息体封装成工具方法public class MsgUtil { /** * 构建protobuf消息体 */ public static MsgBody buildMsg(String channelId, String msgInfo) { MsgBody.Builder msg MsgBody.newBuilder(); msg.setChannelId(channelId); msg.setMsgInfo(msgInfo); return msg.build(); } }MsgBody.newBuilder()返回 Protobuf 生成的 Builder通过链式的setChannelId、setMsgInfo设置字段后调用build()得到不可变的MsgBody实例。服务端、客户端的channelActive与客户端主流程中发送的所有消息都是经由这个工具方法构建的。ApiTest.javaProtobuf 与 JSON 互转验证public class ApiTest { public static void main(String[] args) throws JsonFormat.ParseException { MsgBody.Builder msg MsgBody.newBuilder(); msg.setChannelId(abD01223); msg.setMsgInfo(hi helloworld); MsgBody msgBody msg.build(); //protobuf转Json 需要引入protobuf-java-format String msgBodyStr JsonFormat.printToString(msgBody); System.out.println(msgBodyStr); //json转protobuf 需要引入protobuf-java-format JsonFormat.merge({\channelId\: \HBdhi993\,\msgInfo\: \hi bugstack虫洞栈\}, msg); msgBody msg.build(); System.out.println(msgBody.getChannelId()); System.out.println(msgBody.getMsgInfo()); } }ApiTest验证了两个方向的数据转换Protobuf 转 JSONJsonFormat.printToString(msgBody)把MsgBody序列化为 JSON 字符串输出JSON 转 ProtobufJsonFormat.merge(jsonString, msg)把 JSON 字符串反向合并进已有的 Builder再build()后通过getChannelId()、getMsgInfo()读取字段。需要说明的是代码中使用的JsonFormat来自protobuf-java-format依赖原案例在使用时需要额外引入该工具包Protobuf 官方仓库自身不内置此 JSON 格式转换器。这个工具在业务侧的典型用途是把channelRead收到的MsgBody转成 JSON 字符串打印日志或对接 JSON 接口。七、编译与运行测试编译 proto在 IDEA 的 Terminal 下执行编译命令把MsgInfo.proto编译为 Java 类具体命令见第三节或直接使用工程内提供的protoc-3.5.0-win32/bin/protoc.exe。启动与执行先启动NettyServermain 方法监听 7397 端口再启动NettyClientmain 方法连接 127.0.0.1:7397。服务端执行结果itstack-demo-netty server start done. 链接报告开始 链接报告信息有一客户端链接到本服务端。channelId807679da 链接报告IP:127.0.0.1 链接报告Port:7397 链接报告完毕 2019-08-04 14:06:01 接收到消息类型class org.itstack.demo.netty.domain.MsgBody 2019-08-04 14:06:01 接收到消息内容{channelId: abc14b89,msgInfo: 通知服务端链接建立成功 Sun Aug 04 14:06:01 CST 2019 127.0.0.1} 2019-08-04 14:06:01 接收到消息类型class org.itstack.demo.netty.domain.MsgBody 2019-08-04 14:06:01 接收到消息内容{channelId: abc14b89,msgInfo: 你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。} 2019-08-04 14:06:01 接收到消息类型class org.itstack.demo.netty.domain.MsgBody 2019-08-04 14:06:01 接收到消息内容{channelId: abc14b89,msgInfo: 你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。} 2019-08-04 14:06:01 接收到消息类型class org.itstack.demo.netty.domain.MsgBody 2019-08-04 14:06:01 接收到消息内容{channelId: abc14b89,msgInfo: 你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。} 2019-08-04 14:06:01 接收到消息类型class org.itstack.demo.netty.domain.MsgBody 2019-08-04 14:06:01 接收到消息内容{channelId: abc14b89,msgInfo: 你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。} 2019-08-04 14:06:01 接收到消息类型class org.itstack.demo.netty.domain.MsgBody 2019-08-04 14:06:01 接收到消息内容{channelId: abc14b89,msgInfo: 你好使用protobuf通信格式的服务端我是https://bugstack.cn博主付政委。这是我的公众号bugstack虫洞栈关注我获取案例源码。} 异常信息 远程主机强迫关闭了一个现有的连接。 客户端断开链接/127.0.0.1:7397 Process finished with exit code -1客户端执行结果itstack-demo-netty client start done. 链接报告开始 链接报告信息本客户端链接到服务端。channelIdabc14b89 链接报告IP:127.0.0.1 链接报告Port:51218 链接报告完毕 2019-08-04 14:06:01 接收到消息类型class org.itstack.demo.netty.domain.MsgBody 2019-08-04 14:06:01 接收到消息内容{channelId: 807679da,msgInfo: 通知客户端链接建立成功 Sun Aug 04 14:06:01 CST 2019 127.0.0.1\r\n} Process finished with exit code -1结果分析服务端连续 5 次收到客户端在connect后批量发送的消息接收消息类型均为class org.itstack.demo.netty.domain.MsgBody说明入站数据被ProtobufVarint32FrameDecoder正确切帧、被ProtobufDecoder正确反序列化为协议对象而非原始的字节流双端通过JsonFormat.printToString打印出的内容都是合法 JSON 结构{channelId: ...,msgInfo: ...}验证了 Protobuf 对象与 JSON 的可读转换客户端主动结束进程后服务端触发channelInactive打印客户端断开链接随后exceptionCaught捕获到连接被远端关闭的异常并ctx.close()释放连接属于预期的收尾行为。八、深入理解Protobuf 编解码在 Netty 管道中的定位帧边界与编解码的对称组合从管道装配顺序可以推断出本案例的核心设计思路Netty 的入站与出站处理是两条独立方向四个 Protobuf 组件两两配对——入站方向用ProtobufVarint32FrameDecoder先按 varint32 长度前缀切出完整帧再用ProtobufDecoder把帧解码为MsgBody出站方向用ProtobufEncoder把MsgBody编码为字节再由ProtobufVarint32LengthFieldPrepender补上长度前缀。长度前缀让接收方能够从 TCP 流中精确还原每条消息的边界从而规避半包、粘包带来的解析错乱。这与仓库 基础入门篇九《自定义编码解码器处理半包、粘包数据》 中通过实现ByteToMessageDecoder、MessageToByteEncoder处理字节码传输并控制半包、粘包的思路一脉相承自定义解码器需要自己维护切帧状态而 Protobuf 方案由 Netty 官方提供的四个组件直接完成业务只需保证双端管道一致。与其他传输方案的横向对比在本仓库的 itstack-demo-netty 系列中传输格式的选择形成了清晰的演进脉络字符串方案如 中级拓展篇一《Netty与SpringBoot整合》使用LineBasedFrameDecoderStringDecoder/StringEncoder简单直观但传输的是文本需要自行约定换行等边界符序列化效率与可扩展性有限Protobuf 方案本案例通过 proto 文件定义强类型协议跨语言通用二进制序列化体积小、编解码性能高适合对传输效率与协议规范性要求高的场景protostuff 方案见 中级拓展篇三《Netty传输Java对象》同样基于 Google protobuf但不需要定义 proto 文件、不需要预编译可以直接对现有 POJO 做序列化/反序列化代价是序列化前需预先传入 schema、反序列化要求对象提供默认构造函数适合快速传输自定义 Java 对象而不想维护协议文件的场景。三者对比可以归纳为字符串方案上手最快但边界与性能一般Protobuf 需要先定义协议并编译换来的是强类型、跨语言与高性能protostuff 则是在不写 proto和高性能之间取平衡。实际选型时应结合团队技术栈、跨语言需求与协议维护成本综合决定。本案例在仓库中的定位本案例源码对应仓库文档 docs/md/netty/expand/2019-08-17-netty案例netty4.1中级拓展篇二《Netty使用Protobuf传输数据》.md属于 itstack-demo-netty 中级拓展篇的第二讲。它建立在基础篇NettyServer 收发数据、字符串编解码、客户端半包粘包处理、自定义编解码器等之上又为后续的 Java 对象传输、WebSocket、文件传输、心跳断线重连、集群部署、SSL 加密等拓展篇章奠定了自定义传输协议 编解码器装配的方法论。沿着 docs/md/netty 目录下的 base → expand → application → source-code 顺序阅读可以完整掌握从入门到源码级的 Netty 通信能力。赞分享文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载相关推荐Netty 4.1 入门实战ChannelOutboundHandlerAdapter 出站处理器原理与使用CodeGuide 篇十Netty 4.1 入门实战ChannelOutboundHandlerAdapter 出站处理器原理与使用CodeGuide 篇十 本篇是 CodeGu文档教程后端Netty4.1 ChunkedStream 数据流切块传输实战基于 CodeGuide 中级拓展篇十一的源码级解析Netty4.1 ChunkedStream 数据流切块传输实战基于 CodeGuide 中级拓展篇十一的源码级解析 本篇技术指南围绕小傅哥 CodeGuid文档教程后端Netty 实战SpringBoot Netty Elasticsearch 搭建日志数据收集存储管道Netty 实战SpringBoot Netty Elasticsearch 搭建日志数据收集存储管道 本篇技术指南基于小傅哥 Netty 中级拓展案文档教程后端上一篇终极显卡性能调优指南5分钟掌握NVIDIA Profile Inspector完整教程下一篇7个步骤掌握NVIDIA驱动隐藏参数调校深度解析NVIDIA Profile Inspector创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考