【Linux】序列化与反序列化——网络计算器的实现

从 TCP 字节流困境到结构化通信:为什么我们需要序列化与反序列化有了套接字编程的基础,我们便能快速实现简单的服务端与客户端搭建,完成最基本的数据传输。但在实际网络通信中,一个无法回避的问题会随之浮现:TCP 是面向字节流的传输协议。当对端调用 read 读取数据时,受缓冲区大小限制、发送端数据发送缓慢、网络波动、丢包 / 粘包等多种因素影响,很容易出现读取数据不完整,或是一次读到多条消息、一条消息被拆分成多次读取的情况。字节流本身没有天然的 "消息边界",这会直接导致接收方解析出错误数据,通信逻辑彻底紊乱。因此,在网络传输中明确消息边界、规范数据格式,就成了保障通信稳定的核心需求。

更进一步思考:即便我们通过长度报头等方式解决了消息边界问题,数据传输依然面临另一重巨大风险 ------跨平台、跨环境的兼容性问题。发送端与接收端可能运行在不同 CPU 架构、不同操作系统上,存在大小端字节序差异、内存对齐规则不同、数据类型长度不一致等问题。如果直接传输内存中的原生结构体、哈希表等对象,多字节数值会因字节序解析错误,指针会在跨进程后完全失效,甚至内存布局差异都会导致数据彻底乱掉。

与此同时,如果收发的只是简单文本,用户尚可直接阅读并回复;但当服务端与客户端需要进行功能性交互、指令调度、结构化数据传递时,通信就不再是 "让人看懂" 那么简单 ------ 程序必须能精准识别数据含义、解析字段含义、执行对应的业务逻辑。单纯的字符串或无格式字节流,既无法高效承载复杂结构,也不具备统一的解析标准,难以支撑起稳定、可扩展的网络交互。

正是为了解决消息边界、结构化数据传输、屏蔽平台环境差异这三大核心问题,我们需要一套机制,能够将内存中的对象、结构体、复杂数据,转换成与平台无关、网络中可安全传输的标准字节流,也能将接收到的字节流还原成程序可识别的数据结构。而这套机制,正是我们今天要深入讲解的 ------ 序列化与反序列化。

从问题到方案:序列化 / 反序列化如何破解通信难题

简单来说,序列化(Serialization) 是将程序中结构化的数据(比如类实例、字典、结构体)转换成连续的字节流(或字符串)的过程;而反序列化(Deserialization) 则是其逆过程 ------ 把接收到的字节流还原为程序可直接操作的结构化数据。

但是这里所谓的连续字节流、字符串也并不是简单的对字符串进行拼接,比如网络登录服务,客户端需要传递 "用户名 + 密码 + 登录类型" 三组数据,若直接拼接成字符串 user1:123456:phone 发送,一旦某字段包含特殊字符(如冒号)就会解析出错;而且字符串并没有标记数据边界,很容易出现对端收到的数据不完整导致出错的问题;因此需要解决的问题就有:如何保证对端精确提取数据?如何保证对方正确的拿到完整信息?

如何正确进行序列化-需要注意哪些问题

针对上面的登录案例出现的两个问题,我们进行一一分析:

如何保证对方正确的拿到完整信息

这个问题严格来说并不是序列化以及反序列化问题的关键,但是与序列化反序列化问题之间有强耦合------只有保证数据完整性才能正确进行序列化和反序列化处理。

想要处理这个问题其实很简单,只需要在传输报文前面加上长度信息报头:比如需要传输的报文为usr1:123456:phone,我们就可以将其改为17\n usr1:123456:phone,这样,当对端进行read读取的时候就可以先拿到长度报头,随后继续进行read,当接下来read到的数据长度不够时就返回去继续read,直到读到完整数据;当读取数据长度超出报头数据的时候也可以根据报头信息进行截取。

如何保证对端精确提取数据

解决了第一个问题,那么如何保证对端能够精确地提取到所需数据呢?

若将业务数据拼接为usr1:123456:phone这类分隔符格式传输,反序列化时虽可通过分号(:)拆分出 "用户名、密码、登录类型",但这种方式存在致命缺陷 ------数据完全依赖固定顺序 :一旦发送端调整字段顺序(比如改为phone:usr1:123456),接收端按原有顺序解析会直接得到错误数据(把 "phone" 解析为用户名,"usr1" 解析为密码);甚至当字段缺失 / 新增时,整个解析逻辑会彻底失效。

此时就需要更为精确的方法------键值对标记那么就很容易想到结构体、哈希表(如unordered_map)等做法,但是这里明显忽略了一个问题:不同CPU架构存在大小端差异

