libhv提供了一个接口,可以由用户自己添加事件,接口为:
// NOTE: hloop_post_event is thread-safe
HV_EXPORT void hloop_post_event(hloop_t* loop, hevent_t* ev);
首先看下该接口的实现
void hloop_post_event(hloop_t* loop, hevent_t* ev) {
char buf = '1';
if (loop->sockpair[0] == -1 || loop->sockpair[1] == -1) {
hlogw("socketpair not created!");
return;
}
if (ev->loop == NULL) {
ev->loop = loop;
}
if (ev->event_type == 0) {
ev->event_type = HEVENT_TYPE_CUSTOM;
}
hmutex_lock(&loop->custom_events_mutex);
if (ev->event_id == 0) {
ev->event_id = ++loop->event_counter;
}
hwrite(loop, loop->sockpair[SOCKPAIR_WRITE_INDEX], &buf, 1, NULL);
event_queue_push_back(&loop->custom_events, ev);
hmutex_unlock(&loop->custom_events_mutex);
}
第一个关键点在sockpair,这是一对用来通信的描述符,一个表示读,一个表示写,类似于管道,但是sockpair并不是管道。与该功能相关的初始化在hloop_init中,
// 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;
if (Socketpair(AF_INET, SOCK_STREAM, 0, loop->sockpair) != 0) {
hloge("socketpair create failed!");
}
Socketpair比较长。。。
int Socketpair(int family, int type, int protocol, int sv[2]) {
#ifdef OS_UNIX
if (family == AF_UNIX) {
return socketpair(family, type, protocol, sv);
}
#endif
if (family != AF_INET || type != SOCK_STREAM) {
return -1;
}
int listenfd, connfd, acceptfd;
listenfd = connfd = acceptfd = INVALID_SOCKET;
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
memset(&localaddr, 0, addrlen);
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //回环地址
localaddr.sin_port = 0; //临时端口
// listener
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket");
goto error;
}
if (bind(listenfd, (struct sockaddr*)&localaddr, addrlen) < 0) {
perror("bind");
goto error;
}
//监听
if (listen(listenfd, 1) < 0) {
perror("listen");
goto error;
}
if (getsockname(listenfd, (struct sockaddr*)&localaddr, &addrlen) < 0) {
perror("getsockname");
goto error;
}
// connector
connfd = socket(AF_INET, SOCK_STREAM, 0);
if (connfd < 0) {
perror("socket");
goto error;
}
//连接
if (connect(connfd, (struct sockaddr*)&localaddr, addrlen) < 0) {
perror("connect");
goto error;
}
// acceptor
acceptfd = accept(listenfd, (struct sockaddr*)&localaddr, &addrlen);
if (acceptfd < 0) {
perror("accept");
goto error;
}
//关闭监听套接字
closesocket(listenfd);
sv[0] = connfd;
sv[1] = acceptfd;
return 0;
error:
if (listenfd != INVALID_SOCKET) {
closesocket(listenfd);
}
if (connfd != INVALID_SOCKET) {
closesocket(connfd);
}
if (acceptfd != INVALID_SOCKET) {
closesocket(acceptfd);
}
return -1;
}
建立了一个tcp连接,然后保留了两个通信的描述符。所以实际上sockpair是建立的tcp连接进行通信的。sockpair的读端是在hloop_run接口中加入到loop的
// intern events
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;
}
通过调用hread,将读端加入到loop
hio_t* hread(hloop_t* loop, int fd, void* buf, size_t len, hread_cb read_cb) {
hio_t* io = hio_get(loop, fd); //该接口会根据fd获取相对应的io,如果不存在,创建一个
assert(io != NULL);
io->readbuf.base = (char*)buf; //设置读buf
io->readbuf.len = len;
if (read_cb) {
io->read_cb = read_cb; //设置读回调
}
hio_read(io); //使能读
return io;
}
关于上面的hio_get的源码实现这里就不分析了,只需要知道在这里调用hio_get后就已经将读端加入了loop的管理,最后hio_read使能读,在有读事件发生时就会调用注册的sockpair_read_cb回调函数。什么时候读事件发生呢?再看最上面的hloop_post_event接口,在hloop_post_event接口的下面,使用hwrite写一个字节“1”,并将要 处理的事件加入到custom事件队列中。
hwrite(loop, loop->sockpair[SOCKPAIR_WRITE_INDEX], &buf, 1, NULL);
event_queue_push_back(&loop->custom_events, ev);
可读事件触发后,由loop主循环调用注册的回调函数sockpair_read_cb,按照先进先出的原则,处理队列中用户注册的事件
static void sockpair_read_cb(hio_t* io, void* buf, int readbytes) {
hloop_t* loop = io->loop;
hevent_t* pev = NULL;
hevent_t ev;
for (int i = 0; i < readbytes; ++i) {
hmutex_lock(&loop->custom_events_mutex);
if (event_queue_empty(&loop->custom_events)) { //判断custom事件队列是否为空
goto unlock;
}
pev = event_queue_front(&loop->custom_events); //获取队列首元素
if (pev == NULL) {
goto unlock;
}
ev = *pev;
event_queue_pop_front(&loop->custom_events); //出队列
// NOTE: unlock before cb, avoid deadlock if hloop_post_event called in cb.
hmutex_unlock(&loop->custom_events_mutex);
if (ev.cb) {
ev.cb(&ev); //执行用户注册的回调函数
}
}
return;
unlock:
hmutex_unlock(&loop->custom_events_mutex);
}
看一个例子,在hloop_test.c中,有这么一段测试代码:
// 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);
}
void on_custom_events(hevent_t* ev) {
printf("on_custom_events event_type=%d userdata=%ld\n", (int)ev->event_type, (long)ev->userdata);
}
用户自己注册了10个事件,由loop执行,比较简单。
实际上除了上面由用户自己添加事件的功能,hloop_post_event还有两个地方使用
int hloop_wakeup(hloop_t* loop) {
hevent_t ev;
memset(&ev, 0, sizeof(ev));
hloop_post_event(loop, &ev);
return 0;
}
static void hloop_stop_event_cb(hevent_t* ev) {
ev->loop->status = HLOOP_STATUS_STOP;
}
int hloop_stop(hloop_t* loop) {
loop->status = HLOOP_STATUS_STOP;
if (hv_gettid() != loop->tid) {
hevent_t ev;
memset(&ev, 0, sizeof(ev));
ev.priority = HEVENT_HIGHEST_PRIORITY;
ev.cb = hloop_stop_event_cb;
hloop_post_event(loop, &ev);
}
return 0;
}
hloop_wakeup很简单,就是单纯的唤醒loop,因为正常情况下,loop会阻塞等待事件的发生,可以通过hloop_wakeup触发sockpair的读事件。在hloop_stop接口中,当判断调用线程和loop不是一个线程时,也是通过hloop_post_event唤醒loop线程。所以在两个接口中,hloop_post_event是作为唤醒功能使用的。
因为custom事件功能的实现牵扯到io事件,之后应该会更关于libhv实现tcp服务端和客户端的博客,到时候再详细的说下。