Linux网络编程:自定义应用层协议与 JSON 序列化

一、为什么需要自定义协议?

之前我们已经知道,应用层协议本质上就是通信双方约定好的数据格式和处理规则。

如果只是传输简单的字符串,直接发送和接收就可以。

但如果需要传输结构化数据,比如一个计算请求:

cpp 复制代码
int x;
int y;
char oper;

就需要考虑如何组织这些数据,以及接收方应该按照什么规则解析。

这时候,我们就可以通过自定义协议来解决。

本篇继续使用网络版计算器作为例子,看看如何设计一套简单的应用层协议。

二、设计 Request 和 Response

首先,我们需要明确客户端和服务器之间要交换什么数据。

对于计算器来说,客户端需要发送两个操作数和一个运算符。

因此可以定义一个请求类:

cpp 复制代码
class Request
{
private:
    int _data_x;
    int _data_y;
    char _oper;
};

其中:

  • _data_x:第一个操作数;
  • _data_y:第二个操作数;
  • _oper:运算符,支持 + - * / %。

服务器计算完成之后,还需要把结果返回给客户端。

因此可以定义一个响应类:

cpp 复制代码
class Response
{
private:
    int _result;
    int _code;
};

其中:

  • _result:运算结果;
  • _code:运算状态。

这样,请求和响应的数据结构就确定了。

但是,这还只是 C++ 内部的数据结构,我们还需要解决如何将它们转换成网络中可以传输的数据。

三、使用 Jsoncpp 实现序列化

如果让我们自己设计一套字符串格式,再手动解析每个字段,当然也可以实现。

但是这样需要处理大量字符串操作,代码也比较繁琐。

因此,可以使用现成的 JSON 库------Jsoncpp。

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库,可以帮助我们完成 JSON 的序列化和反序列化。

首先,需要包含头文件:

cpp 复制代码
#include <jsoncpp/json/json.h>

1. 序列化

假设现在有一个 Request 对象:

cpp 复制代码
Request req(10, 20, '+');

我们希望将其中的数据转换成 JSON 字符串。

可以使用 Json::Value 创建 JSON 对象:

cpp 复制代码
Json::Value root;

root["datax"] = _data_x;
root["datay"] = _data_y;
root["oper"] = _oper;

这里的 root 就是一个 JSON 对象。

通过 [] 可以设置对应的键值。

接下来使用 Json::FastWriter 将 JSON 对象转换成字符串:

cpp 复制代码
Json::FastWriter writer;

*out = writer.write(root);

这样就完成了序列化。

例如,原本的结构化数据:

text 复制代码
_data_x = 10
_data_y = 20
_oper = +

就可以转换成类似下面的 JSON 字符串:

json 复制代码
{"datax":10,"datay":20,"oper":43}

这里的 43 是字符 '+' 对应的 ASCII 值,具体表现与字符在 JSON 中的存储方式有关。

2. 反序列化

发送方完成序列化之后,接收方就可以得到 JSON 字符串。

接下来需要把字符串还原成 Request 对象。

首先创建 JSON 对象和解析器:

cpp 复制代码
Json::Value root;
Json::Reader reader;

然后调用 parse:

cpp 复制代码
bool res = reader.parse(in, root);

如果解析成功,就可以取出其中的数据:

cpp 复制代码
if(res)
{
    _data_x = root["datax"].asInt();
    _data_y = root["datay"].asInt();
    _oper = root["oper"].asInt();
}

这里的 asInt() 用于将 JSON 中的值转换成整数。

这样就完成了反序列化。

整个过程可以理解为:

text 复制代码
发送方:

Request
   |
   | Serialize
   v
JSON字符串
   |
   | 网络传输
   v

接收方:

JSON字符串
   |
   | Deserialize
   v
Request

需要注意,反序列化之前必须判断解析是否成功,不能直接认为收到的数据一定是合法 JSON。

四、Response 同样需要序列化