小端(低字节数据存放在内存低地址,高字节数据存放在内存高地址

大端(高字节数据存放在内存低地址,低字节数据存放在内存高地址

若报文以原生二进制形式包含 int/longlong 等多字节变量,且未统一字节序,会因大小端差异导致对端解析错误;而 unordered_map 等数据结构因内部包含指针,直接传输会导致指针跨进程失效,甚至引发内存越界崩溃。

Json串引入

但是JSON的引入很好地解决了这个问题------JSON 是一种轻量级的纯文本结构化数据交换格式,它以简单的键值对结构描述数据,不依赖任何操作系统、CPU 架构与编程语言,天然具备极强的跨平台性。由于采用单字节字符存储数据,JSON 从根本上避开了大小端字节序、内存对齐、指针等底层差异问题,无论在 Windows、Linux 还是嵌入式设备,或是 x86、ARM 等不同硬件平台上,都能被一致解析、准确还原,因此成为网络通信中最通用的序列化方案。

一、核心数据表示(JSON 结构构建)

JSON 的核心是键值对,支持字符串、数字、布尔、数组、嵌套对象等类型,通过Json::Value构建:

cpp 复制代码
#include <iostream>
#include <fstream>
#include <json/json.h> // 引入JsonCpp头文件(需提前安装/链接)

int main() {
    // 1. 构建基础键值对JSON对象
    Json::Value root;
    root["name"] = "张三";       // 字符串值
    root["age"] = 25;            // 数字值
    root["is_student"] = false;  // 布尔值
    root["score"] = Json::nullValue; // 空值

    // 2. 构建数组类型
    root["hobbies"] = Json::Value(Json::arrayValue); // 初始化数组
    root["hobbies"].append("编程");
    root["hobbies"].append("阅读");

    // 3. 构建嵌套对象
    Json::Value info;
    info["phone"] = "12345678901";
    info["addr"] = "北京市海淀区";
    root["user_info"] = info; // 嵌套到根对象
二、序列化(内存 JSON 对象→JSON 字符串 / 流)

Json::Value对象转为可传输 / 存储的 JSON 格式,核心有 3 种写入方式:

cpp 复制代码
#include<jsoncpp/json/json.h>
#include<iostream>
int main(){
    Json::Value root;
    root["name"]="zhangsan";
    root["age"]=14;
    // 格式化写入(带缩进,易读,适合调试/日志)
    Json::StyledWriter _sw;
    // 快速写入(紧凑格式,无缩进,适合网络传输)
    Json::FastWriter _fw;
    // 直接写入流(文件/网络流,低内存占用,适合大数据)
    Json::StyledStreamWriter _ssw;

    std::string SJ_str = _sw.write(root);
    std::string FJ_str = _fw.write(root);
    std::cout<<"StyledStreamWriter\n";
    _ssw.write(std::cout,root);
    std::cout<<std::endl<<"FastWriter\n"<<FJ_str<<std::endl<<"StyledWriter\n"<<SJ_str;
}

运行效果:

bash 复制代码
./a.out
StyledStreamWriter
{
        "age" : 14,
        "name" : "zhangsan"
}

FastWriter
{"age":14,"name":"zhangsan"}

StyledWriter
{
   "age" : 14,
   "name" : "zhangsan"
}
三、反序列化(JSON 字符串 / 流→内存 JSON 对象)

将接收到 / 读取的 JSON 文本还原为程序可操作的Json::Value对象:

cpp 复制代码
    // 1. 从字符串反序列化
    std::string jsonStr = R"({"name":"张三","age":25,"hobbies":["编程","阅读"]})";
    Json::Value parseRoot;
    Json::Reader reader;
    if (reader.parse(jsonStr, parseRoot)) { // 解析字符串
        std::cout << "【字符串反序列化结果】\n";
        std::cout << "姓名:" << parseRoot["name"].asString() << "\n";
        std::cout << "年龄:" << parseRoot["age"].asInt() << "\n";
        std::cout << "第一个爱好:" << parseRoot["hobbies"][0].asString() << "\n\n";
    }

    // 2. 从文件流反序列化
    std::ifstream readFile("user.json");
    Json::Value fileRoot;
    if (reader.parse(readFile, fileRoot)) { // 解析文件流
        std::cout << "【文件反序列化结果】\n";
        std::cout << "手机号:" << fileRoot["user_info"]["phone"].asString() << "\n";
    }
    readFile.close();

    return 0;
}

总的来说,JSON 的操作流程可清晰划分为三个关键环节:首先构建符合业务逻辑的 JSON 对象,其次将该对象序列化为可在网络传输、本地存储的文本格式,最后把接收到的 JSON 文本反序列化还原成程序能识别和操作的对象,这三步恰好对应了序列化与反序列化的核心步骤。

这里具体到我们的网络计算器部分,就可以构建request和response对象,用JSON进行序列化和反序列化:

cpp 复制代码
class Request
{
public:
    Request()
    {
    }
    Request(int x, int y, char oper)
        : _x(x),
          _y(y),
          _oper(oper)
    {
    }
    std::string Serialization()
    {
        Json::Value root;
        root["X"] = _x;
        root["Y"] = _y;
        root["OPER"] = _oper;
        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }
    bool Deserialization(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["X"].asInt();
            _y = root["Y"].asInt();
            _oper = root["OPER"].asInt();
        }
        return ok;
    }
}
cpp 复制代码
class Response
{
public:
    Response()
    {
    }
    Response(int result, int code) : _code(code), _result(result)
    {
    }
    std::string Serialization()
    {
        Json::Value root;
        root["CODE"] = _code;
        root["RESULT"] = _result;
        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }
    bool Deserialization(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _code = root["CODE"].asInt();
            _result = root["RESULT"].asInt();
        }
        return ok;
    }
}

