【计网】从零开始学习http协议 --- http的请求与应答

news2024/9/27 12:14:42

在这里插入图片描述

如果你不能飞,那就跑;
如果跑不动,那就走;
实在走不了,那就爬。
无论做什么,你都要勇往直前。
--- 马丁·路德·金 ---

从零开始学习http协议

  • 1 什么是http协议
  • 2 认识URL
  • 3 http的请求和应答
    • 3.1 服务端设计
    • 3.2 如何让外界可以访问Linux云服务器
    • 3.3 运行测试
  • 4 理解http请求与应答
    • 4.1 宏观理解
    • 4.2 http请求反序列化

1 什么是http协议

虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用。 HTTP(超文本传输协议)就是其中之一。http应用十分的广泛,几乎每一名程序员(无论前后端 无论C++/Java/Go…)都会接触到!

在互联网世界中, HTTP(HyperText Transfer Protocol, 超文本传输协议) 是一个至关重要的协议。 它定义了客户端(如浏览器) 与服务器之间如何通信, 以交换或传输超文本,超文本支持视频,网页 ,图片等等!

HTTP 协议是客户端与服务器之间通信的基础。 客户端通过 HTTP 协议向服务器发送请求, 服务器收到请求后处理并返回响应。 HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。

但是有个疑问?http是基于TCP协议的,也就是面向连接的,为什么http确是无连接的协议呢?
因为http会使用Tcp建立的链接,无需再次建立新的链接。就好比之前我们实现的网络计算器,服务端和客户端的连接是通过TCP建立的,但是通信传输Request和Response直接通过Tcp建立的连接即可,无需再次建立连接!

2 认识URL

平时我们浏览的网站:百度 , 哔哩哔哩 ,力扣…等等网站都有一个域名

百度 :https://www.baidu.com/
哔哩哔哩:https://www.bilibili.com/
力扣:https://leetcode.cn/
...

这些网址都是https协议,这些网址其实就是URL!
在这里插入图片描述
访问时会将网址解析成IP地址!一般成熟的协议名称与端口号是强关联的,称之为知名端口号!

  1. HTTP (Hypertext Transfer Protocol):端口号:80 ,用于在Web服务器和客户端之间传输网页。
  2. HTTPS (HTTP Secure):端口号:443, HTTP的安全版本,通过SSL/TLS加密传输数据。
  3. FTP (File Transfer Protocol):
    • 控制端口:21 ,用于文件传输。
    • 数据端口:20,(主动模式)或随机端口(被动模式)
  4. SSH (Secure Shell):端口号:22 ,用于安全地访问远程服务器。

为什么平时访问网站并没有输入端口号?
只有同时具备IP地址和端口号才可以访问到对应的服务器,浏览器发起请求时会自动拼接端口号80!就类似日常生活中报警会自觉想到拨打110 , 火灾会自然的想到拨打119!

通信中离不开“资源”两个字,通信要么是从别处获取资源,要么是向对方发送资源。http协议下的资源是超文本!
网页,图片,音频,视频都是超文本!在进行通信之前,用户想要获取的资源都在后端的云服务器中,云服务器一般都是Linux系统,那么在Linux视角下不就都是文件吗!

为了将这个文件(资源)发给客户端,就必须要找到这个文件,那么怎么找到这个文件呢?当然是通过文件的唯一标识符 — 路径来实现!在URL中后半部分不就是我们的路径吗!这样通过IP地址确定的唯一主机+唯一的路径就可以标识互联网中的唯一的文件资源!

注意第一个斜杠不是Linux服务器的根目录,而是web根目录,web根目录可以是Linux中的任何目录!

所以URL就叫:统一资源定位符

urlencode 和 urldecode

  • 像 / ? : 等这样的字符,已经被 url 当做特殊意义理解了。因此这些字符不能随意出现。比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。
  • 转义的规则如下: 将需要转码的字符转为 16 进制, 然后从右到左, 取 4位(不足 4 位直接处理), 每 2 位做一位, 前面加上%,编码成%XY 格式

3 http的请求和应答

3.1 服务端设计

