Linux----网络基础(2)--应用层的序列化与反序列化--守护进程--0226

news2024/12/29 5:42:09

文章中有使用封装好的头文件,可以在下面连接处查询。

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;
    else
        op_ = 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\n
        if (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;
        else
            resp.result_ = req.x_ / req.y_;
        break;
    case '%':
        if (0 == req.y_)
            resp.code_ = 2;
        else
            resp.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,SIGCHLD
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    // 2. 不要让自己成为组长
    if (fork() > 0)
        exit(0);
    // 3. 调用setsid
    setsid();
    // 4. 标准输入,标准输出,标准错误的重定向,守护进程不能直接向显示器打印消息
    int devnull = open("/dev/null", O_RDONLY | O_WRONLY);
    if(devnull > 0)
    {
        dup2(0, devnull);
        dup2(1, devnull);
        dup2(2, devnull);
        close(devnull);
    }
}

 使用

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

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

相关文章

最适合你的团队云协作工具

团队云协作工具哪个好&#xff1f;使用Zoho Projects的团队云协作软件套件&#xff0c;在一个平台上无缝协作&#xff0c;激励您的团队在任何地方以最好的状态完成他们的工作。 使您的团队能够使用团队云协作软件在任何地方进行协作和沟通。Zoho Projects提供了一套强大…

三天吃透计算机网络八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

一文读懂光学天线

天线&#xff0c;按维基百科的定义&#xff0c;"是一种用来发射或接收无线电波—或更广泛来讲—电磁波的器件"。例如&#xff0c;在无线通信系统中&#xff0c;天线被用于发射与接收射频与微波波段的电磁波。而在我们的智能手机中&#xff0c;就有内置的平面倒F天线(…

01-认识产品经理

文章目录引入1.1 合格的产品经理1.2 产品经理的分类按服务对象不同划分按产品平台不同划分按公司所属行业不同按工作内容划分按职级高低划分1.3 产品经理的岗位职责产品的开发流程核心团队成员及其职责产品经理工作中常见误区1.4 产品经理的能力素质专业技能&#xff08;干得了…

Unity Lighting -- 配置平行光源和天空盒

识别不同种类的光源 在游戏或实时应用程序中&#xff0c;我们可能会创建多种不同种类的场景&#xff0c;比如室内场景、室外场景、真实的场景或完全想象的场景。即便项目是一个完全的想象的或是科幻的故事&#xff0c;灯光也是非常重要的一环&#xff0c;它能极大提升沉浸感。 …

Python3-条件控制

Python3 条件控制 Python 条件语句是通过一条或多条语句的执行结果&#xff08;True 或者 False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: 代码执行过程&#xff1a; if 语句 Python中if语句的一般形式如下所示&#xff1a; if condi…

Atlassian Server用户新选择 | 数据中心产品是否适合您的企业(3)?

2024年2月&#xff0c;也就是一年不到&#xff0c;Atlassian将终止对Server产品及插件的所有支持。 此公告发布后&#xff0c;许多用户需要了解怎样的前进方向才是最适合企业的。为此&#xff0c;Atlassian不仅提供云版&#xff0c;还提供了本地部署的数据中心&#xff08;Data…

jupyter lab安装和配置

jupyter lab 安装和配置 一、jupyter lab安装并配置 安装jupyterlab pip install jupyterlab启动 Jupyter lab默认会打开实验环境的&#xff0c;也可以自己在浏览器地址栏输入127.0.0.1:8888/lab 汉化 pip install jupyterlab-language-pack-zh-CN刷新一下网页&#xff0…

ChatGPT解答:PYQT5 组件化实例,Python代码实现,给出100个代码实例

ChatGPT解答&#xff1a; PYQT5 组件化实例&#xff0c;Python代码实现&#xff0c;给出100个代码实例 PYQT5 组件化实例&#xff0c;Python代码实现&#xff0c;给出100个代码实例 实现一个简单的窗口 import sys from PyQt5.QtWidgets import QApplication, QWidgetapp QA…

我90后,零基础成功转行python工程师,从月薪5K到现在月入2W+改变真的难吗?

