libfv:基于 C++20 的异步 HTTP 库

2022-04-23 20:52:46 +08:00
 fawdlstty

仓库地址:https://github.com/fawdlstty/libfv

介绍一款船新 HTTP 库。C++的 HTTP 库很多,但基于 C++20 的异步网络 HTTP 库几乎没有。我没找到好用的,因此写了一个。在讲解这个库之前,我先说说为什么我们需要这样的库。

C++ HTTP 库有两种主要的实现方式,第一种是同步 HTTP 网络访问,比如这样的代码:

// 伪代码
Response _r = HttpGet ("https://t.cn");
std::cout << _t.text;

这样的代码写起来很简单,但它存在一个问题:HTTP 网络访问比较耗时,可能需要几百毫秒,这么长时间,这个线程将阻塞在这里,比较消耗线程资源。假如遇到需要同时发起几十、几百个请求,将较大消耗系统资源。很显然,它不是一个较好的设计。

第二种是回调通知,比如这样的代码:

// 伪代码
HttpGet ("https://t.cn", [] (Response _r) {
	std::cout << _t.text;
});

这种方式解决了线程问题,也就是,几十、几百个请求可以同时发起,只需要极少量或者一个线程就行,HTTP 库内部实现了请求的内部管理,在收到请求的回复后,调用回调函数,从而实现请求的高效处理。但这种方式有个问题,假如我们需要根据请求结果内容转给下一个请求,这会带来一个回调地狱问题,比如这样的代码:

// 伪代码
HttpGet ("https://t.cn", [] (Response _r) {
    HttpGet (_t.text, [] (Response _r) {
        HttpGet (_t.text, [] (Response _r) {
            HttpGet (_t.text, [] (Response _r) {
                HttpGet (_t.text, [] (Response _r) {
                    std::cout << _t.text;
                });
            });
        });
    });
});

那么,有没更好的处理方式呢?有,通过 C++20 的 co_await 实现异步等待。下面给出 libfv 的发起请求的代码:

fv::Response _r = co_await fv::Get ("https://t.cn");

一方面它能获得回调方式的好处,也就是少量线程支撑同时大量的请求任务,同时它不会带来回调地狱问题。上面的代码通过 libfv 实现,代码可以这样写:

fv::Response _r = co_await fv::Get ("https://t.cn");
_r = co_await fv::Get (_r.text);
_r = co_await fv::Get (_r.text);
_r = co_await fv::Get (_r.text);
_r = co_await fv::Get (_r.text);
std::cout << _t.text;

这儿特别说明一下。单 CPU 处理效率来说,C++20 的异步性能比回调要低,大概 10%左右,也就是假设理论上跑满网络 IO 带宽情况,回调需要 10%的 CPU ,那么使用 C++20 的异步需要 11%,这是 stackless 需要付出的代价。当然,在我看来这个特性完全可以忽略,毕竟 IO 密集型应用首先需要考虑的是跑满网络带宽,一般不太需要关注 CPU 使用率。

libfv 使用方法见仓库:https://github.com/fawdlstty/libfv

4797 次点击
所在节点    C++
61 条回复
kizunai
2022-04-23 22:22:10 +08:00
mark 一下,以后可能会用得到
Calatrava
2022-04-23 22:25:02 +08:00
连接池,dns 缓存这些都没有。实际项目里做不了什么事的。
fawdlstty
2022-04-23 22:33:03 +08:00
@Calatrava 关于连接池,这个确实没有,后期会加入 http pipeline ,实现链接复用;第二个 dns 缓存,这个得自己处理。库这边已经支持了指定服务地址的功能,也就相当于手写一个 std::map 。当然,dns 缓存这块如果你能想到较好的解决方案,我加进去也行
rophie123
2022-04-23 22:34:53 +08:00
c++11 才用上
leimao
2022-04-23 22:36:01 +08:00
不错,但看成了 lib“废物”,狗头
fawdlstty
2022-04-23 22:42:42 +08:00
@leimao 网络库不都是 lib?v 么? libev 、libuv 、libhv 、etc.(狗头
leimao
2022-04-23 22:43:23 +08:00
@fawdlstty fv 看上去很像 fw ,我肯定是弹幕看多了
ysc3839
2022-04-23 22:53:37 +08:00
个人觉得非高性能场景的话,用线程池+同步 http 库会更简单
enchilada2020
2022-04-23 22:56:00 +08:00
仿佛在看 JS…从回调地狱到 await😌
Danswerme
2022-04-23 23:09:44 +08:00
@enchilada2020 我也这么感觉😂,虽然不懂 c++,但是这几种实现方式和 js 的那一套几乎完全一样啊。
fawdlstty
2022-04-23 23:11:57 +08:00
@ysc3839 非高性能场合确实同步更简单,相比可以少写个 co_await 关键字。这种场合主要就看,是否考虑软件未来并发量上去的情况
fawdlstty
2022-04-23 23:15:16 +08:00
@enchilada2020 c 艹的 co_await 就是微软的提案,来源就是 c#的 await 语法。用法和 python 、js 等等语言都一样。c 艹因为历史原因( c 艹标准委员会巨坑,一个特性得吵好久都没法进入标准),进度比其他语言慢的多。只能说关于 stackless 这块,勉强跟上了时代
fawdlstty
2022-04-23 23:17:42 +08:00
c 艹语法进步了,c 艹原本的库还没能更新呢,一堆库全同步或者回调的用法,标准出来等了两年多了都还没啥好用的库,这都 2202 年了
fawdlstty
2022-04-23 23:23:29 +08:00
@Danswerme c 艹 20 是不是很简单,和 js 一样(狗头
FrankHB
2022-04-24 04:14:44 +08:00
@fawdlstty 硬塞半成品进去就是拖到下一版本擦屁股,结果升级火葬场。实现碎片化一地:VS 支持的特性 GCC 没有,GCC 有的 Clang 又没,这样朝三暮四几年实现才勉强能用,然后又赶上新的版本出来了。估计接下来几年现实一点就是 C++11 直接跳到 C++26 ,中间版本别看了。
至于 async 这种说白了也是半成品,况且传染起来可一点不比 callback 含糊。js 用户都有不爽的:blog.logrocket.com/async-await-is-the-wrong-abstraction
同样是半成品,怎么看都不如这种:
open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0534r3.pdf
其实都挺过气的,co_备胎到处都是。
legacy.cs.indiana.edu/~sabry/papers/yield.pdf
http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf
ysc3839
2022-04-24 09:16:33 +08:00
@fawdlstty 我指的是线程池配合 co_await ,这样侵入性更小,不需要占用一个线程来跑事件循环,尤其适合主线程跑 GUI 的程序。
另外 C++的 coroutine 和 Python 的不一样,后者是和事件循环强绑定的,不能独立运行。C++的和 js 的类似,可以独立运行,可以代替回调函数。
janus77
2022-04-24 09:44:20 +08:00
rxcpp 有试过吗
fawdlstty
2022-04-24 10:10:55 +08:00
@FrankHB 1 、co_await 确实是半成品,直接用确实存在你说的那个问题,但我不是直接用,我用的 boost.asio ,如果这个库也存在你说的问题,那说明你用错了
2 、co_await 作为半成品的原因是,只提供了语法支持,需要自己造 awaitable 对象,c 艹 23 将在标准库引入 awaitable 对象,这代表老的 awaitable 也能继续用,因此升级也不会火葬场。即使库中的 awaitable 全体升级到 c 艹标准的 awaitable 对象,也能 ide 里全局字符串替换,几乎不会有啥问题
3 、任何技术都不可能 100%让所有人满意。我做这个初衷也不是为了在所有场合都能使用,至少,c 艹 17 及之前版本不能用。我做这个库的目的是为了方便实现 co_await 方式去等待 http 响应
fawdlstty
2022-04-24 10:12:28 +08:00
@ysc3839 想要实现 co_await 等待首先就必须得有一个线程池。我这儿是写死的一个线程池就一个线程。其实也可以多个
fawdlstty
2022-04-24 10:14:12 +08:00
@janus77 没有。看了下,也和 http 没啥关系吧

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/848811

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX