Bootstrap

自定义协议

1. 问题引入

问题:TCP是面向字节流的(TCP不关心发送的数据是消息、文件还是其他任何类型的数据。它简单地将所有数据视为一个字节序列,即字节流。这意味着TCP不会对发送的数据进行任何特定的边界划分,它只是确保数据的顺序和完整性。),这怎么能保证,读上来的数据是一个 完整 的报文呢?
解答:通过协议(Protocol)来约定。协议定义了数据交换的规则和标准,使得不同设备之间能够相互通信和理解。
例如:

  1. 固定长度报文: 如果每个报文的长度都是固定的,那么接收方可以简单地读取固定数量的字节来构成一个完整的报文。
  2. 长度前缀: 在报文的开始处添加一个字段,指定了报文的长度。接收方首先读取长度字段,然后根据指定的长度读取后续的字节来构成完整的报文。
  3. 特殊分隔符: 使用一个特殊的字节序列或字符串作为报文的分隔符。接收方在流中搜索这个分隔符来确定报文的边界。例如,HTTP协议使用"\r\n\r\n"作为请求头和请求体的分隔符。

其它知识:传输层TCP是全双工的,意味着,TCP的收发是可以同时进行的。亦即接收的时候可以发送,发送的时候也可以接收,两者互不冲突,可同时进行。实际上客户端和服务端维护者两个缓冲区

image-20241113202509891

2. 协议定制

2.1 序列化和反序列化的概念

序列化(Serialization)

序列化是指将对象的状态信息转换为可以存储或传输的格式(如JSON、XML、二进制等格式)的过程。序列化后的数据可以写入到文件中,或者通过网络发送到其他计算机。序列化的主要目的包括:

  1. 数据持久化:将内存中的数据结构保存到文件中,以便在程序下次运行时能够恢复。
  2. 网络传输:在网络上传输数据时,需要将复杂的数据结构转换为一种可以在网络上传输的格式。
  3. 跨语言和平台:不同的编程语言和平台之间交换数据时,需要一种中间格式来实现互操作性。

反序列化(Deserialization)

反序列化是序列化的逆过程,它是指将序列化后的数据(如文件中的数据或网络上接收到的数据)转换回原始的数据结构或对象状态的过程。反序列化使得程序能够从持久化的数据中恢复对象,或者接收并处理来自其他程序的数据。

序列化和反序列化的用途

  1. 数据库存储:将对象序列化后存储到数据库中,需要时再反序列化以恢复对象。
  2. 网络通信:在分布式系统或网络应用中,对象需要在网络上传输,因此需要序列化和反序列化。
  3. 缓存:将对象序列化后存储在缓存中,可以减少内存的使用。
  4. 消息队列:在使用消息队列(如RabbitMQ、Kafka)时,消息内容通常需要序列化。

2.2 网络版计算器 (服务端)

我们需要实现一个服务器版的加法器,在客户端把要计算的两个加数发过去, 然后由服务器进行计算,最后再把结果返回给客户端。

自己定制序列化规则:比如要计算a+b的结果,将其改为"len\n""a + b\n",第一个len相当于报头,第二个字符串相当于报文的有效载荷

10+20 变为 "7\n""10 + 20\n"

2.2.1 自己定义协议

自定义协议 Protocol.hpp

#pragma once
#include <iostream>
#include <string>
using namespace std;

static const string BLANK_STRING = " ";
static const string PROTOCOL_SEP = "\n";

static string encode(const string& text)
{
    // 添加报头
    string ret = to_string(text.size());
    ret += PROTOCOL_SEP;
    ret += text;
    ret += PROTOCOL_SEP;
    return ret;
}

static bool decode(string& text, string* out)
{
    // 移除报头   "len\n""a op b\n"???
    size_t pos = text.find(PROTOCOL_SEP);
    if(pos == string::npos)    return false;
    string headStr = text.substr(0, pos);
    size_t textLen = stoi(headStr);
    size_t totalLen = textLen + 2 + headStr.size();
    // text可能除了"len\na op b\n"外增加了其它字符,比如"len\na op b\nlen\n..."。这里拿取了一个完整的
    if(text.size() < totalLen)  return false;
    *out = text.substr(pos+1, textLen);
    // 移除这一个完整的报文,防止text越来越大
    text.erase(0, totalLen);
    return true;
}

