从 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)封装自身逻辑,仅对外暴露必要接口,实现 "各司其职、按需协作":
-
服务器层(TcpServer + Socket) 负责底层网络通信的核心能力:基于
Socket封装套接字操作(创建、绑定、监听、连接),通过TcpServer实现 TCP 服务端的启动、客户端连接管理,是整个项目的 "通信基座"。代码中通过std::unique_ptr<TcpServer>创建服务器实例,传入端口号并调用Run()启动,同时将协议层的处理逻辑作为回调注入,实现 "通信层" 与 "业务层" 的解耦。 -
计算核心层(NetCal.hpp 中的 Cal 类) 聚焦业务逻辑本身,封装计算器的核心计算能力(通过
Cal::Execute(req)方法处理具体的计算请求)。该层完全脱离网络细节,仅接收标准化的Request请求、返回Response响应,是项目的 "业务核心"。代码中通过std::unique_ptr<Cal>创建计算实例,仅作为回调参数传递给协议层,保证计算逻辑的独立性 ------ 即使替换通信方式(如 UDP),计算层代码也无需修改。 -
协议层(Protocol.hpp 中的 Protocol 类) 作为 "通信层" 与 "计算层" 的桥梁,负责请求 / 响应的序列化、反序列化、数据解析与封装。代码中
Protocol类通过回调绑定Cal的计算方法,同时通过Getrequest(sock, addr)方法从套接字读取数据、解析为Request对象,调用计算逻辑后再将Response封装为网络可传输格式,屏蔽了底层数据格式与通信细节的耦合。
二、整体执行流程(对应代码逻辑)
- 程序启动时校验端口参数,初始化计算核心
Cal、协议层Protocol(绑定计算回调)、TCP 服务器TcpServer(绑定协议层处理回调); - 调用
server->Run()启动 TCP 服务器,监听指定端口并等待客户端连接; - 当客户端发起连接时,服务器层触发协议层的
Getrequest方法,从套接字读取数据; - 协议层解析数据为
Request对象,调用Cal::Execute完成计算,得到Response后封装为网络格式回传给客户端; - 整个流程中,各层通过回调函数协作,无直接强依赖,实现了 "网络通信 - 协议解析 - 业务计算" 的分层解耦。
三、设计优势
- 低耦合:各层仅通过接口交互,修改任意一层(如将 JSON 协议替换为 Protobuf、将 TCP 改为 UDP、扩展计算功能),无需改动其他层代码;
- 高复用 :
Cal类可脱离网络场景单独复用(如本地计算器),TcpServer可适配其他业务场景(如聊天服务器); - 易维护:问题定位更精准(网络问题查服务器层、计算错误查业务层、格式问题查协议层),便于迭代升级。
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 数据);
- 逻辑:
- 计算 JSON 字符串的长度,转为字符串;
- 按
长度+\r\n+JSON数据+\r\n拼接成完整报文; - 目的:通过 "长度前缀" 明确消息边界,避免粘包 / 拆包导致的解析错误。
2. 解码(Decode):解析完整报文
- 输入:从套接字读取的字节流缓冲区、用于存储解析后 JSON 数据的指针;
- 逻辑:
- 查找第一个分隔符
\r\n,提取长度前缀并转为整数; - 校验缓冲区数据长度是否满足 "长度前缀 + 分隔符 + JSON 数据 + 分隔符" 的总长度;
- 若满足,截取对应长度的 JSON 数据,删除缓冲区中已解析的部分;若不满足,返回 false(等待后续数据);
- 目的:从字节流中精准提取完整的 JSON 消息,保证解析的准确性。
- 查找第一个分隔符
三、服务端核心逻辑:Getrequest 处理客户端请求
作为服务端核心回调方法,绑定到 TCP 服务器的客户端连接事件,完整流程:
-
循环接收数据:从客户端套接字读取数据,追加到缓冲区队列;
-
解码提取完整报文:调用 Decode () 从缓冲区解析出完整的 JSON 请求字符串;
-
反序列化获取请求参数:将 JSON 字符串反序列化为 Request 对象,提取 x/y/oper;
-
调用计算逻辑:通过注入的回调函数(计算层 Cal::Execute)处理请求,得到 Response 响应;
-
序列化 + 编码回传:将 Response 序列化为 JSON 字符串,通过 Encode () 封装成完整报文,发送给客户端;
-
异常处理:若客户端断开连接(n=0)或接收错误(n<0),记录日志并退出循环。
四、客户端辅助逻辑:BuildRequestString/Getresponse
- BuildRequestString:构建客户端请求报文
-
输入:x/y/oper;
-
逻辑:创建 Request 对象→序列化为 JSON→调用 Encode () 封装成完整报文,直接供客户端发送。
- Getresponse:解析服务端响应
-
输入:客户端套接字、响应缓冲区、Response 指针;
-
逻辑:
-
循环接收服务端数据,追加到缓冲区;
-
调用 Decode () 解析出完整的 JSON 响应字符串;
-
将 JSON 字符串反序列化为 Response 对象,返回解析结果;
-
异常处理:服务端断开 / 接收错误时记录日志并返回 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;
};
};