HTTP原理和概念
HTTP(超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。它是最常用的协议之一,用于在Web服务器和客户端之间传输数据。
HTTP步骤
- 建立连接:客户端通过URL与服务器建立TCP连接。
- 发送请求:客户端向服务器发送一个HTTP请求。
- 接收响应:服务器处理请求并返回一个HTTP响应。
- 关闭连接:在交换完数据后,关闭TCP连接(对于HTTP/1.1,连接可能会保持打开状态以用于后续请求)。
HTTP分类
- HTTP/1.0
- HTTP/1.1
- HTTP/2
- HTTP/3
HTTP用途
- 网页浏览
- 文件传输
- Web服务API通信
C语言实现HTTP客户端
以下是一个简单的C语言实现的HTTP客户端示例,它发送一个GET请求到一个指定的URL,并打印出服务器的响应。请注意,这个示例仅用于教学目的,不包含错误处理和复杂的HTTP功能。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#define BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
int sockfd; // 套接字文件描述符
struct sockaddr_in serv_addr; // 服务器地址结构
struct hostent *server; // 服务器主机信息
char buffer[BUFFER_SIZE]; // 缓冲区
if (argc < 3) { // 检查命令行参数
fprintf(stderr, "Usage: %s hostname port\n", argv[0]);
exit(1);
}
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
// 获取服务器主机信息
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr, "ERROR, no such host\n");
exit(1);
}
// 初始化服务器地址结构
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(atoi(argv[2]));
// 连接到服务器
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR connecting");
exit(1);
}
// 构造HTTP GET请求
char *request = "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n";
char request_str[BUFFER_SIZE];
sprintf(request_str, request, argv[1]);
// 发送HTTP请求
write(sockfd, request_str, strlen(request_str));
// 读取服务器响应
int n;
while ((n = read(sockfd, buffer, BUFFER_SIZE - 1)) > 0) {
buffer[n] = '\0'; // 确保字符串结束
printf("%s", buffer); // 打印响应
}
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
// 关闭套接字
close(sockfd);
return 0;
}
这个代码片段创建了一个简单的HTTP客户端,它连接到指定的服务器和端口,发送一个HTTP GET请求,并打印出服务器的响应。每行代码都有注释来解释它的作用。这个程序不处理HTTP响应的解析,只是简单地打印出接收到的所有数据。在实际应用中,你需要解析HTTP响应头来处理状态码、内容类型等。
报头解析
HTTP报头解析涉及读取HTTP响应的头部部分,并从中提取出各个字段及其值。以下是一个HTTP响应头的示例及其解析过程。
假设我们接收到以下HTTP响应头:
HTTP/1.1 200 OK
Date: Mon, 27 Sep 2021 12:28:53 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Content-Length: 12638
Connection: close
以下是解析过程,使用中横杠画出解析字段宽度:
HTTP/1.1 200 OK
----------------- Status Line (包含协议版本、状态码和状态消息)
Date: Mon, 27 Sep 2021 12:28:53 GMT
-------- Field Name (日期字段)
-------------------------- Field Value (日期值)
Server: Apache/2.4.41 (Ubuntu)
------- Field Name (服务器字段)
------------------------ Field Value (服务器信息)
Content-Type: text/html; charset=UTF-8
---------- Field Name (内容类型字段)
------------------------------- Field Value (内容类型和字符集)
Content-Length: 12638
------------- Field Name (内容长度字段)
------------------- Field Value (内容长度值)
Connection: close
----------- Field Name (连接字段)
------------------ Field Value (连接指示)
下面是一个简单的C语言函数,用于解析HTTP响应头中的各个字段:
#include <stdio.h>
#include <string.h>
#define MAX_HEADER_SIZE 1024
// 解析HTTP响应头中的字段
void parse_http_headers(const char *headers) {
char line[MAX_HEADER_SIZE];
const char *end_of_headers = strstr(headers, "\r\n\r\n"); // HTTP头以两个CRLF结束
const char *line_start = headers;
// 遍历每一行
while (line_start < end_of_headers && line_start != NULL) {
const char *line_end = strstr(line_start, "\r\n");
if (line_end == NULL) break;
size_t line_length = line_end - line_start;
if (line_length >= MAX_HEADER_SIZE) {
printf("Header line too long.\n");
return;
}
memcpy(line, line_start, line_length);
line[line_length] = '\0'; // 确保字符串结束
// 提取字段名和值
char *field_value = strchr(line, ':');
if (field_value != NULL) {
*field_value = '\0'; // 切断字段名
field_value++; // 跳过冒号
while (*field_value == ' ') field_value++; // 跳过空格
// 打印字段名和值
printf("%s: %s\n", line, field_value);
}
line_start = line_end + 2; // 移动到下一行开始
}
}
int main() {
const char *http_headers =
"HTTP/1.1 200 OK\r\n"
"Date: Mon, 27 Sep 2021 12:28:53 GMT\r\n"
"Server: Apache/2.4.41 (Ubuntu)\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Content-Length: 12638\r\n"
"Connection: close\r\n"
"\r\n"; // 注意最后的CRLF
parse_http_headers(http_headers);
return 0;
}
在这个函数中,我们通过遍历每一行来解析HTTP头。我们找到每行的结束(由CRLF标记),并从中提取字段名和值。字段名和值由冒号分隔,且值前可能包含空格。解析后,我们打印出字段名和值。这个函数假设HTTP头是正确的,并且不包含任何错误处理。在真实的应用中,你需要添加更多的错误检查和边界条件处理。