Htpp协议基础认识【linux】【网络版计算器小项目 | 守护进程 | Http协议内容】

 博客主页:花果山~程序猿-CSDN博客

文章分栏:Linux_花果山~程序猿的博客-CSDN博客

关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!

目录

一, 再谈协议

网络版计算器小项目

2. 长数据分割优化

二,守护进程

1. setsid

2. 制作守护进程函数

三,json

四,http协议

1. URL

2. 特殊字符的转义 

3. HTTP协议格式 

1)、ifstream——文件流

五,Http协议内容

1. 请求方法

2. HTTP状态码

301永久重定向 & 302,307临时重定向区别

3. HTTP常见的header

Connection

下期:https协议

结语


嗨!收到一张超美的风景图,愿你每天都能顺心! 

一, 再谈协议

        协议是一种 " 约定 ". socket api 的接口 , 在读写数据时 , 都是按 " 字符串" 的方式来发送接收 . 如果我们要传输一些" 结构化的数据 " 怎么办呢 ?
解决方案:
        
  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; (将数据打包好,接收方按照规则再解包)
  • 这个过程叫做 "序列化" 和 "反序列化"。

网络版计算器小项目

目的:理解序列化的过程 

        相比于前面的客户端与服务端通信项目,这次我们的计算器项目进行优化之处有:
        1. 对网络接口进行进一步封装,将与服务代码解藕,减少重复代码。(可见后面源码)
        2.  我们在应用层进行创建约定,实现简单数据的序列化与反序列化接口。
namespace Ser_Change
{
    // 负责数据正反序列化
    class DataConversion
    {
    public:
        DataConversion(): _a(0), _b(0), _synbol(""){}
        DataConversion(int a, int b, std::string sy)
            : _a(a), _b(b), _synbol(sy) {}

        // 转序列化 “1 + 1”
        std::string serialize()
        {
            std::string tmp;
            tmp += std::to_string(_a);
            tmp += " ";
            tmp += _synbol;
            tmp += " ";
            tmp += std::to_string(_b);
            return move(tmp);
        }

        // 反序列化  从"1 + 2"提取数据
        bool Disserialize(const std::string &str)
        {
            size_t left = str.find(" ");
            size_t tail = str.rfind(" ");
            if (left != str.npos && tail != left)
            {
                _a = atoi(str.substr(0, left).c_str());
                _synbol = str.substr(left + 1, 1);
                _b = atoi(str.substr(tail + 1, str.size()).c_str());
                return 1;
            }
            else
            {
                return false;
            }
        }

    private:
        int _a;
        int _b;
        std::string _synbol;
    };

    

编码经验:

服务器端一般都会屏蔽13号信号,SIGPIPE——目的:防止非法写入,服务器开发最容易出现的错误,没有之一。

2. 长数据分割优化

我们知道TCP是字节流式传递,UDP以数据报的形式,后者服务器读取数据时一个一个读,前者是向缓冲区中可以一次读取一大段数据包,试想这个场景,大量请求产生,请求甚至填满服务器的缓冲区, 相互之间相连,那我们如何从其中提取完整的报文呢
假设服务器缓冲区中有“1 + 1” 与 “20 + 3”两数据。
解决方法:"5\r\n1 + 1\r\n6\r\n20 + 3\r\n"这样两数据就能分开了。

简单实现其提取信息序列化与反序列化,代码:

class DataConversion
    {
    public:
        DataConversion(): _a(0), _b(0), _synbol(""){}
        DataConversion(int a, int b, std::string sy)
            : _a(a), _b(b), _synbol(sy) {}

        // 使用更加规范的协议:
        // “lenght\r\n123+4321\r\n”
        // 在客户端高并发的情况下,对服务端socket缓冲区满载打入,服务端可以一次接收数据

        // 转序列化 “1 + 1” 转换
        std::string serialize()
        {
            std::string tmp;
            tmp += std::to_string(_a);
            tmp += SPACE;
            tmp += _synbol;
            tmp += SPACE;
            tmp += std::to_string(_b);
            
            std::string data;
            data += std::to_string(tmp.size());
            data += DECOLL;
            data += tmp;
            data += DECOLL;

            return move(data);
        }

