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

news/2024/9/29 0:33:30/
http://www.w3.org/2000/svg" style="display: none;">

https://i-blog.csdnimg.cn/direct/ee1dcb2433a44439bf833102af5c1e6d.png" alt="在这里插入图片描述" />

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

从零开始学习http协议

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

http_10">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!
https://i-blog.csdnimg.cn/direct/f51b67485a3344ad907a5d74035193bb.png" alt="在这里插入图片描述" />
访问时会将网址解析成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 格式

http_53">3 http的请求和应答

3.1 服务端设计

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

	// 注意设置为静态函数 , 不然参数默认会有TcpServer* this!!!static void *Execute(void *args){pthread_detach(pthread_self()); // 线程分离!!!// 执行Service函数TcpServer::ThreadData *td = static_cast<TcpServer::ThreadData *>(args);// 直接进行IOstd::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范围。
      https://i-blog.csdnimg.cn/direct/4979c2c1594844669052d8a880da62b8.png" alt="在这里插入图片描述" />

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

  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地址和端口号来进行访问:
https://i-blog.csdnimg.cn/direct/cf35e07030e347a1a7a94174360be37f.png" alt="在这里插入图片描述" />
进行访问之后,会获取到对应的信息:
https://i-blog.csdnimg.cn/direct/d6f0ee41990b4100b141fe4b70bec07c.png" alt="在这里插入图片描述" />

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

http_166">4 理解http请求与应答

4.1 宏观理解

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

一个基本的http请求的格式是这个样子的,按行为单位!
https://i-blog.csdnimg.cn/direct/23679daa99a34b9a8225e89094d33b1b.png" alt="在这里插入图片描述" />

  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的应答与请求的格式很类似:https://i-blog.csdnimg.cn/direct/57659642e9aa41dab0be2a088cc01941.png" alt="在这里插入图片描述" />

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

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

http_192">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);//请求正文}

这样就可以将一个字符串切分为四个部分了:
https://i-blog.csdnimg.cn/direct/f15343fc0a4a460c9605fca5d4f0c52f.png" alt="在这里插入图片描述" />
接下来我们可以对数据进行进一步处理:我们加入更加具体的成员变量

		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(); // 处理报头}

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

https://i-blog.csdnimg.cn/direct/57430a8762734999b8730ec2f51f26e3.png" alt="在这里插入图片描述" />
非常好,我们成功将reqstr进行了反序列化,之后我们再来实现业务逻辑的代码!!!

后续文章,敬请期待!


http://www.ppmy.cn/news/1530680.html

相关文章

网络通信——DHCP

目录 一.DHCP应用场景 二.通信过程 三.DHCP报文 四.DHCP通信原理 &#xff08;1&#xff09;租借过程 &#xff08;2&#xff09;DHCP 租期更新 &#xff08;3&#xff09;DHCP重绑定 五.一般路由器的DHCP支持两种地址池 &#xff08;1&#xff09;接口地址池 &…

nlp大语言模型原理

NLP&#xff08;自然语言处理&#xff09;的主要任务可以分为以下几个方面‌&#xff1a; ‌词法分析&#xff08;Lexical Analysis&#xff09;‌&#xff1a;这是NLP的基础&#xff0c;包括分词&#xff08;Tokenization&#xff09;、词性标注&#xff08;Part-of-Speech Ta…

IDEA自动清理类中未使用的import包

目录 1.建议清理包的理由 2.清理未使用包的方式 2.1 手动快捷键清理 2.2 设置自动清理 1.建议清理包的理由 有时候项目类文件中会有很多包被引入了&#xff0c;但是并没有被使用&#xff0c;这会增加项目的编译时间并且代码可读性也会变差。在开发过程中&#xff0c;建议设…

GPS在Linux下的使用(war driving的前置学习)

1.ls /dev/tty* 列出所有与 tty 相关的设备文件。这些设备文件通常对应终端设备 ttyUSB0是GPS端口 2.cat /dev/ttyUSB0 用于读取并显示连接到 /dev/ttyUSB0 串口设备发送的原始数据 这种是GPS定位不全的&#xff0c;要拿到更开阔的地方 这种是GPS定位全的 因为会持续输出…

JavaWeb——Vue组件库Element(1/6):快速入门(什么是Element,安装,引入ElementUI组件库,复制组件代码,启动项目 )

目录 什么是Element 快速入门 安装 引入ElementUI组件库 访问官网&#xff0c;复制组件代码 启动项目 小结 了解完前端的工程化之后&#xff0c;接下来了解一门新的前端技术&#xff1a;Vue 的组件库 Element。 学习完 Element 之后&#xff0c;即使作为一名 Java 后…

VulnHub-Narak靶机笔记

Narak靶机笔记 概述 Narak是一台Vulnhub的靶机&#xff0c;其中有简单的tftp和webdav的利用&#xff0c;以及motd文件的一些知识 靶机地址&#xff1a; https://pan.baidu.com/s/1PbPrGJQHxsvGYrAN1k1New?pwda7kv 提取码: a7kv 当然你也可以去Vulnhub官网下载 一、nmap扫…

[leetcode]39_组合总和_给定数组且数组可重复

给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。说明&#xff1a; 所有数字&#xff08;包括 target&#xff09;都是正整数。 解集不能包含重复的组合…

PCL 求八叉树的体素中心

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 八叉树构建 2.1.2 获取体素中心 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#xf…