Libevent源码分析-----连接监听器evconnlistener



使用evconnlistener:

        基于event和event_base已经可以写一个CS模型了。但是对于服务器端来说,仍然需要用户自行调用socket、bind、listen、accept等步骤。这个过程有点繁琐,为此在2.0.2-alpha版本的Libevent推出了一些对应的封装函数。

        用户只需初始化struct sockaddr_in结构体变量,然后把它作为参数传给函数evconnlistener_new_bind即可。该函数会完成上面说到的那4个过程。下面的代码是一个使用例子。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include<netinet/in.h>  
  2. #include<sys/socket.h>  
  3. #include<unistd.h>  
  4.   
  5. #include<stdio.h>  
  6. #include<string.h>  
  7.   
  8. #include<event.h>  
  9. #include<listener.h>  
  10. #include<bufferevent.h>  
  11. #include<thread.h>  
  12.   
  13.   
  14. void listener_cb(evconnlistener *listener, evutil_socket_t fd,  
  15.                  struct sockaddr *sock, int socklen, void *arg);  
  16.   
  17. void socket_read_cb(bufferevent *bev, void *arg);  
  18. void socket_error_cb(bufferevent *bev, short events, void *arg);  
  19.   
  20. int main()  
  21. {  
  22.     evthread_use_pthreads();//enable threads  
  23.   
  24.     struct sockaddr_in sin;  
  25.     memset(&sin, 0, sizeof(struct sockaddr_in));  
  26.     sin.sin_family = AF_INET;  
  27.     sin.sin_port = htons(8989);  
  28.   
  29.     event_base *base = event_base_new();  
  30.     evconnlistener *listener  
  31.             = evconnlistener_new_bind(base, listener_cb, base,  
  32.                                       LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE | LEV_OPT_THREADSAFE,  
  33.                                       10, (struct sockaddr*)&sin,  
  34.                                       sizeof(struct sockaddr_in));  
  35.   
  36.     event_base_dispatch(base);  
  37.   
  38.      evconnlistener_free(listener);  
  39.     event_base_free(base);  
  40.   
  41.     return 0;  
  42. }  
  43.   
  44.   
  45. //有新的客户端连接到服务器  
  46. //当此函数被调用时,libevent已经帮我们accept了这个客户端。该客户端的  
  47. //文件描述符为fd  
  48. void listener_cb(evconnlistener *listener, evutil_socket_t fd,  
  49.                  struct sockaddr *sock, int socklen, void *arg)  
  50. {  
  51.     event_base *base = (event_base*)arg;  
  52.   
  53.     //下面代码是为这个fd创建一个bufferevent  
  54.     bufferevent *bev =  bufferevent_socket_new(base, fd,  
  55.                                                BEV_OPT_CLOSE_ON_FREE);  
  56.   
  57.     bufferevent_setcb(bev, socket_read_cb, NULL, socket_error_cb, NULL);  
  58.     bufferevent_enable(bev, EV_READ | EV_PERSIST);  
  59. }  
  60.   
  61.   
  62. void socket_read_cb(bufferevent *bev, void *arg)  
  63. {  
  64.     char msg[4096];  
  65.   
  66.     size_t len = bufferevent_read(bev, msg, sizeof(msg)-1 );  
  67.   
  68.     msg[len] = '\0';  
  69.     printf("server read the data %s\n", msg);  
  70.   
  71.     char reply[] = "I has read your data";  
  72.     bufferevent_write(bev, reply, strlen(reply) );  
  73. }  
  74.   
  75.   
  76. void socket_error_cb(bufferevent *bev, short events, void *arg)  
  77. {  
  78.     if (events & BEV_EVENT_EOF)  
  79.         printf("connection closed\n");  
  80.     else if (events & BEV_EVENT_ERROR)  
  81.         printf("some other error\n");  
  82.   
  83.     //这将自动close套接字和free读写缓冲区  
  84.     bufferevent_free(bev);  
  85. }  

        上面的代码是一个服务器端的例子,客户端代码可以使用《Libevent使用例子,从简单到复杂》博文中的客户端。这里就不贴客户端代码了。

 

        从上面代码可以看到,当服务器端监听到一个客户端的连接请求后,就会调用listener_cb这个回调函数。这个回调函数是在evconnlistener_new_bind函数中设置的。现在来看一下这个函数的参数有哪些,下面是其函数原型。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.h文件  
  2. typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);  
  3.   
  4. struct evconnlistener *evconnlistener_new_bind(struct event_base *base,  
  5.     evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,  
  6.     const struct sockaddr *sa, int socklen);  

        第一个参数是很熟悉的event_base,无论怎么样都是离不开event_base这个发动机的。

        第二个参数是一个函数指针,该函数指针的格式如代码所示。当有新的客户端请求连接时,该函数就会调用。要注意的是:当这个回调函数被调用时,Libevent已经帮我们accept了这个客户端。所以,该回调函数有一个参数是文件描述符fd。我们直接使用这个fd即可。真是方便。这个参数是可以为NULL的,此时用户并不能接收到客户端。当用户调用evconnlistener_set_cb函数设置回调函数后,就可以了。

        第三个参数是传给回调函数的用户参数,作用就像event_new函数的最后一个参数。

        参数flags是一些标志值,有下面这些:

  • LEV_OPT_LEAVE_SOCKETS_BLOCKING:默认情况下,当连接监听器接收到新的客户端socket连接后,会把该socket设置为非阻塞的。如果设置该选项,那么就把之客户端socket保留为阻塞的
  • LEV_OPT_CLOSE_ON_FREE:当连接监听器释放时,会自动关闭底层的socket
  • LEV_OPT_CLOSE_ON_EXEC:为底层的socket设置close-on-exec标志
  • LEV_OPT_REUSEABLE: 在某些平台,默认情况下当一个监听socket被关闭时,其他socket不能马上绑定到同一个端口,要等一会儿才行。设置该标志后,Libevent会把该socket设置成reuseable。这样,关闭该socket后,其他socket就能马上使用同一个端口
  • LEV_OPT_THREADSAFE:为连接监听器分配锁。这样可以确保线程安全

        参数backlog是系统调用listen的第二个参数。最后两个参数就不多说了。