struct Request
{
    int _a;
    int _b;
    char _op;
    Request(int a, int b, char op) : _a(a), _b(b), _op(op) {}
    Request() {}

    bool serialize(string* out)
    {   
        // 构建有效载荷,将成员属性变为 "_a op _b"
        string tmp = to_string(_a);
        tmp += BLANK_STRING;
        tmp += _op;
        tmp += BLANK_STRING;
        tmp += to_string(_b);
        *out = tmp;
        return true;
    }

    bool deserialize(const string& in)
    {
        // 反序列化 将"_a op _b"拆分
        size_t left = in.find(BLANK_STRING);
        if(left == string::npos)    {
            cerr << "if(left == string::npos) err" << endl;
            return false;
        }
        _a = stoi(in.substr(0, left));

        size_t right = in.rfind(BLANK_STRING);
        if(right == string::npos)   {
            cerr << "if(right == string::npos) err" << endl;
            return false;
        }
        _b = stoi(in.substr(right+1));

        if(left + 2 != right)   {
            cerr << "if(left + 2 != right) err" << endl;
            return false;
        }
        _op = in[left+1];
        return true;
    }

    void printInfo()
    {
        printf("%d %c %d = ?\n", _a, _op, _b);
    }
};

struct Response
{
    int _res;
    int _exitCode;      // 0 表示可信
    Response(int res, int exitCode) : _res(res), _exitCode(exitCode) {}
    Response() {}
    
    bool serialize(string* out)
    {   
        // 构建有效载荷,将成员属性变为 "_res op _exitCode"
        string tmp = to_string(_res);
        tmp += BLANK_STRING;
        tmp += to_string(_exitCode);
        *out = tmp;
        return true;
    }

    bool deserialize(const string& in)
    {
        // 反序列化 将"_res op _exitCode"拆分
        size_t left = in.find(BLANK_STRING);
        if(left == string::npos)    return false;
        _res = stoi(in.substr(0, left));
        _exitCode = stoi(in.substr(left+1));
    }

    void printInfo()
    {
        printf("res: %d, exitCode: %d\n", _res, _exitCode);
    }
};

测试代码如下 SvrCal.cc

#include <iostream>
#include "TcpSvr.hpp"
#include "Protocol.hpp"

using namespace std;


void test1()
{
    // 测试Request, 序列化 + 添加报头
    Request r(122223, 456, '*');
    string s;
    r.serialize(&s);
    s = encode(s);
    cout << s;

    // 去掉报头
    string out;
    decode(s, &out);
    cout << out << endl;
    
    // 反序列化
    Request tmp;
    tmp.deserialize(out);
    printf("%d %c %d\n", tmp._a, tmp._op, tmp._b);
    printf("===================================\n");
}

void test2()
{
    // 测试Reponse, 序列化 + 添加报头
    Response r(9999, 0);
    string s;
    r.serialize(&s);
    s = encode(s);
    cout << s;

    // 去掉报头
    string out;
    decode(s, &out);
    cout << out << endl;

    // 反序列化
    Response tmp;
    tmp.deserialize(out);
    printf("%d %d\n", tmp._res, tmp._exitCode);
    printf("===================================\n");
}

int main()
{
    test1();
    test2();
    return 0;
}

image-20241115211807675

2.2.2 网络部分

Socket.hpp,封装提供网络的系统调用接口

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <string>
#include <functional>
#include "log.hpp"
#define BACKLOG 10
Log log;

using namespace std;
class Sock
{
public:
    Sock();
    ~Sock();
    void Socket();
    void Bind(uint16_t port);
    void Listen();
    int Accept(string* ip, uint16_t* port);
    bool Connect(const string& ip, const uint16_t& port);
    int GetFd();
    void Close();
private:
    int _socketFd;
};

Sock::Sock() : _socketFd(-1)
{}

Sock::~Sock()
{}

inline void Sock::Socket()
{
    _socketFd = socket(AF_INET, SOCK_STREAM, 0);
    if(_socketFd < 0) {
        log(FATAL, "Sock::Socket() error! why: %s\n", strerror(errno));
        exit(-1);
    }
}

