实战案例8:网络协议解析

网络协议解析,说白了就是让程序能读懂网络上的“对话”。

我刚开始接触网络编程时,总觉得协议解析很神秘。后来用C语言写了一个简单的HTTP解析器,才发现核心逻辑其实不复杂——正则表达式就是那把钥匙。

今天咱们就一起动手,用正则表达式解析HTTP请求行、头部、URL参数和Cookie。这些技能在实际项目中非常实用,比如写一个轻量级Web服务器、抓包工具,或者嵌入式设备的网络模块。

8.1 解析HTTP请求行

HTTP请求行长这样:

GET /index.html?name=alice&age=25 HTTP/1.1

它包含三个部分:方法、URI、协议版本。中间用空格分隔。

对应的正则表达式:

^([A-Z]+)\s+([^\s]+)\s+HTTP/(\d+\.\d+)$

我来拆解一下:

  • ^([A-Z]+) — 匹配请求方法(GET、POST等)
  • \s+ — 一个或多个空格
  • ([^\s]+) — 匹配URI,不含空格
  • \s+HTTP/(\d+\.\d+)$ — 匹配协议版本

核心思路:用分组捕获三个关键字段,然后分别处理。

代码示例:

#include <stdio.h>
#include <regex.h>

int main() {
    char *request_line = "GET /index.html?name=alice&age=25 HTTP/1.1";
    regex_t regex;
    regmatch_t pmatch[4];
    char *pattern = "^([A-Z]+)\\s+([^\\s]+)\\s+HTTP/(\\d+\\.\\d+)$";

    if (regcomp(&regex, pattern, REG_EXTENDED) != 0) {
        printf("正则编译失败\n");
        return 1;
    }

    if (regexec(&regex, request_line, 4, pmatch, 0) == 0) {
        printf("方法: %.*s\n", pmatch[1].rm_eo - pmatch[1].rm_so,
               request_line + pmatch[1].rm_so);
        printf("URI: %.*s\n", pmatch[2].rm_eo - pmatch[2].rm_so,
               request_line + pmatch[2].rm_so);
        printf("版本: %.*s\n", pmatch[3].rm_eo - pmatch[3].rm_so,
               request_line + pmatch[3].rm_so);
    } else {
        printf("匹配失败\n");
    }

    regfree(&regex);
    return 0;
}

小技巧:我习惯用 regmatch_trm_sorm_eo 来截取子串,比用 strncpy 更安全。

8.2 解析HTTP头部

HTTP头部是键值对,每行一个:

Host: www.example.com
Content-Type: application/json
User-Agent: Mozilla/5.0

正则表达式:

^([^:]+):\s*(.*)$

这个模式匹配“键: 值”结构。注意冒号后面可能有空格,用 \s* 处理。

实战中,我经常需要解析多行头部。可以用循环配合 regexec 逐行匹配:

char *headers[] = {
    "Host: www.example.com",
    "Content-Type: application/json",
    "User-Agent: Mozilla/5.0",
    NULL
};

regex_t regex;
regcomp(&regex, "^([^:]+):\\s*(.*)$", REG_EXTENDED);
regmatch_t pmatch[3];

for (int i = 0; headers[i] != NULL; i++) {
    if (regexec(&regex, headers[i], 3, pmatch, 0) == 0) {
        printf("键: %.*s\n", pmatch[1].rm_eo - pmatch[1].rm_so,
               headers[i] + pmatch[1].rm_so);
        printf("值: %.*s\n", pmatch[2].rm_eo - pmatch[2].rm_so,
               headers[i] + pmatch[2].rm_so);
    }
}
regfree(&regex);

注意:头部键是大小写不敏感的。我建议匹配后统一转小写再比较,避免“Host”和“host”不匹配的问题。

8.3 解析URL参数

URL参数在问号后面,格式是 key=value&key=value

/search?q=hello&lang=en&page=2

解析思路分两步:

  1. 先用 \? 分割出参数字符串
  2. 再用 ([^&=]+)=([^&]*) 匹配每个参数

完整代码:

char *uri = "/search?q=hello&lang=en&page=2";
char *params = strchr(uri, '?');
if (params == NULL) {
    printf("没有参数\n");
    return 1;
}
params++; // 跳过 '?'

regex_t regex;
regcomp(&regex, "([^&=]+)=([^&]*)", REG_EXTENDED);
regmatch_t pmatch[3];

char *cursor = params;
while (regexec(&regex, cursor, 3, pmatch, 0) == 0) {
    printf("参数: %.*s = %.*s\n",
           pmatch[1].rm_eo - pmatch[1].rm_so, cursor + pmatch[1].rm_so,
           pmatch[2].rm_eo - pmatch[2].rm_so, cursor + pmatch[2].rm_so);
    cursor += pmatch[0].rm_eo;
}
regfree(&regex);

避坑指南:我曾经遇到URL参数中包含 %20 这样的编码字符。正则只负责提取,解码需要单独处理。建议用 strstr 查找 % 然后转换。

8.4 解析Cookie

Cookie头部格式:

Cookie: session_id=abc123; user=alice; theme=dark

每个Cookie用分号分隔,键值对用等号连接。

正则表达式:

([^=;\s]+)=([^;]*)

这个模式匹配“键=值”,键不含等号、分号和空格,值不含分号。

代码示例:

char *cookie = "session_id=abc123; user=alice; theme=dark";
regex_t regex;
regcomp(&regex, "([^=;\\s]+)=([^;]*)", REG_EXTENDED);
regmatch_t pmatch[3];

char *cursor = cookie;
while (regexec(&regex, cursor, 3, pmatch, 0) == 0) {
    printf("Cookie: %.*s = %.*s\n",
           pmatch[1].rm_eo - pmatch[1].rm_so, cursor + pmatch[1].rm_so,
           pmatch[2].rm_eo - pmatch[2].rm_so, cursor + pmatch[2].rm_so);
    cursor += pmatch[0].rm_eo;
    // 跳过分号和空格
    while (*cursor == ';' || *cursor == ' ') cursor++;
}
regfree(&regex);

个人经验:Cookie值里可能包含特殊字符,比如 user=alice smith。我建议用 [^;] 匹配值,而不是 [^;=],这样能保留空格。

8.5 知识体系总览

下面这张图总结了本章的核心逻辑:

HTTP协议解析 — 正则表达式应用 原始HTTP报文 请求行解析 头部解析 URL参数解析 Cookie解析 方法 / URI / 版本 键: 值 对 key=value & 分隔 key=value ; 分隔 ^([A-Z]+)\s+([^\s]+)\s+HTTP/(\d+\.\d+)$ ^([^:]+):\s*(.*)$ 结构化数据(键值对/字段)

从原始报文出发,经过四个分支解析,最终得到结构化数据。每个分支都用正则表达式做核心匹配。

8.6 综合实战建议

在实际项目中,我通常会把解析逻辑封装成函数:

  • parse_request_line() — 解析请求行
  • parse_headers() — 解析所有头部
  • parse_url_params() — 解析URL参数
  • parse_cookies() — 解析Cookie

每个函数内部用正则表达式提取关键信息,返回结构体或填充回调函数。

性能提醒:正则表达式编译(regcomp)比较耗时。我建议在程序初始化时编译好所有正则,运行时只做匹配(regexec),不要每次解析都重新编译。

嗯,到这里HTTP协议解析的四个核心场景就讲完了。你想想看,其实每个场景的正则都不复杂,关键是把模式写对,然后处理好边界情况。

我曾经在一个嵌入式项目里,用这套方法解析HTTP请求,整个解析器只用了200行C代码,跑在只有64KB RAM的芯片上,稳得很。


公众号:蓝海资料掘金营,微信deep3321