evconnlistener的封装:

        接下来看一下Libevent是怎么封装evconnlistener的。


用到的结构体:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. struct evconnlistener_ops {//一系列的工作函数  
  3.     int (*enable)(struct evconnlistener *);  
  4.     int (*disable)(struct evconnlistener *);  
  5.     void (*destroy)(struct evconnlistener *);  
  6.     void (*shutdown)(struct evconnlistener *);  
  7.     evutil_socket_t (*getfd)(struct evconnlistener *);  
  8.     struct event_base *(*getbase)(struct evconnlistener *);  
  9. };  
  10.   
  11. struct evconnlistener {  
  12.     const struct evconnlistener_ops *ops;//操作函数  
  13.     void *lock; //锁变量,用于线程安全  
  14.     evconnlistener_cb cb;//用户的回调函数  
  15.     evconnlistener_errorcb errorcb;//发生错误时的回调函数  
  16.     void *user_data;//回调函数的参数  
  17.     unsigned flags;//属性标志  
  18.     short refcnt;//引用计数  
  19.     unsigned enabled : 1;//位域为1.即只需一个比特位来存储这个成员  
  20. };  
  21.   
  22. struct evconnlistener_event {  
  23.     struct evconnlistener base;  
  24.     struct event listener; //内部event,插入到event_base  
  25. };  

        在evconnlistener_event结构体有一个event结构体。可以想象,在实现时必然是将服务器端的socket fd赋值给struct event 类型变量listener的fd成员。然后将listener加入到event_base,这样就完成了自动监听工作。这也回归到之前学过的内容。


        下面看一下具体是怎么实现的。

