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…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...