从本文开始,由sample入手,逐渐理解libhv的源码。
如有理解错误,欢迎批评指正。
int main() {
// memcheck atexit
HV_MEMCHECK;
hloop_t* loop = hloop_new(0);
// test idle and priority
for (int i = HEVENT_LOWEST_PRIORITY; i <= HEVENT_HIGHEST_PRIORITY; ++i) {
hidle_t* idle = hidle_add(loop, on_idle, 10);
hevent_set_priority(idle, i);
}
// // test timer timeout
for (int i = 1; i <= 10; ++i) {
htimer_t* timer = htimer_add(loop, on_timer, i*1000, 3);
hevent_set_userdata(timer, (void*)(intptr_t)i);
}
// // test timer period
int minute = time(NULL)%3600/60;
htimer_add_period(loop, cron_hourly, minute+1, -1, -1, -1, -1, INFINITE);
// test network_logger
htimer_add(loop, timer_write_log, 1000, INFINITE);
logger_set_handler(hlog, mylogger);
hlog_set_file("loop.log");
#ifndef _MSC_VER
logger_enable_color(hlog, 1);
#endif
nlog_listen(loop, DEFAULT_LOG_PORT);
// test nonblock stdin
printf("input 'quit' to quit loop\n");
char buf[64];
hread(loop, 0, buf, sizeof(buf), on_stdin);
// test custom_events
for (int i = 0; i < 10; ++i) {
hevent_t ev;
memset(&ev, 0, sizeof(ev));
ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + i);
ev.cb = on_custom_events;
ev.userdata = (void*)(long)i;
hloop_post_event(loop, &ev);
}
hloop_run(loop);
hloop_free(&loop);
return 0;
}
代码的结构和注释非常的清晰,其中包含了多个事件的类型:
1.空闲事件
2.定时器超时事件
3.周期性的超时事件
4.监听listen事件
5.键盘自定义事件
6.用户自定义事件
先看一下代码的开头。
hloop_t* loop = hloop_new(0);
这里创建并初始化了libhv的事件循环。看一下这里面具体做了什么。
hloop_t* hloop_new(int flags) {
hloop_t* loop;
HV_ALLOC_SIZEOF(loop);
hloop_init(loop);
loop->flags |= flags;
return loop;
}
首先看一下这个flags的取值范围
#define HLOOP_FLAG_RUN_ONCE 0x00000001 //事件循环体只执行一次
#define HLOOP_FLAG_AUTO_FREE 0x00000002 //执行完自动释放资源
#define HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS 0x00000004 //当没有事件时,退出事件循环
具体使用,我们后面再看。
接下来看一下hloop_init具体做了什么
static void hloop_init(hloop_t* loop) {
#ifdef OS_WIN
static int s_wsa_initialized = 0;
if (s_wsa_initialized == 0) {
s_wsa_initialized = 1;
WSADATA wsadata;
WSAStartup(MAKEWORD(2,2), &wsadata);
}
#endif
#ifdef SIGPIPE
// NOTE: if not ignore SIGPIPE, write twice when peer close will lead to exit process by SIGPIPE.
signal(SIGPIPE, SIG_IGN);
#endif
loop->status = HLOOP_STATUS_STOP; //初始化状态为 停止状态
loop->pid = hv_getpid(); //获取当前进程号
loop->tid = hv_gettid(); //获取当前线程号
// idles
list_init(&loop->idles); //初始化空闲事件的链表
// timers
heap_init(&loop->timers, timers_compare); //初始化超时事件的堆
// ios
io_array_init(&loop->ios, IO_ARRAY_INIT_SIZE); //初始化IO事件的数组
// readbuf
loop->readbuf.len = HLOOP_READ_BUFSIZE; //分配读缓冲区内存
HV_ALLOC(loop->readbuf.base, loop->readbuf.len);
// iowatcher
iowatcher_init(loop); //初始化IO监控
// custom_events
hmutex_init(&loop->custom_events_mutex);
event_queue_init(&loop->custom_events, CUSTOM_EVENT_QUEUE_INIT_SIZE);
loop->sockpair[0] = loop->sockpair[1] = -1; //创建一对连接的socket
if (Socketpair(AF_INET, SOCK_STREAM, 0, loop->sockpair) != 0) {
hloge("socketpair create failed!");
}
// NOTE: init start_time here, because htimer_add use it.
loop->start_ms = gettimeofday_ms(); //获取当前事件ms
loop->start_hrtime = loop->cur_hrtime = gethrtime_us(); //获取当前事件us
}
从代码上看,hloop_init针对loop做了一系列初始化工作,所以不得不去看一下hloop_t的定义。
typedef struct hloop_s hloop_t;
struct hloop_s {
uint32_t flags; //标记
hloop_status_e status; //状态
uint64_t start_ms; // ms
uint64_t start_hrtime; // us
uint64_t end_hrtime; //结束的时间
uint64_t cur_hrtime; //当前时间
uint64_t loop_cnt; //循环的次数
long pid; //进程号
long tid; //线程号
void* userdata; //用户数据
//private:
// events
uint32_t nactives; //活动的事件的数量
uint32_t npendings; //将要被处理的事件的数量
// pendings: with priority as array.index
hevent_t* pendings[HEVENT_PRIORITY_SIZE]; //将要被处理的事件的数组
// idles //数组的长度为HEVENT_PRIORITY_SIZE(10)个
//这里按事件的优先级,放入到不同链表中,执行时,由高到低执行
struct list_head idles; //空闲事件链表
uint32_t nidles; //空闲事件的数量
// timers
struct heap timers; //超时事件堆
uint32_t ntimers; //超时事件的数量
// ios: with fd as array.index
struct io_array ios; //IO事件数组
uint32_t nios; //IO事件数量
// one loop per thread, so one readbuf per loop is OK.
hbuf_t readbuf; //读缓冲区,这里作者有解释:因为一个线程只有一个事件循环,所有只要分配一个读缓冲区就可以了
void* iowatcher; //io监视器
// custom_events
int sockpair[2]; //本地成对连接的socket
event_queue custom_events; //用户自定义事件队列
hmutex_t custom_events_mutex; //用户事件锁
};
从上面代码里可以看出来,建立本地连接的socket是为了执行用户自定义事件,这里比较有意思,我们后面具体再看如何使用的。
下面继续看sample,hloop_test.c代码
// test idle and priority
for (int i = HEVENT_LOWEST_PRIORITY; i <= HEVENT_HIGHEST_PRIORITY; ++i) {
hidle_t* idle = hidle_add(loop, on_idle, 10);
hevent_set_priority(idle, i);
}
// // test timer timeout
for (int i = 1; i <= 10; ++i) {
htimer_t* timer = htimer_add(loop, on_timer, i*1000, 3);
hevent_set_userdata(timer, (void*)(intptr_t)i);
}
// // test timer period
int minute = time(NULL)%3600/60;
htimer_add_period(loop, cron_hourly, minute+1, -1, -1, -1, -1, INFINITE);
// test nonblock stdin
printf("input 'quit' to quit loop\n");
char buf[64];
hread(loop, 0, buf, sizeof(buf), on_stdin);
// test custom_events
for (int i = 0; i < 10; ++i) {
hevent_t ev;
memset(&ev, 0, sizeof(ev));
ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + i);
ev.cb = on_custom_events;
ev.userdata = (void*)(long)i;
hloop_post_event(loop, &ev);
}
上面是注册各种事件,先看最简单的空闲事件
hidle_t* hidle_add(hloop_t* loop, hidle_cb cb, uint32_t repeat) {
hidle_t* idle;
HV_ALLOC_SIZEOF(idle);
idle->event_type = HEVENT_TYPE_IDLE;
idle->priority = HEVENT_LOWEST_PRIORITY;
idle->repeat = repeat;
list_add(&idle->node, &loop->idles); //将空间事件加入到空闲事件链表中
EVENT_ADD(loop, idle, cb);
loop->nidles++;
return idle;
}
我们可以看到,上面代码将空闲事件加入到了loop的空闲列表中。(其他内容先不深究)
超时事件
htimer_t* htimer_add(hloop_t* loop, htimer_cb cb, uint32_t timeout, uint32_t repeat) {
if (timeout == 0) return NULL;
htimeout_t* timer;
HV_ALLOC_SIZEOF(timer);
timer->event_type = HEVENT_TYPE_TIMEOUT;
timer->priority = HEVENT_HIGHEST_PRIORITY;
timer->repeat = repeat;
timer->timeout = timeout;
hloop_update_time(loop);
timer->next_timeout = hloop_now_hrtime(loop) + timeout*1000;
heap_insert(&loop->timers, &timer->node); //超时事件加入堆
EVENT_ADD(loop, timer, cb);
loop->ntimers++;
return (htimer_t*)timer;
}
同样,将超时事件加入loop的堆中
htimer_t* htimer_add_period(hloop_t* loop, htimer_cb cb,
int8_t minute, int8_t hour, int8_t day,
int8_t week, int8_t month, uint32_t repeat) {
if (minute > 59 || hour > 23 || day > 31 || week > 6 || month > 12) {
return NULL;
}
hperiod_t* timer;
HV_ALLOC_SIZEOF(timer);
timer->event_type = HEVENT_TYPE_PERIOD;
timer->priority = HEVENT_HIGH_PRIORITY;
timer->repeat = repeat;
timer->minute = minute;
timer->hour = hour;
timer->day = day;
timer->month = month;
timer->week = week;
timer->next_timeout = (uint64_t)cron_next_timeout(minute, hour, day, week, month) * 1000000;//将周期时间转为下一次的超时时间
heap_insert(&loop->timers, &timer->node);
EVENT_ADD(loop, timer, cb);
loop->ntimers++;
return (htimer_t*)timer;
}
listen监听事件
hio_t* nlog_listen(hloop_t* loop, int port) {
s_logger.loop = loop;
//这里实例化了一个servet的socket,并把其listen事件加入到IO事件数组中,当有客户端连接时,调用on_accept回调函数
s_logger.listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
list_init(&s_logger.clients);
hmutex_init(&s_mutex);
return s_logger.listenio;
}
键盘事件
hio_t* hread(hloop_t* loop, int fd, void* buf, size_t len, hread_cb read_cb) {
hio_t* io = hio_get(loop, fd);
assert(io != NULL);
io->readbuf.base = (char*)buf;
io->readbuf.len = len;
if (read_cb) {
io->read_cb = read_cb;
}
hio_read(io);
return io;
}
其实就是一个io事件,后面再具体分析
下面是用户自定义事件
for (int i = 0; i < 10; ++i) {
hevent_t ev;
memset(&ev, 0, sizeof(ev));
//定义用户类型,从HEVENT_TYPE_CUSTOM 开始往上定义
ev.event_type = (hevent_type_e)(HEVENT_TYPE_CUSTOM + i);
ev.cb = on_custom_events;
ev.userdata = (void*)(long)i;
//发送用户事件
hloop_post_event(loop, &ev);
}
添加了这么多的事件,libhv是怎样执行的呢?
下面就是真正开始libhv事件循环的核心
int hloop_run(hloop_t* loop) {
loop->pid = hv_getpid();
loop->tid = hv_gettid();
// intern events
//这里,如果定义了本地相互连接的socket,那么就为读socket注册一个读事件,缓冲区为loop初始化时定义的缓冲区
int intern_events = 0;
if (loop->sockpair[0] != -1 && loop->sockpair[1] != -1) {
hread(loop, loop->sockpair[SOCKPAIR_READ_INDEX], loop->readbuf.base, loop->readbuf.len, sockpair_read_cb);
++intern_events;
}
#ifdef DEBUG
htimer_add(loop, hloop_stat_timer_cb, HLOOP_STAT_TIMEOUT, INFINITE);
++intern_events;
#endif
loop->status = HLOOP_STATUS_RUNNING;
while (loop->status != HLOOP_STATUS_STOP) {
//检查loop的状态
if (loop->status == HLOOP_STATUS_PAUSE) {
msleep(HLOOP_PAUSE_TIME); //如果是暂停状态,就休眠一段时间
hloop_update_time(loop);
continue;
}
++loop->loop_cnt;
//如果没有激活的事件,且loop的标志为HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS 就退出事件循环
if (loop->nactives <= intern_events && loop->flags & HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS) {
break;
}
//核心:处理事件
hloop_process_events(loop);
//如果只执行一次的话,就退出事件循环
if (loop->flags & HLOOP_FLAG_RUN_ONCE) {
break;
}
}
loop->status = HLOOP_STATUS_STOP;
loop->end_hrtime = gethrtime_us();
//退出事件循环时,清理释放loop的资源
if (loop->flags & HLOOP_FLAG_AUTO_FREE) {
hloop_cleanup(loop);
HV_FREE(loop);
}
return 0;
}
在这里终于看到了loop的flags的作用
if (loop->nactives <= intern_events && loop->flags & HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS) {
break;
}
if (loop->flags & HLOOP_FLAG_RUN_ONCE) {
break;
}
if (loop->flags & HLOOP_FLAG_AUTO_FREE) {
hloop_cleanup(loop);
HV_FREE(loop);
}
最后,我们看一下libhv是如何处理事件的
// hloop_process_ios -> hloop_process_timers -> hloop_process_idles -> hloop_process_pendings
//作者注释,libhv处理事件的顺序是IO事件->超时事件->空闲事件 => 要执行的事件
static int hloop_process_events(hloop_t* loop) {
// ios -> timers -> idles
int nios, ntimers, nidles;
nios = ntimers = nidles = 0;
// calc blocktime
int32_t blocktime = HLOOP_MAX_BLOCK_TIME;
if (loop->timers.root) {
hloop_update_time(loop);
//从超时事件堆中,找到最近要执行的超时事件
uint64_t next_min_timeout = TIMER_ENTRY(loop->timers.root)->next_timeout;
//计算与当前时间的时间差
int64_t blocktime_us = next_min_timeout - hloop_now_hrtime(loop);
//如果已经超时了,立马执行
if (blocktime_us <= 0) goto process_timers;
//计算可以阻塞的时间(ms)
blocktime = blocktime_us / 1000;
++blocktime;
blocktime = MIN(blocktime, HLOOP_MAX_BLOCK_TIME);
}
//检查是否有IO事件
if (loop->nios) {
nios = hloop_process_ios(loop, blocktime);
}
else {
msleep(blocktime);
}
hloop_update_time(loop);
// wakeup by hloop_stop
if (loop->status == HLOOP_STATUS_STOP) {
return 0;
}
process_timers:
//检查是否有超时事件
if (loop->ntimers) {
ntimers = hloop_process_timers(loop);
}
//查看有没有需要处理的事件,如果没有则查看有没有空间事件
int npendings = loop->npendings;
if (npendings == 0) {
if (loop->nidles) {
nidles= hloop_process_idles(loop);
}
}
//执行需要处理的事件(需要处理的事件其实就是当前需要处理的IO事件,超时事件,空间事件)
int ncbs = hloop_process_pendings(loop);
// printd("blocktime=%d nios=%d/%u ntimers=%d/%u nidles=%d/%u nactives=%d npendings=%d ncbs=%d\n",
// blocktime, nios, loop->nios, ntimers, loop->ntimers, nidles, loop->nidles,
// loop->nactives, npendings, ncbs);
return ncbs;
}
通过上面的源码理解分析,大致明白了libhv的事件循环的过程,总结一下流程就是:
下一次详细分析hloop_process_events函数中的内容