这样一来,我们client端向server端进行数据请求的时候,就可以把操作数、操作符打包成JSON风格的字符串再加上长度报头,server端拿到字符串就可以根据报头信息截取对应长度信息,随后提取字符串中的操作数、操作符信息,随后进行结果计算,然后将计算结果进行打包返回给client端,实现双端'无障碍'沟通。

网络计算器的实现------从分布到整体

该网络计算器项目采用分层解耦的设计思路,通过头文件划分核心功能模块,将整体逻辑拆分为三个独立且低耦合的层,既保证了代码的可维护性,也便于后续扩展:

一、分层设计核心思路

项目核心拆分为三大模块,各模块通过头文件(Socket.hpp/NetCal.hpp/Protocol.hpp/Tcpserver.hpp)封装自身逻辑,仅对外暴露必要接口,实现 "各司其职、按需协作":

  1. 服务器层(TcpServer + Socket) 负责底层网络通信的核心能力:基于Socket封装套接字操作(创建、绑定、监听、连接),通过TcpServer实现 TCP 服务端的启动、客户端连接管理,是整个项目的 "通信基座"。代码中通过std::unique_ptr<TcpServer>创建服务器实例,传入端口号并调用Run()启动,同时将协议层的处理逻辑作为回调注入,实现 "通信层" 与 "业务层" 的解耦。

  2. 计算核心层(NetCal.hpp 中的 Cal 类) 聚焦业务逻辑本身,封装计算器的核心计算能力(通过Cal::Execute(req)方法处理具体的计算请求)。该层完全脱离网络细节,仅接收标准化的Request请求、返回Response响应,是项目的 "业务核心"。代码中通过std::unique_ptr<Cal>创建计算实例,仅作为回调参数传递给协议层,保证计算逻辑的独立性 ------ 即使替换通信方式(如 UDP),计算层代码也无需修改。

  3. 协议层(Protocol.hpp 中的 Protocol 类) 作为 "通信层" 与 "计算层" 的桥梁,负责请求 / 响应的序列化、反序列化、数据解析与封装。代码中Protocol类通过回调绑定Cal的计算方法,同时通过Getrequest(sock, addr)方法从套接字读取数据、解析为Request对象,调用计算逻辑后再将Response封装为网络可传输格式,屏蔽了底层数据格式与通信细节的耦合。

二、整体执行流程(对应代码逻辑)
  1. 程序启动时校验端口参数,初始化计算核心Cal、协议层Protocol(绑定计算回调)、TCP 服务器TcpServer(绑定协议层处理回调);
  2. 调用server->Run()启动 TCP 服务器,监听指定端口并等待客户端连接;
  3. 当客户端发起连接时,服务器层触发协议层的Getrequest方法,从套接字读取数据;
  4. 协议层解析数据为Request对象,调用Cal::Execute完成计算,得到Response后封装为网络格式回传给客户端;
  5. 整个流程中,各层通过回调函数协作,无直接强依赖,实现了 "网络通信 - 协议解析 - 业务计算" 的分层解耦。
三、设计优势
  1. 低耦合:各层仅通过接口交互,修改任意一层(如将 JSON 协议替换为 Protobuf、将 TCP 改为 UDP、扩展计算功能),无需改动其他层代码;
  2. 高复用Cal类可脱离网络场景单独复用(如本地计算器),TcpServer可适配其他业务场景(如聊天服务器);
  3. 易维护:问题定位更精准(网络问题查服务器层、计算错误查业务层、格式问题查协议层),便于迭代升级。