初始化服务器socket:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. struct evconnlistener *  
  3. evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb,  
  4.     void *ptr, unsigned flags, int backlog, const struct sockaddr *sa,  
  5.     int socklen)  
  6. {  
  7.     struct evconnlistener *listener;  
  8.     evutil_socket_t fd;  
  9.     int on = 1;  
  10.     int family = sa ? sa->sa_family : AF_UNSPEC;  
  11.   
  12.     //监听个数不能为0  
  13.     if (backlog == 0)  
  14.         return NULL;  
  15.   
  16.     fd = socket(family, SOCK_STREAM, 0);  
  17.     if (fd == -1)  
  18.         return NULL;  
  19.   
  20.     //LEV_OPT_LEAVE_SOCKETS_BLOCKING选项是应用于accept到的客户端socket  
  21.     //所以对于服务器端的socket,直接将之设置为非阻塞的  
  22.     if (evutil_make_socket_nonblocking(fd) < 0) {  
  23.         evutil_closesocket(fd);  
  24.         return NULL;  
  25.     }  
  26.   
  27.     if (flags & LEV_OPT_CLOSE_ON_EXEC) {  
  28.         if (evutil_make_socket_closeonexec(fd) < 0) {  
  29.             evutil_closesocket(fd);  
  30.             return NULL;  
  31.         }  
  32.     }  
  33.   
  34.     if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0) {  
  35.         evutil_closesocket(fd);  
  36.         return NULL;  
  37.     }  
  38.     if (flags & LEV_OPT_REUSEABLE) {  
  39.         if (evutil_make_listen_socket_reuseable(fd) < 0) {  
  40.             evutil_closesocket(fd);  
  41.             return NULL;  
  42.         }  
  43.     }  
  44.   
  45.     if (sa) {  
  46.         if (bind(fd, sa, socklen)<0) {//绑定  
  47.             evutil_closesocket(fd);  
  48.             return NULL;  
  49.         }  
  50.     }  
  51.   
  52.     listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);  
  53.     if (!listener) {  
  54.         evutil_closesocket(fd);  
  55.         return NULL;  
  56.     }  
  57.   
  58.     return listener;  
  59. }  

        evconnlistener_new_bind函数申请一个socket,然后对之进行一些有关非阻塞、重用、保持连接的处理、绑定到特定的IP和端口。最后把业务逻辑交给evconnlistener_new处理。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. static const struct evconnlistener_ops evconnlistener_event_ops = {  
  3.     event_listener_enable,  
  4.     event_listener_disable,  
  5.     event_listener_destroy,  
  6.     NULL, /* shutdown */  
  7.     event_listener_getfd,  
  8.     event_listener_getbase  
  9. };  
  10.   
  11.   
  12. struct evconnlistener *  
  13. evconnlistener_new(struct event_base *base,  
  14.     evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,  
  15.     evutil_socket_t fd)  
  16. {  
  17.     struct evconnlistener_event *lev;  
  18.   
  19.     if (backlog > 0) {  
  20.         if (listen(fd, backlog) < 0)  
  21.             return NULL;  
  22.     } else if (backlog < 0) {  
  23.         if (listen(fd, 128) < 0)  
  24.             return NULL;  
  25.     }  
  26.   
  27.     lev = mm_calloc(1, sizeof(struct evconnlistener_event));  
  28.     if (!lev)  
  29.         return NULL;  
  30.   
  31.     //赋值  
  32.     lev->base.ops = &evconnlistener_event_ops;  
  33.     lev->base.cb = cb;  
  34.     lev->base.user_data = ptr;  
  35.     lev->base.flags = flags;  
  36.     lev->base.refcnt = 1;  
  37.   
  38.     if (flags & LEV_OPT_THREADSAFE) {//线程安全就需要分配锁  
  39.         EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE);  
  40.     }  
  41.   
  42.     //在多路IO复用函数中,新客户端的连接请求也被当作读事件  
  43.     event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,  
  44.         listener_read_cb, lev);  
  45.   
  46.     //会调用event_add,把event加入到event_base中  
  47.     evconnlistener_enable(&lev->base);  
  48.   
  49.     return &lev->base;  
  50. }  
  51.   
  52. int  
  53. evconnlistener_enable(struct evconnlistener *lev)  
  54. {  
  55.     int r;  
  56.     LOCK(lev);  
  57.     lev->enabled = 1;  
  58.     if (lev->cb)  
  59.         r = lev->ops->enable(lev);//实际上是调用下面的event_listener_enable函数  
  60.     else  
  61.         r = 0;  
  62.     UNLOCK(lev);  
  63.     return r;  
  64. }  
  65.   
  66. static int  
  67. event_listener_enable(struct evconnlistener *lev)  
  68. {  
  69.     struct evconnlistener_event *lev_e =  
  70.         EVUTIL_UPCAST(lev, struct evconnlistener_event, base);  
  71.   
  72.     //加入到event_base,完成监听工作。  
  73.     return event_add(&lev_e->listener, NULL);  
  74. }  

        几个函数的一路调用,思路还是挺清晰的。就是申请一个socket,进行一些处理,然后用之赋值给event。最后把之add到event_base中。event_base会对新客户端的请求连接进行监听。


        在evconnlistener_enable函数里面,如果用户没有设置回调函数,那么就不会调用event_listener_enable。也就是说并不会add到event_base中。

        event_listener_enable函数里面的宏EVUTIL_UPCAST可以根据结构体成员变量的地址推算出结构体的起始地址。有关这个宏,可以查看”结构体偏移量”。


