1.概述
前面我们分析了libevent
中event
,event_callback
及event_base
,有了这些设施,我们便可利用event_base
的实现事件循环。基于实现循环实现事件监听,自动分发,手动分发处理。
最基础的网络库由服务端,客户端组成。
服务端需要通过监听套接字监听,并通过监听套接字的可读事件,取出新的被动连接,用于和客户端进行后续通信。
这里,我们分析libevent
中如何处理服务端监听及其处理的过程。
2.结构
2.1.evconnlistener_event
struct evconnlistener_event {
struct evconnlistener base;
struct event listener;
};
为了实现服务端监听套接字的监听和管理,我们需要一个evconnlistener_event
实例。
其字段含义:
(1). base
是实现监听套接字管理的关键。后面专门说明。
(2). listener
前面已经介绍过,我们借助event
可实现套接字事件监控及事件处理。
这里我们需要借助此event
实现监听套接字可读事件监控,及事件处理。
2.2.evconnlistener
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);
struct evconnlistener {
const struct evconnlistener_ops *ops;
void *lock;
evconnlistener_cb cb;
evconnlistener_errorcb errorcb;
void *user_data;
unsigned flags;
int accept4_flags;
unsigned enabled : 1;
};
其字段含义:
(1). ops
包含控制操作集合。后面单独分析。
(2). 互斥锁指针
每个evconnlistener
拥有一个递归的互斥锁,以便多线程环境下对结构的互斥访问。
(3). cb
这里放置一个回调函数。
每当我们处理可读事件通过accept
返回一个通信套接字就需要触发一次此函数。
(4). errorcb
这里放置一个回调函数。
当我们在处理可读事件过程遭遇错误时,就需触发一次此函数。可以不配置。
(5). user_data
回调函数触发时,在最后一个参数提供用户自定义数据。
(6). flags
标志信息。
(7). accept4_flags
标志信息。
(8). enabled
若为1
,表示关联的event
已经向event_base
注册。若为0
,表示关联的event
尚未向event_base
注册。
2.3.evconnlistener_ops
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 *);
};
操作集合中包含上述操作。
字段含义为:
(1). enable
此操作用于将隶属的evconnlistener
关联的event
注册到event_base
。
(2). disable
此操作用于将隶属的evconnlistener
关联的event
从event_base
取消注册。
(3). destroy
由于隶属的evconnlistener
的释放采用异步释放。所以释放过程分为两个阶段
阶段一的时间点是释放发起时刻,此时允许执行shutdown
执行阶段1
的清理动作。
阶段二的时间点是event_base
中服务于释放的event_callback
的回调被触发时。此时允许执行destroy
来执行阶段2
的清理动作。
(4). shutdown
参考上述。
(5). getfd
获取隶属的evconnlistener
所服务的监听套接字。
(6). getbase
获取隶属的evconnlistener
所关联到的event_base
。
2.动作
2.1. 监听套接字创建,属性设置,开启监听
(1). 我们首先应该创建监听套接字。
(2). 设置其属性。一般会设置的属性及其含义有:
a. SO_KEEPALIVE
启用套接字层面的保活机制。可以帮助服务端及时释放无用的连接。
b. SO_REUSEADDR
地址复用。通过地址复用可以在服务端停止后,之前的被动连接释放可能还处于TIME_WAIT
阶段时,允许重新在指定端口进行本端绑定。可以实现服务端停止后快速重启。
c. SO_REUSEPORT
端口复用。一般用于为多个套接字绑定同样的本端端口,本端地址。属于较新的特性,可用来实现对监听工作的并发处理。
(3). 开启监听。
2.1.创建并返回evconnlistener_event
(1). 创建evconnlistener_event
实例
(2). 初始化
主要是设置其各个字段。各个字段含义参考结构部分的解释。
这里描述下accept4_flags
可设置的标志及其含义:
a. EVUTIL_SOCK_NONBLOCK
表示需对accept
得到的套接字设置非阻塞属性。
b. EVUTIL_SOCK_CLOEXEC
表示需要在evconnlistener_event
实例释放阶段自动关闭监听套接字。
对listener
这个event
的初始化:
event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST, listener_read_cb, lev);
2.2.向关联的event_base
注册event
int evconnlistener_enable(struct evconnlistener *lev) {
int r;
LOCK(lev);
// enabled用于表示对象的注册状态
lev->enabled = 1;
if (lev->cb)
r = lev->ops->enable(lev);
else
r = 0;
UNLOCK(lev);
return r;
}
只有向关联的event_base
注册了event
,后续event_base
的事件循环才能触发依附此event
的event_callback
的回调处理。
2.3.处理事件回调
// 这里对监听套接字可读事件进行处理
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;
ev_socklen_t socklen = sizeof(ss);
// 执行accept获得被动连接,设置属性
evutil_socket_t new_fd = evutil_accept4_(fd, (struct sockaddr*)&ss, &socklen, lev->accept4_flags);
if (new_fd < 0)
break;// 要么accept出错,要么设置属性出错。结束处理。
if (socklen == 0) {// 没取到对端地址
evutil_closesocket(new_fd);
continue;
}
++lev->refcnt;
cb = lev->cb;
user_data = lev->user_data;
UNLOCK(lev);
// 通过提前refcnt加1,保证无锁回调期间,即使用户执行了lev的释放动作。回调中使用的对象依然是有效的。
cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen, user_data);// 通知上层对产生的被动连接执行进一步处理。
LOCK(lev);
if (lev->refcnt == 1) {// 表示在无锁回调期间,用户执行了lev的释放。
int freed = listener_decref_and_unlock(lev);// 这里执行实际释放。并直接返回。
EVUTIL_ASSERT(freed);
return;// 直接返回。
}
--lev->refcnt;
// 若在无锁回调期间,用户从event_callback取消了event,则直接返回。
if (!lev->enabled) {
UNLOCK(lev);
return;
}
}
// 获取最后执行系统调用是否产生错误
err = evutil_socket_geterror(fd);
// 针对系统调用被中断打断而返回,或遇到EAGAIN而返回。视为正常结束。
if (EVUTIL_ERR_ACCEPT_RETRIABLE(err)) {
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);
} else {
event_sock_warn(fd, "Error from accept() call");// 仅仅记录错误日志即可。
UNLOCK(lev);
}
}
2.4.向关联event_base
取消注册event
int evconnlistener_disable(struct evconnlistener *lev) {
int r;
LOCK(lev);
lev->enabled = 0;
r = lev->ops->disable(lev);
UNLOCK(lev);
return r;
}
向关联的event_base
取消注册event
,后续event_base
的事件循环不会再触发依附此event
的event_callback
的回调处理了。
且通过设置enabled
为0
,若事件循环当前正在执行此event
的event_callback
,也能使其尽快结束处理。
2.5.释放event
void evconnlistener_free(struct evconnlistener *lev) {
LOCK(lev);// 先加锁
lev->cb = NULL;
lev->errorcb = NULL;// 取消回调设置
if (lev->ops->shutdown)
lev->ops->shutdown(lev);// 释放阶段1清理
listener_decref_and_unlock(lev);
}
static int listener_decref_and_unlock(struct evconnlistener *listener) {
int refcnt = --listener->refcnt;
if (refcnt == 0) {
listener->ops->destroy(listener);// 本次释放引起实际释放。阶段2清理。
UNLOCK(listener);
EVTHREAD_FREE_LOCK(listener->lock, EVTHREAD_LOCKTYPE_RECURSIVE);
mm_free(listener);
return 1;
} else {
UNLOCK(listener);// 本次释放仅引起引用数递减
return 0;
}
}
上述是libevent
中实现,该实现存在死锁风险,具体为:
(1). event_base
事件循环正准备执行此event
的event_callback
的回调处理。
(2). 用户线程此时执行evconnlistener_free
。函数执行中,触发释放逻辑。
通过listener->ops->destroy
阻塞方式移除event
。死锁。
因为此时event_base
事件循环事件处理还未结束,且事件循环此时正准备执行LOCK(lev);
获取锁。
然而,获取不到。
而获取到此锁的evconnlistener_free
里,我们却在阻塞等待事件循环中当前event_callback
处理结束。
采用异步释放方式来释放event
及其关联资源(这里是释放listener
及其锁资源)可解决此问题。