_Linux (网络版计算器简易实现)

2023-11-23 12:59

本文主要是介绍_Linux (网络版计算器简易实现),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 协议
  • 2. 网络版计算器简易实现代码链接
  • 3. 网络版计算器
    • 2-1. 约定的协议方案有两种
    • 2-3. 协议代码框架
      • 1. 自定义的协议方案
      • 2. json(库里的完整协议方案)
    • 2-4. send和recv单独使用不安全
    • 2-5. 剩余代码写法讲解参考如下:
    • 2-6. 代码运行结果示意图:
  • 4. 守护进程
    • 4-1. 守护进程概念
    • 4-2. 守护代码方法
    • 4-3. 守护进程后的相关示意图:

1. 协议

  • 注意:我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层。
  • 协议是一种 “约定”

socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么做呢?

  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体。

2. 网络版计算器简易实现代码链接

  • 完整代码我放到git仓库了哈~
  • 链接:
    https://gitee.com/ding-xushengyun/linux__cpp/tree/master/NetCal

3. 网络版计算器

2-1. 约定的协议方案有两种

约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 +
  • 数字和运算符之间没有空格。

约定方案二(我们的代码实现采用方案二):

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做 “序列化” 和 “反序列化”

注意:无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是应用层协议。

  • 当然我们自己简单实现的协议比较简陋(不能应用于任何场景);库里的json更强大。

2-3. 协议代码框架

结果和计算过程都有序列和反序列化

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>using std::cout;
using std::endl;
using std::string;namespace ns_protocol
{
#define MYSELF 1#define SPACE " "
#define SPACE_LINE strlen(SPACE)
#define SEP "\r\n"
#define SEP_LINE strlen(SEP)class Request //  计算数据{public://  "_x _op _y"string Serialize() //  序列化{
#ifdef MYSELF//	自己模拟的协议
#else//	json
#endif}//  "111 + 222"bool Deserialized(const string &str) //  反序列化{
#ifdef MYSELF//	自己模拟的协议#else// jsonreturn true;#endif}Request() {}Request(int x, int y, int op) : _x(x), _y(y), _op(op){}public:int _x;int _y;char _op; // '+' '-' '*' '/' '%'};class Response //  结果{public://  _code _resultstring Serialize() //  序列化{
#ifdef MYSELF//	自己模拟的协议#else// json
#endif}//  1 333bool Deserialized(const string &str) //  反序列化{
#ifdef MYSELF//	自己模拟的协议return true;
#else//	json 
#endif}Response() {}Response(int result, int code) : _result(result), _code(code){}public:int _result; //  计算结果int _code;   //   计算结果的状态码};//	下面是接受和发送数据函数(recv, send)bool Recv(int sock, string *out) //  接受信息/数据{char buffer[1024];ssize_t s = recv(sock, buffer, sizeof buffer - 1, 0);if (s > 0){buffer[s] = 0;*out += buffer;}else if (s == 0){// cout << "我也要退出了" << endl;return false;}else{// cout << "接受失败" << endl;return false;}return true;}void Send(int sock, string &str) //  发送信息/数据{ssize_t s = send(sock, str.c_str(), str.size(), 0);if (s < 0){cout << "发送消息失败" << endl;}}

1. 自定义的协议方案

客户发过来的数据例如:2+1

序列化过程:("_x _op _y") 就是把值(类的成员函数)拿出来变成字符串
_x :左值;_op:运算符;_y:右值(例如:2 + 1)
"2+1" -> "2 + 1"	// 字符个数从3变为5,我们以空格作为分割符。
		string Serialize() //  序列化{string str;str = std::to_string(_x);str += SPACE;str += _op;str += SPACE;str += std::to_string(_y);return str;}
反序列化过程:就是把字符串解析出来存起来(类的成员函数)

以空格作为分割符;解析出来客户发的数据。

		bool Deserialized(const string &str) //  反序列化{size_t left = str.find(' ');if (left == string::npos)return false;size_t right = str.rfind(' ');if (right == string::npos)return false;_x = stoi(str.substr(0, left));_y = stoi(str.substr(right + SPACE_LINE));if (left + SPACE_LINE > str.size())return false;else_op = str[left + SPACE_LINE];return true;}

结果序列化和反序列化(例如:3)

序列化过程:("_x _op _y") 就是把值(类的成员函数)拿出来变成字符串
code表示结果状态值,result:表示结果 (例如:0 3)
"03" -> "0 3"	// 字符个数从2变为3,我们以空格作为分割符。
		string Serialize() //  序列化{string str;str = std::to_string(_code);str += SPACE;str += std::to_string(_result);return str;}
反序列化过程:就是把字符串解析出来存起来(类的成员函数)

以空格作为分割符;解析出来客户发的数据。

		bool Deserialized(const string &str) //  反序列化{size_t left = str.find(' ');if (left == string::npos)return false;_code = stoi(str.substr(0, left));_result = stoi(str.substr(left + SPACE_LINE));return true;}

2. json(库里的完整协议方案)

下载json库指令; 我们下载后这个库就在系统路径下(我们使用这个库记住,编译的适合链接就可以了跟线程库一样。)

例如:
在这里插入图片描述

sudo yum install -y jsoncpp-devel
  • json库使用方法固定用法哈~

客户发过来的数据例如:2+1

序列化过程:("_x _op _y") 就是把值(类的成员函数)拿出来变成字符串
_x :左值;_op:运算符;_y:右值(例如:2 + 1)
		string Serialize() //  序列化{Json::Value root;root["x"] = _x;root["y"] = _y;root["op"] = _op;Json::FastWriter write;return write.write(root);}
反序列化过程:就是把字符串解析出来存起来(类的成员函数)
		bool Deserialized(const string &str) //  反序列化{Json::Value root;Json::Reader reader;reader.parse(str, root);_x = root["x"].asInt();_y = root["y"].asInt();_op = root["op"].asInt();return true;}

结果序列化和反序列化(例如:3)

序列化过程:("_x _op _y") 就是把值(类的成员函数)拿出来变成字符串
code表示结果状态值,result:表示结果 (例如:0 3)
		string Serialize() //  序列化{Json::Value root;root["code"] = _code;root["result"] = _result;Json::FastWriter write;return write.write(root);}
反序列化过程:就是把字符串解析出来存起来(类的成员函数)
		bool Deserialized(const string &str) //  反序列化{Json::Value root;Json::Reader reader;reader.parse(str, root);_code = root["code"].asInt();_result = root["result"].asInt();return true;}

2-4. send和recv单独使用不安全

多路转接的时候,出现的发送的问题,我们暂时不考虑。

  • 这里的“不安全” 是指send数据发了好多数据,而recv读取数据不完整那就造成解析失败进而结果不正确。

  • send/write:你是把数据发送到网络甚至是对方的主机中错误的!(你只是把数据发的缓存中,数据通过io拷贝出来滴!)
    在这里插入图片描述
    那么我们怎么保证自己读到的数据完整?通过加一些东西作为分割。

我们通过添加字符串长度和加"\r\n"(传统做法)

  • 例如:length\r\nxxxxx\r\n

    • 我们可以通过length来确定数据的准确新(xxxxx)
  • a. 解析先查找\r\n确定length长度,通过长度来确定这次读到数据是否>=一个完整的数据报。

  • b. 添加就是先加长度和分割符最后末尾加上分割符(“\r\n”)

    //  length\r\nxxxxx\r\nstring Decode(string &buffer) //  解析{int pos = buffer.find(SEP);if (string::npos == pos) // 没找到{return "";}int size = stoi(buffer.substr(0, pos));int length = buffer.size() - SEP_LINE * 2 - pos;if (size < length) // 读取到的数据不完整{return "";}// 读取到一个完成数据报buffer.erase(0, pos + SEP_LINE);   // 去掉length\r\nstring s = buffer.substr(0, size); // 取出buffer.erase(0, size + SEP_LINE);  // 去掉xxxxx\r\n//  解析数据完成return s;}//  xxxxxvoid Encode(string &s) //  添加{string tmp = std::to_string(s.size()); //  lengthtmp += SEP;                            // length\r\ntmp += s;                              //  length\r\nxxxxxtmp += SEP;                            // length\r\nxxxxx\r\nswap(s, tmp);}
}