处理客户端的连接请求:

        现在来看一下event的回调函数listener_read_cb。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. static void  
  3. listener_read_cb(evutil_socket_t fd, short what, void *p)  
  4. {  
  5.     struct evconnlistener *lev = p;  
  6.     int err;  
  7.     evconnlistener_cb cb;  
  8.     evconnlistener_errorcb errorcb;  
  9.     void *user_data;  
  10.     LOCK(lev);  
  11.     while (1) { //可能有多个客户端同时请求连接  
  12.         struct sockaddr_storage ss;  
  13. #ifdef WIN32  
  14.         int socklen = sizeof(ss);  
  15. #else  
  16.         socklen_t socklen = sizeof(ss);  
  17. #endif  
  18.         evutil_socket_t new_fd = accept(fd, (struct sockaddr*)&ss, &socklen);  
  19.         if (new_fd < 0)  
  20.             break;  
  21.         if (socklen == 0) {  
  22.             /* This can happen with some older linux kernels in 
  23.              * response to nmap. */  
  24.             evutil_closesocket(new_fd);  
  25.             continue;  
  26.         }  
  27.   
  28.         if (!(lev->flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING))  
  29.             evutil_make_socket_nonblocking(new_fd);  
  30.   
  31.         //用户还没设置连接监听器的回调函数  
  32.         if (lev->cb == NULL) {  
  33.             UNLOCK(lev);  
  34.             return;  
  35.         }  
  36.   
  37.         //由于refcnt被初始化为1.这里有++了,所以一般情况下并不会进入下面的  
  38.         //if判断里面。但如果程在下面UNLOCK之后,第二个线调用evconnlistener_free  
  39.         //释放这个evconnlistener时,就有可能使得refcnt为1了。即进入那个判断体里  
  40.         //执行listener_decref_and_unlock。在下面会讨论这个问题。  
  41.         ++lev->refcnt;  
  42.         cb = lev->cb;  
  43.         user_data = lev->user_data;  
  44.         UNLOCK(lev);  
  45.         cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen,  
  46.             user_data);//调用用户设置的回调函数,让用户处理这个fd  
  47.         LOCK(lev);  
  48.         if (lev->refcnt == 1) {  
  49.             int freed = listener_decref_and_unlock(lev);  
  50.             EVUTIL_ASSERT(freed);  
  51.             return;  
  52.         }  
  53.         --lev->refcnt;  
  54.     }  
  55.       
  56.     err = evutil_socket_geterror(fd);  
  57.     if (EVUTIL_ERR_ACCEPT_RETRIABLE(err)) {//还可以accept  
  58.         UNLOCK(lev);  
  59.         return;  
  60.     }  
  61.   
  62.     //当有错误发生时才会运行到这里  
  63.     if (lev->errorcb != NULL) {  
  64.         ++lev->refcnt;  
  65.         errorcb = lev->errorcb;  
  66.         user_data = lev->user_data;  
  67.         UNLOCK(lev);  
  68.         errorcb(lev, user_data);//调用用户设置的错误回调函数  
  69.         LOCK(lev);  
  70.         listener_decref_and_unlock(lev);  
  71.     }  
  72. }  

        这个函数所做的工作也比较简单,就是accept客户端,然后调用用户设置的回调函数。所以,用户回调函数的参数fd是一个已经连接好了的socket。


        上面函数说到了错误回调函数,可以通过下面的函数设置连接监听器的错误监听函数。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.h文件  
  2. typedef void (*evconnlistener_errorcb)(struct evconnlistener *, void *);  
  3.   
  4. //listener.c文件  
  5. void  
  6. evconnlistener_set_error_cb(struct evconnlistener *lev,  
  7.     evconnlistener_errorcb errorcb)  
  8. {  
  9.     LOCK(lev);  
  10.     lev->errorcb = errorcb;  
  11.     UNLOCK(lev);  
  12. }  



释放evconnlistener:

        调用evconnlistener_free可以释放一个evconnlistener。由于evconnlistener拥有一些系统资源,在释放evconnlistener_free的时候会释放这些系统资源。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. void  
  3. evconnlistener_free(struct evconnlistener *lev)  
  4. {  
  5.     LOCK(lev);  
  6.     lev->cb = NULL;  
  7.     lev->errorcb = NULL;  
  8.     if (lev->ops->shutdown)//这里的shutdown为NULL  
  9.         lev->ops->shutdown(lev);  
  10.   
  11.     //引用次数减一,并解锁  
  12.     listener_decref_and_unlock(lev);  
  13. }  
  14.   
  15. static int  
  16. listener_decref_and_unlock(struct evconnlistener *listener)  
  17. {  
  18.     int refcnt = --listener->refcnt;  
  19.     if (refcnt == 0) {  
  20.         //实际调用event_listener_destroy  
  21.         listener->ops->destroy(listener);  
  22.         UNLOCK(listener);  
  23.         //释放锁  
  24.         EVTHREAD_FREE_LOCK(listener->lock, EVTHREAD_LOCKTYPE_RECURSIVE);  
  25.         mm_free(listener);  
  26.         return 1;  
  27.     } else {  
  28.         UNLOCK(listener);  
  29.         return 0;  
  30.     }  
  31. }  
  32.   
  33. static void  
  34. event_listener_destroy(struct evconnlistener *lev)  
  35. {  
  36.     struct evconnlistener_event *lev_e =  
  37.         EVUTIL_UPCAST(lev, struct evconnlistener_event, base);  
  38.   
  39.     //把event从event_base中删除  
  40.     event_del(&lev_e->listener);  
  41.     if (lev->flags & LEV_OPT_CLOSE_ON_FREE)//如果用户设置了这个选项,那么要关闭socket  
  42.         evutil_closesocket(event_get_fd(&lev_e->listener));  
  43. }  


        要注意一点,LEV_OPT_CLOSE_ON_FREE选项关闭的是服务器端的监听socket,而非那些连接客户端的socket。



        现在来说一下那个listener_decref_and_unlock。前面注释说到,在函数listener_read_cb中,一般情况下是不会调用listener_decref_and_unlock,但在多线程的时候可能会调用。这种特殊情况是:当主线程accept到一个新客户端时,会解锁,并调用用户设置的回调函数。此时,引用计数等于2。就在这个时候,第二个线程执行evconnlistener_free函数。该函数会执行listener_decref_and_unlock。明显主线程还在用这个evconnlistener,肯定不能删除。此时引用计数也等于2也不会删除。但用户已经调用了evconnlistener_free。Libevent必须要响应。当第二个线程执行完后,主线程抢到CPU,此时引用计数就变成1了,也就进入到if判断里面了。在判断体里面执行函数listener_decref_and_unlock,并且完成删除工作。

 

        总得来说,Libevent封装的这个evconnlistener和一系列操作函数,还是比较简单的。思路也比较清晰。



参考:

        http://www.wangafu.net/~nickm/libevent-book/Ref8_listener.html


