libevent (hello-world代码源码)----event_base_new

libevent (hello-world代码源码)----evconnlistener、event中看了evconnlistener、event的定义,现在我们知道evconnlistener是用来描述tcp服务端的结构体,event结构里有3个event_callback,分别对应超时、io和信号事件。在libevent (hello-world代码源码)----event_base中可以知道event是event_base结构体管理的事件单位。

event_base管理一堆超时、io和信号触发的event事件。

sockaddr_in 

让我们来看第四行:

struct sockaddr_in sin;

创建一个 描述网络连接参数的结构体,里面包含ip、端口、协议信息。

struct sockaddr_in
 
{
 
   short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
 
   unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
 
   struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
 
   unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
 
};

WSAStartup 

然后下面是:

#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

其中#ifdef _WIN32由编译器(ml.exe/ml64.exe)内部定义的。具体描述是:

  _WIN32:Defined for applications for Win32 and Win64. Always defined.

  _WIN64:Defined for applications for Win64.

windows平台上总是会有这种宏,所以用来判断是否是windows平台。

WSAStartup(0x0201, &wsa_data);

当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。为了在应用程序当中调用任何一个Winsock API,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务初始化。

所以这几行的意思就是如果是windows系统,初始化网络服务。(linux不需要)

event_base_new

接下来是创建新的event_base:

	base = event_base_new();
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}

event.h

event_base_new:

/**
 * Create and return a new event_base to use with the rest of Libevent.
 *
 * @return a new event_base on success, or NULL on failure.
 *
 * @see event_base_free(), event_base_new_with_config()
创建并返回一个新的event_ base以与Libevent的其余部分一起使用。
@return一个新的event_ base,成功时返回,失败时返回NULL。
@see event_base_free(), event_base_new_with_config()
 */
EVENT2_EXPORT_SYMBOL
struct event_base *event_base_new(void);

失败时返回NULL,所以后面就是如果创建失败直接return 1,因为是main函数所以直接退出程序。

event_config:

/**
 * Configuration for an event_base.
 *
 * There are many options that can be used to alter the behavior and
 * implementation of an event_base.  To avoid having to pass them all in a
 * complex many-argument constructor, we provide an abstract data type
 * where you set up configuration information before passing it to
 * event_base_new_with_config().
 *
 * @see event_config_new(), event_config_free(), event_base_new_with_config(),
 *   event_config_avoid_method(), event_config_require_features(),
 *   event_config_set_flag(), event_config_set_num_cpus_hint()
 */
struct event_config
#ifdef EVENT_IN_DOXYGEN_
{/*Empty body so that doxygen will generate documentation here.*/}
#endif
;

event_base的配置:

有许多选项可用于更改event_ base的行为和实现。为了避免在复杂的多参数构造函数中传递它们,我们提供了一种抽象数据类型,您可以在其中设置配置信息,然后再将其传递给event_base_new_with_config()。

 * @see event_config_new(), event_config_free(), event_base_new_with_config(),
 *   event_config_avoid_method(), event_config_require_features(),
 *   event_config_set_flag(), event_config_set_num_cpus_hint()

相关函数有点多,先不看了,应该都是改配置相关的吧。

event-internal.h

event_config:

/** Internal structure: describes the configuration we want for an event_base
 * that we're about to allocate. */
struct event_config {
//这个队列中存放的是config中需要避免的IO多路复用模型
	TAILQ_HEAD(event_configq, event_config_entry) entries;
//CPU的个数,仅仅在win下有用
	int n_cpus_hint;

// 检查两次新事件之间的最大时间间隔
	struct timeval max_dispatch_interval;
// 两次检查新事件之间的执行回调函数个数的最大个数
	int max_dispatch_callbacks;
//任务的优先级限制
	int limit_callbacks_after_prio;
	enum event_method_feature require_features;//多路IO复用函数应该满足哪些特征
	enum event_base_config_flag flags;
};

 内部结构:描述我们要分配的事件库的配置。其中flags:

  • EVENT_BASE_FLAG_NOLOCK:不要为event_base分配锁。设置这个选项可以为event_base节省一点加锁和解锁的时间,但是当多个线程访问event_base会变得不安全
  • EVENT_BASE_FLAG_IGNORE_ENV:选择多路IO复用函数时,不检测EVENT_*环境变量。使用这个标志要考虑清楚:因为这会使得用户更难调试程序与Libevent之间的交互
  • EVENT_BASE_FLAG_STARTUP_IOCP:仅用于Windows。这使得Libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。如果设置了这个宏,那么evconn_listener_new和bufferevent_socket_new函数的内部将使用IOCP
  • EVENT_BASE_FLAG_NO_CACHE_TIME:在执行event_base_loop的时候没有cache时间。该函数的while循环会经常取系统时间,如果cache时间,那么就取cache的。如果没有的话,就只能通过系统提供的函数来获取系统时间。这将更耗时
  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告知Libevent,如果决定使用epoll这个多路IO复用函数,可以安全地使用更快的基于changelist 的多路IO复用函数:epoll-changelist多路IO复用可以在多路IO复用函数调用之间,同样的fd 多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给Libevent,epoll-changelist多路IO复用函数会触发一个内核bug,导致不正确的结果。在不使用epoll这个多路IO复用函数的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST 环境变量来打开epoll-changelist选项