下图是http请求的一个信息:
在这里插入图片描述
接下来我们来通过代码实验,来测试一下是否可以获取到这些信息!
首先我们简化一下代码,在传输层直接进行IO,直接在Socket文件中获取数据流,将线程的函数方法修改为以下形式:

	// 注意设置为静态函数 , 不然参数默认会有TcpServer* this!!!
    static void *Execute(void *args)
    {
        pthread_detach(pthread_self()); // 线程分离!!!
        // 执行Service函数
        TcpServer::ThreadData *td = static_cast<TcpServer::ThreadData *>(args);
        // 直接进行IO
        std::string reqstr;
        // 这里默认读取到的是完整的请求
        ssize_t n = td->_sockfd->Recv(&reqstr);
        if (n > 0)
        {
            std::string resstr = td->_this->_service(reqstr);
            td->_sockfd->Send(resstr);
        }

        td->_sockfd->Close();
        delete td;
        return nullptr;
    }

回调函数单独设计一个HttpServer类来获取客户端申请的数据,目前直接进行简单的打印处理就好!

#include <iostream>
#include <string>

class HttpServer
{
public:
    HttpServer()
    {
    }
    std::string HandlerHelperRequest(std::string& Requeststr)
    {
        std::cout<<"------------------"<<std::endl;
        std::cout << Requeststr << std::endl;

        return std::string();//暂时这样
    }
    ~HttpServer()
    {
    }
};

然后ServerMain中,将HandlerHelperRequest作为回调函数构造TcpServer!进行启动即可!那么接下来我们是不是就可以在外界通过IP地址和端口号就可以访问Linux服务器上启动的进程了呢???还不可以,我们需要对Linux云服务器做一些处理,才能让外界成功的访问!

3.2 如何让外界可以访问Linux云服务器

让外界可以访问Linux云服务器需要两步操作:云服务器的安全组设置和服务器操作系统层面的防火墙设置。

云服务器的安全组设置操作步骤如下,这里以阿里云服务器为例:

  1. 首先在控制台中找到安全组,打开需要操作的实例对象
  2. 在实例中手动添加需要使用什么协议开放哪些端口,手动保存即可:
    • 协议类型:选择 TCP
    • 端口范围:填写“8888/8888”或“8888-8888”
    • 授权对象:可以设置为“0.0.0.0/0”以允许所有IP访问,但出于安全考虑,建议限制为您的IP地址或特定IP范围。
      在这里插入图片描述

云服务器设置好时候,接下来就进行服务器操作系统层面的防火墙设置:

  1. 对于CentOS:
    # 查看防火墙状态
    sudo systemctl status firewalld
    # 如果需要,启动防火墙服务
    sudo systemctl start firewalld
    # 检查端口8888是否开放
    sudo firewall-cmd --zone=public --query-port=8888/tcp
    # 如果端口未开放,添加端口规则
    sudo firewall-cmd --zone=public --add-port=8888/tcp --permanent
    # 重新加载防火墙规则
    sudo firewall-cmd --reload
    
  2. 对于Ubuntu:
    # 查看防火墙状态
    sudo ufw status
    # 如果需要,启用ufw
    sudo ufw enable
    # 检查端口8888是否开放
    sudo ufw allow 8888/tcp
    # 如果端口未开放,添加规则
    sudo ufw allow 8888/tcp
    # 重新加载防火墙规则
    sudo ufw reload
    

这样外界就可以通过IP地址和端口访问到对应的进程了!!!

3.3 运行测试

测试之前我们先获取一个当前机器的IP地址:

  1. 使用 curl 命令:
    curl ifconfig.me
    
  2. 使用 wget 命令:
    wget -qO- ifconfig.me
    

都可以获取到机器的外网IP!
我们启动程序,等待外部的链接:
可以通过手机或者电脑的浏览器通过IP地址和端口号来进行访问:
在这里插入图片描述
进行访问之后,会获取到对应的信息:
在这里插入图片描述

可以看到电脑WIndows系统和手机IPhone都成功的访问了我们的服务器!!!非常cool!!!

4 理解http请求与应答

4.1 宏观理解

请求和应答是http协议中双方都认识的结构化数据:

