Json与Tcp帧之间的转换

JSON 与 TCP 帧之间的转换**,本质上就是应用层协议的封装(Encoding)(Decoding)。**

很多机器人 SDK、后端服务器、物联网设备都会做这一层。下面以一个完整流程说明。

一、为什么需要转换?

JSON 只是一个字符串,例如:

{

"seq": 1,

"command": "move",

"speed": 0.5

}

但是 TCP 并不知道哪里是一条完整消息。

TCP 是字节流协议(Stream):

发送:

JSON1

JSON2

JSON3

↓

TCP

↓

接收:

JSON1JSON2JSON3

或者:

JSON1

↓

TCP

↓

第一次收到:

{

"seq"

第二次收到:

":1,"command"

第三次收到:

":"move"...}

也就是说:

TCP 不保证一次 send() 对应一次 recv()。

所以必须自己定义"一帧(Frame)"。

二、什么叫 TCP 帧(Frame)

帧就是:

一条完整消息的数据格式。

例如:

+----------------+

| Frame 1 |

+----------------+

| Frame 2 |

+----------------+

| Frame 3 |

+----------------+

TCP 负责运输。

帧由应用层定义。

三、最常见的两种封包方式

方法一:长度字段(最常见)

很多 SDK 都采用这种方式。

例如:

长度(4字节)

JSON数据

假设 JSON:

复制代码
{"cmd":"move"}

长度:

14

发送:

+------------+-------------------+

| 00 00 00 0E| {"cmd":"move"} |

+------------+-------------------+

服务器收到:

先读4字节

↓

知道后面还有14字节

↓

继续读取14字节

↓

解析JSON

整个过程如下:

JSON

↓

计算长度

↓

前面加4字节长度

↓

TCP发送

接收:

TCP

↓

读取长度

↓

读取正文

↓

JSON

↓

QJsonDocument::fromJson()

方法二:分隔符

例如:

{"cmd":"move"}\n

发送:

{"cmd":"move"}\n

{"cmd":"stop"}\n

{"cmd":"left"}\n

服务器一直读:

buffer += socket.readAll();

当发现:

\n

说明:

一条消息结束。

于是:

取出

↓

{"cmd":"move"}

↓

解析JSON

四、JSON → TCP 帧(编码)

假设:

cpp 复制代码
QJsonObject obj;
obj["seq"] = 1;
obj["command"] = "move";
obj["speed"] = 0.5;

第一步:

生成 JSON

cpp 复制代码
QByteArray json =
QJsonDocument(obj).toJson(QJsonDocument::Compact);

得到:

cpp 复制代码
{"seq":1,"command":"move","speed":0.5}

第二步:

封装 Frame。

如果使用换行协议:

cpp 复制代码
json.append('\n');

变成:

{"seq":1,"command":"move","speed":0.5}\n

第三步:

发送

cpp 复制代码
socket.write(json);

流程:

QJsonObject

↓

QJsonDocument

↓

QByteArray

↓

追加 '\n'

↓

socket.write()

五、TCP 帧 → JSON(解码)

收到:

{"seq":1,"command":"move","speed":0.5}\n

读取:

cpp 复制代码
buffer += socket.readAll();

寻找:

\n

找到以后:

QString line

↓

{"seq":1,"command":"move","speed":0.5}

然后:

cpp 复制代码
QJsonDocument doc =
QJsonDocument::fromJson(line.toUtf8());

得到:

cpp 复制代码
QJsonObject obj = doc.object();

然后:

cpp 复制代码
obj["command"].toString();

得到:

move

流程:

socket.readAll()

↓

buffer

↓

找到 '\n'

↓

取出一行

↓

QJsonDocument::fromJson()

↓

QJsonObject

↓

command

六、为什么要单独做这一层?

将 JSON 与 TCP 的转换独立出来(例如 Protocol::encode() / Protocol::decode())有几个好处:

职责分离:业务代码只负责构造命令,不需要关心网络传输细节。

易于维护:如果通信协议从"换行分隔"改成"长度前缀",只需要修改协议层,业务层几乎不用改动。

便于测试:协议编码和解码可以独立编写单元测试。

方便扩展:未来可以将 JSON 替换为 Protobuf、MessagePack 等序列化格式,而不影响上层 API。

相关推荐
_upupup10 小时前
Linux的调试工具——gdb/cgdb
linux·服务器
木子n110 小时前
车载以太网与SOME/IP服务化通信实战-01.从字节到服务:SOME/IP报文解析与四通道开销账
网络·网络协议·tcp/ip·车载以太网·some/ip
Escalating_xu12 小时前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
爱跳舞的烤冷面12 小时前
Linux篇——网络编程
网络
Knight_AL13 小时前
基于 Netty 实现的 WebSocket 服务端
网络·websocket·网络协议
Android系统攻城狮14 小时前
Linux Gstreamer深度解析之gst_audio_sink_get_type调用流程与实战(六十五)
linux·运维·服务器·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
wuyk55515 小时前
《WiFi 嵌入式物联网开发全套实战》| 第 31 章 休眠唤醒 WiFi 断连、时间同步、网络恢复机制
网络·物联网·php
沫璃染墨15 小时前
从零入门计算机网络系列(一):计算机网络初识——从网络发展史到TCP/IP协议》
linux·网络·网络协议·tcp/ip·计算机网络
zyseo815 小时前
谷歌SEO 站内搜索优化实战:把站内搜索词变成关键词金矿
java·服务器·数据库
王大傻092815 小时前
堆叠注入(Stacked Queries Injection)详解:原理、利用与防御
服务器·网络·数据库·web安全·网络安全