HTTP协议与服务器实现详解
本文介绍了HTTP协议的基本原理及实现简单HTTP服务器的过程。主要内容包括:1)HTTP协议特点,如基于TCP、请求-响应模型和无状态特性;2)HTTP请求方法(GET/POST)、报文结构和状态码分类;3)通过C语言实现HTTP服务器的关键步骤,包括创建TCP套接字、解析请求、处理文件操作和发送响应;4)简要说明HTML基础结构和HTTP长短连接区别。该实现可处理基本文件请求并返回200或40
引言
在学习了TCP协议编程之后,我们已经能够实现客户端和服务器之间的基本通信。然而,真正的互联网应用大多使用的是更高级的协议——HTTP协议。
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的协议之一。当你在浏览器中输入网址并回车时,浏览器与服务器之间就是通过HTTP协议进行通信的。

第一部分:HTTP协议基础
一、HTTP协议的特点
| 特性 | 说明 |
|---|---|
| 应用层协议 | 位于OSI模型的应用层 |
| 基于TCP | HTTP协议在传输层使用TCP协议(默认端口80) |
| 请求-响应模型 | 客户端发起请求,服务器返回响应 |
| 无状态协议 | 每个请求之间相互独立(Cookie解决状态问题) |
二、HTTP请求方法
| 方法 | 说明 | 安全性 | 典型场景 |
|---|---|---|---|
| GET | 请求获取资源 | 安全(只读) | 浏览网页、下载文件 |
| POST | 提交数据 | 不安全(修改服务器状态) | 表单提交、登录注册 |
| PUT | 上传资源 | 不安全 | 文件上传 |
| DELETE | 删除资源 | 不安全 | 删除数据 |
| HEAD | 只请求头部 | 安全 | 检查资源是否存在 |
| OPTIONS | 查询支持的方法 | 安全 | CORS预检请求 |
重点掌握GET与POST的区别:
| 对比项 | GET | POST |
|---|---|---|
| 作用 | 获取资源 | 提交数据 |
| 数据位置 | URL中 | 请求体中 |
| 数据长度限制 | 有(浏览器限制) | 无 |
| 幂等性 | 是 | 否 |
| 缓存 | 可缓存 | 不可缓存 |
三、HTTP请求报文结构
GET /index.html HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
请求报文结构:

四、HTTP响应报文结构
HTTP/1.1 200 OK
Server: my_http_server
Content-Type: text/html
Content-Length: 127<html>
<head><title>Test</title></head>
<body>Hello World</body>
</html>
响应报文结构:

