引言
在学习了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基础知识,理解网页的构成