【计网】自定义序列化反序列化(三) —— 实现网络版计算器【下】
🌎实现网络版计算器【下】
本次序列化与反序列化所用到的代码,Tcp服务自定义序列化反序列化实现网络版计算器。
文章目录:
实实现网络版计算器【下】
客户端实现
基于守护进程的改写
🚀客户端实现
在这之前,我们已经将服务器端的代码部分做好了准备,现在万事俱备只欠客户端发起连接,而客户端在这里不准备那么多的封装了,与之前写的客户端相同,我们想要客户端以 ./cal_client ip port 的形式来创建客户端:
#include <iostream>
#include <string>
#include <memory>
#include <ctime>#include "Socket.hpp"
#include "Log.hpp"
#include "Protocol.hpp"void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}using namespace socket_ns;
using namespace protocol_ns;// ./tcp_client serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];// 服务器端ipuint16_t serverport = std::stoi(argv[2]);// 服务器端portreturn 0;
}
而Udp客户端构建对象就不需要这么麻烦了,因为我们早在最开始已经将Socket类进行了封装,这样我们就不需要在调用原生接口在客户端裸露式调用:
InetAddr serveraddr(serverip, serverport);// 通过ip和port构建InetAddr对象
std::unique_ptr<Socket> cli = std::make_unique<TcpSocket>();// 子类对象构造父类指针方便多态式调用
bool res = cli->BuildClientSocket(serveraddr);// 建立客户端Socket 链接
此时,客户端的Socket服务就已经构建完毕,网络通信已经做好准备,只要服务器通,客户端随时都可以发起连接。接着就是处理客户端的业务逻辑。
我们要知道客户端是要给服务器端发送请求并且获取相应的一个过程,获取成功之后将响应进行反序列化拿到最终的结果。为了方便测试,我们这里让客户端采用固定的提问方式不断对客户端发送请求获取响应并且解析,我们将构建请求以及接收响应封装为一个 Factory类。
其中,客户端请求让 x 为 1-10的随机数,y为 0-4的随机数,让他们进行模运算,并将计算构造为Request类,返回值为Request的指针:
class Factory
{
public:Factory(){srand(time(nullptr) ^ getpid());opers = "+/*/%^&|";}std::shared_ptr<Request> BuildRequest(){int x = rand() % 10 + 1;usleep(x * 10);int y = rand() % 5; // [0,1,2,3,4]usleep(y * x * 5);char oper = opers[rand() % opers.size()];std::shared_ptr<Request> req = std::make_shared<Request>(x, y, oper);return req;}std::shared_ptr<Response> BuildResponse(){return std::make_shared<Response>();}~Factory(){}private:std::string opers;// 操作数: +/-/*/\/% ...
};
为了方便测试,我们这里只启动一个客户端,这个客户端不停的给服务器发送数据,所以我们需要将待发送请求以及返回的响应放在while循环内不断发送获取解析。
我们想要积压一批数据,然后在一次性发送,这样就能测试服务器的功能是否有问题,是否能处理多批数据,所以在这里我们一次性构建五个请求让后在发送给服务器端,同样构建请求时需要对数据进行序列化和添加长度报头:
Factory factory;
std::string inbuffer;while (res)
{sleep(1);// 构建请求std::string str;for (int i = 0; i < 5; ++i)// 一次性构建5个请求{auto req = factory.BuildRequest();// 对请求进行序列化std::string send_str;req->Serialize(&send_str);std::cout << "Serialize: \n"<< send_str << std::endl;// 添加长度报头send_str = Encode(send_str);std::cout << "Encode: \n"<< send_str << std::endl;str += send_str;}// "len"\r\n"{}"\r\ncli->Send(str);// 发送请求
}
我们将求情发送之后,客户端就静静等待服务器端返回的响应,当然,与服务器端接收消息相同,客户端接收的每条应答一定就是完整的应答吗?不一定,所以我们将读取到的数据进行Decode(),这样我们对所有的应答进行解析,如果是一条完整的应答Decode接口就会返回一个response对象,对象里就是解析过后一条完整的应答内容:
while (res)
{sleep(1);// 构建请求std::string str;for (int i = 0; i < 5; ++i){auto req = factory.BuildRequest();// 对请求进行序列化std::string send_str;req->Serialize(&send_str);std::cout << "Serialize: \n"<< send_str << std::endl;// 添加长度报头send_str = Encode(send_str);std::cout << "Encode: \n"<< send_str << std::endl;str += send_str;}// "len"\r\n"{}"\r\ncli->Send(str);// 读取应答int n = cli->Recv(&inbuffer);if (n <= 0)break;std::string package = Decode(inbuffer);if (package.empty())continue;
}
那么此后,我们获取的应答就一定是一条完整的应答,但是这个应答此时还是序列化状态,我们需要将其进行反序列化处理,最后输出响应结果即可:
while (res)
{sleep(1);// 构建请求std::string str;for (int i = 0; i < 5; ++i){auto req = factory.BuildRequest();// 对请求进行序列化std::string send_str;req->Serialize(&send_str);std::cout << "Serialize: \n"<< send_str << std::endl;// 添加长度报头send_str = Encode(send_str);std::cout << "Encode: \n"<< send_str << std::endl;str += send_str;}// "len"\r\n"{}"\r\ncli->Send(str);// 读取应答int n = cli->Recv(&inbuffer);if (n <= 0)break;std::string package = Decode(inbuffer);if (package.empty())continue;// 读到的package一定是一个完整的应答auto resp = factory.BuildResponse();// 反序列化resp->Deserialize(package);// 拿到了结构化的应答std::cout << resp->_result << "[" << resp->_code << "]" << std::endl;
}
为了更好地体现服务器端对报文的处理是否正确,我们在TcpServerMain内的Service服务进行细微调整,前面,我们让客户端不断地对服务器端发出请求,那么服务器端的Service也要不断地去处理请求,并发送到客户端:
void ServiceHelper(socket_sptr sockptr, InetAddr client)
{int sockfd = sockptr->SockFd();LOG(DEBUG, "get a new link, info %s:%d, fd : %d", client.IP().c_str(), client.Port(), sockfd);std::string clientaddr = "[" + client.IP() + ":" + std::to_string(client.Port()) + "]# ";std::string inbuffer;while (true){sleep(5);Request req;// 1. 读取数据int n = sockptr->Recv(&inbuffer);if (n < 0){LOG(DEBUG, "client %s quit", clientaddr.c_str());break;}// 2. 分析数据std::string package;while (true){sleep(1);std::cout << "inbuffer" << inbuffer << std::endl;package = Decode(inbuffer);if (package.empty())break;std::cout << "-----------------------begin----------------------" << std::endl;std::cout << "resq string:\n"<< package << std::endl;// 3.反序列化req.Deserialize(package);// 4. 业务处理Response resp = _cb(req);// 5. 对应答进行序列化std::string send_str;resp.Serialize(&send_str);std::cout << send_str << std::endl;// 6. 添加长度报头send_str = Encode(send_str);// 7. 发送到对端sockptr->Send(send_str);}}
}
那么我们所有准备都已经做好了,接下来就是通信时刻:

这样网路版本计算器我们就实现完成了。在这个项目当中,我们发现,我们把从Tcp内读取的报文,可能读到半个,可能读到一个半,或者其他特殊不完整报文情况,这种情况我们称为 Tcp粘包问题。而我们使用Encode() 和 Decode() 接口就是为了解决tcp粘包问题的。
🚀基于守护进程的改写
我们知道,我们在连接远程服务器的时候,实际上就是打开一个终端文件,如果有多个连接就会打开多个终端文件,我们从一台设备向另一台设备进行重定向的时候就是如此:

并且我们可以将消息发送到另外一个终端文件当中,使用如下命令进行重定向:
echo "message" >> /dev/pts/n #这里n指的是任何一个存在的终端文件
这里如果你是使用XShell来测试上面的命令,你很可能不会成功,因为版本升级的原因,但是我们能知道这个现象就行。总而言之,当我们连接Linux服务器的时候,会给我们打开一个终端文件,再启动bash命令行解释器。

首先是创建终端文件,其次bash被启动,而bash又作为所有进程的父进程,bash则会打开创建的终端文件。而一般终端文件与启动的bash会被打包称为一个 会话(具体在以后守护进程章节中看到),而每个会话都会有自己的 会话id(sid),而一般 会话的id是终端中的第一个进程的pid也就是bash:

但是,只要在当前终端下启动的任何服务(进程),都属于当前的会话!比如:

所以会话就像是bash进程中的管理容器,如果一个会话被销毁了,那么会话里的所有进程也都会终止,

但是今天,我想要一种不受会话影响的进程,也就是不受用户登录退出的影响,独立于会话之外的进程,比如我们的网络版计算器服务器端,我们不想让其受用户注册销毁的影响,所以我们可以编写代码将其变为 守护进程。
实际上这么做的意义就是创建一个新的会话,在Linux中给我们提供了 setsid() 接口:

setsid()会创建出一个新的会话,不过有一个要求:调用进程不能是进程组的组长。那么什么是进程组呢?很简单,每个进程组都有一个唯一的标识符,通常是进程组的组长(Leader)的进程ID,而组长就是他们之中第一个启动的进程:

所以,我们在程序中直接创建子进程,并且退出父进程,那么,那么当前进程就可以调用setid()接口了,这个进程也就独立出会话之外,成为一个全新的会话,我们称之为 守护进程(精灵进程)!使用类似一下代码:
if(fork() > 0) exit(0);
setsid();
当然,如果你嫌麻烦,大可不必写长点的代码,因为Linux早就给我们想好了,给我们提供了一个 Daemon() 接口:

- nochdir 参数:是否更改当前进程的工作目录。如果更改,守护进程的目录就会切换为根目录,如果不更改,则在启动时的路径下。
- nocliose参数:是否需要进行输入输出的处理。
Linux每个终端下都会存在一个null文件:/dev/null,如果去读取这个文件,文件内是没有任何内容的,如果对该文件进行写,同样也不会保存任何信息,而是立刻丢弃。我们知道,当我们创建了守护进程,也就意味着脱离了原本的会话,所以也就没有原本的终端文件了,而如果我们要使网络计算器变为守护进程,而网络计算器中存在大量的IO操作,为了避免因为没有对应的终端文件进行IO而出错,我们可以将 0,1,2三个文件描述符全部重定向到 /dev/null 当中。
#include <iostream>
#include <unistd.h>int main()
{std::cout << "Pid is: " << getpid() << std::endl;sleep(1);daemon(0, 0);while(true){std::cout << "hello test" << std::endl;sleep(1);}return 0;
}
以上是一个简单的测试样例,daemon内部会自动的fork并且退出父进程:

经过测试我们可以看到,hello.exe 的TTY,也就是终端文件变成了 “?”, 也就表示已经不属于当前的会话了,而SID同样与当前进程的SID不同,并且SID为守护进程的pid。如果我们查看守护进程的工作目录:

可以看到,守护进程当前工作目录实际上就是在根目录,如果我们同时查看该守护进程的文件fd就会发现:

由此可见,daemon接口的两个参数实际上是bool值类型的,第一个参数表示是否更改工作目录,第二个参数表示是否更改重定向,如果我们把daemon参数设置为daemon(0, 0):


将daemon参数设置为(1, 1)就会导致我们输出的内容还是在上一个会话下,并且Ctrl C 也无法终止进程(可使用 kill -9 process_pid 杀死进程),当我们查询进程工作目录时,也能发现其在当前的工作目录下,而fd也指向了第一个终端文件。
所以一般情况下,我们直接调用 daemon(0, 0)即可,但是我们网络版计算器不仅仅有许多的IO,还写了很多很重要的日志信息啊,这么设置守护进程,我们就无法在终端上看到日志信息了,不用担心,因为早在编写日志之初,我们就已经给日志设置为两个选择,1. 将信息打印到显示器上。2. 将日志信息打印到终端文件上。我们可以将其打印到日志文件当中:

随后启动服务器,将其变为一个守护进程,然后启动一个客户端连接服务器端:


我们之前定义的文件路径就是在当前目录下,而我们创建了守护进程,并且将工作目录改为了根目录,所以我们的log.txt文件只能出现在根目录了。
以上就是网络版计算器实现的全过程了,如果这三篇文章对您有所帮助的话,还望点赞支持~~
相关文章:
【计网】自定义序列化反序列化(三) —— 实现网络版计算器【下】
🌎实现网络版计算器【下】 本次序列化与反序列化所用到的代码,Tcp服务自定义序列化反序列化实现网络版计算器。 文章目录: 实实现网络版计算器【下】 客户端实现 基于守护进程的改写 🚀客户端实现 在这之前,…...
神经网络中的优化方法(一)
目录 摘要Abstract1. 与纯优化的区别1.1 经验风险最小化1.2 代理损失函数1.3 批量算法和小批量算法 2. 神经网络中优化的挑战2.1 病态2.2 局部极小值2.3 高原、鞍点和其他平坦区域2.4 悬崖和梯度爆炸2.5 长期依赖2.6 非精确梯度2.7 局部和全局结构间的弱对应 3. 基本算法3.1 随…...
Linux 计算机网络基础概念
目录 0.前言 1.计算机网络背景 1.1 独立模式 1.2 网络互联 1.3 局域网(Local Area Network,LAN) 1.4 广域网(Wide Area Network,WAN) 2.协议 2.1什么是协议 2.2协议分层和软件分层 2.3 OSI七层网络模型 2.3…...
qt QGraphicsEllipseItem详解
1、概述 QGraphicsEllipseItem是Qt框架中QGraphicsItem的一个子类,它提供了一个可以添加到QGraphicsScene中的椭圆项。QGraphicsEllipseItem表示一个带有填充和轮廓的椭圆,也可以用于表示椭圆段(通过startAngle()和spanAngle()方法ÿ…...
Python websocket
router.websocket(/chat/{flow_id}) 接口代码,并了解其工作流程、涉及的组件以及如何基于此实现你的新 WebSocket 接口。以下内容将分为几个部分进行讲解: 接口整体概述代码逐行解析关键组件和依赖关系如何基于此实现新功能示例:创建一个新的…...
【MySQL-5】MySQL的内置函数
目录 1. 整体学习的思维导图 2. 日期函数 编辑 2.1 current_date() 2.2 current_time() 2.3 current_timestamp() 2.4 date(datetime) 2.5 now() 2.6 date_add() 2.7 date_sub() 2.8 datediff() 2.9 案例 2.9.1 创建一个出生日期登记簿 2.9.2 创建一个留言版 3…...
深度学习笔记之BERT(三)RoBERTa
深度学习笔记之RoBERTa 引言回顾:BERT的预训练策略RoBERTa训练过程分析静态掩码与动态掩码的比较模型输入模式与下一句预测使用大批量进行训练使用Byte-pair Encoding作为子词词元化算法更大的数据集和更多的训练步骤 RoBERTa配置 引言 本节将介绍一种基于 BERT \t…...
C++知识点总结(59):背包型动态规划
背包型动态规划 一、背包 dp1. 01 背包(限量)2. 完全背包(不限量)3. 口诀 二、例题1. 和是质数的子集数2. 黄金的太阳3. 负数子集和4. NASA的⻝物计划 一、背包 dp 1. 01 背包(限量) 假如有这几个物品&am…...
C++:反向迭代器的实现
反向迭代器的实现与 stack 、queue 相似,是通过适配器模式实现的。通过传入不同类型的迭代器来实现其反向迭代器。 正向迭代器中,begin() 指向第一个位置,end() 指向最后一个位置的下一个位置。 代码实现: template<class I…...
webGL入门教程_04vec3、vec4 和齐次坐标总结
vec3、vec4 和齐次坐标总结 1. vec3 和 vec4 1.1 什么是 vec3 和 vec4? vec3: GLSL 中的三维向量类型,包含 3 个浮点数:(x, y, z)。常用于表示三维坐标、RGB 颜色、法线、方向等。 vec4: GLSL 中的四维向量类型&…...
uniapp中父组件数组更新后与页面渲染数组不一致实战记录
简单描述一下业务场景方便理解: 商品设置功能,支持添加多组商品(点击添加按钮进行增加).可以对任意商品进行删除(点击减少按钮对选中的商品设置进行删除). 问题: 正常添加操作后,对已添加的任意商品删除后,控制台打印数组正常.但是与页面显示不一致.已上图为例,选中尾…...
优化 Conda 下载速度:详细的代理配置和网络管理策略
优化 Conda 下载速度:详细的代理配置和网络管理策略 为了彻底解决使用 Conda 下载 PyTorch 时遇到的速度问题,并确保下载过程稳定可靠,这需要一个详细、综合的技术方案。让我们更深入地分析问题原因,然后详尽地解释采取的解决策略…...
服务器遭受DDoS攻击后如何恢复运行?
当服务器遭受 DDoS(分布式拒绝服务)攻击 后,恢复运行需要快速采取应急措施来缓解攻击影响,并在恢复后加强防护以减少未来攻击的风险。以下是详细的分步指南: 一、应急处理步骤 1. 确认服务器是否正在遭受 DDoS 攻击 …...
MFC音视频播放器-支持电子放大等功能
前言 本播放器在VS2019下开发,使用ffmpegD3D实现视频播放渲染功能。同时本播放器支持录像功能、截图功能、音视频播放功能、码流信息显示、电子放大功能等。D3D的渲染同时支持surface和texture两种方式,电子放大功能是在D3D Texture方式下进行实现。以下…...
c语言编程1.17蓝桥杯历届试题-回文数字
题目描述 观察数字:12321,123321 都有一个共同的特征,无论从左到右读还是从右向左读,都是相同的。这样的数字叫做:回文数字。 本题要求你找到一些5位或6位的十进制数字。满足如下要求: 该数字的各个数位之…...
el-table 纵向 横向 多级表头
<el-table :data"tableData" class"diaTable":span-method"handleSpanMethod"border:header-cell-style"{background:#292929,color:#fff}"><!-- 纵向表头 --><el-table-column label"纵向表头" width"…...
uniapp开发微信小程序笔记8-uniapp使用vant框架
前言:其实用uni-app开发微信小程序的首选不应该是vant,因为vant没有专门给uni-app设置专栏,可以看到目前Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本,并由社区团队维护 React 版本和支付宝小程序版本。 但是vant的优…...
分布式项目使用Redis实现数据库对象自增主键ID
hello。大家好,我是灰小猿,一个超会写bug的程序猿! 在分布式项目中,数据表的主键ID一般可能存在于UUID或自增ID这两种形式,UUID好理解而且实现起来也最容易,但是缺点就是数据表中的主键ID是32位的字符串&a…...
npm-运行项目报错:A complete log of this run can be found .......npm-cache_logs\
1.问题 没有找到对应的某种依赖,node_modules出现问题。 2.解决 (1)查看对应依赖是否引入或者是由于合并分支错误 引入js或依赖不存在。谨慎删除依赖包 (2)查找对应引入依赖进行安装最后解决方法-删除依赖包清除缓存 npm cache clean --force (2)重新向同事引入…...
SolarCube: 高分辨率太阳辐照预测基准数据集
太阳能作为清洁能源在减缓气候变化中的作用日益凸显,其稳定的供应对电网管理至关重要。然而,太阳辐照受云层和天气变化的影响波动较大,给光伏电力的管理带来挑战,尤其是在调度、储能和备用系统管理方面。因此,精确的太…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
