Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6319578
  • 博文数量: 2759
  • 博客积分: 1021
  • 博客等级: 中士
  • 技术积分: 4091
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-11 14:14
文章分类

全部博文(2759)

文章存档

2019年(1)

2017年(84)

2016年(196)

2015年(204)

2014年(636)

2013年(1176)

2012年(463)

分类: LINUX

2015-02-04 04:41:25

在socket的服务器标准编程过程主要包含如下的基本过程:socket()-->bind()-->lister()-->accept()-->新的socket处理(收发报文等)。在编写最基本服务器代码的过程就是如上的几个步骤,在支持了IO复用之后,只是将accept的处理过程并入到了事件处理中。

今天就libevent的服务器的创建基本过程进行分析,如果需要创建对应的服务器,因此必须有一个进行监听的处理,在libevent中的listener.c文件实现了服务器进行监听的基本过程。

数据结构

在Listener中主要实现了如下的三个数据结构,这三个数据结构中的struct evconnlistener用于对外提供,另外两个数据结构在内部使用。其中的struct evconnlistener_event实现了将监听与libevet中事件进行绑定,也就是改监听结构体对应一个具体的事件。

点击(此处)折叠或打开

  1. /* 监听的操作结构体 */
  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.     /* 执行对应的操作函数指针 */
  13.     const struct evconnlistener_ops *ops;
  14.     void *lock;

  15.     /* 新连接的callback */
  16.     evconnlistener_cb cb;
  17.     evconnlistener_errorcb errorcb;
  18.     void *user_data;
  19.     unsigned flags;
  20.     short refcnt;
  21.     unsigned enabled : 1;
  22. };

  23. /* 监听事件,包含了其中的监听对应的事件和监听结构体 */
  24. struct evconnlistener_event {
  25.       /* 监听结构体 */
  26.     struct evconnlistener base;
  27.     /* 具体的事件 */
  28.     struct event listener;
  29. };

最后一个结构体在使用中都是通过base这个成员确定的,也就是根据变量的偏移地址确定,这也是根据成员变量计算结构体起始地址进程会使用的。
#define EVUTIL_UPCAST(ptr, type, field) \
((type *)(((char*)(ptr)) - evutil_offsetof(type, field)))
struct evconnlistener_event *lev_e =
   EVUTIL_UPCAST(lev, struct evconnlistener_event, base);
在内部代码使用中主要通过evconnlistener_event进行代码的编写。

对外提供接口

Listener.h中对外提供的接口主要包括:

点击(此处)折叠或打开

  1. //该接口用于实现监听操作的实现
  2. struct evconnlistener *evconnlistener_new(struct event_base *base,
  3.     evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
  4.     evutil_socket_t fd);
  5. //该接口用于实现绑定和监听    
  6. struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
  7.     evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
  8.     const struct sockaddr *sa, int socklen);
其中的cb主要用于使用者对新accept的socket进行处理,这部分见后面的代码分析。

代码分析

首先是监听操作函数的初始化:

点击(此处)折叠或打开

  1. /* 监听器的操作函数结合初始化 */
  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.     /* 获得对应的fd,监听的fd */
  8.     event_listener_getfd,
  9.     /* 获得对应的event_base */
  10.     event_listener_getbase
  11. };
从接口进行分析:
evconnlistener_new():

点击(此处)折叠或打开

  1. /* 对外提供的接口,用于创建一个用于监听连接的数据结构,其中的参数cb是有客户端连接过来之后的处理 */
  2. struct evconnlistener *
  3. evconnlistener_new(struct event_base *base,
  4.     evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
  5.     evutil_socket_t fd)
  6. {
  7.     struct evconnlistener_event *lev;

  8.     /* 实际的处理函数,创建对应的监听,默认的log为128,提高并发的一个关键参数 */
  9.     if (backlog > 0) {
  10.         if (listen(fd, backlog) < 0)
  11.             return NULL;
  12.     } else if (backlog < 0) {
  13.         if (listen(fd, 128) < 0)
  14.             return NULL;
  15.     }

  16.     /* 创建一个监听事件(其中包含了具体的事件),也就是为对应的fd创建关注的事件 */
  17.     lev = mm_calloc(1, sizeof(struct evconnlistener_event));
  18.     if (!lev)
  19.         return NULL;
  20.     /* 连接的操作函数 */
  21.     lev->base.ops = &evconnlistener_event_ops;
  22.     lev->base.cb = cb;
  23.     lev->base.user_data = ptr;
  24.     lev->base.flags = flags;
  25.     lev->base.refcnt = 1;

  26.     /* 分配一个对应的读事件和永久事件标号给对应的监听fd,对应的响应函数为listener_read_cb */
  27.     event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,
  28.      listener_read_cb, lev);

  29.     /* 使能对应的监听,实际上实现了上述分配事件的添加 */
  30.     evconnlistener_enable(&lev->base);

  31.     return &lev->base;
  32. }
其中最后的几行代码主要采用了event_assign()分配了一个新的事件,主要接收读事件,对应的回调函数为listener_read_cb。evconnlistener_enable()中实现了事件的添加操作,见后面的分析。
evconnlistener_new_bind():