使用evconnlistener:

        基于event和event_base已经可以写一个CS模型了。但是对于服务器端来说,仍然需要用户自行调用socket、bind、listen、accept等步骤。这个过程有点繁琐,为此在2.0.2-alpha版本的Libevent推出了一些对应的封装函数。

        用户只需初始化struct sockaddr_in结构体变量,然后把它作为参数传给函数evconnlistener_new_bind即可。该函数会完成上面说到的那4个过程。下面的代码是一个使用例子。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include<netinet/in.h>  
  2. #include<sys/socket.h>  
  3. #include<unistd.h>  
  4.   
  5. #include<stdio.h>  
  6. #include<string.h>  
  7.   
  8. #include<event.h>  
  9. #include<listener.h>  
  10. #include<bufferevent.h>  
  11. #include<thread.h>  
  12.   
  13.   
  14. void listener_cb(evconnlistener *listener, evutil_socket_t fd,  
  15.                  struct sockaddr *sock, int socklen, void *arg);  
  16.   
  17. void socket_read_cb(bufferevent *bev, void *arg);  
  18. void socket_error_cb(bufferevent *bev, short events, void *arg);  
  19.   
  20. int main()  
  21. {  
  22.     evthread_use_pthreads();//enable threads  
  23.   
  24.     struct sockaddr_in sin;  
  25.     memset(&sin, 0, sizeof(struct sockaddr_in));  
  26.     sin.sin_family = AF_INET;  
  27.     sin.sin_port = htons(8989);  
  28.   
  29.     event_base *base = event_base_new();  
  30.     evconnlistener *listener  
  31.             = evconnlistener_new_bind(base, listener_cb, base,  
  32.                                       LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE | LEV_OPT_THREADSAFE,  
  33.                                       10, (struct sockaddr*)&sin,  
  34.                                       sizeof(struct sockaddr_in));  
  35.   
  36.     event_base_dispatch(base);  
  37.   
  38.      evconnlistener_free(listener);  
  39.     event_base_free(base);  
  40.   
  41.     return 0;  
  42. }  
  43.   
  44.   
  45. //有新的客户端连接到服务器  
  46. //当此函数被调用时,libevent已经帮我们accept了这个客户端。该客户端的  
  47. //文件描述符为fd  
  48. void listener_cb(evconnlistener *listener, evutil_socket_t fd,  
  49.                  struct sockaddr *sock, int socklen, void *arg)  
  50. {  
  51.     event_base *base = (event_base*)arg;  
  52.   
  53.     //下面代码是为这个fd创建一个bufferevent  
  54.     bufferevent *bev =  bufferevent_socket_new(base, fd,  
  55.                                                BEV_OPT_CLOSE_ON_FREE);  
  56.   
  57.     bufferevent_setcb(bev, socket_read_cb, NULL, socket_error_cb, NULL);  
  58.     bufferevent_enable(bev, EV_READ | EV_PERSIST);  
  59. }  
  60.   
  61.   
  62. void socket_read_cb(bufferevent *bev, void *arg)  
  63. {  
  64.     char msg[4096];  
  65.   
  66.     size_t len = bufferevent_read(bev, msg, sizeof(msg)-1 );  
  67.   
  68.     msg[len] = '\0';  
  69.     printf("server read the data %s\n", msg);  
  70.   
  71.     char reply[] = "I has read your data";  
  72.     bufferevent_write(bev, reply, strlen(reply) );  
  73. }  
  74.   
  75.   
  76. void socket_error_cb(bufferevent *bev, short events, void *arg)  
  77. {  
  78.     if (events & BEV_EVENT_EOF)  
  79.         printf("connection closed\n");  
  80.     else if (events & BEV_EVENT_ERROR)  
  81.         printf("some other error\n");  
  82.   
  83.     //这将自动close套接字和free读写缓冲区  
  84.     bufferevent_free(bev);  
  85. }  

        上面的代码是一个服务器端的例子,客户端代码可以使用《Libevent使用例子,从简单到复杂》博文中的客户端。这里就不贴客户端代码了。

 

        从上面代码可以看到,当服务器端监听到一个客户端的连接请求后,就会调用listener_cb这个回调函数。这个回调函数是在evconnlistener_new_bind函数中设置的。现在来看一下这个函数的参数有哪些,下面是其函数原型。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.h文件  
  2. typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);  
  3.   
  4. struct evconnlistener *evconnlistener_new_bind(struct event_base *base,  
  5.     evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,  
  6.     const struct sockaddr *sa, int socklen);  

        第一个参数是很熟悉的event_base,无论怎么样都是离不开event_base这个发动机的。

        第二个参数是一个函数指针,该函数指针的格式如代码所示。当有新的客户端请求连接时,该函数就会调用。要注意的是:当这个回调函数被调用时,Libevent已经帮我们accept了这个客户端。所以,该回调函数有一个参数是文件描述符fd。我们直接使用这个fd即可。真是方便。这个参数是可以为NULL的,此时用户并不能接收到客户端。当用户调用evconnlistener_set_cb函数设置回调函数后,就可以了。

        第三个参数是传给回调函数的用户参数,作用就像event_new函数的最后一个参数。

        参数flags是一些标志值,有下面这些:

  • LEV_OPT_LEAVE_SOCKETS_BLOCKING:默认情况下,当连接监听器接收到新的客户端socket连接后,会把该socket设置为非阻塞的。如果设置该选项,那么就把之客户端socket保留为阻塞的
  • LEV_OPT_CLOSE_ON_FREE:当连接监听器释放时,会自动关闭底层的socket
  • LEV_OPT_CLOSE_ON_EXEC:为底层的socket设置close-on-exec标志
  • LEV_OPT_REUSEABLE: 在某些平台,默认情况下当一个监听socket被关闭时,其他socket不能马上绑定到同一个端口,要等一会儿才行。设置该标志后,Libevent会把该socket设置成reuseable。这样,关闭该socket后,其他socket就能马上使用同一个端口
  • LEV_OPT_THREADSAFE:为连接监听器分配锁。这样可以确保线程安全

        参数backlog是系统调用listen的第二个参数。最后两个参数就不多说了。



