引言
在上一篇文章中,我们深入探讨了“自定义协议”的概念、原理及其应用,并通过一个实际案例——跨网络计算器,展示了自定义协议如何有效地促进不同网络环境下的设备间通信和数据交换。自定义协议的灵活性和高效性为特定场景下的通信提供了强有力的支持。
今天,我们将转换视角,聚焦于互联网世界中最为广泛使用的一种协议——HTTP协议。HTTP(超文本传输协议)是构建万维网(WWW)的基础,它定义了客户端与服务器之间如何进行数据的传输和沟通。通过本文的介绍,我们将了解HTTP协议的基本结构、工作原理以及它在网络通信中的重要性。让我们一起开始这段关于HTTP协议的学习之旅吧。
一、认识URL
URL 代表着是统一资源定位符(Uniform Resource Locator)。URL 无非就是一个给定的独特资源在 Web 上的地址。理论上说,每个有效的 URL 都指向一个唯一的资源。这个资源可以是一个 HTML 页面,一个 CSS 文档,一幅图像,等等。而在实际中,也有一些例外,最常见的情况就是一个 URL 指向了不存在的或是被移动过的资源。由于通过 URL 呈现的资源和 URL 本身由 Web 服务器处理,因此 web 服务器的拥有者需要认真地维护资源以及与它关联的 URL。
一个 URL 由不同的部分组成,其中一些是必须的,而另一些是可选的。让我们以下面这个 URL 为例看看其中最重要的部分:
http 是协议。它表明了浏览器必须使用何种协议。它通常都是 HTTP 协议或是 HTTP 协议的安全版,即 HTTPS。Web 需要它们二者之一,浏览器也知道如何处理其他协议,比如 mailto:(打开邮件客户端)或者 ftp:(处理文件传输),所以当你看到这些协议时,不必惊讶。
www.example.com
是域名。它表明正在请求哪个 Web 服务器。或者,可以直接使用IP address,但是因为它不太方便,所以它不经常在网络上使用。
:80
是端口。它表示用于访问 Web 服务器上的资源的技术“门”。如果 Web 服务器使用 HTTP 协议的标准端口(HTTP 为 80,HTTPS 为 443)来授予其资源的访问权限,则通常会被忽略。否则是强制性的。
/path/to/myfile.html
是网络服务器上资源的路径。在 Web 的早期阶段,像这样的路径表示 Web 服务器上的物理文件位置。如今,它主要是由没有任何物理现实的 Web 服务器处理的抽象。
?key1=value1&key2=value2 是提供给网络服务器的额外参数。这些参数是用 & 符号分隔的键/值对列表。在返回资源之前,Web 服务器可以使用这些参数来执行额外的操作。每个 Web 服务器都有自己关于参数的规则,唯一可靠的方式来知道特定 Web 服务器是否处理参数是通过询问 Web 服务器所有者。
#SomewhereInTheDocument
是资源本身的另一部分的锚点。锚点表示资源中的一种“书签”,给浏览器显示位于该“加书签”位置的内容的方向。例如,在 HTML 文档上,浏览器将滚动到定义锚点的位置;在视频或音频文档上,浏览器将尝试转到锚代表的时间。
🚨注意:#后面的部分(也称为片段标识符)从来没有发送到请求的服务器。上面内容摘自:MDN
在网络通信中,尤其是通过HTTP协议进行数据传输时,经常需要对URL中的某些字符进行编码和解码,以确保数据的正确性和安全性。urlencode和urldecode就是这样两种常用的技术。
二、URL编码和解码
1. Urlencode(URL编码)
urlencode是一种编码机制,用于将非ASCII字符和一些特殊字符转换为可以在URL中安全传输的格式。这是因为URL只允许一小部分字符直接显示,而其他字符可能会引起歧义或被错误地解释。例如,空格' '在URL中通常被视为参数分隔符,而不是字符串的一部分。因此,urlencode会将空格转换为+,或者将其转换为%20这样的百分比编码形式。
在进行urlencode时,以下字符会被保留,不需要编码:
字母(a-z
,A-Z
)
数字(0-9
)
- _ . ! ~ * ' ( )
所有其他字符都会被编码为%
后跟两位十六进制数的形式。
2. Urldecode(URL解码)
与urlencode
相对应,urldecode
是将编码后的URL转换回原始格式的过程。例如,%20
会被解码回空格' '
,+
会被解码回空格,其他编码的字符也会被转换回其原始表示。
应用场景
表单提交 :在HTML表单中,用户输入的数据需要通过URL或POST请求发送到服务器。urlencode
用于确保这些数据在传输过程中不会被误解或损坏。
URL参数 :URL中的查询字符串参数经常需要包含特殊字符,使用urlencode
可以确保这些参数能够正确地被服务器解析。
数据传输 :在网络应用中,为了确保数据的完整性和准确性,发送方需要对数据进行urlencode
,接收方在接收到数据后需要进行urldecode
以还原数据的原始形式。
示例
原始字符串:Hello, World!
urlencode
后:Hello%2C%20World!
urldecode
后:Hello, World!
通过urlencode
和urldecode
,我们可以确保在网络中传输的数据不会因为特殊字符而遭到破坏,从而保证了数据的完整性和安全性。这两种技术在日常网络应用中扮演着至关重要的角色。
三、HTTP的方法
四、HTTP的状态码
下表是一些常见的HTTP状态码
⭕ 这些状态码是HTTP协议中定义的一部分,用于告知客户端请求的处理结果。状态码分为五个类别:1xx(信息性状态码),2xx(成功状态码),3xx(重定向状态码),4xx(客户端错误状态码)和5xx(服务器错误状态码)。每个状态码都有其特定的含义和用途,帮助开发者和用户理解HTTP请求的结果,并采取相应的措施。
五、HTTP常见Header
请注意,上表中的“常见值示例”列仅提供了可能的值,并不是完整的头部字段值。实际的头部字段值可能会根据具体的应用场景和需求而有所不同。
六、最简单的HTTP服务器
#include <sys/socket.h> // 引入套接字相关的头文件
#include <netinet/in.h> // 引入处理IPv4地址的头文件
#include <arpa/inet.h> // 引入INET相关函数的头文件
#include <unistd.h> // 引入UNIX标准函数,如close()
#include <stdio.h> // 引入标准输入输出头文件
#include <string.h> // 引入字符串处理函数的头文件
#include <stdlib.h> // 引入标准库函数,如atoi()
// 打印服务器的使用方法
void Usage() {
printf("usage: ./server [ip] [port]\n");
}
int main(int argc, char* argv[]) {
// 确保命令行参数数量正确(应为3个:程序名、IP地址和端口号)
if (argc != 3) {
Usage();
return 1;
}
// 创建一个基于IPv4的TCP套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket"); // 如果创建失败,打印错误信息
return 1;
}
struct sockaddr_in addr; // 定义一个地址结构体
addr.sin_family = AF_INET; // 设置为IPv4地址族
addr.sin_addr.s_addr = inet_addr(argv[1]); // 设置IP地址
addr.sin_port = htons(atoi(argv[2])); // 设置端口号,并转换为网络字节序
// 将套接字绑定到指定的IP地址和端口
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind"); // 如果绑定失败,打印错误信息
return 1;
}
// 开始监听传入的连接,允许最多10个连接同时等待
ret = listen(fd, 10);
if (ret < 0) {
perror("listen"); // 如果监听失败,打印错误信息
return 1;
}
// 无限循环,持续接受客户端的连接
for (;;) {
struct sockaddr_in client_addr; // 定义客户端地址结构体
socklen_t len = sizeof(client_addr); // 定义长度变量
// 接受一个客户端连接,并将客户端的地址信息存储在client_addr中
int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
if (client_fd < 0) {
perror("accept"); // 如果接受连接失败,打印错误信息
continue; // 继续下一次循环
}
// 定义一个缓冲区,用于存储从客户端读取的数据
char input_buf[1024 * 10] = {0};
// 从客户端读取数据,最多读取缓冲区大小-1字节
ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);
if (read_size < 0) {
perror("read"); // 如果读取失败,打印错误信息
close(client_fd); // 关闭客户端套接字
continue; // 继续下一次循环
}
// 打印接收到的请求
printf("[Request] %s\n", input_buf);
// 定义一个缓冲区,用于存储响应数据
char buf[1024] = {0};
// 定义要发送的HTML内容
const char* hello = "<h1>hello world</h1>";
// 格式化HTTP响应消息,包括HTTP头部和HTML内容
sprintf(buf, "HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s", strlen(hello), hello);
// 将响应消息发送回客户端
write(client_fd, buf, strlen(buf));
// 关闭客户端套接字
close(client_fd);
}
// 关闭服务器套接字
close(fd);
return 0; // 正常退出
}
这段代码是一个简单的HTTP服务器实现,它监听指定的IP地址和端口上的TCP连接,并响应每个连接以"Hello, World!"页面,服务器使用标准的套接字API来处理网络通信。
温馨提示
感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!