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

news2024/11/12 0:16:33

文章目录

  • 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
            // json

            return 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 _result
        string Serialize() //  序列化
        {
#ifdef MYSELF
             //	自己模拟的协议

#else
            // json
#endif
        }
        //  1 333
        bool 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\n
    string 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\n
        string s = buffer.substr(0, size); // 取出
        buffer.erase(0, size + SEP_LINE);  // 去掉xxxxx\r\n
        //  解析数据完成
        return s;
    }
    //  xxxxx
    void Encode(string &s) //  添加
    {
        string tmp = std::to_string(s.size()); //  length
        tmp += SEP;                            // length\r\n
        tmp += s;                              //  length\r\nxxxxx
        tmp += SEP;                            // length\r\nxxxxx\r\n

        swap(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. 调用setsid
    setsid();
    // 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重新登录,我们的服务器一直在运行。
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/377379.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

9.5 PIM-SM

实验目的 熟悉PIM-SM的应用场景掌握PIM-SM的配置方法 实验拓扑 实验拓扑如图9-40所示&#xff1a; 图9-40&#xff1a;PIM-SM 实验步骤 &#xff08;1&#xff09;配置IP地址 MCS1的配置 MCS1的配置如图9-41所示&#xff1a; 图9-41&#xff1a;配置MCS1的IP地址 R1的配置 …

VMware安装FreeBSD虚拟机

1. 下载FreeBSD镜像地址 国内阿里云下载地址&#xff1a; freebsd-releases-ISO-IMAGES安装包下载_开源镜像站-阿里云 选择自己需要的版本下载。 2. 创建FreeBSD虚拟机 2.1. 选择操作系统类型 2.2. 导入FreeBSD镜像 3. 安装FreeBSD 第1步&#xff1a;保持默认让其自动进入…

复习知识点十之方法的重载

目录 方法的重载 练习1: 练习1: 数组遍历 练习2: 数组的最大值 练习3: 练习4: 复制数组 基本数据类型和引用数据类型 方法的重载 Java虚拟机会通过参数的不同来区分同名的方法 练习1: public class Test4 {public static void main(String[] args) {//调用方法 // …

grid宫格布局新手快捷上手-f

前言 grid 网上有很多&#xff0c;但都是大而全的&#xff0c;感觉新人上手很吃力&#xff0c;本文仅以最快捷的方式进行介绍&#xff0c;如何使用grid宫格布局 本文是新人上手&#xff0c;若想了解更多grid布局&#xff0c;请阅读其他文章 使用 声明布局 display: grid;声…

springboot+pgbouncer+postgres数据库连接池集成方案及问题解决

期望通过每一次分享&#xff0c;让技术的门槛变低&#xff0c;落地更容易。 —— around 前言 旨在解决微服务项目全是连接池并影响数据库并发连接&#xff0c;作者的环境是基于sprongboot微服务连接postgres数据库&#xff0c;每个微服务的DAO层配置都使用了连接池技术。后续…

一个线程两次调用start()方法会出现什么情况?

第17讲 | 一个线程两次调用start()方法会出现什么情况&#xff1f; 今天我们来深入聊聊线程&#xff0c;相信大家对于线程这个概念都不陌生&#xff0c;它是 Java 并发的基础元素&#xff0c;理解、操纵、诊断线程是 Java 工程师的必修课&#xff0c;但是你真的掌握线程了吗&am…

如何编程实现从多数据库操作数据

对于数据量很大的复杂系统&#xff0c;有时候会采用分库或者分表的减轻单台数据库服务器压力&#xff0c;截止目前有一些工具直接支持读写分离等&#xff0c;例如ShardingSphere&#xff0c;如果不采用工具框架&#xff0c;从编码出发&#xff0c;如何实现从多个数据库读写数据…

FFmpeg 中的多线程解码

1.共享变量的互斥互斥锁&#xff08;mutex-lock&#xff09;是一种信号量&#xff0c;用来防止两个线程在同一时刻访问相同的共享资源&#xff0c;它有锁定状态和非锁定状态。在任意时刻&#xff0c;一个线程要想存取共享数据&#xff0c;线程必须首先获得mutex-lock&#xff0…

一种全新的图像变换理论的实验(四)——研究目的替代DCT和小波

一、前言 2023年02月28日凌晨1点 以前我定义为这个算法是滤波算法&#xff0c;实则上应该算是一种新变换算法&#xff0c;比如傅里叶变换&#xff08;FFT&#xff09;、离散余弦变换&#xff08;DCT&#xff09;&#xff0c;以及小波变换。所以就把所有的标题改变了一下。 本次…

MySQL的InnoDB 三种行锁,SQL 语句加了哪些锁?

InnoDB 三种行锁&#xff1a; Record Lock&#xff08;记录锁&#xff09;&#xff1a;锁住某一行记录 Gap Lock&#xff08;间隙锁&#xff09;&#xff1a;锁住一段左开右开的区间 Next-key Lock&#xff08;临键锁&#xff09;&#xff1a;锁住一段左开右闭的区间 哪些语句…

前端面试题 —— HTML

目录 一、src 和 href 的区别 二、对 HTML 语义化的理解 三、DOCTYPE(⽂档类型) 的作⽤ 四、script 标签中 defer 和 async 的区别 五、常⽤的 meta 标签有哪些&#xff1f; 六、HTML5 有哪些更新 八、行内元素有哪些&#xff1f;块级元素有哪些&#xff1f; 空(void)元素…

【正点原子FPGA连载】第十九章FreeRtos Hello World实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十九章FreeRto…

项目调研丨以太坊再质押项目EigenLayer白皮书四大看点(内附完整版中文白皮书)

北京时间2月21日下午&#xff0c;被众多一线投研机构视为2023年以太坊最重要的创新&#xff0c;有可能开启以太坊新叙事方向的项目Eigenlayer终于披露了其第一版白皮书。EigenLayer是以太坊的再质押集&#xff0c;允许共识层ETH质押者选择验证构建在以太坊生态系统之上的新软件…

第七节 面向对象

面向对象 1.类和对象是什么? )类:是共同特征的描述(设计图);对象:是真实存在的具体实例。 2.如何设计类? public class 类名 { 1、成员变量(代表属性的,一般是名词) 2、成员方法(代表行为的&#xff0c;一般是动词) 3.如何创建对象? 类名对象名new 类名(); 4.拿到对象后怎么…

ubuntu 编译安装支持CUDA的OpenCV

安装须知 cuda支持 在安装完“ linux CUDAtoolkitcudnntensorrt 的安装”之后进行支持cuda的opencv安装 否则报错&#xff1a;CMake Error at modules/dnn/CMakeLists.txt:41 (message): DNN: CUDA backend requires CUDA Toolkit. Please resolve dependency or disable OPE…

Ubuntu 安装指定版本 Mysql,并设置远程连接(以安装mysql 5.5 为例)

目录 一、安装Mysql 1、卸载Mysql&#xff08;可跳过&#xff09; 2、安装mysql 软件源 3、安装mysql 5.5 4、验证测试 二、设置远程登录 1、允许使用root账号远程连接 2、Mysql 允许远程登录 一、安装Mysql 1、卸载Mysql&#xff08;可跳过&#xff09; 如果之前安装…

数据结构六大排序

1.插入排序 1.插入排序 思路&#xff1a; 从第一个元素开始认为是有序的&#xff0c;去一个元素tem从有序序列从后往前扫描&#xff0c;如果该元素大于tem&#xff0c;将该元素一刀下一位&#xff0c;循环步骤3知道找到有序序列中小于等于的元素将tem插入到该元素后&#xff0…

卡特兰数

文章目录1、简介1.1 何为卡特兰数1.2 卡特兰数的通项公式2、应用2.1 题目1&#xff1a;括号合法题目描述思路分析2.2 题目2&#xff1a;进出栈的方式2.2.1 题目描述2.2.2 思路分析2.3 题目3&#xff1a;合法的序列2.3.1 题目描述2.3.2 思路分析2.3.3 代码实现2.4 题目4&#xf…

分布式ID生成系统

目录背景常用分布式ID生成系统介绍UUIDSnowflake背景 在大多数复杂的分布式系统中&#xff0c;往往需要对大量的数据和消息进行唯一标识。而对分布式系统后台数据库的分库分表后需要有一个唯一的ID来表示一条数据或者是消息。那么我们分布式系统ID一般都有哪些需求呢&#xff1…

IP地址、主机名、域名解析(DNS)

1.什么是IP地址 每一台联网的电脑都会有一个地址&#xff0c;用于和其他计算机进行通讯 IP地址主要有两个版本&#xff1a;v4 v6 IPV4版本的地址格式名为&#xff1a;a.b.c.d&#xff0c;其中abcd表示0-225的数字&#xff0c;如192.168.88.10为一个标准地址 查看IP地址&#x…