LibCurl C++ HTTP/2多路复用场景下如何控制请求发送速率
libcurl HTTP/2多路复用请求限流实现方案
问题答复
- 存在可实现每3毫秒发送一批多路复用请求的方案,核心逻辑为增量添加easy handle到multi handle而非一次性添加所有请求
- 将main完整逻辑封装到for循环中加延迟的方案不可行,会导致每次循环重建TCP连接,丢失HTTP/2多路复用的优势,额外的握手开销也会拖慢整体性能
实现思路
全程复用同一个multi handle维持单TCP连接,不一次性将所有请求的easy handle加入multi handle,而是分批增量添加,每添加一批后插入3毫秒延迟再添加下一批,请求发送过程全程异步非阻塞,无需等待服务端响应即可继续发送下一批请求,完美匹配需求。
多路复用是HTTP/2中的一种机制,可通过单个TCP连接异步发送多个HTTP请求、接收对应响应,是HTTP/2协议的核心特性。
修改后代码
#include <iostream> #include <stdlib.h> #include <string.h> #include <errno.h> #include <chrono> #include <string> #include <algorithm> /* somewhat unix-specific */ #include <sys/time.h> #include <unistd.h> /* curl stuff */ #include <curl/curl.h> #include <curl/mprintf.h> #ifndef CURLPIPE_MULTIPLEX #define CURLPIPE_MULTIPLEX 0 #endif struct CURLMsg *msg; struct transfer { CURL *easy; unsigned int num; std::string contents; }; #define NUM_HANDLES 1000 // 总请求数上限,可根据需求调整 const int BATCH_SIZE = 3; // 每批发送的请求数,可调整 const int SEND_INTERVAL_US = 3000; // 发送间隔3毫秒,单位微秒 static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { transfer *t = (transfer *)userp; size_t realsize = size * nmemb; t->contents.append((const char*)contents, realsize); return realsize; } static void setup(struct transfer *t, int num) { CURL *hnd; hnd = t->easy = curl_easy_init(); curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(hnd, CURLOPT_WRITEDATA, (void *)t); /* set the same URL */ curl_easy_setopt(hnd, CURLOPT_URL, "https://someurl.xyz"); /* HTTP/2 please */ curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); /* 调试用跳过证书校验,生产环境按需开启 */ curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L); #if (CURLPIPE_MULTIPLEX > 0) /* 等待连接复用确认,避免新建多余连接 */ curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L); #endif } int main() { struct transfer trans[NUM_HANDLES]; CURLM *multi_handle; int i; int still_running = 0; int total_transfers = 1000; // 实际要发送的总请求数,需小于等于NUM_HANDLES int added_count = 0; // 已添加到multi handle的请求数 /* 初始化所有easy handle,暂不加入multi */ for(i = 0; i < total_transfers; i++) { setup(&trans[i], i); } multi_handle = curl_multi_init(); curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); curl_multi_setopt(multi_handle, CURLMOPT_MAX_TOTAL_CONNECTIONS, 1L); // 强制只用1个连接,确保多路复用生效 // 主循环 do { // 增量添加请求批次 if (added_count < total_transfers) { int add_num = std::min(BATCH_SIZE, total_transfers - added_count); for (int j = 0; j < add_num; j++) { curl_multi_add_handle(multi_handle, trans[added_count + j].easy); } added_count += add_num; usleep(SEND_INTERVAL_US); // 批次间插入3毫秒延迟 } CURLMcode mc = curl_multi_perform(multi_handle, &still_running); if(still_running) { // poll超时设为1毫秒,提升请求添加时机的精准度 mc = curl_multi_poll(multi_handle, NULL, 0, 1, NULL); } if(mc) { break; } // 处理返回响应 do { int queued; msg = curl_multi_info_read(multi_handle, &queued); if ((msg) && (msg->msg == CURLMSG_DONE) && (msg->data.result == CURLE_OK)) { curl_off_t dl_size; curl_easy_getinfo(msg->easy_handle, CURLINFO_SIZE_DOWNLOAD_T, &dl_size); // 按需处理响应内容,这里省略原打印逻辑 } } while (msg); } while (still_running || added_count < total_transfers); // 所有请求发完、所有响应收完才退出 // 资源清理 for(i = 0; i < total_transfers; i++) { curl_multi_remove_handle(multi_handle, trans[i].easy); curl_easy_cleanup(trans[i].easy); } curl_multi_cleanup(multi_handle); return 0; }
参数调整说明
- 修改
BATCH_SIZE可调整每批发送的请求数,配合3毫秒间隔可精准控制整体QPS - 修改
SEND_INTERVAL_US可调整批次间隔,单位为微秒 - 强制设置
CURLMOPT_MAX_TOTAL_CONNECTIONS为1,可确保所有请求都走同一个HTTP/2连接,完全发挥多路复用的优势
内容的提问来源于stack exchange,提问作者p.luck
相关产品推荐
相关产品推荐