cpp 复制代码
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include "NetCal.hpp"
#include "Tcpserver.hpp"
#include "Protocol.hpp"
using namespace SockModule;
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage " << argv[0] << ": _port";
    }
    std::unique_ptr<Cal> cal = std::make_unique<Cal>();
    // 核心解耦体现:通过回调函数将各层串联,而非直接依赖
    // 1. 协议层绑定计算层(业务逻辑注入)
    std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>([&cal](Request &req)->Response
                                                                    { return cal->Execute(req); });
    // 2. 服务器层绑定协议层(通信逻辑注入)
    std::unique_ptr<TcpServer> server = std::make_unique<TcpServer>([&protocol](std::shared_ptr<Socket> &sock, InetAddr &addr)
                                                                    { protocol->Getrequest(sock, addr); }, std::stoi(argv[1]));
    server->Run();
}

Tcpserver.hpp 服务器构建

cpp 复制代码
#pragma once
#include <functional>
#include <memory>
#include "Common.hpp"
#include "Protocol.hpp"
#include "PInetAddr.hpp"
#include "Exitcode.hpp"
using namespace LogModule;
#define defaultlistenfd -1
#define backlog 15
using namespace SockModule;
using ioserverice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &addr)>;
class TcpServer
{
public:
    TcpServer(ioserverice_t func, uint16_t port)
        : _listenfd(defaultlistenfd),
          _isrunning(false),
          _func(func),
          _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildTCPMethod(port);
    }
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            InetAddr client;
            auto addr = _listensock->Accept(&client);
            pid_t id = fork();
            if (id < 0)
            {
                LOG(LEVEL::FATAL) << "fork err\n";
                exit(FORK_ERR);
            }
            else if (id == 0)
            {
                LOG(LEVEL::DEBUG) << "accept one";
                close(_listenfd);
                if (fork() > 0)
                    exit(OK);
                _func(addr, client);
                exit(OK);
            }
            else
            {
                addr->Close();
                waitpid(id, nullptr, WNOHANG);
            }
        }
    }
    ~TcpServer()
    {
    }

private:
    int _listenfd;
    bool _isrunning;
    std::unique_ptr<Socket> _listensock;
    ioserverice_t _func;
};

Protocol.hpp 协议定制

该协议层是网络计算器的核心中间层,承接 "网络通信层" 与 "业务计算层",核心目标是解决 TCP 字节流无边界问题实现请求 / 响应的跨平台序列化与反序列化,最终完成 "客户端请求解析→调用计算逻辑→响应结果封装回传" 的全流程,整体思路可拆解为以下四部分:

一、核心数据结构设计:Request/Response 封装 + JSON 序列化

1. Request(请求类)

  • 作用:封装客户端发送的计算请求(操作数 x、操作数 y、运算符 oper);
  • 核心能力
    • 序列化(Serialization):将 x/y/oper 封装为 JSON 字符串(纯文本格式,规避大小端 / 平台差异);
    • 反序列化(Deserialization):将接收到的 JSON 字符串解析为 x/y/oper,供计算层调用;
    • 提供 GetX ()/GetY ()/GetOper () 方法,暴露计算所需的核心数据。

2. Response(响应类)

  • 作用:封装计算结果(result)与状态码(code,标识计算是否成功);
  • 核心能力
    • 序列化(Serialization):将 result/code 转为 JSON 字符串,便于网络传输;
    • 反序列化(Deserialization):将服务端返回的 JSON 字符串解析为结果和状态码;
    • 提供 SetResult ()/SetCode ()/ShowResult () 方法,设置 / 展示响应数据。

二、应用层报文封装:解决 TCP 字节流无边界问题

TCP 是面向字节流的协议,存在粘包 / 拆包问题,因此自定义应用层报头格式,核心思路是 "长度 + 分隔符 + 数据 + 分隔符":

1. 编码(Encode):封装完整报文

  • 输入:序列化后的 JSON 字符串(如 Request/Response 的 JSON 数据);
  • 逻辑:
    1. 计算 JSON 字符串的长度,转为字符串;
    2. 长度+\r\n+JSON数据+\r\n 拼接成完整报文;
    3. 目的:通过 "长度前缀" 明确消息边界,避免粘包 / 拆包导致的解析错误。

2. 解码(Decode):解析完整报文

  • 输入:从套接字读取的字节流缓冲区、用于存储解析后 JSON 数据的指针;
  • 逻辑:
    1. 查找第一个分隔符\r\n,提取长度前缀并转为整数;
    2. 校验缓冲区数据长度是否满足 "长度前缀 + 分隔符 + JSON 数据 + 分隔符" 的总长度;
    3. 若满足,截取对应长度的 JSON 数据,删除缓冲区中已解析的部分;若不满足,返回 false(等待后续数据);
    4. 目的:从字节流中精准提取完整的 JSON 消息,保证解析的准确性。

