libevent binding, listening, reading and writing data

Keywords: C socket network Programming

1. Binding and listening

In the previous article, epoll was used as an example to talk about the event mechanism, which calls init and dispatch callback functions in sequence. However, we recall the process of network programming. First, you need to create sockets, bind sockets, listen for sockets, but so far it has not been involved. Looking at the source code, you will seeNow there is listener.c, and this file will do the process of creating a socket.

Look at the evconnlistener_new_bind function as follows:

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;

    //Call socket function
    fd = evutil_socket_(family, socktype, 0);
    if (fd == -1)
        return NULL;

    //Set up inventory detection
    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0)
        goto err;

    //Set Address Reuse
    if (flags & LEV_OPT_REUSEABLE) {
        if (evutil_make_listen_socket_reuseable(fd) < 0)
            goto err;
    }

    //Set Port Reuse
    if (flags & LEV_OPT_REUSEABLE_PORT) {
        if (evutil_make_listen_socket_reuseable_port(fd) < 0)
            goto err;
    }

    //Set Delay Receive
    if (flags & LEV_OPT_DEFERRED_ACCEPT) {
        if (evutil_make_tcp_listen_socket_deferred(fd) < 0)
            goto err;
    }

    //Call the bind function
    if (sa) {
        if (bind(fd, sa, socklen)<0)
            goto err;
    }

    //The listen function is called inside the evconnlistener_new function
    listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);
    if (!listener)
        goto err;

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

I've commented on the code above and it's clear that everything from creating, binding, setting properties to listening is called. I won't say much more here.

The evconnlistener_new function not only calls listen, but also registers a listen callback function as follows:

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;
    //Register the callback function, which is called when a new connection is heard
    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;
}

The evconnlistener_cb callback function is declared as follows:

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

This callback function needs to be implemented by ourselves. Look at the code for the function implemented in hello-world.c in the sample directory as follows:

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. Read and write data

libevent is event-based, and many of its actions are resolved by calling pre-registered callback functions, including reading and writing data.

Looking at the first section above, the listen callback function uses bufferevent_setcb, which registers the callback functions for read and write events as follows:

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

The readcb function is called when there are readable events, the writecb function when there are writable events, and the eventcb function when errors occur.
The bufferevent_data_cb is declared as follows:

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

Or look at the definition of this function in hello-world.c. in the sample directory, as follows:

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

We can refer to the example in the sample directory for our own use, so let's not go into details here.

The article was published synchronously at the cpp gas station (ID:xy13640954449). Welcome!

Posted by bengaltgrs on Thu, 15 Aug 2019 20:28:11 -0700