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


    目录

    一, 再谈协议

    网络版计算器小项目

    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.  我们在应用层进行创建约定,实现简单数据的序列化与反序列化接口。
    1. namespace Ser_Change
    2. {
    3. // 负责数据正反序列化
    4. class DataConversion
    5. {
    6. public:
    7. DataConversion(): _a(0), _b(0), _synbol(""){}
    8. DataConversion(int a, int b, std::string sy)
    9. : _a(a), _b(b), _synbol(sy) {}
    10. // 转序列化 “1 + 1”
    11. std::string serialize()
    12. {
    13. std::string tmp;
    14. tmp += std::to_string(_a);
    15. tmp += " ";
    16. tmp += _synbol;
    17. tmp += " ";
    18. tmp += std::to_string(_b);
    19. return move(tmp);
    20. }
    21. // 反序列化 从"1 + 2"提取数据
    22. bool Disserialize(const std::string &str)
    23. {
    24. size_t left = str.find(" ");
    25. size_t tail = str.rfind(" ");
    26. if (left != str.npos && tail != left)
    27. {
    28. _a = atoi(str.substr(0, left).c_str());
    29. _synbol = str.substr(left + 1, 1);
    30. _b = atoi(str.substr(tail + 1, str.size()).c_str());
    31. return 1;
    32. }
    33. else
    34. {
    35. return false;
    36. }
    37. }
    38. private:
    39. int _a;
    40. int _b;
    41. std::string _synbol;
    42. };

    编码经验:

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

    2. 长数据分割优化

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

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

    1. class DataConversion
    2. {
    3. public:
    4. DataConversion(): _a(0), _b(0), _synbol(""){}
    5. DataConversion(int a, int b, std::string sy)
    6. : _a(a), _b(b), _synbol(sy) {}
    7. // 使用更加规范的协议:
    8. // “lenght\r\n123+4321\r\n”
    9. // 在客户端高并发的情况下,对服务端socket缓冲区满载打入,服务端可以一次接收数据
    10. // 转序列化 “1 + 1” 转换
    11. std::string serialize()
    12. {
    13. std::string tmp;
    14. tmp += std::to_string(_a);
    15. tmp += SPACE;
    16. tmp += _synbol;
    17. tmp += SPACE;
    18. tmp += std::to_string(_b);
    19. std::string data;
    20. data += std::to_string(tmp.size());
    21. data += DECOLL;
    22. data += tmp;
    23. data += DECOLL;
    24. return move(data);
    25. }
    26. // 反序列化 "1 + 2"
    27. std::string Disserialize(std::string &str)
    28. {
    29. size_t left = str.find(DECOLL);
    30. size_t tail = str.find(DECOLL, left + DEC_LENGTH);
    31. // “lenght\r\n123 + 4321\r\n”
    32. if (left != str.npos && atoi(str.substr(0,left).c_str())
    33. == tail - left - DEC_LENGTH)
    34. // 检测数据包,是否包含1个完整包
    35. {
    36. size_t left_data = str.find(SPACE);
    37. size_t tail_data = str.rfind(SPACE, tail);
    38. _a = atoi(str.substr(left + DEC_LENGTH, left_data - left -
    39. DEC_LENGTH).c_str());
    40. _synbol = str.substr(left_data + SPACE_SIZE, SPACE_SIZE);
    41. _b = atoi(str.substr(tail_data + SPACE_SIZE, tail - tail_data -
    42. SPACE_SIZE).c_str());
    43. std::string surplus = str.substr(DEC_LENGTH);
    44. str.erase(0, tail + DEC_LENGTH);
    45. return move(surplus); // 返回拷贝后的剩余字段
    46. }
    47. else
    48. {
    49. return "";
    50. }
    51. }
    52. // private:
    53. int _a;
    54. int _b;
    55. std::string _synbol;
    56. };
    只要保证 , 一端发送时构造的数据 , 在另一端能够正确的进行解析, 就是 ok . 这种约定 , 就是 应用层协议。

    二,守护进程

    知识铺垫

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

    1. setsid

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

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

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

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

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

    2. 制作守护进程函数

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

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

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

    三,json

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

    centos 下载jsoncpp: 

    sudo yum install -y jsoncpp-devel

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

    基本接口使用例子如下:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. int main()
    6. {
    7. std::string fastmessage;
    8. // 序列化
    9. {
    10. Json::Value send;
    11. send["a"] = 1;
    12. send["b"] = 2;
    13. send["op"] = '+';
    14. //还满足啊嵌套使用
    15. Json::Value send2;
    16. send2["a"] = 1;
    17. send2["b"] = 2;
    18. send2["op"] = "+";
    19. send["send2"] = send2;
    20. Json::StyledWriter writer;
    21. //1.产生格式型的数据,如
    22. // {
    23. // "a" : _a
    24. // "b" : _b
    25. // "op" : _synbol
    26. // }
    27. // 更适合dubug时使用
    28. std::string message = writer.write(send);
    29. Json::FastWriter fastwriter;
    30. //2.产生类似数组的数据格式,可读性偏差些,如:
    31. // {"a":_a,"b":_b, "op":_synbol,{"a":_a,"b":_b, "op":_synbol}}
    32. fastmessage = fastwriter.write(send);
    33. std::cout << message << std::endl;
    34. std::cout << fastmessage << std::endl;
    35. }
    36. {
    37. //反序列化
    38. string str;
    39. Json::Value accept;
    40. Json::Reader reader;
    41. reader.parse(fastmessage, accept);
    42. int _a = accept["a"].asInt();
    43. int _b = accept["b"].asInt();
    44. char _op = accept["op"].asInt(); //char类型本质上是整数
    45. printf("_a:%d _b:%d _op:%c\n", _a, _b, _op);
    46. }
    47. return 0;
    48. }

    四,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()方法来检查是否已经读取到文件末尾。例如:

    1. #include
    2. #include
    3. int main() {
    4. std::ifstream file("example.txt", std::ifstream::in); // 以读的形式,打开名为example.txt的文件
    5. if (file.is_open()) { // 检查文件是否成功打开
    6. std::string line;
    7. while (std::getline(file, line)) { // 逐行读取文件内容
    8. std::cout << line << std::endl; // 输出到控制台
    9. }
    10. file.close(); // 关闭文件
    11. } else {
    12. std::cout << "无法打开文件" << std::endl;
    13. }
    14. return 0;
    15. }

    总的来说,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协议

    结语

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

  • 相关阅读:
    计算机毕业设计Java高校防疫物资管理系统(源码+系统+mysql数据库+lw文档)
    kafka基础知识点整理
    JAVA微信小程序核酸预约小程序系统毕业设计 开题报告
    tsconfig.json在配置文件中找不到任何输入,怎么办?
    C51--单片机中断
    Docker 四种制作镜像方式
    Android中另外一种降低Window窗口层级的方法
    R语言ggplot2可视化:基于aes函数中的group参数绘制分组折线图并添加数据点(散点)、geom_point函数中配置数据点形状、大小、颜色、填充色等
    蓬莱小课:应届生,如何找到第一份心仪的数据分析工作?
    做网赚的核心是:流量思维+变现思维
  • 原文地址:https://blog.csdn.net/qq_72112924/article/details/137468540