        // 反序列化  "1 + 2"
        std::string Disserialize(std::string &str)
        {
            size_t left = str.find(DECOLL);
            size_t tail = str.find(DECOLL, left + DEC_LENGTH);
            // “lenght\r\n123 + 4321\r\n”
            if (left != str.npos && atoi(str.substr(0,left).c_str()) 
                == tail - left - DEC_LENGTH)   
              // 检测数据包,是否包含1个完整包
            {
                size_t left_data = str.find(SPACE);
                size_t tail_data = str.rfind(SPACE, tail);
                _a = atoi(str.substr(left + DEC_LENGTH, left_data - left - 
                  DEC_LENGTH).c_str());
                _synbol = str.substr(left_data + SPACE_SIZE, SPACE_SIZE);
                _b = atoi(str.substr(tail_data + SPACE_SIZE, tail - tail_data - 
                     SPACE_SIZE).c_str());

                std::string surplus = str.substr(DEC_LENGTH);
                str.erase(0, tail + DEC_LENGTH);
                return move(surplus); // 返回拷贝后的剩余字段
            }
            else
            {
                return "";
            }
        }

    // private:
        int _a;
        int _b;
        std::string _synbol;
    };
只要保证 , 一端发送时构造的数据 , 在另一端能够正确的进行解析, 就是 ok . 这种约定 , 就是 应用层协议。

二,守护进程

知识铺垫

既然会话结束,会话中的进程将被关闭,而一些重要的进程我们不希望关闭,那我们就可以使用守护进程来保护,会话关闭,目标进程也不被关闭

1. setsid

返回值:成功,返回守护进程ID;失败,-1

创建守护进程条件:不能是进程组的第一个(组长),通常使用fork来保证。

使用setsid系统调用,可以创建一个新的会话,并将调用进程设置为会话的领头进程(session leader)。这个新的会话不会与任何终端相关联,也不会受到父进程的影响,因此可以独立于父进程运行。

意义:可以创建一个独立于终端和父进程的守护进程,使其能够在后台运行并且不受到外部干扰。

 守护进程本质上是孤儿进程的一种,孤儿进程是其他某个会话,而守护进程是自成会话。

2. 制作守护进程函数

当然我们也有现成的守护进程接口——daemon,但在大多数情况下,我们则会更多的自己实现,代码如下:

void MyDaemon()
{
    // 1.屏蔽影响信号,SIGPIPE SIGCHIL
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    // 2.fork, 确保自身不是组长
    pid_t pd = fork();
    if (pd > 0)
        exit(0);
    // 3.setsid,设置守护进程
    setsid();  //设置自己为单独会话
    int devnull = open("dev/null", O_RDONLY | O_WRONLY);
    // 4.将输入,输出,错误重定向到 /dev/null(垃圾黑洞,就是用来丢弃数据的)
    if (devnull)
    {
        dup2(devnull, 0);
        dup2(devnull, 1);
        dup2(devnull, 2);
    }
}

说到这里,我们是否会想,我们自主实现的数据的序列化,反序列化真的没问题吗??答案是肯定的,肯定有问题,不过我们可以通过自己编写来更好的理解协议。下面我们就要了解比较成熟的数据转换方案——json

三,json

简单了解:JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于前后端之间的数据传输。它基于JavaScript的语法,易于阅读和编写,并且能够被多种编程语言解析和生成。(来自chatgpt)

centos 下载jsoncpp: 

sudo yum install -y jsoncpp-devel

由于这是第三方的插件包,下载完成后,会自动将头文件,源码导入系统的头文件目录,源码库中。因此我们不需要-I,-L去手动添加头文件与源码地址。

基本接口使用例子如下:

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;


int main()
{
    std::string fastmessage;
    // 序列化
    {
            Json::Value send;
            send["a"] = 1;
            send["b"] = 2;
            send["op"] = '+';

            //还满足啊嵌套使用
            Json::Value send2;
            send2["a"] = 1;
            send2["b"] = 2;
            send2["op"] = "+";
            send["send2"] = send2;
 
            Json::StyledWriter writer;
            //1.产生格式型的数据,如
            // {
            //    "a" : _a
            //    "b" : _b
            //    "op" : _synbol
            // }
            // 更适合dubug时使用
            std::string message = writer.write(send);

            Json::FastWriter fastwriter;
            //2.产生类似数组的数据格式,可读性偏差些,如:
            //  {"a":_a,"b":_b, "op":_synbol,{"a":_a,"b":_b, "op":_synbol}}
            fastmessage = fastwriter.write(send);
            std::cout << message << std::endl;
            std::cout << fastmessage << std::endl;
    }

    {
        //反序列化
        string str;
        Json::Value accept;
        Json::Reader reader;
        reader.parse(fastmessage, accept);
        int _a = accept["a"].asInt();
        int _b = accept["b"].asInt();
        char _op = accept["op"].asInt(); //char类型本质上是整数
        printf("_a:%d _b:%d _op:%c\n", _a, _b, _op);
    }
    return 0;
}

四,http协议

