📌 核心结论(TL;DR):
HTTP 报文结构 :必须包含请求行/状态行、请求头/响应头、空行(\r\n)、正文。
GET vs POST:GET 把参数放 URL 里,POST 把参数放 Body 正文里。
状态码:200 成功,301/302 重定向,404 找不到资源,500 服务器内部错误。
极简服务器核心 :只要按照 HTTP 规定的字符串格式,通过
write发送给浏览器,浏览器就能渲染出页面。
一、 HTTP 协议概述
HTTP(超文本传输协议)是应用层最核心的协议。它基于 TCP,但通常是短连接。浏览器通过 URL 定位资源,并通过 HTTP 报文与服务器通信。
二、 HTTP 报文格式解剖
1. 请求报文(浏览器 -> 服务器)
cpp
GET / HTTP/1.1\r\n
Host: 8.137.19.140:8888\r\n
User-Agent: Mozilla/5.0...\r\n
\r\n
注意:\r\n 是 HTTP 协议中极其重要的分隔符,空行意味着 Header 结束。
2. 响应报文(服务器 -> 浏览器)
cpp
HTTP/1.1 200 OK\r\n
Content-Type: text/html; charset=utf-8\r\n
Content-Length: 61\r\n
\r\n
<html><body>...</body></html>
Content-Length 是告诉浏览器响应正文有多长,浏览器据此判断数据是否接收完毕。
三、 核心方法:GET 与 POST
-
GET :用于获取数据,参数通过
?key=value拼在 URL 后面。有长度限制,不安全。 -
POST:用于提交数据,参数放在请求体(Body)中。支持大数据量。
四、 极简 HTTP 服务器实战

cpp
// 纯手工拼接响应报文
const char *body = "<h1>Hello HTTP!</h1>";
char response[1024];
snprintf(response,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %ld\r\n"
"Connection: close\r\n"
"\r\n"
"%s", strlen(body), body);
write(client_fd, response, strlen(response));
五、 踩坑记录:少写 \r\n 导致的浏览器死等
-
现象:代码运行正常,但浏览器一直在转圈,页面出不来。
-
原因 :响应头中少写了
\r\n(空行),或者Content-Length写错了。浏览器没有收到明确的报文结束标识,就会一直死等服务器发送后续数据。 -
解决:严格对照 HTTP 报文格式,使用抓包工具或在终端打印确认报文拼接无误。
自我实战操作
咱们写一段不到 50 行的 C 语言代码,纯手工拼接 HTTP 响应报文,实现一个极简服务器。
bash
cd ~/linux_study/network_test
实战:极简 HTTP 服务器
bash
vim http_server.c
(输入以下代码)
cpp
#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() {
// 1. 创建 TCP 套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return 1;
}
// 端口复用,防止服务器重启时提示 Address already in use
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 2. 绑定 IP 和端口
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 = INADDR_ANY;
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
return 1;
}
// 3. 监听
if (listen(server_fd, 5) < 0) {
perror("listen");
return 1;
}
printf("HTTP 服务器启动,请在浏览器访问: http://127.0.0.1:%d\n", PORT);
while (1) {
// 4. 接受客户端连接
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
continue;
}
// 5. 读取浏览器的 HTTP 请求(打印出来看看长啥样)
char buffer[BUF_SIZE] = {0};
int n = read(client_fd, buffer, sizeof(buffer) - 1);
if (n > 0) {
printf("========= 收到请求 =========\n%s\n===========================\n", buffer);
}
// 6. 构造 HTTP 响应报文
const char *body = "<html><body><h1>Hello CSDN! Hello HTTP!</h1><p>This is a simple HTTP server.</p></body></html>";
char response[2048]; // 确保缓冲区足够大
// 🌟 核心修复点:使用 snprintf 防止溢出,并确保参数顺序一致
snprintf(response, sizeof(response),
"HTTP/1.1 200 OK\r\n" // 状态行
"Content-Type: text/html; charset=utf-8\r\n" // 告诉浏览器这是网页,使用 utf-8 编码
"Content-Length: %ld\r\n" // 告诉浏览器正文有多长 (%ld 对应 strlen)
"Connection: close\r\n" // 告诉浏览器发完就断开
"\r\n" // 空行(Header结束)
"%s", strlen(body), body); // 正文 (%s 对应 body 指针)
// 7. 发送给浏览器
write(client_fd, response, strlen(response));
// 8. 关闭客户端套接字(短连接,发完就关)
close(client_fd);
}
close(server_fd);
return 0;
}
测试与排坑
bash
gcc http_server.c -o http_server
./http_server

操作步骤:
-
打开你电脑上的浏览器。
-
地址栏输入服务器的 IP 和 8080 端口(比如你的云服务器公网IP:
http://124.223.167.231:8080)。 -
见证奇迹:浏览器上会显示出巨大的 "Hello CSDN! Hello HTTP!"。
-
看终端 :回到 Linux 终端,你会看到浏览器发来的完整 HTTP 请求报文(包含
GET / HTTP/1.1,User-Agent,Accept等)。 -
排坑 :如果你发现浏览器一直转圈圈,说明你的响应报文格式写错了(最常见的是少写了
\r\n,或者Content-Length长度不对,浏览器还在傻傻等数据)。