1. 绑定和监听

在上一篇文章中,以epoll为例说到了事件机制,会按顺序调用init和dispatch这两个回调函数,但是,我们回忆一下网络编程的过程,首先是需要创建socket、绑定socket、监听socket的,但目前为止还并没有涉及到,再去看源代码,会发现里面有listener.c,这个文件里面就会去做创建socket的过程。

看evconnlistener_new_bind函数,如下:

struct evconnlistener *
evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb,
    void *ptr, unsigned flags, int backlog, const struct sockaddr *sa,
    int socklen)
{
    struct evconnlistener *listener;
    evutil_socket_t fd;
    int on = 1;
    int family = sa ? sa->sa_family : AF_UNSPEC;
    int socktype = SOCK_STREAM | EVUTIL_SOCK_NONBLOCK;

    if (backlog == 0)
        return NULL;

    if (flags & LEV_OPT_CLOSE_ON_EXEC)
        socktype |= EVUTIL_SOCK_CLOEXEC;

    //调用socket函数
    fd = evutil_socket_(family, socktype, 0);
    if (fd == -1)
        return NULL;

    //设置存货检测
    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0)
        goto err;

    //设置地址重用
    if (flags & LEV_OPT_REUSEABLE) {
        if (evutil_make_listen_socket_reuseable(fd) < 0)
            goto err;
    }

    //设置端口重用
    if (flags & LEV_OPT_REUSEABLE_PORT) {
        if (evutil_make_listen_socket_reuseable_port(fd) < 0)
            goto err;
    }

    //设置延迟接收
    if (flags & LEV_OPT_DEFERRED_ACCEPT) {
        if (evutil_make_tcp_listen_socket_deferred(fd) < 0)
            goto err;
    }

    //调用bind函数
    if (sa) {
        if (bind(fd, sa, socklen)<0)
            goto err;
    }

    //evconnlistener_new函数里面会调用listen函数
    listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);
    if (!listener)
        goto err;

    return listener;
err:
    evutil_closesocket(fd);
    return NULL;
}

上面的代码我加了注释,说的很清楚,从创建、绑定、设置属性一直到监听整个都调用了,这里不再多说。

evconnlistener_new函数不只会调用listen,还会注册一个监听回调函数,如下:

struct evconnlistener *
evconnlistener_new(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    evutil_socket_t fd)
{
    struct evconnlistener_event *lev;

#ifdef _WIN32
    if (base && event_base_get_iocp_(base)) {
        const struct win32_extension_fns *ext =
            event_get_win32_extension_fns_();
        if (ext->AcceptEx && ext->GetAcceptExSockaddrs)
            return evconnlistener_new_async(base, cb, ptr, flags,
                backlog, fd);
    }
#endif

    if (backlog > 0) {
        if (listen(fd, backlog) < 0)
            return NULL;
    } else if (backlog < 0) {
        if (listen(fd, 128) < 0)
            return NULL;
    }

    lev = mm_calloc(1, sizeof(struct evconnlistener_event));
    if (!lev)
        return NULL;

    lev->base.ops = &evconnlistener_event_ops;
    //注册回调函数,当监听到有新的连接时,就会调用该函数
    lev->base.cb = cb;
    lev->base.user_data = ptr;
    lev->base.flags = flags;
    lev->base.refcnt = 1;

    lev->base.accept4_flags = 0;
    if (!(flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING))
        lev->base.accept4_flags |= EVUTIL_SOCK_NONBLOCK;
    if (flags & LEV_OPT_CLOSE_ON_EXEC)
        lev->base.accept4_flags |= EVUTIL_SOCK_CLOEXEC;

    if (flags & LEV_OPT_THREADSAFE) {
        EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE);
    }

    event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,
        listener_read_cb, lev);

    if (!(flags & LEV_OPT_DISABLED))
        evconnlistener_enable(&lev->base);

    return &lev->base;
}

evconnlistener_cb回调函数声明如下:

typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);

该回调函数需要我们自己实现,看sample目录中hello-world.c中实现的该函数代码如下:

static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
    struct sockaddr *sa, int socklen, void *user_data)
{
    struct event_base *base = user_data;
    struct bufferevent *bev;

    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev) {
        fprintf(stderr, "Error constructing bufferevent!");
        event_base_loopbreak(base);
        return;
    }
    bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
    bufferevent_enable(bev, EV_WRITE);
    bufferevent_disable(bev, EV_READ);

    bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

2. 读写数据

libevent是基于事件的,它的很多动作都是调用事先注册好的回调函数来解决的,读写数据也不例外。

看上面第一节中,监听回调函数里面使用了bufferevent_setcb,这个函数会注册读写事件的回调函数,如下:

void
bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg)
{
    BEV_LOCK(bufev);

    bufev->readcb = readcb;
    bufev->writecb = writecb;
    bufev->errorcb = eventcb;

    bufev->cbarg = cbarg;
    BEV_UNLOCK(bufev);
}

当有可读事件时会调用readcb函数,当有可写事件时调用writecb函数,发生错误时调用eventcb函数。
bufferevent_data_cb声明如下:

typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);

还是看看sample目录中hello-world.c中对该函数的定义,如下:

static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
    struct evbuffer *output = bufferevent_get_output(bev);
    if (evbuffer_get_length(output) == 0) {
        printf("flushed answer\n");
        bufferevent_free(bev);
    }
}

我们自己使用时可以参照sample目录中的例子,这里就不再细说了。

文章同步发表在cpp加油站(ID:xy13640954449), 欢迎关注!

cpp加油站
31 声望17 粉丝