🌈 个人主页:Zfox_
🔥 系列专栏:Linux
目录
- [一:🔥 应用层](#一:🔥 应用层)
-
- [🦋 再谈 "协议"](#🦋 再谈 "协议")
- [🦋 网络版计算器](#🦋 网络版计算器)
- [🦋 序列化 和 反序列化](#🦋 序列化 和 反序列化)
- [二:🔥 重新理解 read、 write、 recv、 send 和 tcp 为什么支持全双工](#二:🔥 重新理解 read、 write、 recv、 send 和 tcp 为什么支持全双工)
- [三:🔥 开始实现](#三:🔥 开始实现)
-
- [🦋 定制协议](#🦋 定制协议)
- [四:🔥 为关于流式数据的处理](#四:🔥 为关于流式数据的处理)
- [五:🔥 Jsoncpp](#五:🔥 Jsoncpp)
-
- [🦋 特性](#🦋 特性)
- [🦋 安装](#🦋 安装)
- [🦋 序列化](#🦋 序列化)
- [🦋 反序列化](#🦋 反序列化)
- [🦋 总结](#🦋 总结)
- [六:🔥 共勉](#六:🔥 共勉)
一:🔥 应用层
📚 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层
🦋 再谈 "协议"
协议是一种 "约定". socket api 的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "结构化的数据" 怎么办呢?
其实, 协议就是双方约定好的结构化的数据
🦋 网络版计算器
- 例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端
📚 约定方案一:
- 客户端发送一个形如"1+1"的字符串;
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格;
- ...
📚 约定方案二:
定义结构体
来表示我们需要交互的信息;- 发送数据时将这个
结构体按照一个规则转换成字符串
, 接收到数据的时候再按照相同的规则把字符串转化回结构体;- 这个过程叫做
"序列化"
和"反序列化"
🦋 序列化 和 反序列化
无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是 ok 的. 这种约定, 就是 应用层协议
但是, 为了让我们深刻理解协议, 我们打算自定义实现一下协议的过程。
- 我们采用方案 2, 我们也要体现协议定制的细节
- 我们要引入 序列化 和 反序列化 , 只不过我们直接采用现成的方案 -- jsoncpp库
- 我们要对 socket 进行字节流的读取处理
二:🔥 重新理解 read、 write、 recv、 send 和 tcp 为什么支持全双工
所以:
- 在任何一台主机上, TCP 连接既有发送缓冲区, 又有接受缓冲区, 所以, 在内核中, 可以在发消息的同时, 也可以收消息, 即全双工
- 这就是为什么一个 tcp sockfd 读写都是它的原因
- 实际数据什么时候发, 发多少, 出错了怎么办, 由 TCP 控制, 所以 TCP 叫做传输控制协议
- 那么,也就需要应用层保证自己报文的完整性
三:🔥 开始实现
🦋 定制协议
📚 基本结构
- 定制基本的结构化字段, 这个就是协议
报文的自描述字段:
"len\r\nx op y\r\n" : \r\n 不属于报文的一部分, 约定
cpp
#pragma once
#include <string>
#include <iostream>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
// _x _oper _y
class Request
{
public:
Request() : _x(0), _y(0), _oper(0)
{}
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
{
}
bool Serialize(std::string &out_string)
{
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::StreamWriterBuilder wb;
std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
std::stringstream ss;
w->write(root, &ss);
out_string = ss.str();
return true;
}
bool Deserialize(std::string in_string)
{
Json::Value root;
Json::Reader reader;
bool parsingSuccessful = reader.parse(in_string, root);
if(!parsingSuccessful)
{
std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
return false;
}
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
return true;
}
void Print()
{
std::cout << _x << std::endl;
std::cout << _oper << std::endl;
std::cout << _y << std::endl;
}
int X() const {return _x;}
int Y() const {return _y;}
char Oper() const {return _oper;}
private:
int _x;
int _y;
char _oper;
};
class Response
{
public:
Response(): _result(0), _code(0)
{}
Response(int result, int code) : _result(result), _code(code)
{
}
bool Serialize(std::string &out_string)
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::StreamWriterBuilder wb;
std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
std::stringstream ss;
w->write(root, &ss);
out_string = ss.str();
return true;
}
bool Deserialize(std::string in_string)
{
Json::Value root;
Json::Reader reader;
bool parsingSuccessful = reader.parse(in_string, root);
if(!parsingSuccessful)
{
std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
return false;
}
_result = root["result"].asInt();
_code = root["code"].asInt();
return true;
}
int Result() const {return _result;}
int Code() const {return _code;}
void SetResult(int result) {_result = result;}
void SetCode(int code) {_code = code;}
private:
int _result; // 结果
int _code; // 出错码: 0, 1, 2, 3, 4
};
四:🔥 为关于流式数据的处理
- 你如何保证你每次读取就能读完请求缓冲区的所有内容?
- 你怎么保证读取完毕或者读取没有完毕的时候, 读到的就是一个完整的请求呢?
- 处理 TCP 缓冲区中的数据, 一定要保证正确处理请求
cpp
const std::string Sep = "\r\n";
// 报头
bool Encode(std::string &message)
{
if(message.empty()) return false;
std::string package = std::to_string(message.size()) + Sep + message + Sep;
message = package;
return true;
}
// len\n{json}\n 解包
// 123\r\n{json}\r\n -> {json}
bool Decode(std::string &package, std::string *content)
{
auto pos = package.find(Sep);
if(pos == std::string::npos) return false;
std::string content_length_str = package.substr(0, pos);
int content_length = std::stoi(content_length_str);
int full_length = content_length_str.size() + content_length + 2 * Sep.size();
if(package.size() < full_length) return false;
*content = package.substr(pos + Sep.size(), content_length);
package.erase(0, full_length);
return true;
}
📚 完整的处理过程应该是:
五:🔥 Jsoncpp
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。 Jsoncpp 是开源的, 广泛用于各种需要处理 JSON 数据的 C++ 项目中。
🦋 特性
- 简单易用: Jsoncpp 提供了直观的 API, 使得处理 JSON 数据变得简单。
- 高性能: Jsoncpp 的性能经过优化, 能够高效地处理大量 JSON 数据。
- 全面支持: 支持 JSON 标准中的所有数据类型, 包括对象、 数组、 字符串、 数字、 布尔值和 null。
- 错误处理: 在解析 JSON 数据时, Jsoncpp 提供了详细的错误信息和位置, 方便开发者调试。
当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时, 确实存在不同的做法和工具类可供选择。 以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:
🦋 安装
cpp
C++
ubuntu: sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
🦋 序列化
📚 序列化指的是将数据结构或对象转换为一种可以存储或传输的格式(如字节流、JSON、XML 等)
Jsoncpp 提供了多种方式进行序列化:
- 使用
Json::StreamWriter
:
○ 优点: 提供了更多的定制选项, 如缩进、 换行符等。
○ 示例:
cpp
C++
#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" : "男"
}
- 使用 Json::Value 的
toStyledString
方法:
○ 优点: 将 Json::Value 对象直接转换为格式化的 JSON 字符串。
○ 示例:
cpp
C++
#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" : "男"
}
- 使用
Json::FastWriter
○ 优点: 比 StyledWriter 更快, 因为它不添加额外的空格和换行符。
○ 示例:
cpp
C++
#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;
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 提供了以下方法进行反序列化:
- 使用
Json::Reader:
○ 优点: 提供详细的错误信息和位置, 方便调试。
○ 示例:
cpp
C++
#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: 北京
- 使用 Json::CharReader 的派生类(不推荐了, 上面的足够了):
○ 在某些情况下, 你可能需要更精细地控制解析过程, 可以直接使用 Json::CharReader 的派生类。
○ 但通常情况下, 使用 Json::parseFromStream 或 Json::Reader 的 parse 方法就足够了。
🦋 总结
• toStyledString、 StreamWriter 和 FastWriter 提供了不同的序列化选项,你可以根据具体需求选择使用。
• Json::Reader 和 parseFromStream 函数是 Jsoncpp 中主要的反序列化工具,它们提供了强大的错误处理机制。
• 在进行序列化和反序列化时, 请确保处理所有可能的错误情况, 并验证输入和输出的有效性。
六:🔥 共勉
以上就是我对 【Linux】应用层自定义协议与序列化
的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