用 C 写一个极简 HTTP 服务器:从 socket 到解析请求行
HTTP 服务器本质上是一个监听 TCP 端口的程序,接收连接、读取请求、返回响应。本文用 C 语言实现一个仅支持 GET 请求的极简服务器,重点展示从创建 socket 到解析请求行的完整过程。
1. 创建监听 socket
服务器需要创建一个 TCP socket 并绑定到指定端口,然后开始监听:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUF_SIZE 4096
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) { perror("socket"); exit(1); }
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind"); exit(1);
}
if (listen(server_fd, 10) < 0) { perror("listen"); exit(1); }
printf("Listening on port %d\n", PORT);
关键点:SO_REUSEADDR 允许地址重用,避免重启时 bind 失败;INADDR_ANY 表示监听所有网卡。
2. 接受连接并读取请求
使用 accept 接受客户端连接,然后 recv 读取数据:
while (1) {
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) { perror("accept"); continue; }
char buf[BUF_SIZE];
ssize_t n = recv(client_fd, buf, BUF_SIZE - 1, 0);
if (n <= 0) { close(client_fd); continue; }
buf[n] = '\0';
注意:recv 一次不一定读到完整请求,生产环境需循环读取直到遇到 \r\n\r\n。
3. 解析请求行
HTTP 请求的第一行格式为:METHOD PATH VERSION,例如 GET /index.html HTTP/1.1。用 sscanf 或 strtok 解析:
char method[8], path[256], version[16];
if (sscanf(buf, "%7s %255s %15s", method, path, version) != 3) {
close(client_fd); continue;
}
printf("Method: %s, Path: %s, Version: %s\n", method, path, version);
解析后应校验方法是否为 GET,路径是否合法(如不以 .. 开头),防止路径穿越。
4. 构造并发送响应
一个最简单的响应包含状态行、Content-Length 和正文:
const char *body = "Hello, World!";
char response[512];
int len = snprintf(response, sizeof(response),
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: %zu\r\n"
"Connection: close\r\n"
"\r\n"
"%s", strlen(body), body);
send(client_fd, response, len, 0);
close(client_fd);
}
close(server_fd);
return 0;
}
5. 编译与测试
保存为 server.c,编译:
gcc -Wall -o server server.c
./server
另开终端测试:
curl http://localhost:8080/
# 输出 Hello, World!
6. 注意事项
- 上述代码仅用于学习,未处理并发(可用
fork或pthread改进)。 - 未做错误处理与超时控制,实际项目应添加。
- 解析请求行时务必限制缓冲区长度,避免溢出。
通过这个极简实现,可以清晰理解 HTTP 服务器从 socket 到请求解析的基本流程。