Request 是客户端发送给服务器的请求,Response 则是服务器返回给客户端的响应。

因此,Response 也需要提供自己的序列化和反序列化方法。

例如:

cpp 复制代码
bool Serialize(std::string *out)
{
    Json::Value root;

    root["result"] = _result;
    root["code"] = _code;

    Json::FastWriter writer;

    *out = writer.write(root);

    return true;
}

反序列化:

cpp 复制代码
bool Deserialize(std::string &in)
{
    Json::Value root;
    Json::Reader reader;

    bool res = reader.parse(in, root);

    if(res)
    {
        _result = root["result"].asInt();
        _code = root["code"].asInt();
    }

    return res;
}

这样,请求和响应都可以独立完成序列化与反序列化。

五、Jsoncpp 中常用的工具

除了上面使用的 Json::FastWriter 和 Json::Reader,Jsoncpp 还提供了其他工具。

1. 序列化工具

工具 作用
toStyledString() 将 JSON 对象转换成格式化的 JSON 字符串
Json::StreamWriter 提供更多格式定制选项
Json::FastWriter 快速生成 JSON 字符串,输出比较紧凑

例如:

cpp 复制代码
Json::Value root;

root["name"] = "joe";
root["age"] = 20;

std::string s = root.toStyledString();

如果使用 FastWriter:

cpp 复制代码
Json::FastWriter writer;

std::string s = writer.write(root);

两种方式都可以完成序列化,只是生成的字符串格式不同。

2. 反序列化工具

cpp 复制代码
Json::Reader

使用方法:

cpp 复制代码
Json::Reader reader;
Json::Value root;

bool res = reader.parse(json_string, root);

如果解析成功,就可以通过键访问 JSON 中的数据。

另外,也可以通过 Json::Value 提供的类型转换函数获取对应的数据:

cpp 复制代码
root["name"].asString();
root["age"].asInt();
root["score"].asDouble();

六、总结

这部分主要解决了一个问题:如何将 C++ 中的结构化数据转换成可以传输的字符串。

通过自定义 Request 和 Response,我们确定了需要传输的数据结构。

再借助 Jsoncpp,就可以方便地完成序列化和反序列化。

这里需要区分两个概念:

  • 自定义协议:规定通信双方应该传输什么数据,以及数据的组织规则。
  • 序列化:将结构化数据转换成约定的传输形式。
  • 反序列化:将传输形式的数据还原成结构化数据。

不过,仅仅完成序列化还不够。

因为 TCP 是字节流协议,即使我们成功生成了一条完整的 JSON 字符串,也不代表一次 recv 就一定能够收到完整的数据。

这个问题就是下一篇要解决的内容。

相关推荐
青瓦梦滋25 分钟前
Redis主从复制
服务器·数据库·redis·缓存·bootstrap
顧棟1 小时前
INFINI Gateway 服务器端口重用(SO_REUSEPORT)实现与分析
网络·gateway
沫璃染墨1 小时前
从零入门计算机网络系列(十八):TCP连接管理详解——从三次握手与四次挥手到TIME_WAIT机制
运维·服务器·网络·c++·网络协议·tcp/ip·tcp
Felven2 小时前
华为鲲鹏920s芯片serdestool工具使用说明
linux·运维·服务器·华为·鲲鹏920·serdestool
路灯上の马陈2 小时前
Java 内部类 学习笔记
笔记·学习
LabVIEW开发2 小时前
Omron PLC:接 LabVIEW 常踩的 5 个坑
服务器·网络·labview·labview知识·labview功能·labview程序
莫晓燚2 小时前
mall 电商项目完整部署---实战
linux·运维·docker·容器
咋的啥名都用不了呢~2 小时前
Linux项目部署5(安装Nginx的插件)
linux
希和20272 小时前
51 单片机入门笔记:江科大课程代码逐行讲解(按键控制 LED 流水灯)
笔记·单片机·mongodb