点击(此处)折叠或打开

  1. /* 从创建到bind到监听的一系列处理过程 */
  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.     if (backlog == 0)
  12.         return NULL;

  13.     fd = socket(family, SOCK_STREAM, 0);
  14.     if (fd == -1)
  15.         return NULL;

  16.     if (evutil_make_socket_nonblocking(fd) < 0) {
  17.         evutil_closesocket(fd);
  18.         return NULL;
  19.     }

  20.     if (flags & LEV_OPT_CLOSE_ON_EXEC) {
  21.         if (evutil_make_socket_closeonexec(fd) < 0) {
  22.             evutil_closesocket(fd);
  23.             return NULL;
  24.         }
  25.     }

  26.     /* 设置保活 */
  27.     if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0) {
  28.         evutil_closesocket(fd);
  29.         return NULL;
  30.     }
  31.     /* 设置地址可反复的利用 */
  32.     if (flags & LEV_OPT_REUSEABLE) {
  33.         if (evutil_make_listen_socket_reuseable(fd) < 0) {
  34.             evutil_closesocket(fd);
  35.             return NULL;
  36.         }
  37.     }

  38.     if (sa) {
  39.         /* 执行对应的bind操作,也就是将对应的fd绑定的对应的地址和端口上 */
  40.         if (bind(fd, sa, socklen)<0) {
  41.             evutil_closesocket(fd);
  42.             return NULL;
  43.         }
  44.     }

  45.     /* 创建对应的监听 */
  46.     listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);
  47.     if (!listener) {
  48.         evutil_closesocket(fd);
  49.         return NULL;
  50.     }

  51.     return listener;
  52. }
从上述的代码中可知,evconnlistener_new_bind中包含了所有的创建,bind,监听操作。

listener_read_cb()在evconnlistener_new作为监听事件的回调函数进行添加,该事件在有客户端连接到服务以后就会被触发调用,基本的实现逻辑如下所示:

点击(此处)折叠或打开

  1. /* 监听socket接收到对应的新客户端连接事件后的处理 */
  2. static void
  3. listener_read_cb(evutil_socket_t fd, short what, void *p)
  4. {
  5.     struct evconnlistener *lev = p; //强制类型转换,evconnlistener和evconnlistener_event地址一致
  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.         socklen_t socklen = sizeof(ss);

  14.         /* accept返回值为新的fd */
  15.         evutil_socket_t new_fd = accept(fd, (struct sockaddr*)&ss, &socklen);
  16.         if (new_fd < 0)
  17.             break;//非阻塞模式的好处
  18.         if (socklen == 0) {
  19.             evutil_closesocket(new_fd);
  20.             continue;
  21.         }
  22.     
  23.         /* 设置属性,通常设备为不可阻塞 */
  24.         if (!(lev->flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING))
  25.             evutil_make_socket_nonblocking(new_fd);
  26.         
  27.         /* 如无cb,理论上应该不会accept,因此出错 */
  28.         if (lev->cb == NULL) {
  29.             UNLOCK(lev);
  30.             return;
  31.         }
  32.         ++lev->refcnt; //增加接收到socket个数
  33.         /* 对应的回调函数 */
  34.         cb = lev->cb;
  35.         user_data = lev->user_data;
  36.         /* XXX:这边释放锁的操作,主要防止对调函数中进行不可预估的操作,因此释放锁 */
  37.         UNLOCK(lev);
  38.         /* 执行对应的回调函数,也就是说具体的处理由使用者决定,由使用者对new_fd处理 */
  39.         cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen,
  40.          user_data);
  41.         /* XXX:这边重新添加锁,是编写lib库需要值得学习的地方,能避免反复加锁 */
  42.         LOCK(lev);
  43.         if (lev->refcnt == 1) {
  44.             int freed = listener_decref_and_unlock(lev);
  45.             EVUTIL_ASSERT(freed);
  46.             return;
  47.         }
  48.         --lev->refcnt;
  49.     }
  50.     ...
  51. }
这部分有关锁的处理值得借鉴,我之前在写lib库的过程中提供的回调函数,在使用的过程中通常强制要求用户不能进行锁的操作,而这边采用了回调前后释放和重新加锁的操作,这样防止使用者反复的加锁,同时使得使用者更加的方便处理。

点击(此处)折叠或打开

  1. int
  2. evconnlistener_enable(struct evconnlistener *lev)
  3. {
  4.     int r;
  5.     LOCK(lev);
  6.     lev->enabled = 1;
  7.     if (lev->cb)//调用上述分配的使能函数,好处是,如果未传递回调函数,则不使能
  8.         r = lev->ops->enable(lev);
  9.     else
  10.         r = 0;
  11.     UNLOCK(lev);
  12.     return r;
  13. }
由该函数分析可知,在当前监听器的回调函数不存在的情况下,实际上监听操作的使能操作并没有执行,因此回调函数是必须提供的。其中的ops->enable()如下所示:

点击(此处)折叠或打开

  1. /* 监听事件的添加操作 */
  2. static int
  3. event_listener_enable(struct evconnlistener *lev)
  4. {    
  5.     /* 根据对外提供的evconnlistener获取evconnlistener_event的地址,本来强制转换即可,但是为了可移植性 */
  6.     struct evconnlistener_event *lev_e =
  7.      EVUTIL_UPCAST(lev, struct evconnlistener_event, base);  //计算event的地址
  8.     /* 将之前分配的事件添加到base中,因此完成了事件的添加操作 */
  9.     return event_add(&lev_e->listener, NULL);
  10. }
上述主要实现了evconnlistener_event两个成员之间的转换,并将对应事件添加到IO复用模型中。因此在没有提供回调的情况下,该函数并不会被调用,也就不会触发listener_read_cb()执行,因此在listener_read_cb中如cb为null,说明出现了异常。

以上的代码基本实现了服务创建的基本过程,实现了socket从创建到accept的过程。

阅读(1601) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~