当前位置: 首页 > news >正文

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

注:

  1. setsid要被成功调用,必须保证当前进程不是进程组的组长。所以需要配合fork()使用。
  2. 守护进程不能直接向显示器打印消息,一旦打印会被暂停、终止。

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

文章中有使用封装好的头文件&#xff0c;可以在下面连接处查询。 Linux相关博文中使用的头文件_Gosolo&#xff01;的博客-CSDN博客 1. 应用层 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层 1.2 协议 我们在之前的套接字编程中使用的是…...

uipath实现滑动验证码登录

现实需求 在进行RPA流程设计过程中&#xff0c;遇到登录系统需要滑动验证的情况&#xff0c;如图所示&#xff1a; 此时需要在RPA流程设计中&#xff0c;借助现有的活动完成模拟人工操作&#xff0c;完成验证登录操作。 设计思路 这个功能流程的设计思路大体如下&#xff1a; …...

openai-chatGPT的API调用异常处理

因为目前openai对地区限制的原因&#xff0c;即使设置了全局代理使用API调用时&#xff0c;还是会出现科学上网代理的错误问题。openai库 0.26.5【错误提示】&#xff1a;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

题目描述&#xff1a; 就一个这种文件&#xff0c;用记事本打开后&#xff1a; 题目分析&#xff1a; 打开后就一串看不懂的东西&#xff0c;想想这个东西曾经在 010editor 或 winhex中出现过&#xff08;右端&#xff09;既然如此那么我们就用它打开&#xff0c;得到&#…...

Restful API 设计规范

1. 简介 目前 "互联网软件"从用客户端/服务端模式&#xff0c;建立在分布式体系上&#xff0c;通过互联网通讯&#xff0c;具有高延时、高开发等特点。但是软件开发和网络是两个不同的领域&#xff0c;交集很少。要使得两个融合&#xff0c;就要考虑如何在互联网环境…...

sigwaittest测试超标的调试过程

1&#xff0c;问题描述硬件环境&#xff1a;飞腾S2500&#xff08;64核&#xff09;OS&#xff1a;kylinOS, linux preempt rt&#xff0c; 4.19.90测试命令&#xff1a;sigwaittest -p 90 -i 1000 -a 1测试结果&#xff1a;信号混洗值最大超过了80us&#xff0c;与飞腾其他CPU…...

Python进阶-----面对对象4.0(面对对象三大特征之--继承)

目录 前言&#xff1a; Python的继承简介 1.什么是继承 2.继承的好处 3.object类 继承的相关用法 1.继承的定义与法则 2.对继承的重写 3.&#xff08;单继承&#xff09;多层继承 4.多继承 5.多继承重写时调用父类方法 前言&#xff1a; 在讲之前&#xff0c;我想说说中…...

九龙证券|利好政策密集发布,机构扎堆看好的高增长公司曝光

新能源轿车销量和保有量快速增长&#xff0c;带来了充电桩商场的微弱需求。 日前&#xff0c;商务部部长王文涛表明&#xff0c;本年将在落实好方针的一起&#xff0c;活跃出台新方针办法&#xff0c;比方辅导当地展开新能源轿车下乡活动&#xff0c;优化充电等使用环境&#x…...

stm32CubeIDE FMC 驱动LCD(8080)

一&#xff0c;TFT屏硬件接口16位&#xff0c;80并口。二&#xff0c;FMC介绍。FSMC&#xff08;Flexible Static Memory Controller&#xff09;&#xff0c;译为灵活的静态存储控制器。STM32F1 系列芯片使用 FSMC 外设来管理扩展的存储器&#xff0c;它可以用于驱动包括 SRAM…...

Java 数据类型

数据类型用于对数据归类&#xff0c;以便开发者理解和操作。 基本数据类型 Java 确定了每种基本数据类型所占存储空间的大小&#xff0c;不会像其它语言那样随机器硬件架构的变化而变化&#xff0c;这使 Java 程序更具可移植性。 Java 中定义了如下的基本数据类型。 byte …...

Prometheus 监控云Mysql和自建Mysql(多实例)

