协议定制/序列化-反序列化(Linux视角)

目录

协议定制

序列化/反序列化现成方案

JSON:


协议定制

如果想要传输【结构化数据】,例如结构体的多个字段

cpp 复制代码
class User
{
public:
    std::string _url;      // 头像url
    std::string _time;     // 发送时间
    std::string _nickname; // 昵称
    std::string _msg;      // 消息
};

可以将多个字段通过固定格式生成一个字符串报文,例如这里每个字段用":"隔开,再将该字符串通过网络发出

另一端就会接受到该报文,再通过固定格式转换成User类的每个字段(前提是需要知道格式是什么)

在上面的操作中,将多个字段转换成一个报文字符串,就称为序列化

将报文字符串再转换成多个字段的过程,就称为反序列化

而User类就是我们定的业务协议!

但是,如果客户端发送了一段报文后,服务端没来得及接收,此时客户端又发送了几段报文,要怎么保证服务端收到的是**【一个】**完成的报文?

一般有三种做法:定长、特殊符号、自描述方式

例如,每个报文前面加上一段固定大小的报头,该报头内描述的是报文的长度

TCP是全双工通信,它内部分别有发送缓冲区和接收缓冲区,应用层的缓冲区即我们自己定义的buffer\[\]

在报头中可以添加对于整个报文的描述信息,例如有效载荷的大小,这样就可以确定一个完整的报文的边界在哪里

protocol.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>

#include "log.hpp"

const char *SEP = " ";                     // 序列化时的分隔符
const int SEP_LEN = strlen(SEP);           // 分隔符长度
const char *SEP_LINE = "\r\n";             // 序列化时的行分隔符
const int SEP_LINE_LEN = strlen(SEP_LINE); // 行分隔符长度
// const char *LINE_SEP = "\r\n";   // 报文结尾

//"x op y" -> "有效载荷大小"\r\n"x op y"\r\n
bool AddHeader(std::string &payload) // 为 有效载荷 添加报头
{
    std::string message = std::to_string(payload.size());
    message += SEP_LINE + payload + SEP_LINE;
    payload = message;
    return true;
}

//"有效载荷大小"\r\n"x op y"\r\n -> "x op y"
bool DropHeader(std::string &package) // 为 报文 剥开报头
{
    auto sep_line_pos = package.find(SEP_LINE);
    if (sep_line_pos == std::string::npos)
        return false;

    int payload_len = stoi(package.substr(0, sep_line_pos)); // 有效载荷的长度
    std::string message = package.substr(sep_line_pos + SEP_LINE_LEN, payload_len);

    package = message;
    return true;
}

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

    bool serialization(std::string &out) // 序列化
    {
        // 序列化为 "x op y"的格式

        std::string x_str = std::to_string(_x);
        std::string y_str = std::to_string(_y);
        out = x_str + SEP + _op + SEP + y_str;
        return true;
    }

    bool deserialization(const std::string &in) // 反序列化
    {
        // x op y
        // 第一个分隔符和第二个分隔符的迭代器位置
        auto left_sep_pos = in.find(SEP);
        auto right_sep_pos = in.rfind(SEP);

        if (left_sep_pos == right_sep_pos)
            return false;

        _x = stoi(in.substr(0, left_sep_pos));
        _op = in[left_sep_pos + SEP_LEN];
        _y = stoi(in.substr(right_sep_pos + SEP_LEN));
        return true;
    }

public:
    // x op y
    int _x;
    char _op;
    int _y;
};

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

    bool serialization(std::string &out) // 序列化
    {
        // exitcode result
        std::string exitcode = std::to_string(_exitcode);
        std::string result = std::to_string(_result);
        out = exitcode + SEP + result;
        return true;
    }

    bool deserialization(const std::string &in) // 反序列化
    {
        auto sep_pos = in.find(SEP);
        if (sep_pos == std::string::npos)
            return false;

        _exitcode = stoi(in.substr(0, sep_pos));
        _result = stoi(in.substr(sep_pos + SEP_LEN));
        return true;
    }