三、服务端核心逻辑:Getrequest 处理客户端请求

作为服务端核心回调方法,绑定到 TCP 服务器的客户端连接事件,完整流程:

  1. 循环接收数据:从客户端套接字读取数据,追加到缓冲区队列;

  2. 解码提取完整报文:调用 Decode () 从缓冲区解析出完整的 JSON 请求字符串;

  3. 反序列化获取请求参数:将 JSON 字符串反序列化为 Request 对象,提取 x/y/oper;

  4. 调用计算逻辑:通过注入的回调函数(计算层 Cal::Execute)处理请求,得到 Response 响应;

  5. 序列化 + 编码回传:将 Response 序列化为 JSON 字符串,通过 Encode () 封装成完整报文,发送给客户端;

  6. 异常处理:若客户端断开连接(n=0)或接收错误(n<0),记录日志并退出循环。

四、客户端辅助逻辑:BuildRequestString/Getresponse

  1. BuildRequestString:构建客户端请求报文
  • 输入:x/y/oper;

  • 逻辑:创建 Request 对象→序列化为 JSON→调用 Encode () 封装成完整报文,直接供客户端发送。

  1. Getresponse:解析服务端响应
  • 输入:客户端套接字、响应缓冲区、Response 指针;

  • 逻辑:

    1. 循环接收服务端数据,追加到缓冲区;

    2. 调用 Decode () 解析出完整的 JSON 响应字符串;

    3. 将 JSON 字符串反序列化为 Response 对象,返回解析结果;

    4. 异常处理:服务端断开 / 接收错误时记录日志并返回 false。

cpp 复制代码
#pragma once
#include "Common.hpp"
#include "Socket.hpp"
#include "Log.hpp"
#include "PInetAddr.hpp"
using namespace SockModule;
using namespace LogModule;
class Request
{
public:
    Request()
    {
    }
    Request(int x, int y, char oper)
        : _x(x),
          _y(y),
          _oper(oper)
    {
    }
    std::string Serialization()
    {
        Json::Value root;
        root["X"] = _x;
        root["Y"] = _y;
        root["OPER"] = _oper;
        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }
    bool Deserialization(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _x = root["X"].asInt();
            _y = root["Y"].asInt();
            _oper = root["OPER"].asInt();
        }
        return ok;
    }
    int GetX()
    {
        return _x;
    }
    int GetY()
    {
        return _y;
    }
    char GetOper()
    {
        return _oper;
    }

private:
    int _x;
    int _y;
    char _oper;
};

class Response
{
public:
    Response()
    {
    }
    Response(int result, int code) : _code(code), _result(result)
    {
    }
    std::string Serialization()
    {
        Json::Value root;
        root["CODE"] = _code;
        root["RESULT"] = _result;
        Json::FastWriter writer;
        std::string s = writer.write(root);
        return s;
    }
    bool Deserialization(std::string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool ok = reader.parse(in, root);
        if (ok)
        {
            _code = root["CODE"].asInt();
            _result = root["RESULT"].asInt();
        }
        return ok;
    }

    void SetResult(int result)
    {
        _result = result;
    }
    void SetCode(int code)
    {
        _code = code;
    }
    void ShowResult()
    {
        std::cout << "result is :" << _result << "[" << _code << "]" << std::endl;
    }

private:
    int _code;
    int _result;
};
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>;
class Protocol
{
public:
    Protocol()
    {
    }
    Protocol(func_t func) : _func(func)
    {
    }
    // 应用层报头包装 :length sep message sep
    std::string Encode(const std::string &msg)
    {
        std::string len = std::to_string(msg.size());
        return len + sep + msg + sep;
    }

    bool Decode(std::string &msg, std::string *package)
    {
        int pos = msg.find(sep);
        if (pos == std::string::npos)
        {
            return false;
        }
        std::string package_len_str = msg.substr(0, pos);
        int package_len_int = std::stoi(package_len_str);
        int target_len = package_len_int + sep.size() * 2 + package_len_str.size();
        if (msg.size() < target_len)
        {
            std::cout << "<" << std::endl;
            return false;
        }
        *package = msg.substr(pos + sep.size(), package_len_int);
        msg.erase(0, target_len);
        return true;
    }