event.c

event_base_new:

struct event_base *
event_base_new(void)
{
	struct event_base *base = NULL;
	struct event_config *cfg = event_config_new();
	if (cfg) {
		base = event_base_new_with_config(cfg);
		event_config_free(cfg);
	}
	return base;
}

可以看出先通过event_config_new()创建配置文件,(如果配置文件创建成功)通过配置文件创建event_base,然后释放配置配置文件内存。所以重点应该是通过配置文件创建event_base的event_base_new_with_config函数,不过这个函数有点长,还是先看看配置文件怎么创建的吧

event_config_new:

struct event_config *
event_config_new(void)
{//申请内存,并全置1
	struct event_config *cfg = mm_calloc(1, sizeof(*cfg));

	if (cfg == NULL)
		return (NULL);//内存申请失败,返回NULL
    //tailqueue最重要的特点就是在head中增加了一个指向末尾的指针,所以它能够直接在链表的尾部插入数据
	TAILQ_INIT(&cfg->entries);//初始化一个尾部队列
	cfg->max_dispatch_interval.tv_sec = -1;//检查两次新事件之间的最大时间间隔
	cfg->max_dispatch_callbacks = INT_MAX;//两次检查新事件之间的执行回调函数个数的最大个数
	cfg->limit_callbacks_after_prio = 1;//任务优先级限制,限制低优先级的任务执行

	return (cfg);
}

 然后,开始new一个event_base,event_base_new_with_config:

struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
	int i;
	struct event_base *base;
	int should_check_environment;
//如果是debug event_debug_mode_too_late 置1 干嘛的,不知道
#ifndef EVENT__DISABLE_DEBUG_MODE
	event_debug_mode_too_late = 1;
