仿 muduo 高并发服务器项目:使用 C++ regex 解析 URL 和 HTTP 请求行

发布时间:2026/7/8 3:07:22

仿 muduo 高并发服务器项目:使用 C++ regex 解析 URL 和 HTTP 请求行
前言今天继续推进仿 muduo 高并发服务器项目。前面更多是在理解 Reactor、定时器、时间轮这些偏底层的服务器组件。今天没有直接写网络 IO而是先补 HTTP 模块里很重要的一块基础如何从字符串中解析出请求信息。在 HTTP 服务器中客户端发来的第一行数据通常叫做请求行例如GET /bitejiuyeke/login?userxiaomingpass123123 HTTP/1.1这一行里面包含了几个关键信息请求方法GET请求路径/bitejiuyeke/login查询参数userxiaomingpass123123HTTP 版本HTTP/1.1如果后续要实现 HTTP 请求解析器就必须把这些字段从原始字符串中拆出来。今天我先通过两个小实验学习 C 标准库里的std::regex为后面的 HTTP 解析做准备。一、先从一个简单的 URL 匹配开始第一个实验文件命名为regex.cpp。这个实验的目标很简单匹配/numbers/1234这种字符串并且把后面的数字1234单独提取出来。代码如下#include iostream #include string #include regex int main() { std::string str /numbers/1234; // 匹配以 /numbers/ 起始后面跟一个或多个数字字符的字符串 // 并且在匹配过程中提取这个数字字符串。 std::regex e(/numbers/(\\d)); std::smatch matches; bool ret std::regex_match(str, matches, e); if (ret false) { return -1; } for (auto s : matches) { std::cout s std::endl; } return 0; }运行后预期输出/numbers/1234 1234这里输出了两行看起来有点奇怪明明只是想匹配数字为什么还输出了完整字符串原因在于std::smatch matches保存的不只是括号里提取出来的内容还会保存整个正则表达式匹配到的完整结果。也就是说matches[0]表示完整匹配结果也就是/numbers/1234而matches[1]表示第一个捕获分组匹配到的内容也就是1234二、/numbers/(\\d)到底匹配了什么这个正则表达式虽然短但是里面已经包含了几个很关键的点std::regex e(/numbers/(\\d));可以拆成三部分理解。第一部分/numbers/这一段没有特殊含义就是普通字符串匹配。它要求目标字符串必须包含/numbers/这一段内容。第二部分\\d这表示匹配数字字符也就是0到9。这里最容易疑惑的是为什么不是\d而是\\d原因是 C 字符串本身也使用反斜杠做转义。正则表达式里想表达\d写进 C 字符串时就需要写成\\d。可以这样理解C 字符串中的 \\d - 正则表达式真正看到的 \d第三部分表示前面的内容出现一次或多次。所以\\d的意思就是匹配一个或多个数字字符。最后外层还有一对括号(\\d)这对括号表示捕获分组。捕获分组不只是参与匹配还会把匹配到的内容保存到matches中。所以这个正则整体含义就是匹配以 /numbers/ 开头后面跟一个或多个数字的字符串并把数字部分提取出来。三、std::regex_match和std::smatch这个实验里用到了两个核心对象std::regex e(/numbers/(\\d)); std::smatch matches;std::regex表示一个正则表达式对象。std::smatch用来保存匹配结果其中既包括完整匹配结果也包括每个捕获分组的结果。真正执行匹配的是bool ret std::regex_match(str, matches, e);这里要注意regex_match要求整个字符串都符合正则表达式。也就是说字符串/numbers/1234可以匹配成功但是如果字符串是abc /numbers/1234使用当前正则和regex_match就不能匹配成功因为整个字符串并不完全符合/numbers/(\\d)这个规则。这点对 HTTP 请求解析很重要。请求行解析时我希望整行都符合 HTTP 请求行格式而不是只在里面随便找到一小段能匹配的内容。四、解析 HTTP 请求行理解了简单的分组提取以后第二个实验开始解析 HTTP 请求行。文件命名为request.cpp。代码如下#include iostream #include string #include regex #include algorithm int main() { // HTTP 请求行格式 // GET /bitejiuyeke/login?userxiaomingpass123123 HTTP/1.1\r\n std::string str get /bitejiuyeke/login?userxiaomingpass123123 HTTP/1.1\r\n; std::smatch matches; // 请求方法的匹配GET HEAD POST PUT DELETE ... std::regex e( (GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?, std::regex::icase ); bool ret std::regex_match(str, matches, e); if (ret false) { return -1; } std::string method matches[1]; std::transform(method.begin(), method.end(), method.begin(), ::toupper); std::cout method std::endl; for (int i 0; i matches.size(); i) { std::cout i : ; std::cout matches[i] std::endl; } return 0; }这里我额外补充了一个头文件#include algorithm因为代码中使用了std::transform。有些编译环境如果不包含这个头文件会出现编译错误。五、HTTP 请求行正则拆解这次的正则明显比第一个实验复杂(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?可以一段一段拆开看。1. 请求方法(GET|HEAD|POST|PUT|DELETE)这一段用来匹配 HTTP 请求方法。|表示“或者”所以这一段表示可以匹配GET HEAD POST PUT DELETE外层括号表示捕获分组所以请求方法会被保存到matches[1]代码里的原始请求方法是小写的getstd::string str get /bitejiuyeke/login?userxiaomingpass123123 HTTP/1.1\r\n;但是正则对象创建时加了std::regex::icase这表示忽略大小写因此小写的get也可以匹配成功。匹配成功后我又通过下面这段代码把方法统一转成大写std::string method matches[1]; std::transform(method.begin(), method.end(), method.begin(), ::toupper);这样后续处理时就可以统一使用GET、POST这种大写形式避免大小写带来的判断问题。2. 请求路径请求方法后面有一个空格然后是 URL 路径([^?]*)最前面的空格就是普通空格用来匹配请求方法和 URL 之间的分隔。真正提取路径的是[^?]*其中[^?]表示匹配“不是问号的字符”。后面的*表示出现零次或多次。所以[^?]*整体意思就是一直匹配直到遇到?为止。对于这条请求get /bitejiuyeke/login?userxiaomingpass123123 HTTP/1.1路径部分就是/bitejiuyeke/login这部分会保存到matches[2]3. 查询字符串URL 后面可能带查询参数也可能不带查询参数。比如带查询参数/login?userxiaomingpass123123不带查询参数/login所以查询字符串这部分必须设计成可选匹配。对应正则是(?:\\?(.*))?这一段是今天最容易绕的地方。先看里面的\\?正则真正想匹配的是 URL 中的问号?。但是?在正则里本身有特殊含义表示前面的表达式出现零次或一次。所以如果想匹配普通问号需要写成\?。再放进 C 字符串里又要多转义一次于是写成\\?接着看(.*).表示任意字符*表示出现零次或多次。所以(.*)表示提取问号后面的任意内容。在当前请求行中这部分就是userxiaomingpass123123它会保存到matches[3]最外层还有(?: ... )?这里有两个点第一(?: ... )表示非捕获分组。普通括号(...)会把内容保存到matches中而(?:...)只参与匹配不会额外占用一个matches下标。第二最后的?表示这一整段出现零次或一次。也就是说(?:\\?(.*))?整体含义是如果 URL 中有 ?就提取 ? 后面的查询字符串如果没有 ?也允许匹配成功。这就让请求行既支持GET /login HTTP/1.1也支持GET /login?userxiaomingpass123123 HTTP/1.14. HTTP 版本查询字符串后面又有一个空格然后是 HTTP 版本(HTTP/1\\.[01])前面的空格用来匹配 URL 和 HTTP 版本之间的分隔。真正提取版本的是HTTP/1\\.[01]其中HTTP/1是普通字符串。\\.表示匹配普通的点号.。这里也需要注意.在正则里表示任意字符。如果想匹配真正的点号正则里要写成\.C 字符串里再写成\\.。最后[01]表示只能匹配0或1。所以这一段可以匹配HTTP/1.0 HTTP/1.1但是不能匹配HTTP/1.2 HTTP/2.0HTTP 版本会保存到matches[4]5. 行尾换行最后一段是(?:\n|\r\n)?HTTP 请求行通常以\r\n结尾但是在实验或者不同环境中也可能只看到\n。这里的|表示或者所以\n|\r\n表示匹配\n或者\r\n。外层使用了非捕获分组(?: ... )表示这一段只参与匹配不保存到matches。最后的?表示这段换行可以有也可以没有。所以请求行末尾即使没有换行这个正则也可以匹配成功。六、输出结果说明这段代码中for (int i 0; i matches.size(); i) { std::cout i : ; std::cout matches[i] std::endl; }会把所有匹配结果打印出来。对应关系如下matches[0]完整请求行 matches[1]请求方法 matches[2]URL 路径 matches[3]查询参数 matches[4]HTTP 版本对于这条请求get /bitejiuyeke/login?userxiaomingpass123123 HTTP/1.1提取结果可以理解为method GET path /bitejiuyeke/login query userxiaomingpass123123 version HTTP/1.1这样一来一条原始 HTTP 请求行就被拆成了后续服务器可以使用的结构化字段。七、捕获分组和非捕获分组的区别今天这两个实验里一个很重要的知识点就是括号的区别。普通捕获分组(...)它有两个作用参与正则匹配把匹配到的内容保存到matches中比如(GET|HEAD|POST|PUT|DELETE)会保存请求方法。非捕获分组(?:...)它只参与正则匹配不保存到matches中。比如(?:\\?(.*))?外层(?:...)只是为了把“问号和查询字符串”作为一个整体设置成可选并不需要保存这个整体。真正需要保存的是里面的(.*)也就是查询字符串本身。这个设计可以避免matches里出现很多不需要的中间结果让下标更清晰。八、今天这一步对项目的意义在仿 muduo 高并发服务器项目中底层网络库负责把 socket 上的数据读进来。但是读到数据以后服务器还需要知道客户端到底想请求什么资源。对于 HTTP 请求来说请求行就是解析的第一步。今天这个实验虽然只是两个小 demo但它已经能完成 HTTP 请求解析中最基础的字段提取原始请求行 - method path query version后续可以在这个基础上继续封装 HTTP 请求类例如保存请求方法保存请求路径保存查询字符串保存 HTTP 版本继续解析请求头根据路径分发不同的业务处理函数也就是说今天学习的正则表达式并不是孤立的小知识点而是在为后面的 HTTP 服务器模块打基础。九、今日收获今天主要完成了两个实验使用regex.cpp匹配/numbers/1234理解std::regex_match和捕获分组。使用request.cpp解析 HTTP 请求行提取请求方法、路径、查询字符串和 HTTP 版本。几个比较关键的理解点regex_match要求整个字符串都符合正则表达式。std::smatch中的matches[0]表示完整匹配结果。普通括号(...)会捕获内容并保存到matches。非捕获括号(?:...)只参与匹配不保存结果。C 字符串里的反斜杠需要额外转义所以正则里的\d要写成\\d。std::regex::icase可以忽略大小写适合处理 HTTP 方法这种字段。下一步就可以继续把这些解析逻辑封装到 HTTP 请求解析类中让服务器从“能收发 TCP 数据”继续往“能理解 HTTP 请求”的方向推进。

相关新闻

报社登报多少钱?报社登报如何办理?办理须注意什么?

报社登报多少钱?报社登报如何办理?办理须注意什么?

2026/7/8 3:07:22

内容摘要:登报整体费用在80-900 元,价格由报纸发行级别、公告字数、登报类型决定;办理分线上小程序、线下报社窗口两种渠道,备好对应身份材料提交文稿缴费即可,常规次日出刊;办理要认准带 CN 刊号正规报刊、…

bgp基础实验

bgp基础实验

2026/7/8 2:57:21

BGP基础实验实验拓扑及要求:IP配置:R1:interface GigabitEthernet0/0/0ip address 100.1.12.1 255.255.255.0interface GigabitEthernet0/0/1ip address 100.1.13.1 255.255.255.0interface LoopBack0ip address 1.1.1.1 255.255.255.255inte…

2026年国家级制造业单项冠军申报全攻略!

2026年国家级制造业单项冠军申报全攻略!

2026/7/8 2:57:21

近期多地制造企业咨询2026国家级制造业单项冠军申报事宜,行业普遍预判8月底工信部将启动新一轮遴选。作为制造业资质金字塔顶端荣誉,单项冠军评审标准逐年收紧,不少企业卡在硬指标、材料佐证、省级推荐环节遗憾落选。本文结合工信部《制造业单…

[Verilog HDL]第五章 条件语句、循环语句、块语句、生成语句

[Verilog HDL]第五章 条件语句、循环语句、块语句、生成语句

2026/7/8 9:08:13

本章全部语句属于行为级描述语句,仅能放置在initial、always过程块内部,模块顶层不能直接书写。本章语句分为两大类:可综合硬件语句(能生成真实电路)、仿真专用语句(仅用于 Testbench 测试,无法…

【小龙虾 AI】 本地智能体落地教学,45.8MB 资源快速完成整套环境部署(含安装包)

【小龙虾 AI】 本地智能体落地教学,45.8MB 资源快速完成整套环境部署(含安装包)

2026/7/8 9:08:13

OpenClaw 整合部署包|一键搭建本地 AI,告别复杂环境配置 适配系统:Windows10/11 64 位、macOS 12 及以上 当前版本:v2.7.9 核心优势:全程可视化操作,无需命令行、无需手动配置 Python/Node.js,…

如何在Flash消失的时代继续畅玩经典游戏?CefFlashBrowser让你重拾童年记忆 [特殊字符]

如何在Flash消失的时代继续畅玩经典游戏?CefFlashBrowser让你重拾童年记忆 [特殊字符]

2026/7/8 9:08:13

如何在Flash消失的时代继续畅玩经典游戏?CefFlashBrowser让你重拾童年记忆 🎮 【免费下载链接】CefFlashBrowser Flash浏览器 / Flash Browser 项目地址: https://gitcode.com/gh_mirrors/ce/CefFlashBrowser 还记得那些陪伴我们长大的Flash小游戏…

音频处理黑魔法:时间反转与相位反转的技术原理与应用

音频处理黑魔法:时间反转与相位反转的技术原理与应用

2026/7/8 9:08:13

核心结论:声音“反转”通常有两种截然不同的含义:时间反转(倒放)用于创造特殊的艺术效果;相位反转(极性反转)则是音频工程中用于修正相位问题、实现人声消除的基础手段。二者虽然都被称为“反转…

专业级抖音批量下载工具:高效构建你的数字内容资产库

专业级抖音批量下载工具:高效构建你的数字内容资产库

2026/7/8 9:08:13

专业级抖音批量下载工具:高效构建你的数字内容资产库 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback suppor…

照着用就行:盘点2026年顶尖配置的一键生成论文工具

照着用就行:盘点2026年顶尖配置的一键生成论文工具

2026/7/8 8:58:11

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂的一键生成论文工具,实测提速效果惊人,覆盖选题、写作、查重、排版全流程,真正帮你高效搞定论文。 一、全流程王者:一站式搞定论文全链路(一天定稿首选&#x…

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南

2026/7/8 0:44:47

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gi…

6个月转型AI工程师:实战路径与核心技能

6个月转型AI工程师:实战路径与核心技能

2026/7/8 6:29:42

1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公…

YOLOv5模型剪枝与量化实战:边缘设备部署优化

YOLOv5模型剪枝与量化实战:边缘设备部署优化

2026/7/7 13:20:59

1. 项目背景与核心价值在计算机视觉领域,YOLOv5因其出色的实时检测性能成为工业界宠儿。但当我们尝试将其部署到边缘设备(如树莓派、Jetson Nano或手机终端)时,立刻会遇到两个致命问题:模型体积庞大(原始YO…

Qwen Code+Obsidian构建可执行的AI时代认知操作系统

Qwen Code+Obsidian构建可执行的AI时代认知操作系统

2026/7/8 0:07:15

1. 项目概述:这不是又一个笔记软件教程,而是一套可复用的“认知操作系统”搭建逻辑你有没有试过,在读完Andrej Karpathy那篇著名的《Software 2.0》之后,满脑子都是“神经网络即代码”的震撼,但合上网页,打…

校园服饰细分赛道测算程序,学生平价国风,机能穿搭市场规模预估。

校园服饰细分赛道测算程序,学生平价国风,机能穿搭市场规模预估。

2026/7/8 0:07:15

校园服饰细分赛道测算程序(Python)——学生平价国风 & 机能穿搭市场规模预估(TAM / SAM / SOM)一、实际应用场景描述(真实业务抽象)在《时尚产业与品牌创新》课程中,细分市场切入&#xff0…

Visual C++运行库完整解决方案:告别软件兼容性问题

Visual C++运行库完整解决方案:告别软件兼容性问题

2026/7/8 0:07:15

Visual C运行库完整解决方案:告别软件兼容性问题 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾为打开游戏时弹出的"DLL缺失"错误…