百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

使用curl进行http高并发访问(php curl 大量并发获得结果)

suiw9 2025-03-30 20:50 7 浏览 0 评论

本文主要介绍curl异步接口的使用方式,以及获取高性能的一些思路和实践。同时假设读者已经熟悉并且使用过同步接口。

1.curl接口基本介绍

curl一共有三种接口:

  • Easy Interface
  • Multi Interface
  • Share Interface

1.1 Easy Interface

Easy下是同步接口,curl_easy_*的形式,基本处理方式有几个步骤:

  1. curl_easy_init获取easy handle
  2. curl_easy_setopt设置header/cookie/post-filed/网页内容接收回调函数等
  3. curl_easy_perform执行
  4. curl_easy_cleanup清理
    注意在第3步是阻塞的

1.2 Multi Interface

Multi下是异步接口,curl_multi_*的形式,允许在单线程下同时操作多个easy handle,基本处理方式有几个步骤:

  1. curl_multi_init获取multi handle
  2. 调用Easy Interface设置若干easy handle,通过curl_multi_add_handle.加到1里的multi handle
  3. 调用curl_multi_wait/curl_multi_perform等待所有easy handle处理完成
  4. curl_multi_info_read依次读取所有数据
  5. curl_multi/easy_cleanup清理
    注意curl_multi_perform不是阻塞的

1.3 Share Interface

Share是共享接口,curl_shared_*的形式,用于多个easy handle间共享一些数据,例如cookie dns等
注意需要用到锁的情况,比如share了CURL_LOCK_DATA_DNS,如果没有加锁,在curl_multi_perform时会core掉:

Since you can use this share from multiple threads, and libcurl has no internal thread synchronization, you must provide mutex callbacks if you’re using this multi-threaded. You set lock and unlock functions with curl_share_setopt too.

可以参考这两处例子:

  1. http://www.mit.edu/afs.new/sipb/user/ssen/src/curl-7.11.1/tests/libtest/lib506.c
  2. https://curl.haxx.se/mail/lib-2006-01/0218.html

2. 异步接口的例子

curl自带的例子还是介绍的curl_multi_fdset的方法。
实际上已经可以用curl_multi_wait代替了,据说是facebook的工程师提的升级:
Facebook contributes fix to libcurl’s multi interface to overcome problem with more than 1024 file descriptors.
使用方法可以参考这里

通过一个示例来看下multi的工作方式(注意出于简短的目的,有的函数没有判断返回值)

#include "curl/curl.h"
#include 

const char* url = "http://www.baidu.com";
const int count = 1000;

size_t write_data(void* buffer, size_t size, size_t count, void* stream) {
    (void)buffer;
    (void)stream;
    return size * count;
}

void curl_multi_demo() {
    CURLM* curlm = curl_multi_init();

    for (int i = 0; i < count i curl easy_handle='curl_easy_init();' curl_easy_setopteasy_handle curlopt_nosignal 1 curl_easy_setopteasy_handle curlopt_url url curl_easy_setopteasy_handle curlopt_writefunction write_data curl_multi_add_handlecurlm easy_handle int running_handlers='0;' do curl_multi_waitcurlm null 0 2000 null curl_multi_performcurlm running_handlers while running_handlers> 0);

    int msgs_left = 0;
    CURLMsg* msg = NULL;
    while ((msg = curl_multi_info_read(curlm, &msgs_left)) != NULL) {
        if (msg->msg == CURLMSG_DONE) {
            int http_status_code = 0;
            curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &http_status_code);
            char* effective_url = NULL;
            curl_easy_getinfo(msg->easy_handle, CURLINFO_EFFECTIVE_URL, &effective_url);
            fprintf(stdout, "url:%s status:%d %s\n",
                    effective_url,
                    http_status_code,
                    curl_easy_strerror(msg->data.result));

            curl_multi_remove_handle(curlm, msg->easy_handle);
            curl_easy_cleanup(msg->easy_handle);
        }
    }

    curl_multi_cleanup(curlm);
}

