我们程序员写的一个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层.
应用层协议就是应用程序之间通信的规则和格式约定,让不同的程序能够理解彼此发送的数据含义。
协议是一种 “约定”。socket api的接口,在读写数据时,都是按 “字符串” 的方式来发送接收的。如果我们要传输一些 “结构化的数据” 怎么办呢?
在使用 socket API 进行网络通信时,底层只能收发 字节流(或字符串),因此如果要在客户端‑服务器之间传递 结构化的数据,必须先把数据 序列化 为字节流,再在接收端 反序列化 回原来的结构。常见的做法包括:
步骤 | 说明 | 常用实现方式 |
|---|---|---|
1️⃣ 定义数据结构 | 在双方约定好要交换的字段、类型、层次结构。可以用 结构体、类或 .proto、JSON Schema 等形式描述。 | C/C++ struct、Java class、.proto 文件等 |
2️⃣ 序列化 | 把结构体/对象转换为 字节序列(或可读的文本),并在发送前 加上长度前缀(防止粘包/半包)。 | JSON、XML、Protocol Buffers(protobuf)、MessagePack、Thrift、CBOR 等 |
3️⃣ 通过 socket 发送 | 直接 write()/send() 发送字节流;接收端使用 read()/recv() 读取指定长度的数据。 | 参考 Protobuf C++ Socket 示例 中的 write(client_sock, &msg, sizeof(msg)) 方式 |
4️⃣ 反序列化 | 接收端把字节流按照相同的规则恢复为原始结构体/对象。 | 对应的 JSON 解析库、protobuf 生成的 ParseFromArray 等 |
常见序列化方案对比
方案 | 数据形态 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
JSON | 文本(UTF‑8) | 人类可读、调试方便、跨语言支持广泛 | 数据体积相对大、解析速度慢于二进制方案 | 配置、REST API、调试阶段 |
XML | 文本(带标签) | 可自定义结构、支持命名空间 | 冗长、解析开销大 | 老旧系统、需要严格模式校验的场景 |
Protocol Buffers (protobuf) | 二进制 | 体积小、序列化/反序列化速度快、跨语言、向后兼容 | 不可直接阅读,需要 .proto 定义文件 | 高性能 RPC、移动端‑服务器、带宽受限环境 |
MessagePack | 二进制 | 类似 JSON 的结构但更紧凑 | 生态相对 protobuf 较少 | 需要兼顾可读性和效率的轻量服务 |
例如,Google 的 protobuf 将结构化数据编译成二进制字节流,传输时只占用极少的带宽,且支持多语言生成代码。在实际的 C++ socket 示例中,先把 Person 消息序列化为 std::string(或 std::vector),再通过 write() 发送;接收端使用 ParseFromArray 还原对象。
实际实现要点
📌 其实,协议就是双方约定好的结构化的数据
例如, 我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去。 然后由服务器进行计算,最后再把结果返回给客户端.

无论我们采用方案一, 还是方案二, 还是其他的方案,只要保证,一端发送时构造的数据,在另一端能够正确的进行解析就是ok的。这种约定就是 应用层协议
但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。
不过我们会在下篇文章中自定义实现一下协议