inline void Sock::Bind(uint16_t port)
{
    sockaddr_in local;
    memset(&local, 0, sizeof local);
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = INADDR_ANY;

    if(bind(_socketFd, (sockaddr*)&local, sizeof local) < 0) {
        log(FATAL, "Sock::Bind() error! why: %s\n", strerror(errno));
        exit(-1);
    }
}

inline void Sock::Listen()
{
    if(listen(_socketFd, BACKLOG) < 0) {
        log(FATAL, "Sock::Listen() error!\n");
        exit(-1);
    }
}

inline int Sock::Accept(string* peerIp, uint16_t* peerPort)
{
    sockaddr_in peer;
    memset(&peer, 0, sizeof peer);
    socklen_t len = sizeof len;

    int socketFd = accept(_socketFd, (sockaddr*)&peer, &len);
    if(socketFd < 0) {
        log(WARNING, "Sock::Accept() error!\n");
        return -1;
    }

    // 将客户端的ip输出出去
    char buf[64];
    if(inet_ntop(AF_INET, &peer.sin_addr, buf, sizeof buf) == nullptr) {
        log(WARNING, "Sock::Accept() error!\n");
        return -1;
    }
    *peerIp = buf;

    // 将客户端的端口号输出出去
    *peerPort = ntohs(peer.sin_port);
    return socketFd;
}

inline bool Sock::Connect(const string& ip, const uint16_t& port)
{
    sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    inet_pton(AF_INET, ip.c_str(), &server.sin_addr.s_addr);

    int ret = connect(_socketFd, (sockaddr*)&server, sizeof server);
    if(ret < 0) {
        cerr << "Sock::Connect error!" << endl;
        return false;
    }
    return true;
}

inline int Sock::GetFd()
{
    return _socketFd;
}

inline void Sock::Close()
{
    close(_socketFd);
}

2.2.3 处理数据

SvrCal.hpp,处理来自服务器的数据,doCalculate(string &text)中的text需要满足自定义协议要求的字符串

#pragma once
#include "Protocol.hpp"

enum {
    Div_Zero = 1,
    Mod_Zero,
    Other_Err,
};

class SvrCal
{
public:
    SvrCal() {}
    ~SvrCal() {}

    // 计算text中的数据,出错返回空字符串
    string doCalculate(string& text)
    {
        // 将从网络上来的数据,去掉报头
        string out;
        bool r = decode(text, &out);
        if(r == false)  {
            // cerr << "decode() error" << endl;
            return "";
        }
        // printf("Now out: %s\n", out.c_str());
        // 反序列化
        Request req;
        r = req.deserialize(out);
        if(r == false)  {
            cerr << "deserialize() error" << endl;
            return "";
        }

        // 计算结果
        Response resp = calHelper(req);

        // 序列化
        out = "";
        resp.serialize(&out);

        // 将计算结果加上报头
        out = encode(out);

        return out;
    }
private:
    Response calHelper(Request& req)
    {   
        // 将Request转换为Response
        Response resp;
        switch (req._op)
        {
        case '+':
            resp._res = req._a + req._b;
            break;
        case '-':
            resp._res = req._a - req._b;
            break;
        case '*':   
            resp._res = req._a * req._b;
            break;
        case '/':
        {
            if(req._b == 0)  
                resp._exitCode = Div_Zero;
            else 
                resp._res = req._a / req._b;
        }
        break;
        case '%':
        {
            if(req._b == 0)  
                resp._exitCode = Mod_Zero;
            else 
                resp._res = req._a % req._b;
        }
        break;
        default:
            resp._exitCode = Other_Err;
            break;
        }
        return resp;
    }
};

2.2.4 服务器代码

TcpSvr.hpp_callback后面会绑定到SvrCal::doCalculate(string& text)

#pragma once
#include "Socket.hpp"
#include <signal.h>

extern Log log;

/*
目前使用的是SvrCal.hpp中的
string doCalculate(const string& text)
*/
using fun_t = function<string(string&)>;

class TcpSvr
{
public:
    TcpSvr();
    TcpSvr(uint16_t port, fun_t callback) : _port(port), _callBack(callback) {}
    bool initSvr();
    void startSvr();
private:
    uint16_t _port;
    Sock _listSock;
    fun_t _callBack;
};

inline bool TcpSvr::initSvr()
{
    _listSock.Socket();
    _listSock.Bind(_port);
    _listSock.Listen();
    log(INFO, "TcpSvr Init over.\n");
}