#endif
	//申请内存,自定义的内存管理函数,总之就是申请内存空间并初始化
	if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
		event_warn("%s: calloc", __func__);//打印错误日志
		return NULL;
	}

	if (cfg)
		base->flags = cfg->flags;//配置文件赋值
	//检查环境时间:可能系统时间发生了变化
	//先把flags和EVENT_BASE_FLAG_IGNORE_ENV按位与 然后和 cfg 与
	//EVENT_BASE_FLAG_IGNORE_ENV:选择多路IO复用函数时,不检测EVENT_*环境变量。
	//所以就是 当cfg合法并且 设置了EVENT_BASE_FLAG_IGNORE_ENV时 should_check_environment 置为0。
	should_check_environment =
	    !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));

	{
		struct timeval tmp;

    // EVENT_BASE_FLAG_PRECISE_TIMER:通常情况下我们使用的是更快的计数器,如果设置了这个标志的话我们
    // 会使用更精确的时间
	//默认情况下flags采用的是EV_MONOT_PRECISE
	//设置了EVENT_BASE_FLAG_PRECISE_TIMER precise_time置1
		int precise_time =
		    cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
		int flags;
		if (should_check_environment && !precise_time) {
			//获取环境变量的值
			precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
			if (precise_time) {
				base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
			}
		}
		flags = precise_time ? EV_MONOT_PRECISE : 0;
	// POSIX clock_gettime接口提供获得monotonic时间的方式。CLOCK_MONOTONIC基本上都是支持的;
    // linux也提供CLOCK_MONOTONIC_COARSE模式,大约1-4毫秒的准确性。
    // 所有平台上,CLOCK_MONOTONIC实际上是单调递增的。
	// 此函数最终的结果是获取CLOCK_MONOTONIC模式的时间,并将event_base的时钟模式设置为
    // CLOCK_MONOTONIC模式
	// flags默认为1
		evutil_configure_monotonic_time_(&base->monotonic_timer, flags);
	// 根据“base”将“tp”设置为当前时间。我们必须把锁锁在“base”上。如果存在缓存时间,则返回它。
	// 否则,使用clock_gettime或gettimeofday(视情况而定)查找正确的时间。
	// 成功时返回0,失败时返回1。
		gettime(base, &tmp);
	}

	//-----初始化参数-----

	//最小堆最小堆,是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于其左子节点和右子节点的值。
	//libevent的最小堆是通过数组的形式实现的,索引从0开始
	min_heap_ctor_(&base->timeheap);

	//signal.c中默认信号处理实现的数据结构
	//用于从信号处理程序发送通知的Socketpair
	base->sig.ev_signal_pair[0] = -1;
	base->sig.ev_signal_pair[1] = -1;
	//一种宽度足以容纳“socket()”或“accept()”输出的类型。在Windows上,这是一个intptr_;在其他地方,它是一个int。
	//一些th_ notify函数用来唤醒主线程的socketpair。
	base->th_notify_fd[0] = -1;
	base->th_notify_fd[1] = -1;
	//active_later_queue:尾部队列函数event_callbacks的列表,下次处理事件时应该激活,但这次不会。
	//初始化active_later_queue
	TAILQ_INIT(&base->active_later_queue);

	//io:从文件描述符到已启用(添加)事件的映射
	//初始化事件_映射以供使用
	evmap_io_initmap_(&base->io);
	//sigmap:从信号号到启用(添加)事件的映射
	//初始化sigmap
	evmap_signal_initmap_(&base->sigmap);
	//changelist:在下一次调度时告知后端的更改列表。仅由O(1)后端使用。
	//自上次调用eventop.dispatch以来的“更改”列表。仅在后端使用变更集时维护。
	//初始化changelist
	event_changelist_init_(&base->changelist);
	//指向后端特定数据的指针。
	base->evbase = NULL;

	//根据cfg配置初始化参数
	if (cfg) {
		//检查两次新事件之间的最大时间间隔
		memcpy(&base->max_dispatch_time,
		    &cfg->max_dispatch_interval, sizeof(struct timeval));
		//任务优先级限制,限制低优先级的任务执行
		base->limit_callbacks_after_prio =
		    cfg->limit_callbacks_after_prio;
	} else {
		base->max_dispatch_time.tv_sec = -1;
		base->limit_callbacks_after_prio = 1;
	}
	if (cfg && cfg->max_dispatch_callbacks >= 0) {
		//两次检查新事件之间的执行回调函数个数的最大个数
		base->max_dispatch_callbacks = cfg->max_dispatch_callbacks;
	} else {
		base->max_dispatch_callbacks = INT_MAX;
	}
	if (base->max_dispatch_callbacks == INT_MAX &&
	    base->max_dispatch_time.tv_sec == -1)
	    //如果执行回调函数个数的最大个数为INT_MAX,而且最大时间间隔为-1  ,任务优先级置为INT_MAX
		base->limit_callbacks_after_prio = INT_MAX;

	//按优先顺序排列的后端数组,确定系统环境
	//选择IO复用结构体
	for (i = 0; eventops[i] && !base->evbase; i++) {
		if (cfg != NULL) {
			/* determine if this backend should be avoided */
			//确定是否应避免使用此后端(IO复用)
			if (event_config_is_avoided_method(cfg,
				eventops[i]->name))//禁用哪种多路复用
				continue;
			if ((eventops[i]->features & cfg->require_features)
			    != cfg->require_features)//是否满足设置的特征
				continue;
		}

		/* also obey the environment variables */
		//也 服从环境变量
		if (should_check_environment &&
		    event_is_method_disabled(eventops[i]->name))
			continue;
		//找到一个满足条件的IO多路复用
		//函数指针和其他数据来描述这个event_base的后端(系统环境)
		base->evsel = eventops[i];
		//指向后端特定数据的指针
		//初始化ev_base,并且会对信号监听的处理也进行初始化
		base->evbase = base->evsel->init(base);
	}

	if (base->evbase == NULL) {
		event_warnx("%s: no event mechanism available",
		    __func__);
		base->evsel = NULL;
		event_base_free(base);
		return NULL;
	}

	if (evutil_getenv_("EVENT_SHOW_METHOD"))
		event_msgx("libevent using: %s", base->evsel->name);

	/* allocate a single active event queue */
	//llocate单个活动事件队列
	if (event_base_priority_init(base, 1) < 0) {
		event_base_free(base);
		return NULL;
	}

	/* prepare for threading */
	//准备线程
