应用层协议 --- HTTP
序言
在上一篇文章中,我们在应用层实现了一个非常简单的自定义协议,我们在我们报文的首部添加了报文的长度并且使用特定的符号分割。但是想做一个成熟,完善的协议是不简单的,今天我们就一起看看我们每天都会用到的 HTTP协议 。
URL
1.再识 URL
刚接触电脑时我们肯定就会了解到 URL
,我们一般称他为 网址,我们利用网址来定位网络上的特定资源。但是现在我们开始接触计网了,还应该认识他的本质了。
在之前的内容中我们知道,所谓网络通信不过就是 物理距离增加后的 进程间通信
。想要和网络上的某个设备的进程进行通信,我们需要知道该设备的 IP 地址和端口号
,IP地址
用于将数据发送到该设备上,端口号
用于将数据发送到指定进程。
所以我们可以合理的推测 URL 的背后运行逻辑也是这样,但是我们一般的网址都是:
https://www.baidu.com/
没有你说的 IP,端口
呀?这是因为我们还需要 DNS
,网址中的域名部分通过 DNS
系统被解析成 IP
地址。DNS
是一个分布式数据库,负责将人类可读的域名转换成机器可读的 IP
地址。
这样做的原因是 网址提供了一种更加用户友好的方式来定位和访问这些资源
。通过域名系统和默认的端口号约定,用户可以在不知道具体IP地址和端口号的情况下,轻松地访问他们想要的网络资源。
2. URL 的组成
现在我使用百度的搜索引擎输出一个 你好
,URL 生成如下:
输出一个简单的 你好
竟然这么长,我们来层次的划分理解一下:
- 协议部分指定了用于访问资源的应用层协议,如
HTTP、HTTPS、FTP
等 - 域名部分代表了资源所在的主机或服务器的名称
- 路径部分指定了服务器上资源的具体位置,
帮助服务器定位到用户请求的具体资源
- 参数部分用于向服务器传递额外的信息,以便服务器根据信息返回更精确的结果
在这里重点介绍一下 路径部分
,当我们只是输入一个域名时我们可以在后面加上具体要访问的资源的路径,但是如果我们没有加上的话,他会自动跟一个 \
。咦?这不是根目录吗?不是的,服务端接收后会处理你的路径,方法各有不同,但是常见的是将 \
替换为他首页的页面路径,就拿百度举例:
HTTP 协议
1. 什么是 HTTP
在互联网世界中,HTTP(HyperTextTransferProtocol,超文本传输协议
)是一个至关重要的协议。它定义了 客户端(如浏览器)与服务器之间如何通信
,以交换或传输超文本(如 HTML
文档)。经过前面的的铺垫学习,我们知道了只要是协议,双方就达成了一定的约定!
在这里还需要注意重要的一点是,HTTP 的底层是基于 TCP 进行数据传输的!
2. HTTP 的请求与响应
2.1 请求报头
现在我们做一个简单的试验,我们在云服务器上运行我们的服务端程序,并在本地的浏览器上使用 IP + 端口号
的形式访问,可以看到服务端输出:
很奇怪,我们并没有发送任何的内容,这是哪里来的呀?大家别忘了,浏览器在应用层,发送信息是会对报文增添报头,即使报文为空!这里输出的内容,就是 HTTP 协议约定好的报头!!!
现在我们来看一下标准格式的请求头:
我们来了解一下请求行的作用:
- 请求方法:
–GET
: 请求指定的资源,我们用的最多的方式
–POST
:向服务器提交数据,比如登陆账号信息,会将具体的信息放在正文中 - URL:这指定了请求的资源的路径,前面介绍过
- HTTP版本:指定了客户端使用的
HTTP
协议版本,告诉服务器客户端能够理解的协议特性
请求报头数量是非常多的,并且很多都是需要特定的使用场景,我们只是介绍常用的报头:
- Host: 指定请求的服务器的域名和端口号
- User-Agent: 包含了发出请求的用户代理软件信息,通常是浏览器
- Accept: 告诉服务器可以发送哪些媒体类型的内容
- Content-Length: 请求体的长度
- Cookie:缓存部分用户信息
- …
在这里可能需要阐明一点,如果你的请求方式为 GET
,也是可以传递数据的。咦?不应该使用 POST
吗?两个都行!比如搜索引擎,我们需要向输入框内输入搜索查询内容吧,我们的输入的内容就会以参数的格式加在 URL 后面。所以只是两者的方式不同,前者是将传递数据放在正文中,后者是放在 URL 中。
Cookie 非常重要,在后面会专门写一篇文章来进行说明,今天重要的是大体介绍 HTTP 的内容。
2.2 响应报头
有了请求报文,现在我们再获取一下响应报文(在这里并没有找到一个很好抓包的方法,使用 AI 生成了一个效果一样的,嘻嘻):
响应报文的标准格式如下:
HTTP版本 就不多阐述了,我们重点其他内容,首先是状态码和状态码阐述:
一共包含 5 类,每一类的作用都大不相同,我们介绍最常见的状态码:
200 OK
:请求已成功,请求所希望的响应头或数据体将随此响应返回404 Not Found
:服务器无法找到请求的资源。在浏览器中,这意味着URL
输入错误或请求的页面已经被移除或不存在403 Forbidden
:服务器理解请求,但拒绝执行它,通常都是权限不够302 Found (Redirect)
:请求的资源临时移动到了另一个URI
。客户端应该使用GET
方法访问新的URI
,原始请求的方法可能不再适用504 Gateway Timeout
:作为网关或代理的服务器在等待上游服务器的响应时超时
原来这就是当我们搜索出错时弹出来的状态码的意义呀!最后便是我们的报头数据了:
- Content-Type:指示响应主体的数据类型
- Content-Length:指示响应主体的长度,以字节为单位
- Date:指示响应生成的日期和时间
- …
实现 HTTP 响应
首先我们来分析一下在应用层数据需要怎么处理:
- 首先,本地将请求序列化发送给服务端特定端口
- 服务端接收请求数据并反序列化,处理后得到相应的响应数据
- 服务端将响应数据序列化发送给客户端特定端口
- 客户端接受响应数据并反序列化,得到结果
结果分析,看起来我们需要完成请求数据的序列化和反序列化,响应数据的序列化和反序列化。但是其实 请求数据的序列化和响应数据反序列化
浏览器已经帮我们做了!当你访问一个 URL 时其实已经将请求数据序列化并发送了!根据你的搜索呈现相应的结果不就是响应数据的反序列化吗?
明白了这些,咋们直接开敲,首先肯定是构建 TCP(注意:HTTP协议的底层使用的是 TCP 协议,如果你使用 UDP 是不能正常沟通的!!!
) 的创建套接字文件:
// 创建套接字文件
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket:");
exit(1);
}
std::cout << "Successful create sockfd..." << std::endl;
之后便是绑定:
// 绑定
struct sockaddr_in Address;
Address.sin_family = AF_INET;
Address.sin_addr.s_addr = INADDR_ANY;
Address.sin_port = htons(PORT);
int n = bind(sockfd, (struct sockaddr*)&Address, sizeof(Address));
if(n < 0)
{
perror("bind::");
exit(1);
}
std::cout << "Successful bind..." << std::endl;
再者就是监听和接受连接:
// 监听
n = listen(sockfd, 3);
if(n < 0)
{
perror("listen:");
exit(1);
}
std::cout << "Successful listening..." << std::endl;
// 等待接收连接
struct sockaddr_in ClientAddress;
socklen_t len = sizeof(ClientAddress);
int newfd = accept(sockfd, (struct sockaddr*)&ClientAddress, &len);
if(newfd < 0)
{
perror("accept:");
exit(1);
}
std::cout << "Successful accept..." << std::endl;
这都是几乎比较固定的步骤!咋们对特定的业务只需要对数据处理模块进行特定的设计,现在我想要返回一个类似于网页的界面,就简单一下让人工智能帮咋们设计一下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>你好</title>
<style>
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
}
.bounce-text {
font-size: 24px;
animation: bounce 2s infinite;
}
</style>
</head>
<body>
<div class="bounce-text">你好</div>
</body>
</html>
现在当我们和客户端顺利连接时,咋们就按照协议的要求发送该内容:
std::string Content = GetIndex();
while(true)
{
std::string response;
response += "HTTP/1.0 200 OK\n";
response += "Content-Length:" + std::to_string(Content.size()) + '\n';
response += '\n'+ Content;
send(newfd, response.c_str(), response.size(), 0);
}
我们简单的添加了状态行和响应报头最后是我们的上述 html
的内容,最后看一下效果:
成功啦,也不难是吧!
总结
在这篇文章中我们简单认识了 HTTP 协议的组成,并且已经简单的实现了浏览器访问云服务器,返回相应数据的功能,希望大家有所收获!