2-5. 剩余代码写法讲解参考如下:

剩余写法详情参考这篇文章中的TCP网络简单实现:
链接:https://blog.csdn.net/Dingyuan0/article/details/129074597?spm=1001.2014.3001.5501

2-6. 代码运行结果示意图:

我们把结果输入到日志中,便于查看。

  • 自定义协议运行结果:
    在这里插入图片描述
  • json库协议运行结果:
    注意:我们把字符(运算符)转换为整数了。
    在这里插入图片描述

4. 守护进程

用户退出后,服务器还在运行;如果我们的服务器也退出就不合理;就产生了守护进程。

4-1. 守护进程概念

  • 1.前台进程是和终端关联的进程。
  • 2.任何xshell登陆,只允许一个前台进程和多个后台进程。(例如:刚登录时bash是前台进程,我们运行我们的服务器后;bash不在是前台进程,我们的服务器是前台进程。)
  • 3.进程除了有自己的pid,ppid,还有一个组ID。
  • 4.在命令行中,同时用管道启动多个进程,多个进程是兄弟关系,父进程都是bash ->可以用匿名管道来进行通信。
  • 5.而同时被创建的多个进程可以成为一个进程组的概念,组长一般是第一个进程(组长不能成为守护进程)
  • 6.任何一次登陆,登陆的用户,需要有多个进程(组)来给这个用户提供服务的(bash),用户自己可以启动很多进程,或者进程组。我们把给用户提供服务的进程或者用户自己启动的所有的进程或者服务,整体都是要属于一个叫做会话的机制中的。
  • 7.如何将自己变成自成会话(就是守护进程)呢? 调用setsid()函数
  • 8.setsid要成功被调用,必须保证当前进程不是进程组的组长;怎么保证呐?fork()一下让它子进程成为守护进程。
  • 9.守护进程不能直接向显示器打印消息一旦打印,会被暂停、终止。
  • 10.fork()之后此时守护进程也可称为特殊的孤儿进程(因为它的父母是一号进程。)

用户登录时系统创建会话,相关示意图:
在这里插入图片描述

4-2. 守护代码方法

代码写法如下。

#include <signal.h>
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>void MyDaemon()
{//  1. 忽略信号signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);// 2 . 不成为组长if (fork() > 0)exit(0);// 3. 调用setsidsetsid();// 3. cin cout ceer 重定向,守护进程不直接向显示器中打印信息int devnull=open("/dev/null", O_RDONLY | O_WRONLY);if(devnull>0){dup2(0, devnull);dup2(1, devnull);dup2(2, devnull);close(devnull);}
}

4-3. 守护进程后的相关示意图:

无论我们怎样退出xshell重新登录,我们的服务器一直在运行。
在这里插入图片描述
在这里插入图片描述

这篇关于_Linux (网络版计算器简易实现)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/418123

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount