Linux----网络基础(2)--应用层的序列化与反序列化--守护进程--0226
文章中有使用封装好的头文件,可以在下面连接处查询。
Linux相关博文中使用的头文件_Gosolo!的博客-CSDN博客
1. 应用层
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层
1.2 协议
我们在之前的套接字编程中使用的是基于字符串的通信,客户端connect()之后,通过getlne()来输入数据,从而通过send()发送给服务端。如果我们要传输一些"结构化的数据" 怎么办呢?
那就需要指定一些约定。这些约定构成了协议。
客户端发送一个形如"1 + 1"的字符串;
这个字符串中有两个操作数, 都是整形;
两个数字之间会有一个字符是运算符,...
定义结构体来表示我们需要交互的信息;
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;...
2. 序列化与反序列化
2.1 序列化
以聊天软件为例,发送人、发送消息、发送时间明明是三个消息,但是通过一定手段合成一个报文统一的发给对方。
结构化的数据转为字符串数据。
2.2 反序列化
对方读到之后,将该报文再读回三个消息,分别装填到发送人、发送消息、发送时间。
字符串结构转为结构化的数据。
2.3 结构化数据和字节流式数据对比
为什么我们之前实现tcp,udp时就是传递的结构化的数据呢?
一是因为协议一旦定制完成就不会轻易更改,而这个协议可以支持网路通信使用结构化的数据。二是网络协议,有一些大小端问题,代码也有一些条件编译,会自动识别平台,设置好对齐的规则。内核级的协议定制解决了这些问题。
一般而言,结构化的数据是给上层应用来使用的。而字节流的数据更适用于网络传输,这样在上层业务和网络通信之间增加了一个软件层,使其解耦。
3.网络版计算器
要实现使用结构化的数据进行传输,首先需要有两个类,一个用于发送待计算的数字和运算方法,一个用于保存结果。假定输入格式为"x_ op_ y_",那还需要定义一个分隔符。
//封装到一个命名空间里 即没有命名冲突 到时候还可以展开 using namespace ns_protocol
namespace ns_protocol
{#define SPACE " "
#define SPACE_LEN strlen(SPACE)一个用于返回结果//请求class Request{public:Request(){}Request(int x,int y,char op):x_(x),y_(y),op_(op){}~Request() {}//序列化//反序列化public:int x_;int y_;char op_;};//回复class Response{public:Response(){}Response(int result, int code, int x, int y, char op) :result_(result),code_(code),x_(x),y_(y),op_(op){}~Response() {}//序列化//反序列化public:int result_;//返回结果int code_;//用于评判本次计算是否合法int x_;int y_;char op_;};
}
3.1 自定义协议
如果要想使用这个计算器,那么需要满足一些约定。仅规定输入格式为"_x _op _y"够用吗?
对于TCP而言,其是面向字节流的,IO读写操作,其实本质上是拷贝函数。
send || write 其实就是将我们自己定义的buffer中的内容拷贝到客户端的发送缓冲区中。
recv || read 其实就是将服务端的接收缓冲区中拷贝到我们自己定义的buffer里面。
那这些缓冲区什么时候发,发多少,出错了怎么办都是由TCP传输控制协议中决定的。所以发送的次数和接收的次数没有任何关系。
所以如果我们自主实现协议,就需要增加一些报文(特殊标识符)。来确定我们接收到的数据中,是否能组成一组“合法”的操作。
所以我们规定输入格式为 "length\r\nx_ op_ y_\r\n",至少读够一个这样的字符串才开始执行。
#define SEP "\r\n"
#define SEP_LEN strlen(SEP)
3.1.1 序列化
std::string Requst::Serialize()
{std::string str;str=std::to_string(x_);str+=SPACE;str+=op_;str+=SPACE;str+=std::to_string(y_);return str;
}
std::string Response::Serialize()
{std::string s;s = std::to_string(code_);s += SPACE;s += std::to_string(result_);return s;
}
3.1.2 反序列化
bool Requst::Deserialized(const std::string &str)
{size_t left=str.find(SPACE);if(left==std::string::npos) return false;size_t right=str.rfind(SPACE);if(right==std::string::npos) return false;//左右两个空格都找到了x_=atoi(str.substr(0,left).c_str());y_=atoi(str.substr(right+SPACE_LEN).c_str());if (left + SPACE_LEN > str.size())return false;elseop_ = str[left + SPACE_LEN];return true;
}
bool Respon::Deserialized(const std::string &s)
{std::size_t pos = s.find(SPACE);if (pos == std::string::npos)return false;code_ = atoi(s.substr(0, pos).c_str());result_ = atoi(s.substr(pos + SPACE_LEN).c_str());return true;
}
3.2 使用 json定制协议
安装json
sudo yum install jsoncpp-devel
json的使用
用{}的KV结构,支持数组
3.3 添加报文
namespace ns_protocol
{//添加报文std::string Encode(std::string &s){std::string new_package = std::to_string(s.size());new_package += SEP;new_package += s;new_package += SEP;return new_package;}//删除报文// "length\r\nx_ op_ y_\r\n..." //std::string Decode(std::string &buffer){std::size_t pos = buffer.find(SEP);if(pos == std::string::npos) return "";//拿到报文前面那个length int size = atoi(buffer.substr(0, pos).c_str());//拿到真实的有效载荷的长度int surplus = buffer.size() - pos - 2*SEP_LEN;if(surplus >= size){//至少具有一个合法完整的报文, 可以动手提取了buffer.erase(0, pos+SEP_LEN);std::string s = buffer.substr(0, size);//提取完成了 但是删干净 不影响下次进行判断buffer.erase(0, size + SEP_LEN);return s;}else{return "";}}
}
3.4 接受信息和发送信息
因为客户端和用户端都需要调用recv()和send(),而且这两端都需要包括协议文件,所以将这两个接口封装到这里。
namespace ns_protocol
{ bool Recv(int sock, std::string *out){char buffer[1024];ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0); // 9\r\n123+789\r\nif (s > 0){buffer[s] = 0;*out += buffer;}else if (s == 0){std::cout << "client quit" << std::endl;return false;}else{std::cout << "recv error" << std::endl;return false;}return true;}void Send(int sock, const std::string str){std::cout << "send in" << std::endl;int n = send(sock, str.c_str(), str.size(), 0);if (n < 0)std::cout << "send error" << std::endl;}}
4.客户端 服务端的代码逻辑
4.1 服务端 CalServer.cc
#include "TcpServer.hpp"
#include "Protocol.hpp"#include <memory>
#include <signal.h>using namespace ns_tcpserver;//这个是TcpServer.hpp内的命名空间
using namespace ns_protocol;static void Usage(const std::string &process)
{std::cout << "\nUsage: " << process << " port\n"<< std::endl;
}static Response calculatorHelper(const Request &req)
{Response resp(0, 0, req.x_, req.y_, req.op_);switch (req.op_){case '+':resp.result_ = req.x_ + req.y_;break;case '-':resp.result_ = req.x_ - req.y_;break;case '*':resp.result_ = req.x_ * req.y_;break;case '/':if (0 == req.y_)resp.code_ = 1;elseresp.result_ = req.x_ / req.y_;break;case '%':if (0 == req.y_)resp.code_ = 2;elseresp.result_ = req.x_ % req.y_;break;default:resp.code_ = 3;break;}return resp;
}//返回值类型为void 参数类型为int 符合包装器
void calculator(int sock)
{std::string inbuffer;while(true){//1.读取数据bool res=Recv(sock,&inbuffer);if(!res) break;std::cout << "begin: inbuffer: " << inbuffer << std::endl;//2.协议解析,得到一个完整的报文std::string package=Decode(inbuffer);if(package.empty()){//进行下一次读取continue;}logMessage(NORMAL, "%s", package.c_str());Request req;//3.进行反序列化,将字符数据存进结构数据req.Deserialized(package);//4.对这些数据进行处理Response resp = calculatorHelper(req);//5. 再序列化回去std::string respString= resp.Serialize();//6.添加报文respString = Encode(respString);//7.发送数据Send(sock, respString);}}
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(1);}// 一般服务器,都是要忽略SIGPIPE信号的,防止在运行中出现非法写入而发生抛异常signal(SIGPIPE, SIG_IGN);std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));server->BindService(calculator);//屏蔽信号是为了这里 server->Start();return 0;
}
4.2 服务端 CalClient.cc
#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"using namespace ns_protocol;static void Usage(const std::string &process)
{std::cout << "\nUsage: " << process << " serverIp serverPort\n"<< std::endl;
}// ./client server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string server_ip = argv[1];uint16_t server_port = atoi(argv[2]);Sock sock;int sockfd = sock.Socket();//连接if (!sock.Connect(sockfd, server_ip, server_port)){std::cerr << "Connect error" << std::endl;exit(2);}bool quit = false;std::string buffer;while (!quit){// 1. 获取需求Request req;std::cout << "Please Enter # ";std::cin >> req.x_ >> req.op_ >> req.y_;// 2. 序列化std::string s = req.Serialize();// 3. 添加长度报头s = Encode(s);// 4. 发送给服务端Send(sockfd, s);// 5. 正常读取while (true){bool res = Recv(sockfd, &buffer);if (!res){quit = true;break;}//去除报文std::string package = Decode(buffer);if (package.empty())continue;Response resp;//反序列化resp.Deserialized(package);std::string err;switch (resp.code_){case 1:err = "除0错误";break;case 2:err = "模0错误";break;case 3:err = "非法操作";break;default:std::cout << resp.x_ << resp.op_ << resp.y_ << " = " << resp.result_ << " [success]" << std::endl;break;}if(!err.empty()) std::cerr << err << std::endl;break;}}close(sockfd);return 0;
}
4.3 运行截图
注意不能少打空格 有bug