    void Getrequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        std::string buffer_queue;
        while (true)
        {
            int n = sock->Recv(&buffer_queue);
            if (n > 0)
            {
                std::string Json_str;
                std::cout << "recv success:" << buffer_queue;
                bool ok = Decode(buffer_queue, &Json_str);
                if (!ok)
                {
                    std::cout << "decode fail" << std::endl;
                    continue;
                }
                // 拿到完整报文
                Request req;
                bool des = req.Deserialization(Json_str);
                if (!des)
                {
                    continue;
                }
                Response res = _func(req);
                std::string result = res.Serialization();
                std::string send_str = Encode(result);
                sock->Send(send_str);
            }
            else if (n == 0)
            {
                LOG(LEVEL::INFO) << "client:" << client.StringIp() << "Quit!";
                break;
            }
            else
            {
                LOG(LEVEL::WARNING) << "client:" << client.StringIp() << ", recv error";
                break;
            }
        }
    }
    std::string BuildRequestString(int x, int y, char oper)
    {
        Request req(x, y, oper);
        std::string json_req = req.Serialization();
        return Encode(json_req);
    }
    bool Getresponse(std::shared_ptr<Socket> &sock, std::string &resp_buff, Response *res)
    {
        while (true)
        {
            int n = sock->Recv(&resp_buff);
            if (n > 0)
            {
                std::string Json_str;
                while (Decode(resp_buff, &Json_str))
                {
                    res->Deserialization(Json_str);
                }
                return true;
            }
            else if (n == 0)
            {
                LOG(LEVEL::WARNING) << "server quit!\n";
                return false;
            }
            else
            {
                LOG(LEVEL::FATAL) << "recv error\n";
                return false;
            }
            return false;
        }
        return true;
    }
    ~Protocol() {}

private:
    func_t _func;
};

NetCal.hpp 业务逻辑实现

cpp 复制代码
#pragma once
#include "Protocol.hpp"
#include <iostream>

class Cal
{
public:
    Response Execute(Request &req)
    {
        Response res(0, 0);
        switch (req.GetOper())
        {
        case '+':
            res.SetResult(req.GetX() + req.GetY());
            std::cout << req.GetX() << "+" << req.GetY();
            break;
        case '-':
            res.SetResult(req.GetX() - req.GetY());
            break;
        case '*':
            res.SetResult(req.GetX() * req.GetY());
            break;
        case '/':
            if (req.GetY() == 0)
            {
                res.SetCode(1); // 防止除0导致程序崩溃,直接返回错误码1
            }
            else
            {
                res.SetResult(req.GetX() / req.GetY());
            }
            break;
        case '%':
            if (req.GetY() == 0)
            {
                res.SetCode(2); // 防止模0导致程序崩溃,直接返回错误码2
            }
            else
            {
                res.SetResult(req.GetX() % req.GetY());
            }
            break;
        default:
            break;
        }
        return res;
    }
};

其他文件(大部分是对变量进行面向对象的封装)

Log.hpp