evconnlistener的封装:

        接下来看一下Libevent是怎么封装evconnlistener的。


用到的结构体:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. struct evconnlistener_ops {//一系列的工作函数  
  3.     int (*enable)(struct evconnlistener *);  
  4.     int (*disable)(struct evconnlistener *);  
  5.     void (*destroy)(struct evconnlistener *);  
  6.     void (*shutdown)(struct evconnlistener *);  
  7.     evutil_socket_t (*getfd)(struct evconnlistener *);  
  8.     struct event_base *(*getbase)(struct evconnlistener *);  
  9. };  
  10.   
  11. struct evconnlistener {  
  12.     const struct evconnlistener_ops *ops;//操作函数  
  13.     void *lock; //锁变量,用于线程安全  
  14.     evconnlistener_cb cb;//用户的回调函数  
  15.     evconnlistener_errorcb errorcb;//发生错误时的回调函数  
  16.     void *user_data;//回调函数的参数  
  17.     unsigned flags;//属性标志  
  18.     short refcnt;//引用计数  
  19.     unsigned enabled : 1;//位域为1.即只需一个比特位来存储这个成员  
  20. };  
  21.   
  22. struct evconnlistener_event {  
  23.     struct evconnlistener base;  
  24.     struct event listener; //内部event,插入到event_base  
  25. };  

        在evconnlistener_event结构体有一个event结构体。可以想象,在实现时必然是将服务器端的socket fd赋值给struct event 类型变量listener的fd成员。然后将listener加入到event_base,这样就完成了自动监听工作。这也回归到之前学过的内容。


        下面看一下具体是怎么实现的。