public:
    int _exitcode; // 例如0表示成功,1表示除零,2表示....
    int _result;
};

bool recvmessage(int sockfd, std::string &inbuffer, std::string &message) // 读取一个完整的报文
{
    char buffer[1024] = {0};
    while (true)
    {
        int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer += buffer;

            // 判断此时inbuffer中有没有一个完整的报文(TCP是面向字节流,当读取时可能对方还没发送完)
            auto sep_line_pos = inbuffer.find(SEP_LINE);
            if (sep_line_pos == std::string::npos)
                continue; // 继续读
            std::string payload_len_str = inbuffer.substr(0, sep_line_pos);
            int payload_len = stoi(payload_len_str);
            int total_len = payload_len_str.size() + 2 * SEP_LINE_LEN + payload_len;
            if (inbuffer.size() < total_len) // 还没读取到一个完整的报文,继续读
                continue;

            // 此时inbuffer中至少有一个完整的报文,提出来并在inbuffer中去掉
            message = inbuffer.substr(0, total_len);
            inbuffer.erase(0, total_len);
            break;
        }
        else
            return false;
    }
    return true;
}

服务端CalServer:

cpp 复制代码
//CalServer.cpp:
#include <iostream>
#include "CalServer.hpp"
#include "log.hpp"

using namespace std;
using namespace Server;

void usage(string proc) // 使用手册
{
    cout << GREEN << "\nUsage: \n\t" << ED << RED << proc << " [port]\n\n"
         << ED;
}

//根据req,计算出结果后填充到res中
void cal(const Request& req, Response& res)
{
    switch(req._op)
    {
        case '+':
	        res._result = req._x + req._y;
	        break;
        case '-':
	        res._result = req._x - req._y;
	        break;
        case '*':
            res._result = req._x * req._y;
            break;
        case '/':
            if(req._y == 0)
                res._exitcode = DEV_ZERO;
            else
                res._result = req._x / req._y;
            break;
        case '%':
            if(req._y == 0)
                res._exitcode = MOD_ZERO;
            else
                res._result = req._x % req._y;
            break;
        default: 
            res._exitcode = OP_ERR;
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);//字符串port转整数

    CalServer server(port);

    server.init();
    server.start(cal);
    return 0;
}


// CalServer.hpp:
#include <iostream>
#include <functional>
#include <string>

#include <cstring>
#include <cerrno>
#include <csignal>

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "log.hpp"
#include "protocol.hpp"

namespace Server
{
    // typedef function<void (std::string)> func_t;//回调函数类型

    typedef std::function<void(const Request &req, Response &res)> func_t; // 将请求处理为响应
    const int gbacklog = 5;                                                // 全局的全连接队列长度

    void handlerEnter(int sockfd, func_t func)
    {
        std::string inbuffer;
        while (true)
        {
            // 读取一个完整的请求
            std::string req_str;
            if (!recvmessage(sockfd, inbuffer, req_str))
            {
                LogMessage(DEBUG, (char *)"客户端退出...");
                break;
            }
            LogMessage(DEBUG, (char *)"请求(Request) 读取报文成功");

            if (!DropHeader(req_str))
            {
                LogMessage(ERROR, (char *)"请求(Request) DropHeader失败,可能是报文格式不对!读取下一段报文...");
                continue;
            }
            LogMessage(DEBUG, (char *)"请求(Request) 取出有效载荷成功");

            // 反序列化
            Request request;
            if (!request.deserialization(req_str))
            {
                LogMessage(ERROR, (char *)"请求(Request) 有效载荷反序列化失败");
                continue;
            }
            LogMessage(DEBUG, (char *)"请求(Request) 有效载荷反序列化成功");

#ifdef test // DEBUG时输出
            std::cout << request._x << request._op << request._y << std::endl;
#endif

            Response response;
            func(request, response); // 计算器部分 x op y = result,得到结构化的响应;

            // 序列化响应
            std::string res_str;
            response.serialization(res_str);
            LogMessage(DEBUG, (char *)"响应(Response) 序列化成功");
            // 添加报头
            if (!AddHeader(res_str))
            {
                LogMessage(ERROR, (char *)"响应(Response) 为有效载荷添加报头失败,重新读取下一段报文...");
                continue;
            }
            LogMessage(DEBUG, (char *)"响应(Response) 添加报头成功");

            int n = send(sockfd, res_str.c_str(), res_str.size(), 0); //[TODO]有bug
            if(n == -1)
            {
               LogMessage(ERROR, (char *)"响应(Response) send失败,重新读取下一段报文");
               continue;
            }
            else
            {
                LogMessage(DEBUG, (char *)"响应(Response) 报文发送成功");
            }
        }
        exit(0); //若break跳出,要关闭该孙子进程,否则会出函数继续accept等待连接
    }