一个基本的http请求的格式是这个样子的,按行为单位!
在这里插入图片描述

  1. 请求行:指出请求类型(如GET或POST)、资源路径和使用的HTTP版本:
    • 方法(Method):表明对资源的请求类型,如 GET(获取资源)、POST(提交数据)、PUT(更新资源)、DELETE(删除资源)等。
    • URI(Uniform Resource Identifier):请求的资源的路径,例如一个网页的地址的后半部分。
    • HTTP版本(HTTP Version):表明使用的HTTP协议版本,如 HTTP/1.1 或 HTTP/2。
  2. 请求报头:提供关于客户端环境和请求本身的信息,如用户代理、接受的内容类型等。其中是以键值对的方式进行存储。
  3. 空行:请求报头和请求正文之间的分隔符。
  4. 请求正文(可选):包含要发送给服务器的数据,如表单数据。

http的应答与请求的格式很类似:在这里插入图片描述

  1. 状态行:包含HTTP版本、状态码和状态消息。例如,HTTP/1.1 200 OK 表示服务器成功处理了请求。
  2. 响应报头:提供关于响应的信息,如内容类型、内容长度、服务器类型、设置Cookie等。例如:
  3. 空行:响应报头和响应正文之间的分隔符。
  4. 响应正文(可选):包含从服务器返回的实际内容,如HTML页面、图片或其他数据。

知道了请求和报文的结构,其本质上还是报文,那么如何将其报头与有效载荷进行分离呢?
我们看到的请求和应答的结构可以看到,报头和报文是通过换行符进行分割的!巧了我们之前不也是这样进行操作的吗!而且只要有正文,就会有对应的content-length:xxx来帮我我们判断正文的是否完整!

4.2 http请求反序列化

接下来我们简单设计一下HttpRequesthttp请求的结构化数据!
首先根据其整体的结构我们可以加入四个成员变量:请求行 ,请求报头, 空行 ,请求正文

// 设计http协议
class HttpRequest
{
public:
    HttpRequest()
    {
    }
    void Serialization(std::string &reqstr)
    {
    }
    void Deserialization(std::string &reqstr)
    {
    }
    ~HttpRequest()
    {
    }

private:
    std::string _req_line;                 // 请求行
    std::vector<std::string> _req_headers; // 请求报头
    std::string _blank_line;               // 分割行
    std::string _req_body_text;            // 正文
};

这是最基本的四块数据,我们先对这四部分进行反序列化。因为他们都是根据分隔符\r\n进行分割的字符串,所以十分好处理:


    std::string GetLine(std::string &reqstr)
    {
        // 寻找分隔符
        auto pos = reqstr.find(base_sep);
        if (pos == std::string::npos)
            return std::string();
        std::string line = reqstr.substr(0, pos);
        if (line.empty())
            return base_sep;
        // 在原字符串中删除
        reqstr.erase(0, base_sep.size() + line.size());
        return line;
    }
    void Deserialization(std::string &reqstr)
    {
        // 进行反序列化
        _req_line = GetLine(reqstr);//请求行
        do
        {
            std::string header = GetLine(reqstr);
            if (header == "")
                break;
            else if (header == base_sep)
                break;
            else
                _req_headers.push_back(header);

        } while (true);//请求报头
        _blank_line = GetLine(reqstr);//空行
        _req_body_text = GetLine(reqstr);//请求正文
    }

这样就可以将一个字符串切分为四个部分了:
在这里插入图片描述
接下来我们可以对数据进行进一步处理:我们加入更加具体的成员变量

		std::string _method; //请求方法
        std::string _url; //请求路径
        std::string _version;//版本
        std::unordered_map<std::string , std::string> _kv_headers;//报头

处理很简单:按照字符串结构编写代码即可!

 void ParseReqLine()
    {
        // 优雅的操作!!!
        std::stringstream ss(_req_line);
        ss >> _method >> _url >> _version;
    }
    void ParseReqHeader()
    {
        for (auto &header : _req_headers)
        {
            auto pos = header.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = header.substr(0, pos);
            std::string v = header.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _kv_headers.insert(std::make_pair(k, v));
        }
    }

    void Deserialization(std::string &reqstr)
    {
        // 进行反序列化
		//...
        //----------------具体数据的处理-------------------
        ParseReqLine();   // 请求行的处理!!!
        ParseReqHeader(); // 处理报头
    }

我们可以将结果打印出来看看:

在这里插入图片描述
非常好,我们成功将reqstr进行了反序列化,之后我们再来实现业务逻辑的代码!!!