         应用层的协议是可以我们自己编写,但目前有比较成熟的协议如,http(底层是基于TCP协议),https。

1. URL

url也就是我们俗称的网址

现在主流的是HTTPS协议,这个我们后面再学。 

2. 特殊字符的转义 

当我们输入一些, / ? +: 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.  某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

转义的规则如下 :
将需要转码的字符转为 16 进制,然后从右到左,取 4 ( 不足 4 位直接处理 ) ,每 2 位做一位,前面加上 % ,编码成%XY格式。 "+" 被转义成了 "%2B",服务端接受到请求后也就是将转义后的信息转会原文, urldecode 就是 urlencode 的逆过程;
网络中这种转义算法比较多,需要用的时候不用自己造轮子: UrlEncode编码/UrlDecode解码 - 站长工具 (chinaz.com)

3. HTTP协议格式 

下面我们以一段图片展示HTTP格式的初步印象:
服务端请求http请求,如下:
1)、ifstream——文件流
ifstream是 C++ 标准库中用于从文件读取数据的输入流类。它继承自istream类,因此可以使用istream类提供的所有输入操作符和函数。以下是ifstream类的一些常用方法和操作:

1. 打开文件:使用 open()方法可以打开一个文件,并将文件和 ifstream对象关联起来。例如:

2. 读取数据:可以使用 >>操作符或getline()函数从文件中读取数据。例如:

3. 检查文件是否打开成功:可以使用 is_open()方法来检查文件是否成功打开。例如:

4. 关闭文件:使用 `close()` 方法可以关闭文件。关闭文件后,不再能从文件中读取数据。例如:

5. 检查文件是否到达文件末尾:可以使用 eof()方法来检查是否已经读取到文件末尾。例如:

#include <iostream>
#include <fstream>

int main() {
    std::ifstream file("example.txt", std::ifstream::in); // 以读的形式,打开名为example.txt的文件

    if (file.is_open()) { // 检查文件是否成功打开
        std::string line;
        while (std::getline(file, line)) { // 逐行读取文件内容
            std::cout << line << std::endl; // 输出到控制台
        }

        file.close(); // 关闭文件
    } else {
        std::cout << "无法打开文件" << std::endl;
    }

    return 0;
}

总的来说,ifstream类提供了一种方便的方式来从文件中读取数据,并且具有与istream类相同的输入功能。通过使用 ifstream类,可以轻松地读取文件中的数据并进行处理。

五,Http协议内容

1. 请求方法

     
总结:POST较GET,对于计算机小白来说,有了更多的 隐私性,但 不代表安全,因为协议安全得通过加密保证。

2. HTTP状态码

         我们只要最基本的状态码信息就行,具体信息可以查找状态码表

最常见的状态码 , 比如 200(OK), 404(Not Found), 403(Forbidden), 504(Bad Gateway

301永久重定向 & 302,307临时重定向区别

1. 如果一个公司永久变更一个域名,那么原先的旧域名将不使用,而为了防止老用户寻找不到,于是访问旧域名会让用户跳转到新域名。而临时重定向只是一段时间内,访问一个域名跳转到另一个域名,老用户未来依旧可以使用老域名。
2. 永久重定向:影响未来决策
3. 临时重定向:不影响未来决策
客户端访问重定向流程(以浏览器为例)

3. HTTP常见的header

  • Content-Type: 数据类型(根据你请求的文件名后缀来判断,如text/html等)
  • Content-Length: Body的长度(资源长度)
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的(上一次的旧页面地址)
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能
这里讲解一下 Cookie, 但首先我们先小结一下,http协议的特征:
1. 简单快速
2. 无连接(底层虽然是TCP保持连接,但作为应用层是不维护连接的)
3. 无状态(http协议不会记录曾经是否登陆过服务端,换句话说不会记录用户信息,因此客户端每次请求(在需要登陆)都会向服务器请求登陆)
但这就有个疑问了,为什么我们登陆过的网站,在退出后,下次登陆依然能保持登陆信息呢?

Connection

下期:https协议

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

  • 35
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值