五、HTTP状态码
| 分类 | 含义 | 典型状态码 |
|---|---|---|
| 1xx | 信息类 | 100 Continue(继续发送) |
| 2xx | 成功类 | 200 OK(请求成功) |
| 3xx | 重定向类 | 301(永久重定向)、302(临时重定向)、304(未修改) |
| 4xx | 客户端错误 | 400(错误请求)、401(未授权)、403(禁止)、404(未找到) |
| 5xx | 服务器错误 | 500(内部错误)、503(服务不可用) |
第二部分:HTTP服务器的实现
一、核心功能概述
我们要实现的HTTP服务器具备以下功能:
-
监听80端口(HTTP默认端口)
-
接收浏览器的HTTP请求
-
解析请求报文,获取请求的文件名
-
在本地查找文件
-
如果文件存在,返回200 OK和文件内容
-
如果文件不存在,返回404 Not Found
二、基础TCP服务器框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define PORT 80
#define BUFFER_SIZE 1024
#define FILE_BUFFER_SIZE 512
#define WEB_ROOT "/home/user/mycode/http" // 网站根目录
// 创建并初始化套接字
int init_socket() {
// 1. 创建套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket error");
return -1;
}
// 2. 设置端口复用(解决TIME_WAIT问题)
int opt = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 3. 绑定地址和端口
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind error");
close(sock_fd);
return -1;
}
// 4. 监听
if (listen(sock_fd, 5) == -1) {
perror("listen error");
close(sock_fd);
return -1;
}
printf("HTTP服务器启动成功,端口:%d\n", PORT);
return sock_fd;
}
三、解析请求获取文件名
// 从HTTP请求报文中提取请求的文件名
char* get_filename(char* buffer) {
// 按空格分割请求行
char* method = strtok(buffer, " ");
if (method == NULL) return NULL;
// 获取请求的URL(如 /index.html 或 /)
char* url = strtok(NULL, " ");
if (url == NULL) return NULL;
// 如果请求的是根路径(/),返回默认首页
if (strcmp(url, "/") == 0) {
return "index.html";
}
// 去掉开头的斜杠,返回相对路径
return url + 1;
}
四、打开文件并获取文件大小
// 打开文件并获取文件大小
int open_file(const char* filename, off_t* file_size) {
if (filename == NULL || file_size == NULL) return -1;
// 拼接完整路径
char fullpath[256];
snprintf(fullpath, sizeof(fullpath), "%s/%s", WEB_ROOT, filename);
// 打开文件
int fd = open(fullpath, O_RDONLY);
if (fd == -1) {
printf("文件不存在: %s\n", fullpath);
return -1;
}
// 获取文件大小
struct stat st;
if (stat(fullpath, &st) == -1) {
close(fd);
return -1;
}
*file_size = st.st_size;
return fd;
}
五、发送HTTP响应头
// 发送200 OK响应头
void send_http_header(int client_fd, off_t file_size) {
char header[512];
// 状态行
snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n"
"Server: my_http_server\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %ld\r\n"
"\r\n",
file_size);
send(client_fd, header, strlen(header), 0);
}
// 发送404 Not Found响应头
void send_404_header(int client_fd) {
const char* header =
"HTTP/1.1 404 Not Found\r\n"
"Server: my_http_server\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 58\r\n"
"\r\n"
"<html><body><h1>404 Not Found</h1></body></html>";
send(client_fd, header, strlen(header), 0);
}
六、发送文件内容
// 发送文件内容
void send_file_content(int client_fd, int file_fd) {
char buffer[FILE_BUFFER_SIZE];
ssize_t n;
while ((n = read(file_fd, buffer, FILE_BUFFER_SIZE)) > 0) {
send(client_fd, buffer, n, 0);
}
}
七、主函数
int main() {
int listen_fd = init_socket();
if (listen_fd == -1) {
exit(1);
}
while (1) {
// 接受客户端连接
int client_fd = accept(listen_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept error");
continue;
}
// 接收HTTP请求
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE);
int n = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
if (n > 0) {
printf("收到请求:\n%s\n", buffer);
// 解析请求,获取文件名
char* filename = get_filename(buffer);
printf("请求文件: %s\n", filename ? filename : "(null)");
// 打开文件并获取大小
off_t file_size;
int file_fd = open_file(filename, &file_size);
if (file_fd == -1) {
// 文件不存在,发送404
send_404_header(client_fd);
} else {
// 文件存在,发送200 OK和文件内容
send_http_header(client_fd, file_size);
send_file_content(client_fd, file_fd);
close(file_fd);
}
}
// 关闭连接(短连接)
close(client_fd);
}
close(listen_fd);
return 0;
}
第三部分:编译与运行
一、编译注意事项
# 80端口需要管理员权限
sudo gcc http_server.c -o http_server# 运行
sudo ./http_server
二、测试
-
启动服务器后,打开浏览器
-
访问
http://127.0.0.1/ -
服务器会返回
index.html文件 -
访问
http://127.0.0.1/test.html(如果文件存在)
第四部分:HTML基础知识
一、HTML基本结构
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>页面标题</title>
</head>
<body>
<h1>一级标题</h1>
<p>这是一个段落。</p>
<img src="image.jpg" alt="图片">
<a href="next.html">下一页</a>
</body>
</html>
二、常用HTML标签
| 标签 | 作用 |
|---|---|
<h1> - <h6> |
标题 |
<p> |
段落 |
<a> |
超链接 |
<img> |
图片 |
<div> |
区块容器 |
<span> |
内联容器 |
<ul>、<li> |
列表 |
<table> |
表格 |
第五部分:HTTP长短连接
一、短连接
-
每次请求都建立新的TCP连接
-
请求结束后立即关闭连接
-
适用于请求频率较低的场景
二、长连接
-
多次请求复用同一个TCP连接
-
通过
Connection: keep-alive头部声明 -
减少连接建立的开销,提高效率
// 长连接示例:循环处理同一个客户端的多个请求
while (1) {
memset(buffer, 0, BUFFER_SIZE);
int n = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
if (n <= 0) break; // 客户端关闭连接
// 处理请求...
// 不关闭连接,继续处理下一个请求
}
总结
一、HTTP核心知识点
| 知识点 | 说明 |
|---|---|
| 端口 | 默认80端口 |
| 请求方法 | GET(获取)、POST(提交) |
| 状态码 | 200(成功)、404(未找到)、500(服务器错误) |
| 请求结构 | 请求行、请求头、空行、请求体 |
| 响应结构 | 状态行、响应头、空行、响应体 |
| 长短连接 | Connection头部控制 |
二、实现HTTP服务器的关键步骤
-
创建TCP服务器,监听80端口
-
接收客户端连接
-
读取HTTP请求报文
-
解析请求行,获取请求的文件名
-
在本地查找文件
-
组装HTTP响应报文
-
发送响应头和文件内容
-
关闭连接(短连接)或保持连接(长连接)
本文从一个简单的HTTP服务器实现入手,深入讲解了HTTP协议的基本原理。理解HTTP协议不仅有助于网络编程,更是Web开发的基础。
学习建议:
-
动手实现HTTP服务器,观察浏览器请求的原始报文
-
理解GET和POST的区别
-
掌握常见状态码的含义
-
学习HTML基础知识,理解网页的构成
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)