初始化服务器socket:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. struct evconnlistener *  
  3. evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb,  
  4.     void *ptr, unsigned flags, int backlog, const struct sockaddr *sa,  
  5.     int socklen)  
  6. {  
  7.     struct evconnlistener *listener;  
  8.     evutil_socket_t fd;  
  9.     int on = 1;  
  10.     int family = sa ? sa->sa_family : AF_UNSPEC;  
  11.   
  12.     //监听个数不能为0  
  13.     if (backlog == 0)  
  14.         return NULL;  
  15.   
  16.     fd = socket(family, SOCK_STREAM, 0);  
  17.     if (fd == -1)  
  18.         return NULL;  
  19.   
  20.     //LEV_OPT_LEAVE_SOCKETS_BLOCKING选项是应用于accept到的客户端socket  
  21.     //所以对于服务器端的socket,直接将之设置为非阻塞的  
  22.     if (evutil_make_socket_nonblocking(fd) < 0) {  
  23.         evutil_closesocket(fd);  
  24.         return NULL;  
  25.     }  
  26.   
  27.     if (flags & LEV_OPT_CLOSE_ON_EXEC) {  
  28.         if (evutil_make_socket_closeonexec(fd) < 0) {  
  29.             evutil_closesocket(fd);  
  30.             return NULL;  
  31.         }  
  32.     }  
  33.   
  34.     if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0) {  
  35.         evutil_closesocket(fd);  
  36.         return NULL;  
  37.     }  
  38.     if (flags & LEV_OPT_REUSEABLE) {  
  39.         if (evutil_make_listen_socket_reuseable(fd) < 0) {  
  40.             evutil_closesocket(fd);  
  41.             return NULL;  
  42.         }  
  43.     }  
  44.   
  45.     if (sa) {  
  46.         if (bind(fd, sa, socklen)<0) {//绑定  
  47.             evutil_closesocket(fd);  
  48.             return NULL;  
  49.         }  
  50.     }  
  51.   
  52.     listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);  
  53.     if (!listener) {  
  54.         evutil_closesocket(fd);  
  55.         return NULL;  
  56.     }  
  57.   
  58.     return listener;  
  59. }  

        evconnlistener_new_bind函数申请一个socket,然后对之进行一些有关非阻塞、重用、保持连接的处理、绑定到特定的IP和端口。最后把业务逻辑交给evconnlistener_new处理。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. static const struct evconnlistener_ops evconnlistener_event_ops = {  
  3.     event_listener_enable,  
  4.     event_listener_disable,  
  5.     event_listener_destroy,  
  6.     NULL, /* shutdown */  
  7.     event_listener_getfd,  
  8.     event_listener_getbase  
  9. };  
  10.   
  11.   
  12. struct evconnlistener *  
  13. evconnlistener_new(struct event_base *base,  
  14.     evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,  
  15.     evutil_socket_t fd)  
  16. {  
  17.     struct evconnlistener_event *lev;  
  18.   
  19.     if (backlog > 0) {  
  20.         if (listen(fd, backlog) < 0)  
  21.             return NULL;  
  22.     } else if (backlog < 0) {  
  23.         if (listen(fd, 128) < 0)  
  24.             return NULL;  
  25.     }  
  26.   
  27.     lev = mm_calloc(1, sizeof(struct evconnlistener_event));  
  28.     if (!lev)  
  29.         return NULL;  
  30.   
  31.     //赋值  
  32.     lev->base.ops = &evconnlistener_event_ops;  
  33.     lev->base.cb = cb;  
  34.     lev->base.user_data = ptr;  
  35.     lev->base.flags = flags;  
  36.     lev->base.refcnt = 1;  
  37.   
  38.     if (flags & LEV_OPT_THREADSAFE) {//线程安全就需要分配锁  
  39.         EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE);  
  40.     }  
  41.   
  42.     //在多路IO复用函数中,新客户端的连接请求也被当作读事件  
  43.     event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,  
  44.         listener_read_cb, lev);  
  45.   
  46.     //会调用event_add,把event加入到event_base中  
  47.     evconnlistener_enable(&lev->base);  
  48.   
  49.     return &lev->base;  
  50. }  
  51.   
  52. int  
  53. evconnlistener_enable(struct evconnlistener *lev)  
  54. {  
  55.     int r;  
  56.     LOCK(lev);  
  57.     lev->enabled = 1;  
  58.     if (lev->cb)  
  59.         r = lev->ops->enable(lev);//实际上是调用下面的event_listener_enable函数  
  60.     else  
  61.         r = 0;  
  62.     UNLOCK(lev);  
  63.     return r;  
  64. }  
  65.   
  66. static int  
  67. event_listener_enable(struct evconnlistener *lev)  
  68. {  
  69.     struct evconnlistener_event *lev_e =  
  70.         EVUTIL_UPCAST(lev, struct evconnlistener_event, base);  
  71.   
  72.     //加入到event_base,完成监听工作。  
  73.     return event_add(&lev_e->listener, NULL);  
  74. }  

        几个函数的一路调用,思路还是挺清晰的。就是申请一个socket,进行一些处理,然后用之赋值给event。最后把之add到event_base中。event_base会对新客户端的请求连接进行监听。


        在evconnlistener_enable函数里面,如果用户没有设置回调函数,那么就不会调用event_listener_enable。也就是说并不会add到event_base中。

        event_listener_enable函数里面的宏EVUTIL_UPCAST可以根据结构体成员变量的地址推算出结构体的起始地址。有关这个宏,可以查看”结构体偏移量”。


