转载请注明出处:http://blog.csdn.net/luotuo44/article/details/38800363
基于event和event_base已经可以写一个CS模型了。但是对于服务器端来说,仍然需要用户自行调用socket、bind、listen、accept等步骤。这个过程有点繁琐,为此在2.0.2-alpha版本的Libevent推出了一些对应的封装函数。
用户只需初始化struct sockaddr_in结构体变量,然后把它作为参数传给函数evconnlistener_new_bind即可。该函数会完成上面说到的那4个过程。下面的代码是一个使用例子。#include<netinet/in.h> #include<sys/socket.h> #include<unistd.h> #include<stdio.h> #include<string.h> #include<event.h> #include<listener.h> #include<bufferevent.h> #include<thread.h> void listener_cb(evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *arg); void socket_read_cb(bufferevent *bev, void *arg); void socket_error_cb(bufferevent *bev, short events, void *arg); int main() { evthread_use_pthreads();//enable threads struct sockaddr_in sin; memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(8989); event_base *base = event_base_new(); evconnlistener *listener = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE | LEV_OPT_THREADSAFE, 10, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)); event_base_dispatch(base); evconnlistener_free(listener); event_base_free(base); return 0; } //有新的客户端连接到服务器 //当此函数被调用时,libevent已经帮我们accept了这个客户端。该客户端的 //文件描述符为fd void listener_cb(evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *arg) { event_base *base = (event_base*)arg; //下面代码是为这个fd创建一个bufferevent bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, socket_read_cb, NULL, socket_error_cb, NULL); bufferevent_enable(bev, EV_READ | EV_PERSIST); } void socket_read_cb(bufferevent *bev, void *arg) { char msg[4096]; size_t len = bufferevent_read(bev, msg, sizeof(msg)-1 ); msg[len] = '\0'; printf("server read the data %s\n", msg); char reply[] = "I has read your data"; bufferevent_write(bev, reply, strlen(reply) ); } void socket_error_cb(bufferevent *bev, short events, void *arg) { if (events & BEV_EVENT_EOF) printf("connection closed\n"); else if (events & BEV_EVENT_ERROR) printf("some other error\n"); //这将自动close套接字和free读写缓冲区 bufferevent_free(bev); }
上面的代码是一个服务器端的例子,客户端代码可以使用《Libevent使用例子,从简单到复杂》博文中的客户端。这里就不贴客户端代码了。
从上面代码可以看到,当服务器端监听到一个客户端的连接请求后,就会调用listener_cb这个回调函数。这个回调函数是在evconnlistener_new_bind函数中设置的。现在来看一下这个函数的参数有哪些,下面是其函数原型。
//listener.h文件 typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *); struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen);
第一个参数是很熟悉的event_base,无论怎么样都是离不开event_base这个发动机的。
第二个参数是一个函数指针,该函数指针的格式如代码所示。当有新的客户端请求连接时,该函数就会调用。要注意的是:当这个回调函数被调用时,Libevent已经帮我们accept了这个客户端。所以,该回调函数有一个参数是文件描述符fd。我们直接使用这个fd即可。真是方便。这个参数是可以为NULL的,此时用户并不能接收到客户端。当用户调用evconnlistener_set_cb函数设置回调函数后,就可以了。
第三个参数是传给回调函数的用户参数,作用就像event_new函数的最后一个参数。
参数flags是一些标志值,有下面这些:
参数backlog是系统调用listen的第二个参数。最后两个参数就不多说了。
接下来看一下Libevent是怎么封装evconnlistener的。
//listener.c文件 struct evconnlistener_ops {//一系列的工作函数 int (*enable)(struct evconnlistener *); int (*disable)(struct evconnlistener *); void (*destroy)(struct evconnlistener *); void (*shutdown)(struct evconnlistener *); evutil_socket_t (*getfd)(struct evconnlistener *); struct event_base *(*getbase)(struct evconnlistener *); }; struct evconnlistener { const struct evconnlistener_ops *ops;//操作函数 void *lock; //锁变量,用于线程安全 evconnlistener_cb cb;//用户的回调函数 evconnlistener_errorcb errorcb;//发生错误时的回调函数 void *user_data;//回调函数的参数 unsigned flags;//属性标志 short refcnt;//引用计数 unsigned enabled : 1;//位域为1.即只需一个比特位来存储这个成员 }; struct evconnlistener_event { struct evconnlistener base; struct event listener; //内部event,插入到event_base };
在evconnlistener_event结构体有一个event结构体。可以想象,在实现时必然是将服务器端的socket fd赋值给struct event 类型变量listener的fd成员。然后将listener加入到event_base,这样就完成了自动监听工作。这也回归到之前学过的内容。
//listener.c文件 struct evconnlistener * evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen) { struct evconnlistener *listener; evutil_socket_t fd; int on = 1; int family = sa ? sa->sa_family : AF_UNSPEC; //监听个数不能为0 if (backlog == 0) return NULL; fd = socket(family, SOCK_STREAM, 0); if (fd == -1) return NULL; //LEV_OPT_LEAVE_SOCKETS_BLOCKING选项是应用于accept到的客户端socket //所以对于服务器端的socket,直接将之设置为非阻塞的 if (evutil_make_socket_nonblocking(fd) < 0) { evutil_closesocket(fd); return NULL; } if (flags & LEV_OPT_CLOSE_ON_EXEC) { if (evutil_make_socket_closeonexec(fd) < 0) { evutil_closesocket(fd); return NULL; } } if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0) { evutil_closesocket(fd); return NULL; } if (flags & LEV_OPT_REUSEABLE) { if (evutil_make_listen_socket_reuseable(fd) < 0) { evutil_closesocket(fd); return NULL; } } if (sa) { if (bind(fd, sa, socklen)<0) {//绑定 evutil_closesocket(fd); return NULL; } } listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd); if (!listener) { evutil_closesocket(fd); return NULL; } return listener; }
evconnlistener_new_bind函数申请一个socket,然后对之进行一些有关非阻塞、重用、保持连接的处理、绑定到特定的IP和端口。最后把业务逻辑交给evconnlistener_new处理。
//listener.c文件 static const struct evconnlistener_ops evconnlistener_event_ops = { event_listener_enable, event_listener_disable, event_listener_destroy, NULL, /* shutdown */ event_listener_getfd, event_listener_getbase }; struct evconnlistener * evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd) { struct evconnlistener_event *lev; if (backlog > 0) { if (listen(fd, backlog) < 0) return NULL; } else if (backlog < 0) { if (listen(fd, 128) < 0) return NULL; } lev = mm_calloc(1, sizeof(struct evconnlistener_event)); if (!lev) return NULL; //赋值 lev->base.ops = &evconnlistener_event_ops; lev->base.cb = cb; lev->base.user_data = ptr; lev->base.flags = flags; lev->base.refcnt = 1; if (flags & LEV_OPT_THREADSAFE) {//线程安全就需要分配锁 EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE); } //在多路IO复用函数中,新客户端的连接请求也被当作读事件 event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST, listener_read_cb, lev); //会调用event_add,把event加入到event_base中 evconnlistener_enable(&lev->base); return &lev->base; } int evconnlistener_enable(struct evconnlistener *lev) { int r; LOCK(lev); lev->enabled = 1; if (lev->cb) r = lev->ops->enable(lev);//实际上是调用下面的event_listener_enable函数 else r = 0; UNLOCK(lev); return r; } static int event_listener_enable(struct evconnlistener *lev) { struct evconnlistener_event *lev_e = EVUTIL_UPCAST(lev, struct evconnlistener_event, base); //加入到event_base,完成监听工作。 return event_add(&lev_e->listener, NULL); }
几个函数的一路调用,思路还是挺清晰的。就是申请一个socket,进行一些处理,然后用之赋值给event。最后把之add到event_base中。event_base会对新客户端的请求连接进行监听。
在evconnlistener_enable函数里面,如果用户没有设置回调函数,那么就不会调用event_listener_enable。也就是说并不会add到event_base中。
event_listener_enable函数里面的宏EVUTIL_UPCAST可以根据结构体成员变量的地址推算出结构体的起始地址。有关这个宏,可以查看”结构体偏移量”。
现在来看一下event的回调函数listener_read_cb。
//listener.c文件 static void listener_read_cb(evutil_socket_t fd, short what, void *p) { struct evconnlistener *lev = p; int err; evconnlistener_cb cb; evconnlistener_errorcb errorcb; void *user_data; LOCK(lev); while (1) { //可能有多个客户端同时请求连接 struct sockaddr_storage ss; #ifdef WIN32 int socklen = sizeof(ss); #else socklen_t socklen = sizeof(ss); #endif evutil_socket_t new_fd = accept(fd, (struct sockaddr*)&ss, &socklen); if (new_fd < 0) break; if (socklen == 0) { /* This can happen with some older linux kernels in * response to nmap. */ evutil_closesocket(new_fd); continue; } if (!(lev->flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING)) evutil_make_socket_nonblocking(new_fd); //用户还没设置连接监听器的回调函数 if (lev->cb == NULL) { UNLOCK(lev); return; } //由于refcnt被初始化为1.这里有++了,所以一般情况下并不会进入下面的 //if判断里面。但如果程在下面UNLOCK之后,第二个线调用evconnlistener_free //释放这个evconnlistener时,就有可能使得refcnt为1了。即进入那个判断体里 //执行listener_decref_and_unlock。在下面会讨论这个问题。 ++lev->refcnt; cb = lev->cb; user_data = lev->user_data; UNLOCK(lev); cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen, user_data);//调用用户设置的回调函数,让用户处理这个fd LOCK(lev); if (lev->refcnt == 1) { int freed = listener_decref_and_unlock(lev); EVUTIL_ASSERT(freed); return; } --lev->refcnt; } err = evutil_socket_geterror(fd); if (EVUTIL_ERR_ACCEPT_RETRIABLE(err)) {//还可以accept UNLOCK(lev); return; } //当有错误发生时才会运行到这里 if (lev->errorcb != NULL) { ++lev->refcnt; errorcb = lev->errorcb; user_data = lev->user_data; UNLOCK(lev); errorcb(lev, user_data);//调用用户设置的错误回调函数 LOCK(lev); listener_decref_and_unlock(lev); } }
这个函数所做的工作也比较简单,就是accept客户端,然后调用用户设置的回调函数。所以,用户回调函数的参数fd是一个已经连接好了的socket。
上面函数说到了错误回调函数,可以通过下面的函数设置连接监听器的错误监听函数。
//listener.h文件 typedef void (*evconnlistener_errorcb)(struct evconnlistener *, void *); //listener.c文件 void evconnlistener_set_error_cb(struct evconnlistener *lev, evconnlistener_errorcb errorcb) { LOCK(lev); lev->errorcb = errorcb; UNLOCK(lev); }
调用evconnlistener_free可以释放一个evconnlistener。由于evconnlistener拥有一些系统资源,在释放evconnlistener_free的时候会释放这些系统资源。
//listener.c文件 void evconnlistener_free(struct evconnlistener *lev) { LOCK(lev); lev->cb = NULL; lev->errorcb = NULL; if (lev->ops->shutdown)//这里的shutdown为NULL lev->ops->shutdown(lev); //引用次数减一,并解锁 listener_decref_and_unlock(lev); } static int listener_decref_and_unlock(struct evconnlistener *listener) { int refcnt = --listener->refcnt; if (refcnt == 0) { //实际调用event_listener_destroy listener->ops->destroy(listener); UNLOCK(listener); //释放锁 EVTHREAD_FREE_LOCK(listener->lock, EVTHREAD_LOCKTYPE_RECURSIVE); mm_free(listener); return 1; } else { UNLOCK(listener); return 0; } } static void event_listener_destroy(struct evconnlistener *lev) { struct evconnlistener_event *lev_e = EVUTIL_UPCAST(lev, struct evconnlistener_event, base); //把event从event_base中删除 event_del(&lev_e->listener); if (lev->flags & LEV_OPT_CLOSE_ON_FREE)//如果用户设置了这个选项,那么要关闭socket evutil_closesocket(event_get_fd(&lev_e->listener)); }
要注意一点,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