5. 守护进程
我们之前写的程序,全部都是在前台进行的,即启动服务器必须输入
./CalServer 8080
如果我退出了XShell 这个进程就退出了。如果我们不想让它退出,就需要让它守护进程化。
一些概念:
- 前台进程:和终端关联的进程。
判断一个进程是不是前台进程:看能否处理你的输入。
- 任何xshell登录,只允许一个前台进程和多个后台进程。
- 进程除了有自己的pid,ppid,还有一个组id。
- 在命令行中,同时用管道启动多个进程,多个进程是兄弟进程,而同时被创建的多个进程可以成为一个进程组的概念,组长一般是第一个进程。
- 任何一次登录,登录的用户,需要有多个进程(组),来给这个用户提供服务。用户也可以自己启动很多进程(组)。我们把给用户提供服务的进程或者用户自己启动的所有的进程或者服务,整体归纳到一个叫做会话的机制中。
守护进程化其实就是让进程变成一个单独的会话。使退出bash也不会影响这个进程的存活。
如何将自己变成自成会话呢?
5.1 setsid
#include <unistd.h>
pid_t setsid(void);
让自己自成会话并变成这个进程组的组长。成功返回pid 失败返回-1
注:
- setsid要被成功调用,必须保证当前进程不是进程组的组长。所以需要配合fork()使用。
- 守护进程不能直接向显示器打印消息,一旦打印会被暂停、终止。
5.2 写一个函数 让调用的进程变成守护进程
目标:
1.忽略SIGPIPE,SIGCHLD。
2.不要让自己成为组长 ——fork()
3.调用setsid()
4.标准输入、标准输出、标准错误的重定向。——重定向到哪里呢? /dev/null
/dev/null 该文件是一个写入自动丢弃,读入的时候不阻塞但是什么都读不到。
#pragma once#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>void MyDaemon()
{// 1. 忽略信号,SIGPIPE,SIGCHLDsignal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);// 2. 不要让自己成为组长if (fork() > 0)exit(0);// 3. 调用setsidsetsid();// 4. 标准输入,标准输出,标准错误的重定向,守护进程不能直接向显示器打印消息int devnull = open("/dev/null", O_RDONLY | O_WRONLY);if(devnull > 0){dup2(0, devnull);dup2(1, devnull);dup2(2, devnull);close(devnull);}
}
使用