我是25岁转行学python的。说实在&#xff0c;转行就是奔着挣钱去的。希望我的经历可以给想转行的朋友带来一点启发和借鉴。 先简单介绍下个人背景&#xff0c;三流大学毕业&#xff0c;物流专业&#xff0c;学习能力一般&#xff0c;没啥特别技能&#xff0c;反正就很普通的一…

CSS3新特性-变量

2017年三月&#xff0c;微软宣布 Edge 浏览器将支持 CSS 变量。 这个重要的 CSS 新功能&#xff0c;所有主要浏览器已经都支持了。本文全面介绍如何使用它&#xff0c;你会发现原生 CSS 从此变得异常强大。 一、变量的声明 声明变量的时候&#xff0c;变量名前面要加两根连词…

python入门应该怎么学习

国外Python的使用率非常高&#xff0c;但在国内Python是近几年才火起来&#xff0c;Python正处于高速上升期市场对于Python开发人才的需求量急剧增加&#xff0c;学习Python的前景比较好。 Python应用领域广泛&#xff0c;意味着选择Python的同学在学成之后可选择的就业领域有…

虚函数与多态性

5.1多态性概述&#xff1a; 按实施的机制&#xff0c;多态可以分为两类&#xff1a; 虚函数的定义&#xff1a; &#xff08;前面思维是虚基类&#xff0c;别搞混了&#xff09; 运行时多态的条件&#xff1a; 运行时的多态&#xff1a; 基类中有show(),派生类中也有show&…

叠氮化物标记糖92659-90-0,2-[(Azidoacetyl)amino]-2-deoxy-D-glucose广泛用于体内代谢标记

基础产品数据&#xff1a;CAS号&#xff1a;92659-90-0中文名&#xff1a;2-[(叠氮基乙酰基)氨基]-2-脱氧葡萄糖英文名&#xff1a;2-[(Azidoacetyl)amino]-2-deoxy-D-glucose性 状&#xff1a;白色粉末温馨提示&#xff1a;所有的试剂仅用于科研实验。结构式&#xff08;Struc…

ChatGPT解答:纯前端文档预览,Vue实现,无需后端,支持Word、Excel、PPT、pdf、文本、图片,附接入demo和文档

ChatGPT解答&#xff1a;纯前端文档预览&#xff0c;Vue实现&#xff0c;无需后端&#xff0c;支持Word、Excel、PPT、pdf、文本、图片&#xff0c;附接入demo和文档 ChatGPTDemo Based on OpenAI API (gpt-3.5-turbo). 纯前端文档预览&#xff0c;Vue实现&#xff0c;无需后…

【软件测试】性能测试面试题都问什么?面试官想要什么?回答惊险避坑......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 1、你认为不同角色关…

A Bio-Inspired Multi-Exposure Fusion Frameworkfor Low-light Image Enhancement

Abstract弱光图像的能见度较低&#xff0c;不利于人类观察和计算机视觉算法。尽管许多图像增强技术已经被提出来解决这个问题&#xff0c;现有的方法不可避免地引入对比度增强不足和过度。受人类视觉系统的启发&#xff0c;我们设计了一个用于微光图像增强的多曝光融合框架。在…

机器学习错题集(1)

生成模型与判别模型的区别&#xff1a; 在机器学习中&#xff0c;对于有监督学习可以将其分为两类模型&#xff1a;判别式模型和生成式模型。简单地说&#xff0c;判别式模型是针对条件分布建模&#xff0c;而生成式模型则针对联合分布进行建模。已知输入变量x&#xff1a; 生成…

深度学习之卷积神经网络学习笔记二

1. 引言在学习笔记一中&#xff0c;我们介绍了几种常用的分类模型框架&#xff0c;如VGGNet&#xff0c;GoogleNet&#xff0c;和ResNet&#xff0c;并且介绍了几种简单的分割模型。如FCN&#xff0c;UNet&#xff0c;SegNet和Deeplab。从深度学习兴起到现在&#xff0c;模型一…

UML1——用图说话

目录 一、前言 二、使用工具 三、UML面对对象 四、UML图标 4.1 事物 4.2 关系 4.3 关系线数字 4.4 关系图示例 五、UML开发思路 一、前言 不管是系统项目工程师&#xff0c;还是开发人员&#xff0c;熟悉使用UML都必不可少。UML 是一种为面向对象开发系统的产品进行说明…