应用层
程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用 层.
"协议"
协议是一种 "约定". socket api 的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "结构化的数据" 怎么办呢?
协议就是双方约定好的结构化的数据
因为客户端和服务器是同一家公司写的, cs的协议是一样的(也就是客户端与服务器都能拿到结构体(协议)), 以便解释网络传输的数据
为什么网络传输采用字节流传输实现序列化?(why)
例如我们想要将客户端中的结构体数据message,通过网络传给服务器,目前有两种方案
- 二进制传输(直接以内存中的二进制形式传输结构体): 直接以二进制传输结构体数据 此方案难度大,cs兼容差 ,存在内存对齐等等问题
- 字节流传输(结构体数据转化成字节流): 数据以 连续的字节序列(Byte Stream) 形式在网络中传输,不保留原始数据的边界信息。这种方式广泛应用于 TCP 协议
- 二进制传输不代表不能实现 ,如果严格约定对齐方式可以跨平台使用,但维护成本高。OS在底层就是将结构体直接转化二进制
- 字节流传输 ≠ 只能用于文本,也可以传输图片视频等
- 序列化 是将 数据结构或对象 转换为 可存储或可传输的格式(如字节流、JSON、XML) 的过程
- 字节流传输(如 TCP)将数据视为无结构的连续字节序列,需结合序列化协议(如 JSON、Protobuf)或自定义边界处理(如长度前缀)来保证数据的完整解析。"
网络传输如何采用字节流传输实现序列化?(how)
只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是 ok 的. 这种约定, 就是 应用层协议
序列化 和 反序列化的实现有两种方案
方案一(自己根据协议实现序列化) :
方案二(利用已有的方法将协议序列化) :xml &&json &&protobuf
重新理解 read、write、recv、send 和 tcp 为什 么支持全双工
所以:
- 在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工
- 每创建一个sockfd时 ,OS就在内部给TCP创建了两个缓冲区 ,10个sockfd就有20个缓冲区
- 这就是为什么一个 tcp sockfd 读写都是它的原因
- 实际数据什么时候发,发多少,出错了怎么办,由 TCP 控制,所以 TCP 叫做传 输控制协议
TCP有流量控制, 当只发一半时,需要应用层自主保证报文的完整性(TCP特有问题)
应用层如何自主保证报文的完整性?
通过 报头(Header)+ 校验机制 实现
网络版计算器
我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
实现网络版计算器大体步骤:
1. 网络功能 由TCP实现
2. 协议+序列化/反序列化的方案
2.1协议定制
2.2我们此次序列化使用jsoncpp
开始实现网络版计算器
期望的报文格式
报头(Header)+ 校验机制
关于流式数据的处理
- 你如何保证你每次读取就能读完请求缓冲区的所有内容?
- 你怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢?
- 处理 TCP 缓冲区中的数据,一定要保证正确处理请求
所以,完整的处理过程应该是:
Jsoncpp
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字 符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各 种需要处理 JSON 数据的 C++ 项目中
Jsoncpp特性
- 简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
- 高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。
- 全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数 字、布尔值和 null。
- 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便 开发者调试。
当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类 可供选择。以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:
安装
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
序列化
序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件 中。Jsoncpp 提供了多种方式进行序列化:
1. 使用 Json::Value 的 toStyledString 方法:
优点:将 Json::Value 对象直接转换为格式化的 JSON 字符串。
示例:
#include <iostream> #include <string> #include <jsoncpp/json/json.h> int main() { Json::Value root; root["name"] = "joe"; root["sex"] = "男"; std::string s = root.toStyledString(); std::cout << s << std::endl; return 0; } $ ./test.exe { "name" : "joe", "sex" : "男" }
2. 使用 Json::StreamWriter:
优点:提供了更多的定制选项,如缩进、换行符等。
示例:
#include <iostream> #include <string> #include <sstream> #include <memory> #include <jsoncpp/json/json.h> int main() { Json::Value root; root["name"] = "joe"; 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; } $ ./test.exe { "name" : "joe", "sex" : "男" }
3. 使用 Json::FastWriter:
优点:比 StyledWriter 更快,因为它不添加额外的空格和换行符。
示例:
#include <iostream> #include <string> #include <sstream> #include <memory> #include <jsoncpp/json/json.h> int main() { Json::Value root; root["name"] = "joe"; root["sex"] = "男"; son::FastWriter writer; std::string s = writer.write(root); std::cout << s << std::endl; return 0; } $ ./test.exe {"name":"joe","sex":"男"} #include <iostream> #include <string> #include <sstream> #include <memory> #include <jsoncpp/json/json.h> int main() { Json::Value root; root["name"] = "joe"; root["sex"] = "男"; // Json::FastWriter writer; Json::StyledWriter writer; std::string s = writer.write(root); std::cout << s << std::endl; return 0; } $ ./test.exe { "name" : "joe", "sex" : "男" }
反序列化
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。
Jsoncpp 提供 了以下方法进行反序列化:
1. 使用 Json::Reader:
优点:提供详细的错误信息和位置,方便调试。
示例:
#include <iostream> #include <string> #include <jsoncpp/json/json.h> 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; } $ ./test.exe Name: 张三 Age: 30 City: 北京
2. 用 Json::CharReader 的派生类(不推荐了,上面的足够了):
在某些情况下,可能需要更精细地控制解析过程,可以直接使用 Json::CharReader 的派生类。
但通常情况下,使用 Json::parseFromStream 或 Json::Reader 的 parse 方法就足够了。