write和read操作不直接将数据发送到网络中,而是在主机内部完成数据拷贝:
因此,write和read的本质是主机内部的数据拷贝,而非直接与网络交互。
主机间通信的本质是 “数据拷贝”:
整个过程的核心是数据在“应用缓冲区-内核缓冲区-网络”之间的拷贝,而非“数据直接通过网络传输”(网络传输由TCP/IP协议栈底层完成)。
TCP通信是全双工的,原因是 双方各自拥有独立的“发送缓冲区”和“接收缓冲区”:
总结
下面我们来介绍一个序列化方案Jsoncpp
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中。
特性
当使用Jsoncpp库进行JSON的序列化和反序列化时,确实存在不同的做法和工具类可供选择。以下是对Jsoncpp中序列化和反序列化操作的详细介绍:
安装
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp提供了多种方式进行序列化:
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "张三";
root["sex"] = "男";
std::string s = root.toStyledString();
std::cout << s << std::endl;
return 0;
}解析:
注意: 编译时需要链接 JsonCpp 库:
g++ -o testjson testjson.cc -ljsoncpp运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ g++ -o testjson testjson.cc -ljsoncpp
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ ./testjson
{
"name" : "\u5f20\u4e09",
"sex" : "\u7537"
}我们可以看到代码输出显示 Unicode 转义序列(如 \u5f20\u4e09)而不是中文字符,这是因为 JsonCpp 的 toStyledString() 方法默认会将非 ASCII 字符(如中文)转换为 Unicode 转义序列,这是一种符合 JSON 标准的安全处理方式,确保跨平台兼容性。不过在实际数据传输或存储中,转义序列是通用做法,解析时会自动还原为中文。
关键特点:toStyledString() 会自动添加缩进和换行,适合人类阅读
int main()
{
Json::Value root;
root["name"] = "Bob";
root["sex"] = "男";
Json::StreamWriterBuilder wbuilder; // StreamWriter的工厂
std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
std::stringstream ss;
writer->write(root, &ss);
std::cout << ss.str() << std::endl;
return 0;
}解析:
步骤分解:
注意:这段代码与之前使用toStyledString()不同,它使用了StreamWriter来写入流。默认情况下,StreamWriter生成的JSON字符串是紧凑格式(没有换行和缩进)的,但是Json::StreamWriterBuilder可以设置多种输出格式。
运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ g++ -o testjson testjson.cc -ljsoncpp
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ ./testjson
{
"name" : "Bob",
"sex" : "\u7537"
}可以看到输出结果默认带缩进和换行,如果需设置其他输出格式,则需要配置 以下是主要的配置参数及其说明:
indentation”:设置缩进字符串,用于美化输出(默认值为 “\t”,即制表符)。若设置为空字符串 “”,则输出为紧凑格式(无缩进和换行)。emitUTF8”:控制是否直接输出 UTF-8 字符。默认值为 false(会将非 ASCII 字符转义为 Unicode 序列,如 \u7537),设为 true 可避免转义(直接输出原始字符)。commentStyle”:处理 JSON 中的注释(如 // 或 /* */),可选值为 “None”(忽略注释)或其他模式。enableYAMLCompatibility”:启用 YAML 兼容性输出(如将空值表示为 ~),默认值为 false。dropNullPlaceholders”:控制是否忽略 null 值。若设为 true,则不会输出 null 字段。precision”:设置浮点数输出精度(小数位数),例如 writerBuilder[“precision”] = 3 会保留 3 位小数。precisionType”:指定精度类型(如 “decimal” 表示十进制格式)。lineBreak”:自定义换行符(如设置为 “\n” 或 “\r\n”),影响多行输出的换行方式。示例:
int main()
{
Json::Value root;
root["name"] = "Bob";
root["sex"] = "男";
Json::StreamWriterBuilder wbuilder; // StreamWriter的工厂
wbuilder["indentation"] = ""; // 紧凑输出
wbuilder["emitUTF8"] = true; // 直接输出 UTF-8 字符(避免转义)
std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
std::stringstream ss;
writer->write(root, &ss);
std::cout << ss.str() << std::endl;
return 0;
}运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ g++ -o testjson testjson.cc -ljsoncpp
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ ./testjson
{"name":"Bob","sex":"男"}int main()
{
Json::Value root;
root["name"] = "Bob";
root["sex"] = "男";
Json::StyledWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ g++ -o testjson testjson.cc -ljsoncpp
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ ./testjson
{
"name" : "Bob",
"sex" : "\u7537"
}缺点:
int main()
{
Json::Value root;
root["name"] = "Bob";
root["sex"] = "男";
Json::FastWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ g++ -o testjson testjson.cc -ljsoncpp
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ ./testjson
{"name":"Bob","sex":"\u7537"}反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp提供了以下方法进行反序列化:
int main()
{
// JSON 字符串
std::string json_string = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";
// 解析 JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取 JSON 数据
bool parsingSuccessful = reader.parse(json_string, root);
if (!parsingSuccessful)
{
// 解析失败,输出错误信息
std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
return 1;
}
// 访问 JSON 数据
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();
// 输出结果
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "City: " << city << std::endl;
return 0;
}解析:
运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ g++ -o testjson testjson.cc -ljsoncpp
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ ./testjson
Name: 张三
Age: 30
City: 北京int main()
{
std::string json_string = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";
Json::Value root;
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
std::string errors;
bool parsingSuccessful = reader->parse(json_string.c_str(),
json_string.c_str() + json_string.length(),
&root,
&errors);
if (!parsingSuccessful)
{
std::cout << "Failed to parse JSON: " << errors << std::endl;
return 1;
}
// 访问数据(同上)
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "City: " << city << std::endl;
return 0;
}运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ g++ -o testjson testjson.cc -ljsoncpp
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_network/NetCal/testJson$ ./testjson
Name: 张三
Age: 30
City: 北京总结 • toStyledString 、 StreamWriter 和 FastWriter 提供了不同的序列化选项,你可以根 据具体需求选择使用。 • Json::Reader 和 parseFromStream 函数是Jsoncpp中主要的反序列化工具,它们提供了强大的错误处理机制。 • 在进行序列化和反序列化时,请确保处理所有可能的错误情况,并验证输入和输出的有效性
Json::Value 是 Jsoncpp 库中的一个重要类,用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表: