一、引言
最近在工作中,遇到了这么一个需求:
我们希望拥有一个高性能的 http 请求处理客户端程序,这个客户端要求有这样的架构:
- 它拥有两个线程
- 一个线程接收业务程序通过消息队列发来的批量的 http 请求信息,进行批量的 http 请求
- 另一个线程接收外部的 http 应答,并将应答信息放到本地的消息队列中供业务程序使用
- 要求在 http 请求的处理过程中,尽量保持不阻塞高性能处理
这个需求的框架图大概是这个样子:
可以看到,我们的需求,其实就是满足平台中大量的业务程序的 http 请求处理需求。其中业务程序自然不止一个,业务程序要请求的 http 服务端也不止一个,我们要做的 http 请求客户端程序实际上就像一个网关中间层。
那么现在问题来了,如果做到 http client 程序的高性能处理。我们当然不想它请求一次就阻塞掉,这样会非常非常的慢;我们也不想让处理应答这种耗时的操作占用太多时间,这样也会非常非常的慢。
此时,我想到了当初学习 libcurl 的时候在官网上读到的那句话:
The multi interface allows a single-threaded application to perform the same kinds of multiple, simultaneous transfers that multi-threaded programs can perform. It allows many of the benefits of multi-threaded transfers without the complexity of managing and synchronizing many threads.
尤其是最关键的执行请求的那个函数 curl_multi_perform 函数是异步的:
curl_multi_perform is asynchronous. It will only perform what can be done now and then return back control to your program. It is designed to never block. You need to keep calling the function until all transfers are completed.
这个接口非常适合用于我们现在的需求场景里面,当 http client 发起批量的 http 请求的时候,使用 libcurl multi 接口就可以实现不阻塞式异步处理。
那么如何设计和实现呢?
这里,我通过参考 libcurl 的官方示例 10-at-a-time.c 的代码框架,设计实现了两个设计方案(思路略有不同)的实现;并且因为工作环境的 libcurl 是老版本的库(没有 curl_multi_wait 函数的版本)而同时实现了新版本(使用 curl_multi_wait)的代码和旧版本(没有 curl_multi_wait 函数的版本)的代码。
接下来让我们开始吧:)
ps:
1. 10-at-a-time.c 官方示例代码网址 https://curl.haxx.se/libcurl/c/10-at-a-time.html
2. 本篇博客中所有的实现代码托管在 GitHub 上,仓库地址 https://github.com/wangying2016/libcurl_multi_http_client
二、分析:10-at-a-time.c 到底实现了什么
在设计实现我们自己的 http client 程序之前,我们先要来看看这个让我们启发很大的官方示例 demo 10-at-a-time.c 到底实现了什么。
根据 libcurl 的版本不同,10-at-a-time.c 的代码也稍有不同,这里我只分析最新的使用 curl_multi_wait 函数的版本(这一版本的代码也相当简洁),思路都是一样的,只是实现方式不一样(如果你跟我一样,也要考虑兼容老版本的 libcurl,那么建议你去官网上下载一个 curl-7.20.0.tar,进去里面的 docs/examples/10-at-a-time.c 中去看看老版本的实现方式)。
这里大家可以通过网址 https://curl.haxx.se/libcurl/c/10-at-a-time.html 点击进去阅读 10-at-a-time.c 的代码。我这里简单分析下这个程序的设计。
1. 10-at-a-time.c 程序功能
通过编译:
$ gcc -o 10-at-a-time 10-at-a-time.c -lcurl
你就可以拿到 10-at-a-time 的执行文件:
$ ./10-at-a-time
你就可以发现,这个程序实现了大量的 http 请求功能。具体并发量是多少呢?10,这也是它的名字的来源。
那么它是怎么实现的呢?
2. 10-at-a-time.c 框架分析
这里我也不浪费口舌,直接上流程图(细节比如说 transfers 是否小于当前全部 url 总数、获取消息是否处理成功等等判断未体现在流程图中,可自行仔细研读代码):
外层循环
使用 curl_multi_perform 批量发起 http 请求,使用 curl_multi_wait 轮询全部请求 handle 可读状态。外层循环主要是在发起请求,并且轮询请求 handle 状态。