今天主要分析下IO事件中的读事件的过程。
如有理解错误,欢迎批评指正。
int main()
{
hloop_t* loop = hloop_new(0);
// 客户端fd
int connFd = ConnectTimeout("127.0.0.1",1234);
//io事件
hio_t * hio = hio_get(loop,connFd);
//设置read 回调函数
hio_setcb_read(hio,on_rev);
//注册监听读事件
hio_read(hio);
// 设置connect 回调函数
hio_setcb_connect(hio,connt);
//注册连接事件
hio_connect(hio);
//发送消息
char a[20] ="hello libhv\r\n";
hio_write(hio,a,10);
hloop_run(loop);
hloop_free(&loop);
return 0;
}
探究目标:on_rev是在何时调用的,入参是怎么来的?
首先看一下hio_read(),位置:/libhv/event/nio.c
int hio_read (hio_t* io) {
if (io->closed) {
hloge("hio_read called but fd[%d] already closed!", io->fd);
return -1;
}
return hio_add(io, hio_handle_events, HV_READ);
}
int hio_add(hio_t* io, hio_cb cb, int events) {
printf("hio_add fd=%d io->events=%d events=%d\n", io->fd, io->events, events);
#ifdef OS_WIN
// Windows iowatcher not work on stdio
if (io->fd < 3) return -1;
#endif
hloop_t* loop = io->loop;
//如果事件没被激活,就添加事件
if (!io->active) {
EVENT_ADD(loop, io, cb);
loop->nios++;
}
//激活io事件
if (!io->ready) {
hio_ready(io);
}
//添加事件的回调函数
if (cb) {
io->cb = (hevent_cb)cb;
}
//events 与io->events如果不相同的话,就加入新的监听事件events
//比如原来是write事件,现在是read事件,那么(io->events & events)
if (!(io->events & events)) {
iowatcher_add_event(loop, io->fd, events);
io->events |= events;
}
return 0;
}
注意看一下,这里的回调函数是hio_handle_events,并不是我们定义的回调函数on_rev
,看一下,这个回调函数做了什么
static void hio_handle_events(hio_t* io) {
if ((io->events & HV_READ) && (io->revents & HV_READ)) {
if (io->accept) {
//处理accpet事件
nio_accept(io);
}
else {
//处理读事件
nio_read(io);
}
}
if ((io->events & HV_WRITE) && (io->revents & HV_WRITE)) {
// NOTE: del HV_WRITE, if write_queue empty
hrecursive_mutex_lock(&io->write_mutex);
if (write_queue_empty(&io->write_queue)) {
iowatcher_del_event(io->loop, io->fd, HV_WRITE);
io->events &= ~HV_WRITE;
}
hrecursive_mutex_unlock(&io->write_mutex);
if (io->connect) {
// NOTE: connect just do once
// ONESHOT
io->connect = 0;
nio_connect(io);
}
else {
//处理写事件
nio_write(io);
}
}
io->revents = 0;
}
这个函数主要处理了一些IO事件,那么这个回调函数在哪里被回调的呢?
我们再回到事件循环里,事件被执行的那个部分代码
static int hloop_process_pendings(hloop_t* loop) {
if (loop->npendings == 0) return 0;
hevent_t* cur = NULL;
hevent_t* next = NULL;
int ncbs = 0;
// NOTE: invoke event callback from high to low sorted by priority.
for (int i = HEVENT_PRIORITY_SIZE-1; i >= 0; --i) {
cur = loop->pendings[i];
while (cur) {
next = cur->pending_next;
if (cur->pending) {
if (cur->active && cur->cb) {
cur->cb(cur); //===>执行回调
++ncbs;
}
cur->pending = 0;
// NOTE: Now we can safely delete event marked as destroy.
if (cur->destroy) {
EVENT_DEL(cur);
}
}
cur = next;
}
loop->pendings[i] = NULL;
}
loop->npendings = 0;
return ncbs;
}
所以hio_handle_events函数是在收到IO事件时,被执行,那么如果是读事件的话,就会执行 nio_read(io);
static void nio_read(hio_t* io) {
printf("nio_read fd=%d\n", io->fd);
void* buf;
int len, nread;
read:
//如果io事件没有分配缓冲区,就使用事件循环里的缓冲区
if (io->readbuf.base == NULL || io->readbuf.len == 0) {
hio_set_readbuf(io, io->loop->readbuf.base, io->loop->readbuf.len);
}
buf = io->readbuf.base;
len = io->readbuf.len;
//读取数据,返回实际读取到的数据
nread = __nio_read(io, buf, len);
//printd("read retval=%d\n", nread);
if (nread < 0) {
if (socket_errno() == EAGAIN) {
//goto read_done;
return;
}
else {
io->error = socket_errno();
// perror("read");
goto read_error;
}
}
//连接断开了,关闭连接
if (nread == 0) {
goto disconnect;
}
//调用用户注册的回调函数
__read_cb(io, buf, nread);
if (nread == len) {
goto read;
}
return;
read_error:
disconnect:
hio_close(io);
}
到这里,我们终于揭开了我们探究的目标:on_rev是在何时调用的,入参是怎么来的?
注意:这里我们可以看到,如果我们没有设置io的读缓冲区的话,那么默认是使用的事件循环的读缓冲区,所以,在on_rev回调中,如果解析出数据不完整的话,一定要copy到其他缓冲区中,否则下次收到读事件后,会被覆盖掉。
具体过程如下: