custom事件

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服务端和客户端的博客,到时候再详细的说下。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值