后续文章,敬请期待!

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

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

相关文章

Stack Overflow 如何提升其单元测试水平

在 Stack Overflow 成立之初&#xff0c;我们只是一个快速、精简运行的网站。Stackoverflow.com 是由开发人员为开发人员创建的小型初创公司。像所有初创公司一样&#xff0c;我们优先考虑对我们来说最重要的质量属性&#xff0c;而忽视了许多其他属性&#xff0c;包括根据最佳…

功耗或600W,RTX 5090、5080集体降级国内特供版

早在一个多月前&#xff0c;国外知名硬件泄密者 kopite7kimi 就曾透露 NVIDIA RTX 50 系显卡或推迟到明年初 CES 大会上发布。 来源&#xff1a;X 结合前几天爆出的台积电 4NP 工艺产能告急、NVIDIA 为提升下代显卡良率重新流片等。 如果不出意外&#xff0c;这原定在今年第四…

Π-系上的最小 d-系等于 Π-系上的最小集代数

称空间 Ω \Omega Ω 中满足下述条件的集系为 d d d-系: Ω ∈ D \Omega \in \mathscr{D} Ω∈D若 A , B ∈ D A, B \in \mathscr{D} A,B∈D 且 A ∩ B ϕ A \cap B\phi A∩Bϕ, 则 A B ∈ D AB \in \mathscr{D} AB∈D;若 A ⊂ B , A , B ∈ D A \subset B, A, B \in \…

visio 2021入门直通车(一天全搞定)

安装Visio 2021 (64bit)安装教程 1.1. 模板类型 1.2. 界面布局 1.3. 插入对象 1.4. 添加页面 1.5. 全屏演示|页面自适应|visio文件切换 1.6. 快捷键 快捷键说明 Shift 鼠标滚轮 按下shift&#xff0c;点击鼠标滚轮水平页面滚动 鼠标滚轮 垂直页面滚动 Ctrl 鼠标滚轮 按…

每天分享一个FPGA开源代码(6)- 浮点数运算

FPGA&#xff08;现场可编程门阵列&#xff09;是一种高度可配置的集成电路&#xff0c;它可以用于实现各种数字信号处理任务&#xff0c;包括浮点数运算。 在FPGA上进行浮点数运算通常涉及以下几个步骤&#xff1a; 1. 选择浮点数格式 浮点数运算首先要确定使用哪种浮点数格…

百度amis框架经验分享

百度amis框架经验分享 官方文档 amis - 低代码前端框架 这篇文章讲了amis的设计 为什么说百度AMIS框架是一个优秀的设计_百度前端框架-CSDN博客 学习方法&#xff1a; 最好的学习方法就是GPT官方文档 不要去很大力气通读官方文档&#xff0c;大概浏览一遍就行&#xff0c; 以你…

docker中搭建nacos并将springboot项目的配置文件转移到nacos中

前言 网上搜索docker中搭建nacos发现文章不是很好理解&#xff0c;正好最近在搭建nacos练手。记录一下整个搭建过程。 docker中搭建nacos并将springboot项目的配置文件转移到nacos中 前言1 docker中下拉nacos镜像2 配置nacos信息1. 创建docker的挂载目录&#xff0c;实现数据的…

SpringBoot之登录校验关于JWT、Filter、interceptor、异常处理的使用

什么是登录校验&#xff1f; 所谓登录校验&#xff0c;指的是我们在服务器端接收到浏览器发送过来的请求之后&#xff0c;首先我们要对请求进行校验。先要校验一下用户登录了没有&#xff0c;如果用户已经登录了&#xff0c;就直接执行对应的业务操作就可以了&#xff1b;如果用…

机器学习西瓜书笔记(十一) 第十一章特征选择与稀疏学习+代码

第十一章 特征选择与稀疏学习11.1 子集搜索与评价小结 11.2 过滤式选择小结 11.3 包裹式选择小结 11.4 嵌入式选择与L1正则化小结 11.5 稀疏表示与字典学习小结 11.6 压缩感知小结 11.7 代码单变量特征选择 11.8 章末小结 特征选择与稀疏学习 11.1 子集搜索与评价 小结 子集搜…

Facebook Marketplace无法使用的原因及解决方案