处理客户端的连接请求:

        现在来看一下event的回调函数listener_read_cb。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. static void  
  3. listener_read_cb(evutil_socket_t fd, short what, void *p)  
  4. {  
  5.     struct evconnlistener *lev = p;  
  6.     int err;  
  7.     evconnlistener_cb cb;  
  8.     evconnlistener_errorcb errorcb;  
  9.     void *user_data;  
  10.     LOCK(lev);  
  11.     while (1) { //可能有多个客户端同时请求连接  
  12.         struct sockaddr_storage ss;  
  13. #ifdef WIN32  
  14.         int socklen = sizeof(ss);  
  15. #else  
  16.         socklen_t socklen = sizeof(ss);  
  17. #endif  
  18.         evutil_socket_t new_fd = accept(fd, (struct sockaddr*)&ss, &socklen);  
  19.         if (new_fd < 0)  
  20.             break;  
  21.         if (socklen == 0) {  
  22.             /* This can happen with some older linux kernels in 
  23.              * response to nmap. */  
  24.             evutil_closesocket(new_fd);  
  25.             continue;  
  26.         }  
  27.   
  28.         if (!(lev->flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING))  
  29.             evutil_make_socket_nonblocking(new_fd);  
  30.   
  31.         //用户还没设置连接监听器的回调函数  
  32.         if (lev->cb == NULL) {  
  33.             UNLOCK(lev);  
  34.             return;  
  35.         }  
  36.   
  37.         //由于refcnt被初始化为1.这里有++了,所以一般情况下并不会进入下面的  
  38.         //if判断里面。但如果程在下面UNLOCK之后,第二个线调用evconnlistener_free  
  39.         //释放这个evconnlistener时,就有可能使得refcnt为1了。即进入那个判断体里  
  40.         //执行listener_decref_and_unlock。在下面会讨论这个问题。  
  41.         ++lev->refcnt;  
  42.         cb = lev->cb;  
  43.         user_data = lev->user_data;  
  44.         UNLOCK(lev);  
  45.         cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen,  
  46.             user_data);//调用用户设置的回调函数,让用户处理这个fd  
  47.         LOCK(lev);  
  48.         if (lev->refcnt == 1) {  
  49.             int freed = listener_decref_and_unlock(lev);  
  50.             EVUTIL_ASSERT(freed);  
  51.             return;  
  52.         }  
  53.         --lev->refcnt;  
  54.     }  
  55.       
  56.     err = evutil_socket_geterror(fd);  
  57.     if (EVUTIL_ERR_ACCEPT_RETRIABLE(err)) {//还可以accept  
  58.         UNLOCK(lev);  
  59.         return;  
  60.     }  
  61.   
  62.     //当有错误发生时才会运行到这里  
  63.     if (lev->errorcb != NULL) {  
  64.         ++lev->refcnt;  
  65.         errorcb = lev->errorcb;  
  66.         user_data = lev->user_data;  
  67.         UNLOCK(lev);  
  68.         errorcb(lev, user_data);//调用用户设置的错误回调函数  
  69.         LOCK(lev);  
  70.         listener_decref_and_unlock(lev);  
  71.     }  
  72. }  

        这个函数所做的工作也比较简单,就是accept客户端,然后调用用户设置的回调函数。所以,用户回调函数的参数fd是一个已经连接好了的socket。


        上面函数说到了错误回调函数,可以通过下面的函数设置连接监听器的错误监听函数。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.h文件  
  2. typedef void (*evconnlistener_errorcb)(struct evconnlistener *, void *);  
  3.   
  4. //listener.c文件  
  5. void  
  6. evconnlistener_set_error_cb(struct evconnlistener *lev,  
  7.     evconnlistener_errorcb errorcb)  
  8. {  
  9.     LOCK(lev);  
  10.     lev->errorcb = errorcb;  
  11.     UNLOCK(lev);  
  12. }  



释放evconnlistener:

        调用evconnlistener_free可以释放一个evconnlistener。由于evconnlistener拥有一些系统资源,在释放evconnlistener_free的时候会释放这些系统资源。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //listener.c文件  
  2. void  
  3. evconnlistener_free(struct evconnlistener *lev)  
  4. {  
  5.     LOCK(lev);  
  6.     lev->cb = NULL;  
  7.     lev->errorcb = NULL;  
  8.     if (lev->ops->shutdown)//这里的shutdown为NULL  
  9.         lev->ops->shutdown(lev);  
  10.   
  11.     //引用次数减一,并解锁  
  12.     listener_decref_and_unlock(lev);  
  13. }  
  14.   
  15. static int  
  16. listener_decref_and_unlock(struct evconnlistener *listener)  
  17. {  
  18.     int refcnt = --listener->refcnt;  
  19.     if (refcnt == 0) {  
  20.         //实际调用event_listener_destroy  
  21.         listener->ops->destroy(listener);  
  22.         UNLOCK(listener);  
  23.         //释放锁  
  24.         EVTHREAD_FREE_LOCK(listener->lock, EVTHREAD_LOCKTYPE_RECURSIVE);  
  25.         mm_free(listener);  
  26.         return 1;  
  27.     } else {  
  28.         UNLOCK(listener);  
  29.         return 0;  
  30.     }  
  31. }  
  32.   
  33. static void  
  34. event_listener_destroy(struct evconnlistener *lev)  
  35. {  
  36.     struct evconnlistener_event *lev_e =  
  37.         EVUTIL_UPCAST(lev, struct evconnlistener_event, base);  
  38.   
  39.     //把event从event_base中删除  
  40.     event_del(&lev_e->listener);  
  41.     if (lev->flags & LEV_OPT_CLOSE_ON_FREE)//如果用户设置了这个选项,那么要关闭socket  
  42.         evutil_closesocket(event_get_fd(&lev_e->listener));  
  43. }  


        要注意一点,LEV_OPT_CLOSE_ON_FREE选项关闭的是服务器端的监听socket,而非那些连接客户端的socket。



        现在来说一下那个listener_decref_and_unlock。前面注释说到,在函数listener_read_cb中,一般情况下是不会调用listener_decref_and_unlock,但在多线程的时候可能会调用。这种特殊情况是:当主线程accept到一个新客户端时,会解锁,并调用用户设置的回调函数。此时,引用计数等于2。就在这个时候,第二个线程执行evconnlistener_free函数。该函数会执行listener_decref_and_unlock。明显主线程还在用这个evconnlistener,肯定不能删除。此时引用计数也等于2也不会删除。但用户已经调用了evconnlistener_free。Libevent必须要响应。当第二个线程执行完后,主线程抢到CPU,此时引用计数就变成1了,也就进入到if判断里面了。在判断体里面执行函数listener_decref_and_unlock,并且完成删除工作。

 

        总得来说,Libevent封装的这个evconnlistener和一系列操作函数,还是比较简单的。思路也比较清晰。



参考:

        http://www.wangafu.net/~nickm/libevent-book/Ref8_listener.html


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值