/*
libevent关于多线程的使用需要在所有的初始化之前加evthread_use_pthreads()函数的原因:
evthread_use_pthreads()定义在evthread_pthread.c里面。
在这个函数里,初始化了一个evthread_lock_callbacks对象 cbs,
然后调用evthread_set_lock_callbacks(&cbs);来的对cbs这个evthread_lock_callbacks对象做操作。
evthread_set_lock_callbacks定义在evthread.c里面。
在这个函数里,其实就是将cbs的值赋值给了全局变量_evthread_lock_fns。

在定义了_EVENT_DISABLE_THREAD_SUPPORT的情况下
在add_event函数里面,libevent调用了EVBASE_ACQUIRE_LOCK这个宏。
这个宏定义在evthread-internal.h, 同时EVBASE_ACQUIRE_LOCK这个宏又调用了EVLOCK_LOCK,
EVLOCK_LOCK又调用了全局变量_evthread_lock_fns的lock成员。这个_evthread_lock_fns就是之前说过的那个。
所以其实就是调用了evthread_use_pthreads()函数设置的_evthread_lock_fns这个结构体的lock成员。
而这个lock成员函数,根据evthread_use_pthreads()函数里面设置的值,就是evthread_posix_lock函数,其中参数mode是0,
参数_lock是base.th_base_lock。所以其实就是pthread_mutex_lock(base.th_base_lock)

在没有定义_EVENT_DISABLE_THREAD_SUPPORT的情况下
在add_event函数里面,libevent调用了EVBASE_ACQUIRE_LOCK这个宏。这个宏定义在evthread-internal.h,
 同时EVBASE_ACQUIRE_LOCK这个宏又调用了EVLOCK_LOCK,EVLOCK_LOCK又调用了函数_evthreadimpl_lock_lock(),
 参数mode是0,参数lock是base.th_base_lock。_evthreadimpl_lock_lock定义在evthread.c里面。
 在_evthreadimpl_lock_lock函数里面,会先判断全局变量_evthread_lock_fns的lock存不存在。
 如果存在就调用_evthread_lock_fns的lock成员,相当于就是调用evthread_posix_lock函数了,
 就和定义了_EVENT_DISABLE_THREAD_SUPPORT的情况一样了。如果不存在就什么都不干,返回0。
因为我的环境里面是没有定义_EVENT_DISABLE_THREAD_SUPPORT的,所以如果不在开始的时候调用evthread_use_pthreads(),
那么全局变量_evthread_lock_fns就没有被赋值,他的lock成员自然也就是NULL了。所以,EVBASE_ACQUIRE_LOCK宏其实什么都没干,
也就没有加锁,所以在多个线程里面add_event会乱掉
*/
#if !defined(EVENT__DISABLE_THREAD_SUPPORT) && !defined(EVENT__DISABLE_DEBUG_MODE)
	event_debug_created_threadable_ctx_ = 1;
#endif
//如果定义了EVENT__DISABLE_THREAD_SUPPORT 设置base->th_base_lock base->current_event_cond
#ifndef EVENT__DISABLE_THREAD_SUPPORT
	if (EVTHREAD_LOCKING_ENABLED() &&
	    (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
		int r;
		EVTHREAD_ALLOC_LOCK(base->th_base_lock, 0);
		EVTHREAD_ALLOC_COND(base->current_event_cond);
		r = evthread_make_base_notifiable(base);
		if (r<0) {
			event_warnx("%s: Unable to make base notifiable.", __func__);
			event_base_free(base);
			return NULL;
		}
	}
#endif

#ifdef _WIN32
	if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
		event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif

	return (base);
}

 似懂非懂,迷迷糊糊

你可能感兴趣的:(libevent,开发语言,c++,c语言,网络)