int main() {
    curl_multi_demo();
    return 0;
}

注意L35~36 L37~38不能互换,否则url为空,原因没有继续深追。

可以看到异步处理的方式是通过curl_multi_add_handle接口不断的把待抓的easy handle放到multi handle里。然后通过curl_multi_wait/curl_multi_perform等待所有easy handle处理完毕。
因为是同时在等待所有easy handle处理完毕,耗时比easy方式里逐个同步等待大大减少。其中产生hold作用的是在这段代码里:

//等待所有easy handle处理完毕
do {
    ...
} while (running_handlers > 0);

3.应用

而在实际应用中,我们的使用场景往往是这样的:

某个负责抓取数据的模块,service监听端口接收url,抓取数据后发往下游。

因为不希望所有的线程都处于上面multi示例中的等待(什么都不做)。于是就有了这种想法:接收到一条url后构造对应的easy handle,通过curl_multi_add_handle接口放入curlm后返回。同时两个线程不断的wait/perform和read,如果有完成的url,那么就调用对应的回调函数即可。
相当于将curlm当做一个队列,两个线程分别充当了生产者和消费者。

模型类似于:

CURLM* curlm = curl_multi_init()

//Thread-Consumer
	while true:
		//读取已经完成的url
		curl_multi_info_read
		//通知该url已完成,并且从curlm里删除
		curl_multi_remove_handle
		
//Thread-Producer
	while true:
		//等待可读socket
		curl_multi_wait
		//查看运行中的easy handle数目
		curl_multi_perform
		
//Thread-Other
	//根据url构造CURL easy handle
	CURL* curl = make_curl_easy_handle(url)
	//添加到curlm
	curl_multi_add_handle

做了一下测试很快就放弃了,程序在libcurl内部函数里core掉。
实际上curl是线程安全的,但同时也格外强调了这点:

The first basic rule is that you must never share a libcurl handle (be it easy or multi or whatever) between multiple threads. Only use one handle in one thread at a time.

具体可以参考这里,说明上面的模型是不可行的。

看到这里有一个基于libcurl的单线程I:O多路复用HTTP框架,dispatch部分和chrome源码里的thread模型很像,CURLM*对象在Dispatch::IO线程里统一操作,同时全局唯一,在一个LazyInstance的ConnectionRunner里维护。不过没有找到dispatch_after的实现,所以不太确定。

在StackOverflow上看到了复用curl的想法:curl handler放在一个池子中,需要时从中获取,使用后归还,同样不可行。

因此,标准的写法就是之前的示例的代码,正如这里提到的:

The multi interface is designed for this purpose: you add multiple handles and then process all of them with one call, all in the same thread.

4.优化

接下来就是优化的问题,在不使用curl_multi_socket_*的接口的情况下,是否有办法提升性能呢?
参考了curl的Persistence一节,主要是持久化部分信息来加速(缓存)。
其中提到

Each easy handle will attempt to keep the last few connections alive for a while in case they are to be used again.

这里说到每个easy handle会缓存之前的若干连接来避免重连、缓存DNS等以提高性能。因此一些思路就是easy handle重用、dns全局缓存等。

5.测试&结论

按照上面的思路分别测试抓取1000次baidu首页

  1. 串行使用curl easy接口
  2. 10个线程并行使用curl easy接口
  3. 使用curl multi接口
  4. 使用curl multi接口,并且reuse connection。(方法是第一遍curl easy handle抓取后不cleanup,计算第二次全部抓取完成的时间)
  5. 使用dns cache(使用curl_share_*接口,第一次抓取用于dnscache填充,计算第二次全部抓取完成的时间。效果上打开VERBOSE可以看到hostname found in DNS Cache)

其中处理时间测试结论如下:

methodavgmaxmineasy16.45721.61714.262easy parallel2.3319.4961.723multi0.7348.8950.259multi reuse0.001130.0015570.000898multi reuse cache0.001090.001400.000828

