libevent,libcurl 以及php扩展libevent,php curl_multi_exec区别

来源:互联网 发布:unity3d导入贴图 编辑:程序博客网 时间:2024/05/29 04:30
yar是一个轻量级的php rpc框架。有意思的是它的并行,其实就是libcurl作为网络库提供http请求,然后用epoll做为事件监听来实现整个异步并行调用的。在此基础上,就是如何利用zend api来对整个逻辑的封装了。我们先抛开zend api,单独看看libcurl 结合 epoll 是如何来做到异步并行调用的。 
先大致熟悉一下libcurl,官网http://curl.haxx.se/libcurl/c/libcurl.html。 
1、Easy interface 

如何发起一个curl调用?直接上码:


#include<stdio.h>#include<curl/curl.h>int main(int argc, char *argv[]){    CURL *curl;    CURLcode res;    curl_global_init(CURL_GLOBAL_ALL);    curl = curl_easy_init();    if(curl) {       curl_easy_setopt(curl, CURLOPT_URL, "http://google.com");       res = curl_easy_perform(curl);       if(res != CURLE_OK) {           fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));       }          curl_easy_cleanup(curl);    }       curl_global_cleanup();    return 0;}    

大致3个步骤: 
1)、curl_easy_init :获得curl的处理句柄。 
2)、curl_easy_setopt :用来针对网络传输过程中的一些选项设置,比如访问的地址、超时、设置请求回调、数据回写变量等等,详情去查看官网。 
3)、curl_easy_perform:执行curl请求。 
到这里你可以知道,一个curl句柄就是一个请求,既然是网络请求,你肯定猜到libcurl都为每个curl句柄都维护一个socket fd。 
可以看出这种调用方式的特点是最简单,同步调用,但也效率低下。

2、Multi interface 
Multi interface是基于多个curl句柄来实现并发的,大致原理是把多个curl_easy_init 创建的curl加入一个stack里面,然后并行去执行stack里面的curl句柄,来达到并行的目的。 
对于Multi interface,一般有两种使用方法,一种是curl_multi_perform 结合select / poll 方式调用,虽然能实现异步,但终究还是基于select/poll这种模型,需主动轮询fd事件,在特定场合下效率还是没法保证。第二种是”multi_socket”方式,即利用curl_multi_socket_action来替代curl_multi_perform来执行请求,顺便可以获得请求状态的变化,以方便用事件监听的方式来掌控请求读写状态,事件监听的方式有很多成熟的库,出了名的比如libevent,libev,libuv等,咱们等会儿用epoll来说,yar用的也是epoll。 
咱们来讨论multi_socket + epoll方式,直接上码:

#include<stdio.h>#include<stdlib.h>#include<string.h>#include <sys/epoll.h>#include<curl/curl.h>typedef struct _global_info {    int epfd;    CURLM *multi;} global_info;typedef struct _easy_curl_data {    CURL *curl;    char data[1024] = {0};} easy_curl_data;typedef struct _multi_curl_sockinfo {    curl_socket_t fd;    CURL *cp;} multi_curl_sockinfo;char curl_cb_data[1024] = {0};static int sock_cb (CURL *e, curl_socket_t s, int what, void *cbp, void *sockp){    struct epoll_event ev = {0};    global_info * g = (global_info *) cbp;    multi_curl_sockinfo  *fdp = (multi_curl_sockinfo *) sockp;    if (what == CURL_POLL_REMOVE) {        if (fdp) {            free(fdp);        }        epoll_ctl(g->epfd, EPOLL_CTL_DEL, s, &ev);    } else {        if (what == CURL_POLL_IN) {            ev.events |= EPOLLIN;        } else if (what == CURL_POLL_OUT) {            ev.events |= EPOLLOUT;        } else if (what == CURL_POLL_INOUT) {            ev.events |= EPOLLIN | EPOLLOUT;        }        if (!fpd) {            fpd = (multi_curl_sockinfo *)malloc(sizeof(multi_curl_sockinfo));            fpd->fd = s;            fpd->cp = e;            epoll_ctl(g->epfd, EPOLL_CTL_ADD, s, &ev);            curl_multi_assign(g->multi, s, &ev);        }    }    return 0;}static void set_curl_opt(CURL *curl){    //set curl options..    curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_cb_data);    //other options..}int main(int argc, char *argv[]){    char *urls[3] = {"https://google.com", "http://qq.com", "http://xxx.com"};    curl_global_init(CURL_GLOBAL_ALL);    global_info g;    memset(&g, 0, sizeof(global_info));    g.epfd = epoll_create(10);    g.multi = curl_multi_init();    int i=0;    for(;i<3;i++) {        CURL *curl;        curl = curl_easy_init();        set_curl_opt(curl);        curl_multi_add_handle(g.multi, curl);    }    curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, sock_cb);    curl_multi_setopt(multi->cm, CURLMOPT_SOCKETDATA, &g);    int running_count;    struct epoll_event events[10]    while (CURLM_CALL_MULTI_PERFORM == curl_multi_socket_action(g.multi, CURL_SOCKET_TIMEOUT, 0, &running_count));    if (running_count) {        do {            nfds = epoll_wait(g.epfd, events, 10, 500);            if(nfds > 0) {                int z=0;                for (;z<nfds; z++) {                    if (events[i].events & EPOLLIN) {                        curl_multi_socket_action(g.multi, CURL_CSELECT_IN, events[i].data.fd, &running_count);                    } else if (events[i].events & EPOLLOUT) {                        curl_multi_socket_action(g.multi, CURL_CSELECT_OUT, events[i].data.fd, &running_count);                    }                }            }        } while (running_count);    }    curl_global_cleanup();    return 0;}

