libevent綁定、監聽和讀寫數據

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;
}

上面的代碼我加了註釋,說的很清楚,從建立、綁定、設置屬性一直到監聽整個都調用了,這裏再也不多說。socket

evconnlistener_new函數不僅會調用listen,還會註冊一個監聽回調函數,以下:async

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回調函數聲明以下:tcp

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是基於事件的,它的不少動做都是調用事先註冊好的回調函數來解決的,讀寫數據也不例外。oop

看上面第一節中,監聽回調函數裏面使用了bufferevent_setcb,這個函數會註冊讀寫事件的回調函數,以下:spa

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聲明以下:code

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), 歡迎關注!
相關文章
相關標籤/搜索