cpp 复制代码
    #pragma once
    #include <string>
    #include <iostream>
    #include "Mutex.hpp"
    #include <cstdio>
    #include <sstream>
    #include <fstream>
    #include <unistd.h>
    #include <sys/types.h>
    #include <memory>
    #include <filesystem>
    using namespace MutexModule;
    namespace LogModule
    {
    #define gsep "\r\n"
        class LogStrategy
        {
        public:
            ~LogStrategy() = default;
            virtual void SyncLog(const std::string &info) = 0;
        };
        // 显示器打印
        class ScreenStratey : public LogStrategy
        {
        public:
            void SyncLog(const std::string &info) override
            {
                LockGuard _lock(_mutex);
                std::cout << info << gsep;
            }

        private:
            Mutex _mutex;
        };

        // 指定文件打印
        std::string defaultpath = "./Log";
        std::string defaultfile = "Mylog.log";
        class FileStrategy : public LogStrategy
        {
        public:
            FileStrategy(const std::string path = defaultpath, const std::string filename = defaultfile)
                : _path(path),
                _filename(filename)
            {
                if (std::filesystem::exists(_path))
                {
                    return;
                }
                try
                {
                    std::filesystem::create_directories(_path);
                }
                catch (const std::filesystem::filesystem_error &e)
                {
                    std::cerr << e.what() << '\n';
                }
            }
            void SyncLog(const std::string &message) override
            {
                LockGuard lockguard(_mutex);

                std::string filename = _path + (_path.back() == '/' ? "" : "/") + _filename; // "./log/" + "my.log"
                std::ofstream out(filename, std::ios::app);                                  // 追加写入的 方式打开
                if (!out.is_open())
                {
                    return;
                }
                out << message << gsep;
                out.close();
            }
            ~FileStrategy() {};

        private:
            std::string _path;
            std::string _filename;
            Mutex _mutex;
        };
        enum class LEVEL
        {
            DEBUG,
            INFO,
            WARNING,
            ERROR,
            FATAL
        };
        std::string leveltos(const LEVEL &level)
        {
            switch (level)
            {
            case LEVEL::DEBUG:
                return "DEBUG";
            case LEVEL::INFO:
                return "INFO";
            case LEVEL::WARNING:
                return "WARNING";
            case LEVEL::ERROR:
                return "ERROR";
            case LEVEL::FATAL:
                return "FATAL";
            default:
                return "UNKNOW";
            }
        }
        std::string GetTime()
        {
            time_t curr = time(nullptr);
            struct tm curr_tm;
            localtime_r(&curr, &curr_tm);
            char timebuffer[128];
            snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
                    curr_tm.tm_year + 1900,
                    curr_tm.tm_mon + 1,
                    curr_tm.tm_mday,
                    curr_tm.tm_hour,
                    curr_tm.tm_min,
                    curr_tm.tm_sec);
            return timebuffer;
        }
        // 默认执行类
        class Logger
        {
        public:
            Logger()
            {
                Enable_Screen_log_Strategy();
            }
            void Enable_Screen_log_Strategy()
            {
                ffulsh_strategy = std::make_unique<ScreenStratey>();
            }
            void Enable_File_log_Strategy()
            {
                ffulsh_strategy = std::make_unique<FileStrategy>();
            }
            class Logmessage
            {
            public:
                Logmessage(const LEVEL &level, std::string filename, int line, Logger &logger)
                    : _ctime(GetTime()),
                    _level(level),
                    _pid(getpid()),
                    _filename(filename),
                    _line(line),
                    _logger(logger)

                {
                    std::stringstream ss;
                    ss << "[" << _ctime << "] "
                    << "[" << leveltos(_level) << "] "
                    << "[" << _pid << "] "
                    << "[" << _filename << "] "
                    << "[" << _line << "] "
                    << "- ";
                    finfo = ss.str();
                }
                template <typename T>
                Logmessage &operator<<(const T &info)
                {
                    std::stringstream ss;
                    ss << info;
                    finfo += ss.str();
                    return *this;
                }
                ~Logmessage()
                {
                    _logger.ffulsh_strategy->SyncLog(finfo);
                }

            private:
                std::string _ctime;
                LEVEL _level;
                pid_t _pid;
                std::string _filename;
                int _line;
                Logger &_logger;
                std::string finfo; // 完整信息
            };
            Logmessage operator()(LEVEL level, std::string name, int line)
            {
                return Logmessage(level, name, line, *this);
            }
            ~Logger()
            {
            }

        private:
            std::unique_ptr<LogStrategy> ffulsh_strategy;
        };
        Logger logger;
    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define Enable_Screen_log_Strategy logger.Enable_Screen_log_Strategy()
    #define Enable_File_log_Strategy logger.Enable_File_log_Strategy()
    }
    // 日志格式:[ctime][level][pid][filename][line]

Mutex.hpp

cpp 复制代码
// Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex;
    };
    Mutex defaultmutex;
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex = defaultmutex) : _mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }

    private:
        Mutex &_mutex;
    };
}

ExitCode.hpp

cpp 复制代码
#pragma once
enum Exitcode{
    OK=0,
    SOCK_ERR,
    BIND_ERR,
    LISTEN_ERR,
    ACCEPT_ERR,
    CONNECT_ERR,
    USAGE_ERR,
    FORK_ERR,

};

Common.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <jsoncpp/json/json.h>
#include <netinet/in.h>
#include <functional>
#include <cstdio>
#include <sys/wait.h>
#include <string>
#include <unistd.h>
#define CONV(addr) ((sockaddr *)&addr)

PInetAddr.hpp

cpp 复制代码
#pragma once
#include "Common.hpp"
class InetAddr
{
public:
    InetAddr(const sockaddr_in &addr, std::string &ip, uint16_t &port)
        : _addr(addr),
          _ip(ip),
          _port(port)
    {
    }
    InetAddr()
    {
    }
    InetAddr(sockaddr_in &addr)
    {
        SetAddr(addr);
    }
    InetAddr(std::string &ip, uint16_t &port) : _ip(ip),
                                                _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr);
        _addr.sin_family = AF_INET;
    }
    InetAddr(uint16_t port) : _ip(),
                              _port(port)
    {
        memset(&_addr,0,sizeof(_addr));
        _addr.sin_port = htons(port);
        _addr.sin_addr.s_addr = INADDR_ANY;
        _addr.sin_family = AF_INET;
    }
    void SetAddr(sockaddr_in &addr)
    {
        _addr = addr;
        _port = ntohs(addr.sin_port);
        char ipbuffer[64];
        inet_ntop(AF_INET, &addr.sin_addr, ipbuffer, sizeof(addr));
        _ip = ipbuffer;
    }
    std::string StringIp()
    {
        return _ip;
    }
    uint16_t GetPort()
    {
        return _port;
    }
    sockaddr_in &Getaddr()
    {
        return _addr;
    }
    const struct sockaddr *NetAddrPtr()
    {
        return CONV(_addr);
    }
    socklen_t NetAddrLen(){
        return sizeof(_addr);
    }
