<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[高效通信协议构想]]></title><description><![CDATA[<p dir="auto">一、协议设计概述<br />
1.1、 通信协议设计核心<br />
（1）解析效率。<br />
（2）可扩展、可升级。</p>
<p dir="auto">1.2、协议设计细节<br />
（1）数据帧的完整性判断。<br />
（2）序列化和反序列化。<br />
（3）协议升级，兼容性。<br />
（4）协议安全。<br />
（5）数据压缩。</p>
<p dir="auto">1.3、协议设计目标<br />
（1）解析效率：高并发场景下，解析效率决定了使用协议的CPU成本。<br />
（2）编码长度：决定了使用协议的网络带宽和存储成本。<br />
（3）易于实现：满足需求的协议就是好协议，不追求大而全的。<br />
（4）可读性：决定了使用协议的调试和维护成本。<br />
（5）兼容性：协议可能会经常升级，使⽤协议的双⽅是否可以独⽴升级协 议、增减协议中的字段⾮常重要。<br />
（6）跨平台语言：协议适用于任何语言来实现。⽐如Windows⽤C++，Android⽤Java， Web⽤Js，IOS⽤object-c。<br />
（7）安全可靠：防止数据被破解。</p>
<p dir="auto">1.4、协议概述<br />
协议是⼀种约定，通过约定，不同的进程可以对⼀段数据产⽣相同的理解，从⽽可以相互协 作，存在进程间通信的程序就⼀定需要协议。<br />
图片</p>
<p dir="auto">⽐如不同表的插头，还需要进⾏各种转换，如果我们两端进⾏通信没有约定好协议，那彼此是不知道对⽅ 发送的数据是什么意义。</p>
<p dir="auto">二、协议设计<br />
（1）消息边界。使用什么方式界定消息边界。<br />
（2）版本区分。版本号放在何处合适。<br />
（3）消息类型区分。对应不同的业务。</p>
<p dir="auto">协议设计不是为了通用，主要是为了适合业务，避免臃肿。</p>
<p dir="auto">2.1、消息的完整性判断<br />
为了能让对端知道如何给消息帧分界，目前一般有一下做法：<br />
（1）固定大小。不推荐。<br />
以固定⼤⼩字节数⽬来分界，如每个消息100个字节（不足100就填充，超过100就分包），对端每收⻬100个字节，就当成⼀个消息来解析。</p>
<p dir="auto">（2）以特定符号分界。<br />
如每个消息都以特定的字符来结尾（如\r\n），当在字节流中读取到该字符时， 则表明上⼀个消息到此为⽌。HTTP就是以特定符号分界。</p>
<p dir="auto">（3）固定消息头+消息体结构。推荐。<br />
这种结构中⼀般消息头部分是⼀个固定字节⻓度的结构，并且消息头中会有 ⼀个特定的字段指定消息体的⼤⼩。收消息时，先接收固定字节数的头部，解出这个消息完整⻓度， 按此⻓度接收消息体。这是⽬前各种⽹络应⽤⽤的最多的⼀种消息格式；header + body。</p>
<p dir="auto">（4）特殊字符+消息长度+分隔符。<br />
在序列化后的buffer前⾯增加⼀个字符流的头部，其中有个字段存储消息总⻓度，根据特殊字符（⽐ 如根据\n或者\0）判断头部的完整性。这样通常⽐3要麻烦⼀些，HTTP和REDIS采⽤的是这种⽅式。收消息的时候，先判断已收到的数据中是否包含结束符，收到结束符后解析消息头，解出这个消息完 整⻓度，按此⻓度接收消息体。</p>
<p dir="auto">2.2、示例1：即时通信的协议设计<br />
字段 类型 ⻓度(字节) 说明<br />
length unsigned int 4 整个消息的⻓度包括 协议头 + BODY<br />
version unsigned short 2 通信协议的版本号<br />
appid unsigned short 2 对外SDK提供服务时，⽤来识别不同的客户<br />
service_id unsigned short 2 对应命令的分组类⽐，⽐如login和msg是不同分组<br />
command_id unsigned short 2 分组⾥⾯的⼦命令，⽐如login和login response<br />
seq_num unsigned short 2 消息序号<br />
reserve unsigned short 2 预留字节<br />
body unsigned char[] n 具体的协议数据<br />
注意：<br />
（1）length一定要约定好是body的长度还是header+body的长度。<br />
（2）版本号尽量靠前，是为了版本升级的便携性，反正不同版本的后续字段不同导致的未知问题。<br />
（3）内部有不同业务，可以考虑使用appid来做识别。<br />
（4）消息类型的识别。比如登录业务和消息聊天业务，登录有登录请求和响应等，消息聊天又有私聊和群聊等。</p>
<p dir="auto">图片</p>
<p dir="auto">（5）消息序列号主要用来业务的应答。判断消息是否已被接收处理成功，要不要重发等。TCP数据传输可靠不代表业务可靠。<br />
（6）一般来说，设计协议的时候要留一些预留位，为了后期有变动或扩展时能兼容。</p>
<p dir="auto">2.3、示例2：云平台节点服务器<br />
字段 类型 ⻓度(字节) 说明<br />
STAG unsigned short 2 通信协议数据包的开始标志 0xff 0xfe。比如h264 0 0 0 1<br />
version unsigned short 2 通信协议的版本号<br />
check_sum unsigned char 1 计算协议数据校验和，如果为加密数据，则计算密⽂校验 和。校验和计算范围：协议头CheckSum字段后数据，协议 体全部数据。<br />
type unsigned char 1 0表示协议体是json格式，其它值未定义。设备⼼跳消息类型 的值为0xA0<br />
seq_num unsigned int 4 通信数据报⽂的序列号，应答报⽂序列号必须与请求报⽂序 列号相同<br />
length unsigned int 4 报⽂内容⻓度，即从该字段后报⽂内容⻓度<br />
reserve unsigned int 4 预留字节<br />
body unsigned char[] n 具体数据<br />
注意：这里有一个STAG用于标志数据包的开始，其他和上面的含义类似。</p>
<p dir="auto">2.4、示例3：nginx<br />
typedef struct{<br />
ngx_char_t magic[2]; //magic number<br />
ngx_short_t version; // protocol version<br />
ngx_short_t type; // protocol type: json、xml、binary、....<br />
ngx_short_t len; // body length<br />
ngx_uint_t seq; // message number<br />
ngx_short_t id; // message id<br />
ngx_char_t reserve[2]; // reserve<br />
} ngx_message_head_t;<br />
2.5、示例4：HTTP协议<br />
图片<br />
HTTP协议是最常⻅的协议。但是这个⼀般是不适合采⽤HTTP协议作为互联⽹后台的协议，主要是考虑到以下2个原因：<br />
（1） HTTP协议只是⼀个框架，没有指定包体的序列化⽅式，所以还需要配合其他序列化的⽅式使⽤才能传 递业务逻辑数据。<br />
（2）HTTP协议解析效率低，⽽且⽐较复杂（不知道有没有⼈觉得HTTP协议简单，其实不是http协议简单， ⽽是HTTP⼤家⽐较熟悉⽽已） 。</p>
<p dir="auto">有些情况下是可以使⽤HTTP协议的：<br />
（1）对公⽹⽤户api，HTTP协议的穿透性最好，所以最适合；<br />
（2）效率要求没那么⾼的场景；<br />
（3） 希望提供更多⼈熟悉的接口，⽐如新浪微、腾讯博提供的开放接⼝。</p>
<p dir="auto">HTTP的body是文本还是二进制？<br />
这依赖于是否压缩，如果没有压缩就是文本；如果压缩了就是二进制，需要客户端解压成文本；如果传输的是视频流或图片，那么body就是二进制的。头部一定是文本的。</p>
<p dir="auto">2.6、示例5：redis协议<br />
基本原理是：先发送⼀个字符串表示参数个数，然后再逐个发送参数，每个参数发送的时候，先发送⼀个 字符串表示参数的数据⻓度，再发送参数的内容。</p>
<p dir="auto">在redis 中, ⼀些数据的类型通过它的第⼀个字节进⾏判断：<br />
（1）单⾏(Simple Strings)回复：回复的第⼀个字节是 “+” 。<br />
（2）错误(Errors)信息：回复的第⼀个字节是 “-” 。<br />
（3）整形数字(Integers)：回复的第⼀个字节是 “:” 。<br />
（4）多⾏字符串(Bulk Strings)：回复的第⼀个字节是 “$” 。<br />
（5）数组(Arrays)：回复的第⼀个字节是 “*”。</p>
<p dir="auto">此外，redis能够使⽤稍后指定的Bulk Strings或Array的特殊变体来表示Null值。在redis中，协议的不 同部分始终以“\r\n”（CRLF）结束。</p>
<p dir="auto">三、序列化方法<br />
（1）TVL编码及其变体（TVL是tag，length和value的缩写）：比如protobuf。<br />
（2）文本流编码：比如xml、json。<br />
（3）固定结构编码：基本原理是，协议约定了传输字段类型和字段含义，和TLV的⽅式类似，但是没有了 tag和len，只有value，⽐如TCP/IP。<br />
（4）内存dump：基本原理是，把内存中的数据直接输出，不做任何序列化操作。反序列化的时候，直接还 原内存。</p>
<p dir="auto">3.1、常见序列化方法<br />
主流序列化协议：xml，json，protobuf。<br />
（1）XML指可扩展标记语⾔（eXtensible Markup Language）。是⼀种通⽤和重量级的数据交换格式。以⽂本⽅式存储。<br />
（2） JSON(JavaScript ObjectNotation, JS 对象简谱) 是⼀种通⽤和轻量级的数据交换格式。以⽂本结构 进⾏存储。<br />
（3）protocol buffer是Google的⼀种独⽴和轻量级的数据交换格式。以⼆进制结构进⾏存储。</p>
<p dir="auto">类型 通⽤性 ⼤⼩ 格式<br />
XML 通⽤ 重量级 ⽂本格式<br />
JSON 通⽤ 轻量级 ⽂本格式（⽅便调试）<br />
Protobuf（编译器， ⽣成对应语⾔的代 码） 独⽴ 轻量级 ⼆进制格式<br />
3.2、序列化结果数据对比<br />
XML：</p>
<p dir="auto">&lt;?xml version="1.0" encoding="utf-8" ?&gt;<br />
&lt;?xml-stylesheet type="text/css" href="test.css"?&gt;<br />
&lt;test&gt;<br />
&lt;name&gt;HelloWorld&lt;/name&gt;<br />
&lt;sex&gt;male&lt;/sex&gt;<br />
&lt;birthday&gt;7.1&lt;/birthday&gt;<br />
&lt;skill&gt;AI&lt;/skill&gt;<br />
&lt;/test&gt;<br />
JSON：</p>
<p dir="auto">{<br />
"name": "HelloWorld",<br />
"age": 80,<br />
"languages": ["C","linux","C++"],<br />
"phone": {<br />
"number": "12345678901",<br />
"type": "home"<br />
},<br />
"china": true,<br />
"books":[<br />
{<br />
"name": "Linux c development",<br />
"price": 18.8<br />
},<br />
{<br />
"name": "Linux server development",<br />
"price": 188.8<br />
}<br />
],<br />
}<br />
protobuf：</p>
<p dir="auto">16 36 16 36 00 00 16 36 16 36 16 36 16 36 00 00 sf<br />
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 sf<br />
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 sf<br />
00 00 00 00 00 00 9C 00 00 00 00 00 E7 00 36 76 sf<br />
11 11 40<br />
序列化方法对每个字段有边界的约束。比如xml中的&lt; name&gt;是字段开始，&lt; /name&gt;是字段结束。</p>
<p dir="auto">为什么需要序列化？因为字段值是变长的，需要一个方法约束起始和接收的边界。</p>
<p dir="auto">3.3、序列化、反序列化速度对⽐<br />
测试10W+。</p>
<p dir="auto">序列化：</p>
<p dir="auto">库 默认 -O1 序列化后字节<br />
cJSON(C语⾔) 488ms 452ms 297<br />
jsoncpp(C++语⾔) 871ms 709ms 255<br />
rapidjson(C++语⾔) 701ms 113ms 239<br />
tinyxml2(XML) 1383ms 770ms 474<br />
protobuf 241ms 83ms 117<br />
可以看到，同样是json，为什么序列化后数据大小不一样？这是由于排版的问题，比如不换行不缩进，紧凑占用的字节就少了。</p>
<p dir="auto">反序列化：</p>
<p dir="auto">库 默认 -O1<br />
cJSON 284ms 251ms<br />
jsoncpp 786ms 709ms<br />
rapidjson 1288ms 128ms<br />
tinyxml2 1781ms 953ms<br />
protobuf 190ms 80ms<br />
和序列化的速度差不多的。</p>
<p dir="auto">四、数据传输<br />
4.1、数据安全<br />
数据加密。<br />
（1）AES<br />
（2）openssl<br />
（3）Signal protocol端到端的通讯加密协议。</p>
<p dir="auto">4.2、数据压缩<br />
文本情况下压缩，二进制压缩没有太多意义。<br />
（1）defate<br />
（2）gzip<br />
（3）lzw</p>
<p dir="auto">4.3、协议升级<br />
协议升级：增加字段。<br />
（1）通过版本号指明协议版本，即是通过版本号辨别不同类型的协议 。<br />
（2） ⽀持协议头部可扩展，即是在设计协议头部的时候有⼀个字段⽤来指明头部的⻓度。</p>
<p dir="auto">总结<br />
通信协议设计的核心目标是为了解析效率、可扩展、可升级；高并发下的通信协议应该高解析效率、易于实现、兼容性强、跨语言、安全可靠。</p>
<p dir="auto">消息帧的完整性判断方式有：固定长度（不推荐）、header+body（推荐）、以特定符号分界、特殊字符+消息长度+分隔符。</p>
<p dir="auto">序列化方法有：TVB编码及变体、文本流编码、固定结构编码、内存dump。<br />
主流序列化协议有XML、JSON、protobuf</p>
]]></description><link>https://bbs.leaguetech.net/topic/16/高效通信协议构想</link><generator>RSS for Node</generator><lastBuildDate>Wed, 22 Jul 2026 23:25:36 GMT</lastBuildDate><atom:link href="https://bbs.leaguetech.net/topic/16.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 18 Jun 2026 10:56:29 GMT</pubDate><ttl>60</ttl></channel></rss>