    class CalServer
    {
    public:
        CalServer(uint16_t port) : _port(port) {}
        CalServer(std::string ip, uint16_t port)
            : _ip(ip), _port(port)
        {
        }

        void init()
        {
            // 创建监听套接字
            _ListenSock = socket(AF_INET, SOCK_STREAM, 0);
            if (_ListenSock == -1)
            {
                LogMessage(FATAL, (char *)"socket创建监听套接字失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                exit(SOCKET_ERR);
            }
            LogMessage(DEBUG, (char *)"socket创建监听套接字成功");

            // bind绑定ip+port
            struct sockaddr_in ServerAddr;
            memset(&ServerAddr, 0, sizeof(ServerAddr));

            ServerAddr.sin_family = AF_INET;
            ServerAddr.sin_port = htons(_port);

            if (inet_pton(AF_INET, _ip.c_str(), &ServerAddr.sin_addr) != 1)
            {
                LogMessage(FATAL, (char *)"点分十进制ip转网络序列失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                exit(INETPN_ERR);
            }
            LogMessage(DEBUG, (char *)"点分十进制ip转网络序列成功");

            if (bind(_ListenSock, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) != 0)
            {
                LogMessage(FATAL, (char *)"bind绑定失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                exit(BIND_ERR);
            }
            LogMessage(DEBUG, (char *)"bind绑定成功");

            // 开启监听状态
            if (listen(_ListenSock, gbacklog) != 0)
            {
                LogMessage(FATAL, (char *)"listen监听状态开启失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                exit(LISTEN_ERR);
            }
            LogMessage(DEBUG, (char *)"listen监听状态开启成功");
        }

        void start(func_t func)
        {

            signal(SIGCHLD, SIG_IGN); // OS自动回收子进程资源
            while (true)
            {
                // 建立连接
                struct sockaddr_in ClientAddr;
                memset(&ClientAddr, 0, sizeof(ClientAddr));
                socklen_t socklen = sizeof(ClientAddr);
                int sockfd = accept(_ListenSock, (struct sockaddr *)&ClientAddr, &socklen);
                if (sockfd == -1)
                {
                    LogMessage(FATAL, (char *)"accept建立新连接失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                    exit(ACCEPT_ERR);
                }
                LogMessage(DEBUG, (char *)"accept建立新连接成功,sockfd = %d", sockfd);

                pid_t pid = fork();
                if (pid == 0) // 子进程
                {
                    close(_ListenSock); // 关掉无用文件描述符
                    if (fork() > 0)     // 子进程本身退出
                        exit(0);
                    // 孙子进程,被OS领养,不等待也不会变成僵尸进程
                    handlerEnter(sockfd, func);
                }

                // close(sockfd); // 父进程关掉该文件描述符,防止文件描述符被用完
            }
        }

    private:
        int _ListenSock;             // listen监听套接字
        std::string _ip = "0.0.0.0"; // 默认接收所有ip
        uint16_t _port;              // 服务器端口号
        // func_t _callback;
    };
}

客户端:

cpp 复制代码
//CalClient.cpp:
#include <iostream>
#include "CalClient.hpp"
using namespace std;
using namespace Client;

void usage(string proc) // 使用手册
{
    cout << GREEN << "\nUsage:\n\t" ED RED << proc << " [ip] [port]\n\n" ED;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[2]);

    CalClient client(argv[1], port);
    
    client.init();
    client.start();
    return 0;
}

//CalClient.hpp:
#pragma once
#include <iostream>
#include <string>

#include <cstring>
#include <cerrno>
#include <cassert>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "log.hpp"
#include "protocol.hpp"

namespace Client
{
    class CalClient
    {
    public:
        CalClient(std::string ip, uint16_t port)
            : _ip(ip), _port(port)
        {
        }
        ~CalClient()
        {
        }

        void init()
        {
            // 创建套接字
            _SocketFd = socket(AF_INET, SOCK_STREAM, 0);
            if (_SocketFd == -1)
            {
                LogMessage(FATAL, (char *)"socket创建套接字失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                exit(SOCKET_ERR);
            }
            LogMessage(DEBUG, (char *)"socket创建套接字成功");

            // 无需显式bind绑定

            // 初始化服务端的addr
            memset(&_ServerAddr, 0, sizeof(_ServerAddr));
            _ServerAddr.sin_family = AF_INET;
            _ServerAddr.sin_port = htons(_port);

            if (inet_pton(AF_INET, _ip.c_str(), &_ServerAddr.sin_addr) != 1)
            {
                LogMessage(FATAL, (char *)"点分十进制ip转网络序列失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                exit(INETPN_ERR);
            }
            LogMessage(DEBUG, (char *)"点分十进制ip转网络序列成功");
        }

        void start()
        {
            // 向服务器申请建立连接
            if (connect(_SocketFd, (struct sockaddr *)&_ServerAddr, sizeof(_ServerAddr)) != 0)
            {
                LogMessage(FATAL, (char *)"connect申请建立连接失败, 错误码: %d, 错误描述:%s", errno, strerror(errno));
                std::cout << strerror(errno);
                exit(CONNECT_ERR);
            }
            LogMessage(DEBUG, (char *)"connect申请建立连接成功");

            // 收发消息
            std::string message;
            std::string inbuffer;
            while (true)
            {
                // 写入
                do
                {
                    std::cout << RED "请输入表达式# " ED;
                    std::getline(std::cin, message);
                } while (message.size() == 0); // 防止写入0字节数据

                // 判断格式是否正确
                if (!isFormat(message))
                {
                    LogMessage(ERROR, (char *)"输入格式错误,请重新输入...");
                    continue;
                }

                // 将字符串格式化为Request
                Request request = ParseMes(message);

                // 序列化并添加报头(变为报文)后发送
                std::string req_str;
                request.serialization(req_str);
                assert(AddHeader(req_str));
                send(_SocketFd, req_str.c_str(), req_str.size(), 0); //[TODO]有Bug

                // 读取服务端发过来的报文
                if (!recvmessage(_SocketFd, inbuffer, message)) // 若还没有一个完整的报文,就回去继续读
                    continue;
                LogMessage(DEBUG, (char*)"请求(Request) 读取报文成功");
                // 到这里一定有至少一个完整的报文
                if (!DropHeader(message))
                {
                    LogMessage(ERROR, (char*)"请求(Request) DropHeader失败,请重新输入表达式...");
                    continue;
                }
                    
                LogMessage(DEBUG, (char*)"请求(Request) 读取报文成功");
                // exitcode result
                Response response;
                response.deserialization(message);
                LogMessage(DEBUG, (char*)"请求(Request) 反序列化成功");

                std::cout << "exitcode: " << response._exitcode << std::endl
                          << "result: " << response._result << std::endl;
            }
        }

    private:
        Request ParseMes(const std::string message) // 将字符串解析为Request
        {
            int i = 0;
            int status = 0;
            std::string left, right; // 左操作数和右操作数
            char op;
            // 简易状态机
            while (i < message.size())
            {
                switch (status)
                {
                case 0: // 左操作数部分
                    left += message[i++];
                    if (!isdigit(message[i]))
                    {
                        op = message[i++];
                        status = 1;
                    }
                    break;
                case 1: // 右操作数部分
                    right += message[i++];
                }
            }

            return Request(stoi(left), op, stoi(right));
        }

        bool isFormat(const std::string message) // 判断是否符合num op num的格式
        {
            int len = message.size();
            if (len < 3)
                return false; // 最小长度为3
            int i = 0;
            while (i < len)
            {
                if (isdigit(message[i]))
                {
                    i++;
                    continue;
                }
                if (!isdigit(message[i]))
                    break;
            }
            if (i != 0 && i < len && !isdigit(message[i]))
                i++;
            else
                return false; // 没有操作符
            while (i < len)
            {
                if (isdigit(message[i++]))
                    continue;
                else
                    return false; // 有操作数有非数字
            }
            return true;
        }

    private:
        int _SocketFd;                  // 通信的套接字
        std::string _ip;                // 服务端的ip
        uint16_t _port;                 // 服务端的端口号
        struct sockaddr_in _ServerAddr; // 服务端的addr
    };
}

该程序分为了三部分:

  • 建立和断开连接部分
  • 序列化/反序列化部分
  • 协议定制部分

这正好对应着OSI模型的会话层、表示层、应用层 !而这三层合起来也就是TCP/IP的应用层

如果要用OSI模型,就代表会话层/表示层也要交给OS内定,那么我们的建立连接和序列化/反序列化的部分都不能自己定制,所以OSI没有得以推广,而是用这三层都可以用户定制的TCP/IP模型

序列化/反序列化现成方案

上面的代码中,是自己实现的序列化/反序列化,麻烦且不好用

实际上已经有很多现成方案,例如:JSON、protobuf、xml

在本篇介绍JSON

JSON:

用下面命令安装jsoncpp

bash 复制代码
sudo yum install -y jsoncpp-devel

此时就可以看到jsoncpp的库文件和动态库文件了

在头文件处包含<json/jsoncpp.h>即可用

Json::Value对象用于存储Json的Key/Value键值对

要将结构化数据转换为Json字符串,需要用到Json::StreamWriterBuilder 对象和 Json::StreamWriter 对象

它们采用了建造者模式 ,前者用于配置后续生产出的StreamWriter对象的相关属性并负责生产StreamWriter对象

后者即用于将Json::Value对象转为字符串,相当于工厂与工人的关系

例如,下面代码定义了一个工厂:builder,将该工厂设置属性后,直接调用writeString接口,该接口用builder工厂生产一个临时工人(StreamWriter对象),并返回该工人所转化的string对象

cpp 复制代码
Json::Value Data;
Data["left operand"] = _x;
Data["right operand"] = _y;
Data["oper"] = _op;

Json::StreamWriterBuilder builder;      // 用于配置转换时的属性
builder["indentation"] = "";            // 设置缩进为空字符串,输出紧凑的单行 JSON
out = Json::writeString(builder, Data); // 返回Json的string格式

当反序列化时,需要用到CharReaderBuilder 对象和CharReader 对象,它们两者同样是建造者模式,前者用于配置后续生产的工人(CharReader)的相关属性,后者用于将字符串反序列化为Json::Value对象

例如,下面代码定义了一个工厂builder,没有需要配置的属性,该工厂的newCharReader可以在堆区中开辟一个CharReader对象,因此返回指针,这里用智能指针维护(这就是工厂生产出来的工人),后续由该工人负责解析字符串为Json::Value对象(调用parse接口)

cpp 复制代码
Json::Value Data;
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); // 用建造者生产一个读者对象
std::string errs;                                                  // 若reader.parse失败,需要向errs中写入错误信息
bool ok = reader->parse(in.c_str(), in.c_str() + in.size(), &Data, &errs);
if (ok) // 解析成功,进行反序列化
{
    _x = Data["left operand"].asInt();
    _y = Data["right operand"].asInt();
    _op = Data["oper"].asInt();
}
else // 解析失败,打印失败信息
{
    LogMessage(ERROR, &errs[0]); // &errs[0]表示char*类型的字符串
    return false;
}

下面为用Json进行序列化/反序列化的protocol.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <memory>
#include <json/json.h>
#include <string>
#include <cstring>
#include <sys/socket.h>

#include "log.hpp"

const char *SEP = " ";                     // 序列化时的分隔符
const int SEP_LEN = strlen(SEP);           // 分隔符长度
const char *SEP_LINE = "\r\n";             // 序列化时的行分隔符
const int SEP_LINE_LEN = strlen(SEP_LINE); // 行分隔符长度
// const char *LINE_SEP = "\r\n";   // 报文结尾

//"x op y" -> "有效载荷大小"\r\n"x op y"\r\n
bool AddHeader(std::string &payload) // 为 有效载荷 添加报头
{
    std::string message = std::to_string(payload.size());
    message += SEP_LINE + payload + SEP_LINE;
    payload = message;
    return true;
}

//"有效载荷大小"\r\n"x op y"\r\n -> "x op y"
bool DropHeader(std::string &package) // 为 报文 剥开报头
{
    auto sep_line_pos = package.find(SEP_LINE);
    if (sep_line_pos == std::string::npos)
        return false;

    int payload_len = stoi(package.substr(0, sep_line_pos)); // 有效载荷的长度
    std::string message = package.substr(sep_line_pos + SEP_LINE_LEN, payload_len);

    package = message;
    return true;
}

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

    bool serialization(std::string &out) // 序列化
    {
#ifdef MY_SERIALIZATION
        // 序列化为 "x op y"的格式

        std::string x_str = std::to_string(_x);
        std::string y_str = std::to_string(_y);
        out = x_str + SEP + _op + SEP + y_str;
        return true;

#else // Json格式序列化
        Json::Value Data;
        Data["left operand"] = _x;
        Data["right operand"] = _y;
        Data["oper"] = _op;

        Json::StreamWriterBuilder builder;      // 用于配置转换时的属性
        builder["indentation"] = "";            // 设置缩进为空字符串,输出紧凑的单行 JSON
        out = Json::writeString(builder, Data); // 返回Json的string格式

        return true;
#endif
    }

    bool deserialization(const std::string &in) // 反序列化
    {
#ifdef MY_SERIALIZATION
        // x op y
        // 第一个分隔符和第二个分隔符的迭代器位置
        auto left_sep_pos = in.find(SEP);
        auto right_sep_pos = in.rfind(SEP);

        if (left_sep_pos == right_sep_pos)
            return false;

        _x = stoi(in.substr(0, left_sep_pos));
        _op = in[left_sep_pos + SEP_LEN];
        _y = stoi(in.substr(right_sep_pos + SEP_LEN));
        return true;

#else // Json格式反序列化
        Json::Value Data;
        Json::CharReaderBuilder builder;
        std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); // 用建造者生产一个读者对象
        std::string errs;                                             // 若reader.parse失败,需要向errs中写入错误信息
        bool ok = reader->parse(in.c_str(), in.c_str() + in.size(), &Data, &errs);
        if (ok) // 解析成功,进行反序列化
        {
            _x = Data["left operand"].asInt();
            _y = Data["right operand"].asInt();
            _op = Data["oper"].asInt();
        }
        else // 解析失败,打印失败信息
        {
            LogMessage(ERROR, &errs[0]); // &errs[0]表示char*类型的字符串
            return false;
        }
        return true;
#endif
    }

public:
    // x op y
    int _x;
    char _op;
    int _y;
};

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

    bool serialization(std::string &out) // 序列化
    {
#ifdef MY_SERIALIZATION
        // exitcode result
        std::string exitcode = std::to_string(_exitcode);
        std::string result = std::to_string(_result);
        out = exitcode + SEP + result;
        return true;

#else // Json格式序列化
        Json::Value Data;
        Data["exitcode"] = _exitcode;
        Data["result"] = _result;

        Json::StreamWriterBuilder writer;      // 用于将Json::Value转为Json格式字符串
        writer["indentation"] = "";            // 设置缩进为空字符串,输出紧凑的单行 JSON
        out = Json::writeString(writer, Data); // 返回Json的string格式

        return true;
#endif
    }

    bool deserialization(const std::string &in) // 反序列化
    {
#ifdef MY_SERIALIZATION
        auto sep_pos = in.find(SEP);
        if (sep_pos == std::string::npos)
            return false;

        _exitcode = stoi(in.substr(0, sep_pos));
        _result = stoi(in.substr(sep_pos + SEP_LEN));
        return true;

#else // Json格式反序列化
        Json::Value Data;
        Json::CharReaderBuilder builder;
        std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); // 用建造者生产一个读者对象
        std::string errs;                                             // 若reader.parse失败,需要向errs中写入错误信息
        bool ok = reader->parse(in.c_str(), in.c_str() + in.size(), &Data, &errs);
        if (ok) // 解析成功,进行反序列化
        {
            _exitcode = Data["exitcode"].asInt();
            _result = Data["result"].asInt();
        }
        else // 解析失败,打印失败信息
        {
            LogMessage(ERROR, &errs[0]); // &errs[0]表示char*类型的字符串
            return false;
        }
        return true;
#endif
    }

public:
    int _exitcode; // 例如0表示成功,1表示除零,2表示....
    int _result;
};

bool recvmessage(int sockfd, std::string &inbuffer, std::string &message) // 读取一个完整的报文
{
    char buffer[1024] = {0};
    while (true)
    {
        int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer += buffer;

            // 判断此时inbuffer中有没有一个完整的报文(TCP是面向字节流,当读取时可能对方还没发送完)
            auto sep_line_pos = inbuffer.find(SEP_LINE);
            if (sep_line_pos == std::string::npos)
                continue; // 继续读
            std::string payload_len_str = inbuffer.substr(0, sep_line_pos);
            int payload_len = stoi(payload_len_str);
            int total_len = payload_len_str.size() + 2 * SEP_LINE_LEN + payload_len;
            if (inbuffer.size() < total_len) // 还没读取到一个完整的报文,继续读
                continue;

            // 此时inbuffer中至少有一个完整的报文,提出来并在inbuffer中去掉
            message = inbuffer.substr(0, total_len);
            inbuffer.erase(0, total_len);
            break;
        }
        else
            return false;
    }
    return true;
}
相关推荐
阿成学长_Cain4 小时前
Linux dirs命令详解|Bash目录堆栈管理快速切换目录实战教程
linux·运维·前端·数据库
山登绝顶我为峰 3(^v^)35 小时前
C/C++ 交叉编译方法
java·c语言·c++
ICECREAM5 小时前
Linux 进程间通讯(IPC)——总结
linux
驭渊的小故事5 小时前
网络原理01(两千字解析)
网络
想你依然心痛6 小时前
嵌入式Linux安全加固:SELinux、Capabilities与Seccomp——强制访问控制与沙箱
linux·运维·安全
KaMeidebaby7 小时前
卡梅德生物技术快报|小 RNA 适配体合成 + 多方法亲和力表征全流程标准化操作手册
前端·网络·数据库·人工智能·算法
MDM.Plus7 小时前
苹果MDM技术演进:从远程控制到设备信任体系的构建
运维·服务器·安全·ios·mdm·手机店
REDcker8 小时前
macOS 挂载 Linux 远程目录详解
linux·运维·macos