Linux 学习笔记(四十四):HTTP 协议解析与极简 Web 服务器实战

📌 核心结论(TL;DR):

  1. HTTP 报文结构 :必须包含请求行/状态行、请求头/响应头、空行(\r\n)、正文。

  2. GET vs POST:GET 把参数放 URL 里,POST 把参数放 Body 正文里。

  3. 状态码:200 成功,301/302 重定向,404 找不到资源,500 服务器内部错误。

  4. 极简服务器核心 :只要按照 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

操作步骤:

  1. 打开你电脑上的浏览器。

  2. 地址栏输入服务器的 IP 和 8080 端口(比如你的云服务器公网IP:http://124.223.167.231:8080)。

  3. 见证奇迹:浏览器上会显示出巨大的 "Hello CSDN! Hello HTTP!"。

  4. 看终端 :回到 Linux 终端,你会看到浏览器发来的完整 HTTP 请求报文(包含 GET / HTTP/1.1, User-Agent, Accept 等)。

  5. 排坑 :如果你发现浏览器一直转圈圈,说明你的响应报文格式写错了(最常见的是少写了 \r\n,或者 Content-Length 长度不对,浏览器还在傻傻等数据)。

相关推荐
叼烟扛炮1 小时前
Linux 学习笔记(四十三):网络编程进阶——自定义协议解决 TCP 粘包与守护进程
linux·开发语言·网络·笔记·tcp/ip
Escalating_xu2 小时前
【Linux 网络编程】DNS 与 ICMP:从域名解析到 ping、traceroute,把“网络通不通”问清楚
linux·网络·php
songyuc2 小时前
【Manskill3】TableSceneBuilder学习笔记
pytorch·笔记·学习
外收内放2 小时前
Python基础语法练习题(75)
开发语言·python·学习
开开心心就好3 小时前
音频降噪剪辑工具,音量裁剪一键处理
运维·服务器·前端·spring·ocr·音视频·1024程序员节
一枕槐安4874 小时前
大模型限流、重试、超时处理:API 调用健壮性方案
linux·服务器·php·大模型api·指数退避·限流控制·超时预算
黑十4 小时前
PyTorch 报 “no CUDA GPUs are available“ 怎么办?3 步排查
服务器·网络·人工智能·pytorch·python
西索斯coding4 小时前
gpt-image-2.5-sunburst 和 gemini-3.1-flash-image 差多少?60 组项目素材的成功成本评测框架
java·linux·gpt·ai
箓维4 小时前
应用层协议基础_序列化与反序列化
linux