libevent源码解析--evconnlistener, evconnlistener_event

1.概述
前面我们分析了libeventeventevent_callbackevent_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 关联的eventevent_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的事件循环才能触发依附此eventevent_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的事件循环不会再触发依附此eventevent_callback的回调处理了。
且通过设置enabled0,若事件循环当前正在执行此eventevent_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事件循环正准备执行此eventevent_callback的回调处理。
(2). 用户线程此时执行evconnlistener_free。函数执行中,触发释放逻辑。
通过listener->ops->destroy阻塞方式移除event。死锁。
因为此时event_base事件循环事件处理还未结束,且事件循环此时正准备执行LOCK(lev);获取锁。
然而,获取不到。
而获取到此锁的evconnlistener_free里,我们却在阻塞等待事件循环中当前event_callback处理结束。

采用异步释放方式来释放event及其关联资源(这里是释放listener及其锁资源)可解决此问题。

你可能感兴趣的:(4.5.网络-Libevent,evconnlistener,服务端监听)