http_conn 类
封装了 HTTP 连接的核心功能,主要包括以下方法:
void init():初始化连接。void close_conn():关闭连接。void process():主处理函数,由 epoll 触发后调用。bool read_once():一次性读取数据。bool write():发送响应数据。HTTP_CODE process_read():驱动状态机解析 HTTP 请求。bool process_write():生成 HTTP 响应。bool add_response():格式化字符串并追加到写缓冲区。bool add_content():添加内容到响应体。bool add_status_line():添加状态行。bool add_headers():添加通用响应头。bool add_content_length():单独添加 content_length 头。bool add_linger():添加Connection: keep-alive或close。bool add_blank_line():表示响应头结束。LINE_STATUS parse_line():从 m_read_buf 中查找完整一行。HTTP_CODE parse_request_line():解析请求行。HTTP_CODE parse_headers():解析单个请求头字段。HTTP_CODE parse_content():处理 POST 请求体。HTTP_CODE do_request():决定返回什么内容。void initmysql_result():一次性从数据库加载所有用户到全局。
事件驱动 (Event-Driven)
基于 Linux epoll I/O 多路复用模型的非阻塞 I/O 模型。程序不主动轮询或阻塞等待 I/O,而是注册对某些'事件'的兴趣,当事件发生时,由系统通知程序进行处理。
核心组件:
| 组件 | 作用 |
|---|---|
| epoll | Linux 高效 I/O 多路复用机制(替代 select/poll) |
| 非阻塞 socket | 避免 read/write 阻塞线程 |
| 事件循环(Event Loop) | 主线程不断调用 epoll_wait() 等待事件 |
| 回调/处理函数 | 事件触发时执行的逻辑(如 http_conn::process()) |
具体体现:
- 主线程使用
epoll_wait()监听多个 socket 事件。 - 当某个客户端 socket 可读或者可写,epoll 通知服务器。
- 非阻塞 socket + ET/LT 模式。
状态机 (State Machine)
HTTP 请求是分阶段、异步到达的(可能分多个 TCP 包),状态机能增量解析,避免等待完整数据,提升响应速度和内存效率。
| 状态 | 任务 |
|---|---|
| 解析请求行 | 找到第一行 "GET ..." |
| 解析请求头 | 逐行读取 Host:, Content-Length: 等 |
| 解析请求体 | 如果是 POST,读取 username=... 这部分内容 |
| 将复杂的任务拆分为小步骤 | - |
代码实现:
enum CHECK_STATE {
CHECK_STATE_REQUESTLINE = 0, // 解析请求行
CHECK_STATE_HEADER, // 解析请求头
CHECK_STATE_CONTENT // 解析请求体
};
process_read() 状态转移:
http_conn::HTTP_CODE http_conn::process_read() {
LINE_STATUS line_status = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char* text = 0;
while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) ||
((line_status = parse_line()) == LINE_OK)) {
text = get_line();
m_start_line = m_checked_idx;
LOG_INFO("%s", text);
switch (m_check_state) {
case CHECK_STATE_REQUESTLINE: {
ret = parse_request_line(text); // 可能转移到 HEADER
if (ret == BAD_REQUEST) return BAD_REQUEST;
break;
}
case CHECK_STATE_HEADER: {
ret = parse_headers(text); // 可能转移到 CONTENT 或完成
if (ret == BAD_REQUEST) return BAD_REQUEST;
else if (ret == GET_REQUEST) return do_request();
break;
}
case CHECK_STATE_CONTENT: {
ret = parse_content(text); // 完成
if (ret == GET_REQUEST) return do_request();
line_status = LINE_OPEN;
break;
}
default: return INTERNAL_ERROR;
}
}
return NO_REQUEST;
}
零拷贝 (Zero-Copy)
通过 mmap() + writev() 发送文件,减少 CPU 参与数据的搬运,在用户态与内核态之间无冗余拷贝。
- 传统发送文件: 磁盘 → 内核 → 用户 → 内核 → 网卡,4 次上下文切换,4 次数据拷贝。
- 零拷贝实现:
// 1. mmap 将文件映射到用户空间(实际是内核页缓存的映射)
m_file_address = (char*)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, m_file_fd, 0);
// 2. 构造 iovec 数组(响应头 + 文件内容)
m_iv[0].iov_base = m_write_buf; // 响应头(小)
m_iv[0].iov_len = m_write_idx;
m_iv[1].iov_base = m_file_address; // 文件内容(大)
m_iv[1].iov_len = m_file_stat.st_size;
// 3. 一次系统调用发送全部
ssize_t ret = writev(m_sockfd, m_iv, 2);
- 优势: CPU 不参与数据的搬运,适合大文件传输。
HTTP 请求与响应
HTTP 请求结构:
GET /index.html HTTP/1.1 ← 请求行(方法 + 路径 + 协议版本)
Host: example.com ← 请求头(Header)
User-Agent: Chrome/120
Connection: keep-alive ← 空行(分隔头和体)
username=admin&password=123 ← 请求体 (Body,仅 POST/PUT 有)
- GET 请求通常没有请求体,参数在 URL 中。
- POST 请求由请求体携带数据。
响应头 (Response Header):
HTTP/1.1 200 OK ← 状态行(协议 + 状态码 + 描述)
Content-Type: text/html ← 响应头
Content-Length: 1024
Connection: keep-alive
Set-Cookie: sessionid=abc123
← 空行(头结束)
<html>...</html> ← 响应体
常见头字段及作用:
| 头字段 | 作用 |
|---|---|
Content-Type | 告诉浏览器内容类型(text/html, image/png, application/json) |
Content-Length | 响应体有多少字节 |
Connection | 是否保持连接(keep-alive 或 close) |
Location | 用于重定向(302 状态码) |
mmap 映射 (Memory Mapping)
一种将文件直接映射到内存的技术。你可以像访问数组一样访问文件内容,而无需调用 read()。
- 传统方式: 数据经过多次拷贝(磁盘 → 内核 → 用户 → 内核 → 网卡)。
- mmap 方式:
int fd = open("a.jpg", O_RDONLY);
struct stat sb;
fstat(fd, &sb);
char* data = (char*)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
// 现在 data 就是文件内容!
send(socket, data, sb.st_size, ...); // 内核直接从页缓存发数据,无需用户缓冲区
- 优势: 文件不经过用户空间缓冲区,CPU 不参与数据搬运,适合大文件传输。
函数返回为 HTTP_CODE
使用枚举类型代替 int 或 bool,语义更清晰,统一错误处理机制。
enum HTTP_CODE {
NO_REQUEST, // 请求不完整,需要继续读
GET_REQUEST, // GET 请求,可以处理
BAD_REQUEST, // 400 错误:语法错误
NO_RESOURCE, // 404 错误:文件不存在
FORBIDDEN_REQUEST, // 403 错误:无权限
FILE_REQUEST, // 静态文件请求,准备发送
INTERNAL_ERROR, // 500 错误:服务器内部错误
CLOSED_CONNECTION // 连接已关闭
};
大量枚举的存在增加了代码容错率,以及更加清晰的状态返回参数。