4 5的区别不大,同时不确定重用connection的情况下,dnscache是否还能起到正向作用

对应的测试代码都放到了gist上:1 2 3 4 5


6.补充

关于curl性能的讨论帖子很多,比如这里,其中也讲到了获取网页的一个基本流程:

  1. Request from your DNS server, the IP corresponding to the name of the site you requested
  2. Use the server’s reply to open a socket to that IP, port 80
  3. Send a small HTTP message describing what you want
  4. Receive the html code

这里有一些关于performance的建议,用到了curl_multi_socket_*接口,我没有用到。

相关推荐

看完这一篇数据仓库干货,终于搞懂什么是hive了

一、Hive定义Hive最早来源于FaceBook,因为FaceBook网站每天产生海量的结构化日志数据,为了对这些数据进行管理,并且因为机器学习的需求,产生了Hive这们技术,并继续发展成为一个成...

真正让你明白Hive参数调优系列1:控制map个数与性能调优参数

本系列几章系统地介绍了开发中Hive常见的用户配置属性(有时称为参数,变量或选项),并说明了哪些版本引入了哪些属性,常见有哪些属性的使用,哪些属性可以进行Hive调优,以及如何使用的问题。以及日常Hi...

HIVE SQL基础语法(hive sql是什么)

引言与关系型数据库的SQL略有不同,但支持了绝大多数的语句如DDL、DML以及常见的聚合函数、连接查询、条件查询。HIVE不适合用于联机事务处理,也不提供实时查询功能。它最适合应用在基于大量不可变数据...

[干货]Hive与Spark sql整合并测试效率

在目前的大数据架构中hive是用来做离线数据分析的,而在Spark1.4版本中spark加入了sparksql,我们知道spark的优势是速度快,那么到底sparksql会比hive...

Hive 常用的函数(hive 数学函数)

一、Hive函数概述及分类标准概述Hive内建了不少函数,用于满足用户不同使用需求,提高SQL编写效率:...

数仓/数开面试题真题总结(二)(数仓面试时应该讲些什么)

二.Hive...

Tomcat处理HTTP请求流程解析(tomcat 处理请求过程)

1、一个简单的HTTP服务器在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,那么接下来我们看下如何用Java来实现一个简单...

Python 高级编程之网络编程 Socket(六)

一、概述Python网络编程是指使用Python语言编写的网络应用程序。这种编程涉及到网络通信、套接字编程、协议解析等多种方面的知识。...

[904]ScalersTalk成长会Python小组第20周学习笔记

Scalers点评:在2015年,ScalersTalk成长会Python小组完成了《Python核心编程》第1轮的学习。到2016年,我们开始第二轮的学习,并且将重点放在章节的习题上。Python小...

「web开发」几款http请求测试工具

curl命令CURL(CommandLineUniformResourceLocator),是一个利用URL语法,在命令行终端下使用的网络请求工具,支持HTTP、HTTPS、FTP等协议...

x-cmd pkg | hurl - 强力的 HTTP 请求测试工具,让 API 测试更加简洁高效

简介...

Mac 基于HTTP方式访问下载共享文件,配置共享服务器

方法一:使用Python的SimpleHTTPServer进行局域网文件共享Mac自带Python,所以不需要安装其他软件,一条命令即可...

Python 基础教程十五之 Python 使用requests库发送http请求

前言...

使用curl进行http高并发访问(php curl 大量并发获得结果)

本文主要介绍curl异步接口的使用方式,以及获取高性能的一些思路和实践。同时假设读者已经熟悉并且使用过同步接口。1.curl接口基本介绍curl一共有三种接口:EasyInterface...

Django 中的 HttpResponse理解和用法-基础篇1

思路是方向,代码是时间,知识需积累,经验需摸索。希望对大家有用,有错误还望指出。...

取消回复欢迎 发表评论: