【网络编程】自定义协议+Json序列化与反序列化

本文详细介绍了序列化与反序列化在TCP通信中的作用,以及如何设计自定义协议来确保数据完整传输。通过示例展示了服务端和客户端如何处理请求和响应,包括计算流程、报文长度头的使用,以及Json库在序列化和反序列化中的应用。
摘要由CSDN通过智能技术生成

一、序列化与反序列化概念

上一章讲解了TCP通信【网络编程】demo版TCP网络服务器实现,我们知道TCP是面向字节流的方式进行通信。
在这里插入图片描述
但是这里就会引发一个问题:怎么保证正好就读到一个完整的数据呢?

举个例子:我们使用QQ发送消息的时候别人接收到的不仅仅只有消息,而是包含了头像信息,昵称,消息。这就叫做结构化的数据。这些结构化的数据可以打包成一个报文(变成一个整体),这个过程就叫做序列化。而把这个整体报文解开的过程就叫做反序列化

结构化数据要先序列化再发送到网络中,收到序列字节流后,要先反序列化再使用。

而这里序列化和反序列化的过程用的就是业务协议

二、自定义协议设计网络计算机

2.1 服务端

自定义协议里要包含两各类,一个是请求,一个是响应
服务端会收到请求,客户端收到响应。

// 请求
class Request
{
   
public:
    
public:
    int _x = 0;
    int _y = 0;
    char _op = 0;
};

// 响应
class Response
{
   
public:
    int _exitcode = 0;// 退出码
    int _result = 0;// 结果
};

请求就是左操作符、右操作符和符号
响应包含了退出码和结果,如果正常结束退出码为0,如果有错误,我们可以自定义不同的退出码表示不同的错误。

2.1.1 服务端业务处理流程

先来看一下服务端处理数据流程

客户端发过来的数据已经序列化成了一个序列字节流数据(报文),所以服务端首先要先把报文反序列化,构成一个结构化请求对象Request。然后就可以进行计算处理形成一个Response对象,再序列化后发送给客户端。

可以看到计算处理这一步其实跟接收发送消息、序列化与反序列化没什么关系,所以可以把计算处理任务在服务端启动的时候传递进去

计算处理函数:
typedef std::function<bool(const Request& req, Response& resp)> func_t;
这里的req是输入型参数(已经反序列化好的对象),resp是输出型参数,为了获取计算结果。

2.1.2 TCP的发送与接收缓冲区

在这里插入图片描述

我们前面使用的write和read接口并不是直接往网络里发送数据或者从网络里读取数据,write其实是把数据拷贝到传输层的缓冲区,由TCP协议决定什么时候把缓冲区的数据发送到网络中。所以TCP协议也叫传输控制协议
发送数据的本质就是将数据从发送缓冲区拷贝到接收缓冲区。

所以客户端/服务端发送数据不会影响接受数据。
所以TCP是全双工的。

而这就会导致一个问题:可能数据堆积在缓冲区来不及度,一次会读取多个报文挨在一起。那么怎么保证读取完整报文呢?

2.1.3 保证读取完整报文

因为TCP是面向字节流的,所以要明确报文与报文的分界。
为什么要这样呢?举个例子:
现在要把两个数字合并成字符串发送,1、12,如果不处理的话就是"112",这样我们反序列化的时候就不知道到底怎么组合了。
而如果我们在分割的地方加一个符号比如,,序列化后:"1,12",这样就很容易拆分。

保证报文读取完整性的方法:
1️⃣ 定长: 规定长度,每次就读取这么多。
2️⃣ 特殊字符: 就是上面的方法。
3️⃣ 自描述方式: 比如在报文前面带上四个字节的字段,标识报文长度。

2.1.4 自定义协议——序列化与反序列化

先来看请求的序列化与反序列化

2.1.4.1 请求
int _x = 0;
int _y = 0;
char _op = 0;

我们希望序列化成这样:"_x _op _y"

#define SEP " "
#define SEP_LEN strlen(SEP)
#define SEP_LINE "\r\n"
#define SEP_LINE_LEN strlen(SEP_LINE)

// 请求
class Request
{
   
public:
    Request(int x, int y, char op)
       : _x(x)
       , _y(y)
       , _op(op)
    {
   }
    
    Request()
    {
   }

    // 序列化
    bool serialize(std::string* out/*输出型参数*/)
    {
   
        // "_x _op _y"
        std::string sx = std::to_string(_x);
        std::string sy = std::to_string(_y);
        *out = sx + SEP + _op + SEP + sy;
        return true;
    }

    // 反序列化
    bool deserialize(const std::string& in)
    {
   
        // "_x _op _y"
        auto lsep = in.find(SEP);
        auto rsep = in.rfind(SEP);
        if(lsep == std::string::npos || rsep == std::string::npos
        || lsep == rsep) return false;
        std::string sx = in.substr(0, lsep);
        std::string sy = in.substr(rsep + SEP_LEN);
        if(sx.empty() || sy.empty()) return false;
        _x = stoi(sx);
        _y = stoi(sy);
        _op = in[lsep + SEP_LEN];
        return true;
    }
public:
    int _x = 0;
    int _y = 0;
    char _op = 0;
};

这里的反序列化我们传进去的字符串已经把"\r\n"去掉了。
先来看响应的序列化与反序列化

2.4.1.2 响应

我们希望序列化成这样:"_exitcode _result"

// 响应
class Response
{
   
public:
    Response(int exitcode, int result)
        : _exitcode(exitcode)
        , _result(result)
    {
   }

    Response()
    {
   }

    // 序列化
    bool serialize(std::string* out/*输出型参数*/)
    {
   
        std::string se = std::to_string(_exitcode);
        std::string sr = std::to_string(_result);
        *out = se + SEP + sr;
        return true;
    }
    
    // 反序列化
    bool deserialize(const std::string& in)
    {
   
        // "_exitcode _result"
        
  • 45
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 43
    评论
评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

命由己造~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值