相关文章:
Linux----网络基础(2)--应用层的序列化与反序列化--守护进程--0226
文章中有使用封装好的头文件,可以在下面连接处查询。 Linux相关博文中使用的头文件_Gosolo!的博客-CSDN博客 1. 应用层 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层 1.2 协议 我们在之前的套接字编程中使用的是…...
uipath实现滑动验证码登录
现实需求 在进行RPA流程设计过程中,遇到登录系统需要滑动验证的情况,如图所示: 此时需要在RPA流程设计中,借助现有的活动完成模拟人工操作,完成验证登录操作。 设计思路 这个功能流程的设计思路大体如下: …...
openai-chatGPT的API调用异常处理
因为目前openai对地区限制的原因,即使设置了全局代理使用API调用时,还是会出现科学上网代理的错误问题。openai库 0.26.5【错误提示】:raise error.APIConnectionError(openai.error.APIConnectionError: Error communicating with OpenAI: …...
css实现音乐播放器页面 · 笔记
效果 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, …...
buu [NPUCTF2020]这是什么觅 1
题目描述: 就一个这种文件,用记事本打开后: 题目分析: 打开后就一串看不懂的东西,想想这个东西曾经在 010editor 或 winhex中出现过(右端)既然如此那么我们就用它打开,得到&#…...
Restful API 设计规范
1. 简介 目前 "互联网软件"从用客户端/服务端模式,建立在分布式体系上,通过互联网通讯,具有高延时、高开发等特点。但是软件开发和网络是两个不同的领域,交集很少。要使得两个融合,就要考虑如何在互联网环境…...
sigwaittest测试超标的调试过程
1,问题描述硬件环境:飞腾S2500(64核)OS:kylinOS, linux preempt rt, 4.19.90测试命令:sigwaittest -p 90 -i 1000 -a 1测试结果:信号混洗值最大超过了80us,与飞腾其他CPU…...
Python进阶-----面对对象4.0(面对对象三大特征之--继承)
目录 前言: Python的继承简介 1.什么是继承 2.继承的好处 3.object类 继承的相关用法 1.继承的定义与法则 2.对继承的重写 3.(单继承)多层继承 4.多继承 5.多继承重写时调用父类方法 前言: 在讲之前,我想说说中…...
九龙证券|利好政策密集发布,机构扎堆看好的高增长公司曝光
新能源轿车销量和保有量快速增长,带来了充电桩商场的微弱需求。 日前,商务部部长王文涛表明,本年将在落实好方针的一起,活跃出台新方针办法,比方辅导当地展开新能源轿车下乡活动,优化充电等使用环境&#x…...
stm32CubeIDE FMC 驱动LCD(8080)
一,TFT屏硬件接口16位,80并口。二,FMC介绍。FSMC(Flexible Static Memory Controller),译为灵活的静态存储控制器。STM32F1 系列芯片使用 FSMC 外设来管理扩展的存储器,它可以用于驱动包括 SRAM…...
Java 数据类型
数据类型用于对数据归类,以便开发者理解和操作。 基本数据类型 Java 确定了每种基本数据类型所占存储空间的大小,不会像其它语言那样随机器硬件架构的变化而变化,这使 Java 程序更具可移植性。 Java 中定义了如下的基本数据类型。 byte …...
Prometheus 监控云Mysql和自建Mysql(多实例)
本文您将了解到 Prometheus如何配置才能监控云Mysql(包括阿里云、腾讯云、华为云)和自建Mysql。 Prometheus 提供了很多种Exporter,用于监控第三方系统指标,如果没有提供也可以根据Exporter规范自定义Exporter。 本文将通过MySQL server exporter 来监控…...
Vue3中的h函数
文章目录简介简单使用参数使用计数器进阶使用函数组件插槽专栏目录请点击 简介 众所周知,vue内部构建的其实是虚拟DOM,而虚拟DOM是由虚拟节点生成的,实质上虚拟节点也就是一个js对象事实上,我们在vue中写的template,最终也是经过…...
阿尔法开发板 IMX6ULL 说明
一. IMX6ULL开发板 IMX6ULL开发板即正点原子的阿尔法(ALPHA)开发板,采用恩智浦芯片,cortex-A7架构的。 二. IM6ULL开发板说明 1. IO说明 对于IMX6ULL芯片,一个IO对应两个寄存器,第一个寄存器负责配置其复用功能,…...
Altium Designer19 #学习笔记# | 基础应用技巧汇总
全文目录一.元件符号库二.元件封装库1.AD09 集成元件库/封装库三.电路原理图1. 巧用查找"相似对象功能"1.1 查找相同元件1.2. 查找相同文本1.3. 查找相同网络 :E - S - C四.PCB原理图【AD PCB模式下的常用快捷键】PCB视图放大/缩小PCB视图左/右移动PCB切换…...
Python 元类编程实现一个简单的 ORM
概述 什么是ORM? ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。 现在我们就要实…...
《C++ Primer Plus》第18章:探讨 C++ 新标准(7)
C11 新增的其他功能 C11 增加了很多功能,本书无法全面介绍;另外,本书编写期间,其中很多功能还未得到广泛实现。然而,有些功能有必要简要地介绍一下。 并行编程 当前,为提高计算机性能,增加处…...
Redis学习(二):Redis安装测试
概述 Redis是什么 Redis, Remote Dictionary Server, 即远程字典服务。免费开源的数据库。 由C语言编写,支持网络,可基于内存亦可持久化的日志型、KV数据库,并提供所种语言的API。 Redis能干嘛 用于内存存储,持久化。rdb、ao…...
Vector - CAPL - 简介及数据结构
对于想进入车载行业或者已经在车载行业工作的朋友对于CAPL这个词都会相当的熟悉,都知道他是做车载网络测试脚本的语言,并且跟C有点类似,但是它到底是什么呢?CAPL全称(Communication Access Programming Language&#…...
20230304英语学习
What Would Happen if the Moon Disappeared Tomorrow? 如果明天月球消失了会怎样? The closest object to our planet, the Moon, may seem like Earth’s little sibling.Since its birth, the satellite has mostly just hung around, playing gravitational t…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...
基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...
Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...
Python学习(8) ----- Python的类与对象
Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...