本文您将了解到 Prometheus如何配置才能监控云Mysql(包括阿里云、腾讯云、华为云)和自建Mysql。 Prometheus 提供了很多种Exporter&#xff0c;用于监控第三方系统指标&#xff0c;如果没有提供也可以根据Exporter规范自定义Exporter。 本文将通过MySQL server exporter 来监控…...

Vue3中的h函数

文章目录简介简单使用参数使用计数器进阶使用函数组件插槽专栏目录请点击 简介 众所周知&#xff0c;vue内部构建的其实是虚拟DOM&#xff0c;而虚拟DOM是由虚拟节点生成的&#xff0c;实质上虚拟节点也就是一个js对象事实上&#xff0c;我们在vue中写的template,最终也是经过…...

阿尔法开发板 IMX6ULL 说明

一. IMX6ULL开发板 IMX6ULL开发板即正点原子的阿尔法(ALPHA)开发板&#xff0c;采用恩智浦芯片&#xff0c;cortex-A7架构的。 二. IM6ULL开发板说明 1. IO说明 对于IMX6ULL芯片&#xff0c;一个IO对应两个寄存器&#xff0c;第一个寄存器负责配置其复用功能&#xff0c;…...

Altium Designer19 #学习笔记# | 基础应用技巧汇总

全文目录一.元件符号库二.元件封装库1.AD09 集成元件库/封装库三.电路原理图1. 巧用查找"相似对象功能"1.1 查找相同元件1.2. 查找相同文本1.3. 查找相同网络 &#xff1a;E - S - C四.PCB原理图【AD PCB模式下的常用快捷键】PCB视图放大/缩小PCB视图左/右移动PCB切换…...

Python 元类编程实现一个简单的 ORM

概述 什么是ORM?    ORM全称“Object Relational Mapping”&#xff0c;即对象-关系映射&#xff0c;就是把关系数据库的一行映射为一个对象&#xff0c;也就是一个类对应一个表&#xff0c;这样&#xff0c;写代码更简单&#xff0c;不用直接操作SQL语句。 现在我们就要实…...

《C++ Primer Plus》第18章:探讨 C++ 新标准(7)

C11 新增的其他功能 C11 增加了很多功能&#xff0c;本书无法全面介绍&#xff1b;另外&#xff0c;本书编写期间&#xff0c;其中很多功能还未得到广泛实现。然而&#xff0c;有些功能有必要简要地介绍一下。 并行编程 当前&#xff0c;为提高计算机性能&#xff0c;增加处…...

Redis学习(二):Redis安装测试

概述 Redis是什么 Redis, Remote Dictionary Server, 即远程字典服务。免费开源的数据库。 由C语言编写&#xff0c;支持网络&#xff0c;可基于内存亦可持久化的日志型、KV数据库&#xff0c;并提供所种语言的API。 Redis能干嘛 用于内存存储&#xff0c;持久化。rdb、ao…...

Vector - CAPL - 简介及数据结构

对于想进入车载行业或者已经在车载行业工作的朋友对于CAPL这个词都会相当的熟悉&#xff0c;都知道他是做车载网络测试脚本的语言&#xff0c;并且跟C有点类似&#xff0c;但是它到底是什么呢&#xff1f;CAPL全称&#xff08;Communication Access Programming Language&#…...

20230304英语学习

What Would Happen if the Moon Disappeared Tomorrow? 如果明天月球消失了会怎样&#xff1f; 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…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

VisualXML全新升级 | 新增数据库编辑功能

VisualXML是一个功能强大的网络总线设计工具&#xff0c;专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑&#xff08;如DBC、LDF、ARXML、HEX等&#xff09;&#xff0c;并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...

uni-app学习笔记三十五--扩展组件的安装和使用

由于内置组件不能满足日常开发需要&#xff0c;uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件&#xff0c;需要安装才能使用。 一、安装扩展插件 安装方法&#xff1a; 1.访问uniapp官方文档组件部分&#xff1a;组件使用的入门教程 | uni-app官网 点击左侧…...