Facebook Marketplace是一项广受欢迎的买卖平台&#xff0c;然而&#xff0c;有时候用户可能会遇到无法访问或使用该功能的问题。通常&#xff0c;这些问题可以归结为以下几类原因&#xff1a; 地理位置限制&#xff1a; Facebook Marketplace并非在全球每个地区都可用。在某些…

【C++笔试强训】如何成为算法糕手Day2

学习编程就得循环渐进&#xff0c;扎实基础&#xff0c;勿在浮沙筑高台 循环渐进Forward-CSDN博客 目录 循环渐进Forward-CSDN博客 第一题&#xff1a;牛牛的快递 第二题&#xff1a;最小花费爬楼梯 第三题&#xff1a;数组中两个字符串的最小距离 补充0x3f3f3f3f 第一题…

OpenWrt学习(二)

OpenWrt是基于Linux系统进行开发需要学习Linux系统基本知识。下面介绍一下OpenWrt系统下常用的指令。 时间命令 查看当前时间 date查看当前日历 cal关机和重启 立即安全关闭系统 shutdown -h now 嵌入式设备不会关机&#xff0c;只会停止运行系统。 重新启动系统 reb…

内核是如何发送数据包

1、网络发包总览 网络发包总流程图如下&#xff1a; 从上图中可以看到用户数据被拷贝到内核态&#xff0c;然后经过协议栈处理后进入RingBuffer。随后网卡驱动真正的将数据发送了出去。当发送完成的时候&#xff0c;是通过硬中断来通知CPU&#xff0c;然后清理RingBuffer。 …

2024.9.25 作业和思维导图

栈 #include <iostream> #include <stdexcept> using namespace std;class My_stack { private:int * data; //栈空间int capacity;int top; //栈顶元素的下标 protected:public:/******************成员函数*************///构造函数My_stack(int c 10):capac…

JS中的事件和DOM操作

一、事件[重要] 1、 事件介绍 事件: 就是发生在浏览器(页面)上一件事,键盘事件,鼠标事件,表单事件,加载事件等等 2、 事件绑定方式 事件要想发生,就得将事件和标签先绑定(确定哪个标签发生什么事情,又有什么响应) 一个完整的事件有三部分 事件源(标签),哪里发出的事. 什么事(…

【DAY20240925】随机梯度下降:高效优化背后的原理与进阶策略

文章目录 前言随机梯度下降SGDMini-batch 随机梯度下降常见优化算法的改进版本 前言 梯度下降更新的通用形式&#xff1a; 论文中类似的表达形式&#xff0c;都表示根据 损失函数对这些参数的梯度 进行更新参数。梯度值较大时&#xff0c;说明当前控制参数对损失有较大的影响…

排序个人总结

插入排序 思路&#xff1b;定义 i 和 j&#xff0c;默认 i 前面的数都是有序的&#xff0c;j 定义为 i 的前一个数&#xff0c;把 i 的值给tmp&#xff0c;tmp与j对应的值进行比较&#xff0c;如果arr[j] > tmp,将arr[j] (大的数前移一位)&#xff0c;如下图 代码&#xf…

【亲子英语】英语故事有声绘本分享

文章目录 一、视觉与听觉的双重盛宴二、语言学习的最佳伙伴三、亲子共读的温馨时光四、适用人群广泛&#xff0c;随时随地学习五、获取方式 在这个快速发展的时代&#xff0c;英语学习已经不再局限于课本和课堂。特别是对于活泼好动的孩子们来说&#xff0c;一种既有趣又高效的…

open-resty 服务安装jwt插件

作者&#xff1a;程序那点事儿 日期&#xff1a;2023/11/16 22:07 lua-resty-jwt 插件 如果想使用Lua识别用户令牌&#xff0c;我们需要引入lua-resty-jwt模块&#xff0c;是用于 ngx_lua 和 LuaJIT 的 Lua 实现库&#xff0c;在该模块能实现Jwt令牌生成、Jwt令牌校验。 下载…

9.25作业

手动实现队列 代码如下 MyQueue.h #ifndef MYQUEUE_H #define MYQUEUE_H #include <iostream> #include <cstring> using namespace std;class Queue{ private:char* data; //字符串数据int len; //当前数量int size; //最大容量int front; //头索引int …