private:
    std::string _ip;
    uint16_t _port;
    sockaddr_in _addr;
};

Socket.hpp

cpp 复制代码
#pragma once
#include "Common.hpp"
#include "PInetAddr.hpp"
#include "Log.hpp"
#include "Exitcode.hpp"
const int defaultbacklog = 16;
using namespace LogModule;
namespace SockModule
{
    class Socket
    {
    public:
        virtual void CreateSocket() = 0;
        virtual void Bind(uint16_t port) = 0;
        virtual void Listen(int backlog) = 0;
        virtual void Close() = 0;
        virtual std::shared_ptr<Socket> Accept(InetAddr *addr) = 0;
        virtual int Send(std::string &buffer) = 0;
        virtual int Recv(std::string *buffer) = 0;
        virtual int Connect(std::string &server_ip, uint16_t &port) = 0;
        ~Socket()
        {
        }

    public:
        void BuildTCPMethod(uint16_t port, int backlog = defaultbacklog)
        {
            CreateSocket();
            Bind(port);
            Listen(backlog);

            LOG(LEVEL::DEBUG) << "bulid success\n";
        }
        void BuildTcpClientMethod()
        {
            CreateSocket();
        }
    };
    class TcpSocket : public Socket
    {
    public:
        TcpSocket()
        {
        }
        TcpSocket(int fd) : _sockfd(fd)
        {
        }
        void CreateSocket() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LEVEL::FATAL) << "socket error\n";
                exit(SOCK_ERR);
            }
            LOG(LEVEL::DEBUG) << "socket success\n";
        }
        void Bind(uint16_t port) override
        {
            InetAddr local(port);
            int n = bind(_sockfd, local.NetAddrPtr(), local.NetAddrLen());
            if (n < 0)
            {
                LOG(LEVEL::FATAL) << "bind error\n";
                exit(BIND_ERR);
            }
            LOG(LEVEL::DEBUG) << "bind success\n";
        }
        void Listen(int backlog = defaultbacklog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                LOG(LEVEL::FATAL) << "listen error\n";
                exit(LISTEN_ERR);
            }
            LOG(LEVEL::DEBUG) << "listen success\n";
        }
        std::shared_ptr<Socket> Accept(InetAddr *addr) override
        {
            sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = accept(_sockfd, CONV(peer), &len);
            addr->SetAddr(peer);
            if (fd < 0)
            {
                LOG(LEVEL::WARNING) << "accept error\n";
            }
            return std::make_shared<TcpSocket>(fd);
        }
        int Send(std::string &msg) override
        {
            return send(_sockfd, msg.c_str(), msg.size(), 0);
        }
        int Recv(std::string *out) override
        {
            char buffer[1024];
            int n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                *out += buffer;
            }
            return n;
        }
        int Connect(std::string &server_ip, uint16_t &port) override
        {
            InetAddr server(server_ip, port);
            return ::connect(_sockfd, CONV(server.Getaddr()), sizeof(sockaddr));
        }
        void Close() override
        {
            close(_sockfd);
        }

    private:
        int _sockfd;
    };
};
相关推荐
脆皮的饭桶2 小时前
给负载均衡做高可用的工具Keepalived
运维·服务器·负载均衡
深念Y2 小时前
多拨与双WAN提速:原理、误区与运营商的“隐藏限制”
网络·智能路由器·ip·光猫·wan·多拨·opppe
23.2 小时前
【网络】TCP与HTTP:网络通信的核心机制解析
网络·tcp/ip·http
袁小皮皮不皮2 小时前
【HCIA】第三章TCP/IP协议栈中其他主要协议
运维·服务器·网络·网络协议·tcp/ip
阿梦Anmory2 小时前
保姆级教程:Flask应用实现后台常驻运行(Linux服务器)
linux·服务器·flask
夏日听雨眠2 小时前
Linux学习1
linux·服务器·学习
小生不才yz2 小时前
【Makefile 专家之路 | 函数篇】10. 逻辑增强:逻辑函数(if/and/or)与环境断言(info/error)
linux
头孢头孢2 小时前
效率提升 10 倍!我用 OpenClaw 实现了工作自动化
运维·自动化
Agent产品评测局2 小时前
中国龙虾ai软件有哪些选择?2026自动化选型指南
运维·人工智能·ai·chatgpt·自动化