inline void TcpSvr::startSvr()
{
    signal(SIGCHLD, SIG_IGN);
    for(;;) {
        string peerIp; 
        uint16_t peerPort;

        int socketFd = _listSock.Accept(&peerIp, &peerPort);
        if(socketFd < 0) {
            sleep(1);
            continue;
        }
        log(INFO, "TcpSvr is Accepting, client IP: %s, clientPort %d\n", peerIp.c_str(), peerPort);

        pid_t id = fork();
        if(id < 0) {
            log(WARNING, "TcpSvr::startSvr() fork error!\n");
            sleep(1);
            continue;
        } else if(id == 0) {
            // 关闭_listenFd的目的是为了防止子进程修改父进程文件描述符下的内容
            _listSock.Close();
            string inStr="";
            for(;;) {
                char buf[128];
                ssize_t n = read(socketFd, buf, sizeof buf);
                if (n < 0) {
                    log(WARNING, "TcpSvr::startSvr() read error!\n");
                    // sleep(1);
                    break;
                } else if(n == 0) {
                    log(WARNING, "TcpSvr::startSvr() read over!\n");
                    // sleep(1);
                    break;
                } else {
                    buf[n] = 0;
                    inStr += buf;
                    log(DEBUG, "Read from clinet: \n%s", inStr.c_str());
                    fflush(stdout);

                    // 回调函数,计算结果
                    string r = _callBack(inStr);
                    if(r == "") {
                        // 报文不正确,重新读取
                        // log(WARNING, "TcpSvr::startSvr() doCalculate error!\n");
                        continue;
                        // sleep(1);
                    }

                    write(socketFd, r.c_str(), r.size());
                }
            }   
            exit(0);
        } else {
            // 父进程关闭socketFd的意义是为了防止文件描述符越用越少,关闭了之后能保证每一个新的进程用的都是fd都是4
            close(socketFd);
        }
    }
}

SvrCal.cc,编译的是该文件

#include <iostream>
#include <functional>
#include "TcpSvr.hpp"
#include "Protocol.hpp"
#include "SvrCal.hpp"

using namespace std;


// test1() 和 test()和2.2.1中测试代码部分一样
void test1()
{}

void test2()
{}

int main(int argc, char* argv[])
{
    if(argc != 2) {
        cout << "Usage error!\n";
        return -1;
    }

    uint16_t port = stoi(argv[1]);
    SvrCal cal;
    TcpSvr *svr = new TcpSvr(port, bind(&SvrCal::doCalculate, &cal, placeholders::_1));
    // printf("after new TcpSvr()---\n");
    svr->initSvr();
    // printf("after new initSvr()---\n");
    svr->startSvr();
    return 0;
}

image-20241121164117198

3\n6 0Response序列化加上报头结果

2.3 网络计算器 (客户端)

2.3.1 随机生成数字

客户端也要遵循协议

CliCal.cc

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include "Socket.hpp"
#include "Protocol.hpp"

using namespace std;

// ./myClient ip port
int main(int argc, char* argv[])
{
    if(argc != 3) {
        cerr << "Usage error" << endl;
        return -1;
    }

    string serverIp = argv[1];
    uint16_t serverPort = stoi(argv[2]);
    Sock sock;
    sock.Socket();

    bool ret = sock.Connect(serverIp, serverPort);
    if(ret == false) {
        return -1;
    }

    srand(time(0));
    const string ops = "+-*/&?=";       // 有一些不正确的符号,目的是为了测试出错的情况
    string inStr = "";

    for (int i = 1; i <= 10; ++i) {
        printf("==============第%d次================\n", i);
        int x = rand() % 100;
        usleep(100);
        int y = rand() % 100;
        usleep(100);
        char op = ops[rand() % ops.size()];
        string text;
        Request req(x, y, op);
        req.printInfo();
        req.serialize(&text);
        text = encode(text);
        printf("将要发送给服务器的请求:\n%s", text.c_str());

        // 向服务器发送数据
        ssize_t n = write(sock.GetFd(), text.c_str(), text.size());
        // n = write(sock.GetFd(), text.c_str(), text.size());
        // n = write(sock.GetFd(), text.c_str(), text.size());
        // n = write(sock.GetFd(), text.c_str(), text.size());
        if(n < 0) {
            cerr << "Client write error!\n" << endl;
            break;
        }

        // 从服务器读取数据
        char buf[128];
        memset(buf, 0, sizeof buf);

        n = read(sock.GetFd(), buf, sizeof(buf));
        if(n > 0) {
            buf[n] = 0;
            inStr += buf;
            string text;

            bool r = decode(inStr, &text);
            if(r == false) {
                cerr << "Client decode error!\n" << endl;
                break;
            }

            Response resp;
            resp.deserialize(text);
            printf("从服务器得到结果:\n");
            resp.printInfo();
        }
        printf("======================================\n");
        sleep(1);
    }
    sock.Close();
    return 0;
}

由于可能有多个客户端向服务器发送请求,所以TcpSvr.hpp中的startSvr()改为如下的格式

inline void TcpSvr::startSvr()
{
    signal(SIGCHLD, SIG_IGN);
    for(;;) {
        string peerIp; 
        uint16_t peerPort;

        int socketFd = _listSock.Accept(&peerIp, &peerPort);
        if(socketFd < 0) {
            sleep(1);
            continue;
        }
        log(INFO, "TcpSvr is Accepting, client IP: %s, clientPort %d\n", peerIp.c_str(), peerPort);

        pid_t id = fork();
        if(id < 0) {
            log(WARNING, "TcpSvr::startSvr() fork error!\n");
            sleep(1);
            continue;
        } else if(id == 0) {
            // 关闭_listenFd的目的是为了防止子进程修改父进程文件描述符下的内容
            _listSock.Close();
            string inStr="";
            for(;;) {
                char buf[128];
                ssize_t n = read(socketFd, buf, sizeof buf);
                if (n < 0) {
                    log(WARNING, "TcpSvr::startSvr() read error!\n");
                    // sleep(1);
                    break;
                } else if(n == 0) {
                    log(WARNING, "TcpSvr::startSvr() read over!\n");
                    // sleep(1);
                    break;
                } else {
                    buf[n] = 0;
                    inStr += buf;
                    log(DEBUG, "Read from clinet: \n%s", inStr.c_str());
                    fflush(stdout);

                    while (true) {
                        // 客户端可能发送多次数据,所以这里一次全部处理干净
                        // 回调函数,计算结果
                        string r = _callBack(inStr);
                        if(r == "") {
                            break;
                        }
                        write(socketFd, r.c_str(), r.size());
                    }
                }
            }   
            exit(0);
        } else {
            // 父进程关闭socketFd的意义是为了防止文件描述符越用越少,关闭了之后能保证每一个新的进程用的都是fd都是4
            close(socketFd);
        }
    }
}

现在的运行结果如下图

image-20241122160552239

2.4 使用json

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于JavaScript的一个子集,但是独立于语言,可以被多种编程语言读取。JSON采用文本格式,易于阅读和编写,同时也易于机器解析和生成。

JSON的结构包括:

  1. 键值对:JSON中的每个元素都由键值对组成,键和值之间用冒号(:)分隔,键名必须用双引号括起来。
  2. 数据类型:JSON支持的数据类型包括字符串(String)、数字(Number)、对象(Object)、数组(Array)、布尔值(Boolean)和空值(null)。
  3. 数组:数组在JSON中用方括号([])表示,数组中的元素可以是任何类型的值。
  4. 对象:对象在JSON中用花括号({})表示,对象中的元素是键值对。

一个简单的JSON示例如下:

{
  "name": "John",
  "age": 30,
  "is_student": false,
  "courses": ["Math", "Science", "History"],
  "address": {
    "street": "123 Main St",
    "city": "Anytown",
    "state": "CA"
  }
}

在这个例子中,nameageis_student 是键值对,courses 是一个数组,address 是一个对象。JSON格式的数据可以被JavaScript直接解析,也可以被其他支持JSON的编程语言解析和生成。

2.4.1 安装json

sudo yum install -y jsoncpp-devel

安装完成后,json库用到的头文件

image-20241122162624498

json库的位置
image-20241122162811030

2.4.2 简单使用

序列化,写一个main.cc用于测试

#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
using namespace std;

int main()
{
    // 创建一个 Json::Value 类型的对象 root,它将作为 JSON 对象的根。
    Json::Value root;
    // 构建键值对
    root["x"] = 1;
    root["y"] = 2;
    root["op"] = "+";
    root["desc"] = "add operation";
    // 创建一个 Json::FastWriter 对象 w,用于将 JSON 对象转换为字符串。
    Json::FastWriter w;
    // 使用 w.write(root) 将 root JSON 对象转换为字符串,并存储在变量 res 中。
    string res = w.write(root);
    cout << res << endl;
    return 0;
}

image-20241122171520203

除了使用Json::FastWriter,也可以使用Json::StyledWriter,可读性会好一点

// 将上面代码的第16行改为:
Json::StyledWriter w;

image-20241122172323194

继续写上面的代码,下面进行反序列化

int main()
{
    // 创建一个 Json::Value 类型的对象 root,它将作为 JSON 对象的根。
    Json::Value root;
    root["x"] = 1;
    root["y"] = 2;
    root["op"] = "+";
    root["desc"] = "add operation";
    // 创建一个 Json::FastWriter 对象 w,用于将 JSON 对象转换为字符串。
    // Json::FastWriter w;
    Json::StyledWriter w;
    // 使用 w.write(root) 将 root JSON 对象转换为字符串,并存储在变量 res 中。
    string res = w.write(root);
    cout << res << endl;


    // 下面是反序列化
    Json::Value v;      // 用来存储解析后的 JSON 数据。
    Json::Reader r;     // 用来解析 JSON 字符串。
    r.parse(res, v);    // 将 JSON 字符串 res 解析到 v 对象中。
    int x = v["x"].asInt();
    int y = v["y"].asInt();
    string op = v["op"].asString();
    string desc = v["desc"].asString();
    cout << x << endl;
    cout << y << endl;
    cout << op << endl;
    cout << desc << endl;
    return 0;
}

image-20241122173337616

Json里面也可以再套一个Json

#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
using namespace std;

int main()
{
    // 序列化
    Json::Value inner;
    inner["hello"] = "你好";
    inner["world"] = "世界";
    Json::Value root;
    root["test"] = inner;
    Json::StyledWriter w;
    string res = w.write(root);
    cout << res;
    // 反序列化
    Json:: Value v;
    Json:: Reader r;
    r.parse(res, v);
	cout << v["test"]["hello"].asString() << endl;
    cout << v["test"]["world"].asString() << endl;
    return 0;
}

image-20241122174438348

2.4.3 修改协议部分

给2.2.1中的Protocol.hpp添加条件编译,使用jsoncpp这个库

#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;

static const string BLANK_STRING = " ";
static const string PROTOCOL_SEP = "\n";

static string encode(const string& text)
{
    // 添加报头
    string ret = to_string(text.size());
    ret += PROTOCOL_SEP;
    ret += text;
    ret += PROTOCOL_SEP;
    return ret;
}

static bool decode(string& text, string* out)
{
    // 移除报头   "len\n""a op b\n"???
    size_t pos = text.find(PROTOCOL_SEP);
    if(pos == string::npos)    return false;
    string headStr = text.substr(0, pos);
    size_t textLen = stoi(headStr);
    size_t totalLen = textLen + 2 + headStr.size();
    // text可能除了"len\na op b\n"外增加了其它字符,比如"len\na op b\nlen\n..."。这里拿取了一个完整的
    if(text.size() < totalLen)  return false;
    *out = text.substr(pos+1, textLen);
    // 移除这一个完整的报文,防止text越来越大
    text.erase(0, totalLen);
    return true;
}

struct Request
{
    int _a;
    int _b;
    char _op;
    Request(int a, int b, char op) : _a(a), _b(b), _op(op) {}
    Request() {}

    bool serialize(string* out)
    {   
#ifdef MYSELF
        // 构建有效载荷,将成员属性变为 "_a op _b"
        string tmp = to_string(_a);
        tmp += BLANK_STRING;
        tmp += _op;
        tmp += BLANK_STRING;
        tmp += to_string(_b);
        *out = tmp;
        return true;
#else 
        Json::Value tmp;
        tmp["x"] = _a;
        tmp["op"] = _op;
        tmp["y"] = _b;
        Json::FastWriter w;
        *out = w.write(tmp);
        return true;
#endif
    }

