开篇:本文解决什么问题?
- 拆解HTTP协议的核心特性与传输流程
- 区分HTTP长连接与短连接的适用场景
- 实现一个能处理静态资源的HTTP服务器
一、HTTP协议基础
1 什么是HTTP?
HTTP(超文本传输协议)是基于TCP/IP的应用层协议 ,主要用于客户端与服务器之间的静态资源(如HTML、图片)传输,遵循请求-响应的交互模式。
2 HTTP的核心特性
无状态:每次请求相互独立,服务器不存储客户端的会话信息
明文传输:数据在网络中以明文形式传输,安全性较低
基于TCP:依赖TCP的可靠传输特性,通信前需完成三次握手建立连接
3 无状态的弥补方案
为解决无状态导致的用户身份识别问题,方案如下:
Cookie:服务器通过响应头向客户端下发小型数据,客户端后续请求自动携带
Session:服务器为用户创建专属会话存储状态,通过Cookie传递 sessionID 匹配用户
Token:客户端登录后获取加密令牌,后续请求携带令牌完成身份认证,无需服务器存储会话
4 HTTP与HTTPS的对比
|------|---------|---------------|
| 对比维度 | HTTP | HTTPS |
| 安全性 | 低,明文传输 | 高,基于SSL/TLS加密 |
| 默认端口 | 80 | 443 |
| 地址标识 | http:// | https:// |
| 适用场景 | 普通网页浏览 | 支付、登录等敏感数据传输 |
二、传输过程
1. 建立TCP连接
客户端与服务器通过TCP三次握手建立可靠连接。
2. 发送HTTP请求
客户端发送符合协议格式的请求数据,包含请求行、请求头、请求体三部分。
请求行:如 GET /index.html HTTP/1.1 ,包含请求方法、资源路径、协议版本
请求头:携带附加信息(如浏览器类型、Cookie)
请求体:仅POST请求存在,用于传输表单、JSON等数据
3. 服务器处理并响应
服务器解析请求,返回响应数据,包含响应行、响应头、响应体三部分。
响应行:如 HTTP/1.1 200 OK ,包含协议版本、状态码、状态描述
响应头:携带数据长度、数据类型等信息
响应体:核心资源内容(如HTML代码、图片二进制数据)
4. 关闭TCP连接
短连接模式下,一次请求响应后通过四次挥手断开连接。
三、长连接与短连接
|------|---------------------|------------------------------|
| 对比维度 | 短连接 | 长连接 |
| 生命周期 | 一次请求-响应后关闭TCP连接 | 连接建立后可承载多次请求,空闲超时后关闭 |
| 协议标识 | HTTP/1.0 | HTTP/1.1,通过 keep-alive 头字段标识 |
| 资源消耗 | 高,频繁建立/断开连接 | 低,复用连接减少开销 |
| 适用场景 | 简单页面 | 复杂页面(多图片、CSS、JS资源加载) |
四、辅助协议
DNS:将域名转换为IP地址,解决"记域名比记IP更方便"的问题
ARP:将IP地址转换为MAC地址,实现局域网内设备寻址
DHCP:自动为设备分配IP地址、网关、DNS等网络配置
五、静态资源解析
1 核心概念
index.html:网站默认首页,用户访问域名时若未指定文件,服务器自动返回该文件
HTML:超文本标记语言,通过标签构建网页结构,核心标签包含 <html> 、 <head> 、 <body>
2 lseek函数(文件指针操作)
在处理静态文件时, lseek 用于移动文件读写指针,实现文件随机访问,函数原型:
cpp
off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符
offset:偏移字节数
whence:基准位置( SEEK_SET -文件开头、 SEEK_CUR -当前位置、 SEEK_END -文件末尾)
六、实现HTTP服务器
1 功能说明
本代码基于TCP套接字和HTTP/1.0协议,实现一个简易HTTP服务器,核心功能如下:
-
监听本地 127.0.0.1:80 端口,接收 客户端HTTP请求
-
解析GET请求中的资源路径,读取 对应本地静态文件
-
若文件存在,返回 200 OK 状态码和文件内容
-
若文件不存在,返回 404 Not Found 状态码和预设的404错误页面
-
单线程阻塞处理,同一时间仅能响应一个客户端请求
2 代码实现
(1)头文件与宏定义
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#define DATALENGTH 1024 // 数据缓冲区大小
#define PATHLENGTH 128 // 文件路径最大长度
(2)发送HTTP响应头
cpp
// 功能:构造并发送HTTP响应头
// 参数:c 客户端通信套接字,size 响应体大小,flag 文件存在标识(1存在/0不存在)
void SendHeadData(int c, int size, int flag) {
char buff[DATALENGTH] = "HTTP/1.0";
// 根据文件状态设置响应状态码
if (flag) {
strcat(buff, "200 OK\r\n");
} else {
strcat(buff, "404 Not Found\r\n");
}
// 填充响应头字段
strcat(buff, "Server: MyWeb/1.0\r\n");
strcat(buff, "Content-Length: ");
sprintf(buff + strlen(buff), "%d", size);
strcat(buff, "\r\n");
strcat(buff, "Content-Type: text/html;charset=utf-8\r\n");
strcat(buff, "\r\n"); // 响应头与响应体的分隔符(必须有)
// 发送响应头
send(c, buff, strlen(buff), 0);
}
(3)发送文件内容
cpp
// 功能:读取文件内容并发送给客户端(作为响应体)
// 参数:c 客户端通信套接字,fd 打开的文件描述符
void SendFileData(int c, int fd) {
while (1) {
char buff[DATALENGTH] = {0};
// 分块读取文件内容
ssize_t n = read(fd, buff, DATALENGTH - 1);
// 读取完毕或失败则退出循环
if (n <= 0) {
break;
}
// 发送读取到的文件内容
send(c, buff, n, 0);
}
}
(4)处理客户端请求
cpp
// 功能:接收并解析客户端HTTP请求,处理静态文件读取与响应
// 参数:c 客户端通信套接字
void DealClientData(int c) {
char requestBuff[DATALENGTH] = {0};
// 接收客户端请求数据
int n = recv(c, requestBuff, DATALENGTH - 1, 0);
if (n <= 0) {
return;
}
// 解析HTTP请求行,提取资源路径
// 示例请求行:GET /index.html HTTP/1.1 → 切割后获取 /index.html
char *file = strtok(requestBuff, " ");
file = strtok(NULL, " ");
int flag = 1; // 文件存在标识,默认存在
char path[PATHLENGTH] = "./"; // 本地文件根路径
strcat(path, file); // 拼接完整文件路径
// 以只读方式打开请求的文件
int fd = open(path, O_RDONLY);
// 文件不存在则打开404错误页面
if (fd == -1) {
fd = open("./404.html", O_RDONLY);
flag = 0;
}
// 获取文件状态(包含文件大小)
struct stat st;
fstat(fd, &st);
// 发送HTTP响应头
SendHeadData(c, st.st_size, flag);
// 发送文件内容
SendFileData(c, fd);
// 关闭文件描述符
close(fd);
}
(5)初始化服务器套接字
cpp
// 功能:创建、绑定、监听TCP套接字,完成服务器初始化
// 返回值:成功返回监听套接字,失败返回-1
int InitSocket() {
// 1. 创建TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
return -1;
}
// 2. 配置服务器地址结构体
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET; // IPv4协议族
saddr.sin_port = htons(80); // HTTP默认端口(转换为网络字节序)
saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本地回环地址
// 3. 绑定套接字与地址端口
int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (res == -1) {
return -1;
}
// 4. 开启监听(5为半连接队列最大长度)
res = listen(sockfd, 5);
if (res == -1) {
return -1;
}
return sockfd;
}
(6)主函数
cpp
// 功能:程序入口,启动服务器并循环接收客户端连接
int main() {
// 初始化服务器套接字
int sockfd = InitSocket();
// 断言检查初始化是否成功,失败则终止程序
assert(sockfd != -1);
// 循环等待客户端连接(服务器常驻运行)
while (1) {
struct sockaddr_in caddr; // 客户端地址结构体
socklen_t len = sizeof(caddr);
// 接受客户端连接(阻塞等待)
int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
if (c < 0) {
continue;
}
// 处理客户端请求
DealClientData(c);
// 关闭客户端通信套接字
close(c);
}
exit(0);
}
3 运行说明
(1)环境准备
在代码同级目录创建index.html (首页内容)和 404.html (404错误页面)
确保Linux环境下安装gcc编译器
(2)编译代码
bash
gcc http_server.c -o http_server
(3)运行服务器
bash
./http_server
(4)测试访问
浏览器访问 http://127.0.0.1 ,可查看 index.html内容
访问不存在的文件(如 http://127.0.0.1/test.html ),将显示 404.html内容