以上代码就是libcurl + epoll实现异步并行调用的模板,可以稍作修改就能放到你的项目中运行。 
如果你对上面的代码看完后没啥感觉,这里重点说一下: 
curl_multi_init 创建multi curl 句柄,这个句柄是来管理easy_curl_init 创建 curl句柄的,你可以理解为一个大池子,这个池子里面存放着多个curl句柄。一个multi curl代表一个池子,多个multi curl 互不影响。之后multi curl 也需要设置一些选项,通过curl_multi_setopt 函数来设置,最关键的2个分别是CURLMOPT_SOCKETFUNCTION 选项和 CURLMOPT_TIMERFUNCTION选项。这两个选项设置了sock calback 和 timer callback ,看名字你也大概猜出各自代表的意思,没错,我们知道一个curl句柄里面维护着一个socket fd ,当这个fd状态变化的时候是靠这个sock callback来通知的,而timer callback ,是libcurl用来向调用方通知超时回调的。只需要设置一下函数指针就好。具体用法看官网。 
我们知道通过CURLMOPT_SOCKETFUNCTION 选项来设置sock callback来获知sock的状态了,这点非常重要,但是对于这个fd是何时读、写,对于libcurl来说它是无能为力的。我们需要一个能够监听事件的库,这时候epoll就华丽登场了。epoll在这这里如何使用?使其就是把sock calback 通知的sock fd 状态,同步一份给epoll就行了(其实就是上面代码的sock_cb函数)。然后,剩下的就是epoll event loop了,epoll_wait 返回可用fd后,你就可以进行相关的读写操作了,读写操作用这个curl_multi_socket_action来搞定。还记得之前说的curl_easy_init 创建curl句柄吧?创建的时候是通过curl_easy_setopt 来设置选项的,其中CURLOPT_WRITEFUNCTION 和 CURLOPT_WRITEDATA 分别设置当curl执行完成后回调函数和写入变量,你可以在这个回调函数里面进行curl执行结果的处理。所以,当调用curl_multi_socket_action的时候,就会触发这2个选项的作用了。一个完整的sock_multi + epoll的异步执行调用就是这样。

看到这里,再回去看上面的代码,估计问题不大了。

OK,剩下的事情就是把这块的逻辑整合zend api ,如果你熟悉php扩展的编写,熟悉zend api,再结合上面的分析,然后去看yar源码,就畅通无阻了。想想要不要完整的把yar的源码分析写出来,但一想到一堆zend api , 想想还是算了…不过有个很好的编写PHP扩展的模板,请参照这个项目https://github.com/linkaisheng/edge,这是一个接口平台的php框架,纯C编写,里面用到了各种zend api ,包括类的创建、属性操作、函数调用、垃圾回收、返回值、对象复制等等等等,为了弄清楚这些用法,我亲身经历了N多个segfault 才搞清楚并且运行正常起来,亲测可用..你可以大胆参照上面的来。



curl_multi_exec 里面调用curl_multi_socket_action


curl_multi_socket_action(g.multi, CURL_SOCKET_TIMEOUT,0, &running_count));accept

curl_multi_socket_action(g.multi, CURL_CSELECT_IN, events[i].data.fd, &running_count);read

curl_multi_socket_action(g.multi, CURL_CSELECT_OUT, events[i].data.fd, &running_count); write

http://blog.chinaunix.net/uid-20384806-id-1954347.html


0 0
原创粉丝点击