    bool deserialize(const string& in)
    {
#ifdef MYSELF
        // 反序列化 将"_a op _b"拆分
        size_t left = in.find(BLANK_STRING);
        if(left == string::npos)    {
            cerr << "if(left == string::npos) err" << endl;
            return false;
        }
        _a = stoi(in.substr(0, left));

        size_t right = in.rfind(BLANK_STRING);
        if(right == string::npos)   {
            cerr << "if(right == string::npos) err" << endl;
            return false;
        }
        _b = stoi(in.substr(right+1));

        if(left + 2 != right)   {
            cerr << "if(left + 2 != right) err" << endl;
            return false;
        }
        _op = in[left+1];
        return true;
#else 
        Json::Value v;
        Json::Reader r;
        r.parse(in, v);
        _a = v["x"].asInt();
        _op = v["op"].asInt();
        _b = v["y"].asInt();
        return true;
#endif
    }

    void printInfo()
    {
        printf("%d %c %d = ?\n", _a, _op, _b);
    }
};

struct Response
{
    int _res;
    int _exitCode;      // 0 表示可信
    Response(int res, int exitCode) : _res(res), _exitCode(exitCode) {}
    Response() {}
    
    bool serialize(string* out)
    {   
#ifdef MYSELF
        // 构建有效载荷,将成员属性变为 "_res op _exitCode"
        string tmp = to_string(_res);
        tmp += BLANK_STRING;
        tmp += to_string(_exitCode);
        *out = tmp;
        return true;
#else 
        Json::Value tmp;
        tmp["res"] = _res;
        tmp["code"] = _exitCode;
        Json::FastWriter w;
        *out = w.write(tmp);
        return true;
#endif
    }

    bool deserialize(const string& in)
    {
#ifdef MYSELF
        // 反序列化 将"_res op _exitCode"拆分
        size_t left = in.find(BLANK_STRING);
        if(left == string::npos)    return false;
        _res = stoi(in.substr(0, left));
        _exitCode = stoi(in.substr(left+1));
#else
        Json::Value v;
        Json::Reader r;
        r.parse(in, v);
        _res = v["res"].asInt();
        _exitCode = v["code"].asInt();
        return true;
#endif
    }

    void printInfo()
    {
        printf("res: %d, exitCode: %d\n", _res, _exitCode);
    }
};

Makefile格式如下,默认将flag注释,这样就没有定义MYSELF

.PHONY : all
all : myClient myServer

lib = -ljsoncpp
#flag = -DMYSELF=1		# 是否有该宏

myClient : CliCal.cc
	g++ -o $@ $^ -std=c++11 $(lib) $(flag)
myServer : SvrCal.cc
	g++ -o $@ $^ -std=c++11 $(lib) $(flag)

.PHONY : clean
clean:
	rm -rf myClient myServer

运行结果如下

image-20241122201543505

2.4.4 守护进场话

可以使用链接中的3.3.2deamon.hpp
也可以调用下面的系统调用

#include <unistd.h>

int daemon(int nochdir, int noclose);

参数说明:

  • nochdir:如果设置为非零值,daemon 函数不会改变当前工作目录到根目录(/)。默认情况下,daemon 函数会将当前工作目录改变到根目录。
  • noclose:如果设置为非零值,daemon 函数不会关闭所有文件描述符。默认情况下,daemon 函数会关闭所有文件描述符。

返回值:

  • 如果成功,返回 0。
  • 如果失败,返回 -1,并设置 errno 以指示错误。

修改SvrCal.c,加上daemon()

#include <iostream>
#include <functional>
#include <unistd.h>
#include "TcpSvr.hpp"
#include "Protocol.hpp"
#include "SvrCal.hpp"

using namespace std;

int main(int argc, char* argv[])
{
    if(argc != 2) {
        cout << "Usage error!\n";
        return -1;
    }

    uint16_t port = stoi(argv[1]);
    SvrCal cal;
    TcpSvr *svr = new TcpSvr(port, bind(&SvrCal::doCalculate, &cal, placeholders::_1));
    svr->initSvr();
    int r = daemon(0, 0);
    if(r < 0) {
        cout << "Daemon error!\n";
        return -2;
    }
    svr->startSvr();
    return 0;
}

可以看到,守护进程已经被正确初始化了

image-20241122203922223

;