Libevent Echo Server Tutorial

Awhile back I had to use libevent for a high-performance application I was writing, and several times before that I had stumbled into it on various projects. I think its bloody awesome and its so awesome I decided to write a tutorial on it. So now your asking: what is libevent?

From the libevent website: The libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, libevent also support callbacks due to signals or regular timeouts. libevent is meant to replace the event loop found in event driven network servers. An application just needs to call event_dispatch() and then add or remove events dynamically without having to change the event loop.

In other words, libevent can be used with systems that support select, epoll etc... to provide high-performance edge based event handling. One of the downsides is that it isn't ready yet for UDP sockets, but it does handle Unix Sockets, TCP, and provides facilities for DNS and HTTP. Libevent can handle numerous connections with minimal overhead and can provide the answer to the 10k problem.

If your familiar with C and network programming... game on!

Compiling and Installing libevent

Download, untar the source code and enter the directory:

  1. wget github.com/downloads/libevent/libevent/libevent-2.0.20-stable.tar.gz
  2. tar -xzvf libevent-2.0.20-stable.tar.gz
  3. cd libevent-2.0.20-stable

Configure, make and install the libraries:

  1. ./configure
  2. make
  3. sudo make install

Libevent Echo Server Tutorial

One of the areas that I think Libevent is lacking in is useful beginner orientated tutorials that allow aspiring programmers to pick up this library. Its documentation is pretty good and well-defined, but I felt that it needed some clarification so hopefully this document can help other programmers out.

Libevent Basics

Libevent's most basic principle is that is runs off of events and these events are used by the event base. The event base uses these events through either/or listener and an event dispatcher. As a result, a good place for you - the programmer to start is knowing whether there will be read events and/or write events. Then you define your event callbacks and finally implement them. Its not really that complex - I promise.

Libevent Echo Unix Socket Server Example

One of the easiest ways to learn something is to create it and so you are going to create a unix socket echo server that implements Libevent. Open up your favorite code editor and build a skeleton C program - main and the usual suspect includes.

Add the following includes and define:

  1. #include <event2/listener.h>
  2. #include <event2/bufferevent.h>
  3. #include <event2/buffer.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <sys/un.h>
  7. #include <string.h>
  8. #include <stdlib.h>
  9. #include <stdio.h>
  10. #include <errno.h>
  11. #define UNIX_SOCK_PATH "/tmp/mysocket"

Now lets make your main look like this:

  1. int main(int argc, char **argv)
  2. {
  3.     struct event_base *base;
  4.     struct evconnlistener *listener;
  5.     struct sockaddr_un sin;
  6.  
  7.     /* Create new event base */
  8.     base = event_base_new();
  9.     if (!base) {
  10.         puts("Couldn't open event base");
  11.         return 1;
  12.     }
  13.  
  14.     /* Clear the sockaddr before using it, in case there are extra
  15.      * platform-specific fields that can mess us up. */
  16.     memset(&sin, 0, sizeof(sin));
  17.     sin.sun_family = AF_LOCAL;
  18.     strcpy(sin.sun_path, UNIX_SOCK_PATH);
  19.  
  20.     /* Create a new listener */
  21.     listener = evconnlistener_new_bind(base, accept_conn_cb, NULL,
  22.                                        LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1,
  23.                                        (struct sockaddr *) &sin, sizeof(sin));
  24.     if (!listener) {
  25.         perror("Couldn't create listener");
  26.         return 1;
  27.     }
  28.     evconnlistener_set_error_cb(listener, accept_error_cb);
  29.  
  30.     /* Lets rock */
  31.     event_base_dispatch(base);
  32.     return 0;
  33. }

UHOH! main() is now referencing two functions that don't exist: accept_error_cb() and accept_conn_cb(). These two functions allow libevent to handle when a new connection is created and what to do when the listener encounters an error. Lets add these two functions - they are pretty explanatory.

  1. static void
  2. accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx)
  3. {
  4.     /* We got a new connection! Set up a bufferevent for it. */
  5.     struct event_base *base = evconnlistener_get_base(listener);
  6.     struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
  7.  
  8.     bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);
  9.  
  10.     bufferevent_enable(bev, EV_READ | EV_WRITE);
  11. }
  12.  
  13. static void accept_error_cb(struct evconnlistener *listener, void *ctx)
  14. {
  15.     struct event_base *base = evconnlistener_get_base(listener);
  16.     int err = EVUTIL_SOCKET_ERROR();
  17.     fprintf(stderr, "Got an error %d (%s) on the listener. "
  18.             "Shutting down.\n", err, evutil_socket_error_to_string(err));
  19.  
  20.     event_base_loopexit(base, NULL);
  21. }

Again the accept_connection callback references two unknown functions as parameters in the bufferevent_setcb() function. Lets talk about this function a bit with an illustration from the Libevent documentation:

  1. void bufferevent_getcb(struct bufferevent *bufev,
  2.     bufferevent_data_cb *readcb_ptr,
  3.     bufferevent_data_cb *writecb_ptr,
  4.     bufferevent_event_cb *eventcb_ptr,
  5.     void **cbarg_ptr);

As you can see, it requires a bufferevent, a callback for read events, a callback for write events, a callback for an event and a parameter for callback args. I've mentioned callbacks a few times so I'm sure you either know what it is, or have googled it by now ;) From the documentation a Bufferevent is:

  1. A "bufferevent" consists of an underlying transport (like a socket), a read buffer, and a write buffer. Instead of regular events, which give callbacks when the underlying transport is ready to be read or written, a bufferevent invokes its user-supplied callbacks when it has read or written enough data.
  2. Every bufferevent has an input buffer and an output buffer. These are of type "struct evbuffer". When you have data to write on a bufferevent, you add it to the output buffer; when a bufferevent has data for you to read, you drain it from the input buffer.

Now you know what buffer events are and their general usage. Add the following two functions:

  1. static void echo_read_cb(struct bufferevent *bev, void *ctx)
  2. {
  3.     /* This callback is invoked when there is data to read on bev. */
  4.     struct evbuffer *input = bufferevent_get_input(bev);
  5.     struct evbuffer *output = bufferevent_get_output(bev);
  6.  
  7.     size_t len = evbuffer_get_length(input);
  8.     char *data;
  9.     data = malloc(len);
  10.     evbuffer_copyout(input, data, len);
  11.  
  12.     printf("we got some data: %s\n", data);
  13.  
  14.     /* Copy all the data from the input buffer to the output buffer. */
  15.     evbuffer_add_buffer(output, input);
  16.     free(data);
  17. }
  18.  
  19. static void echo_event_cb(struct bufferevent *bev, short events, void *ctx)
  20. {
  21.     if (events & BEV_EVENT_ERROR)
  22.         perror("Error from bufferevent");
  23.     if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
  24.         bufferevent_free(bev);
  25.     }
  26. }

The above functions should be fairly self explanatory, but a possible stumbling block for new programmers is how to get data out of the Libevent read buffer in this example - did you see this: evbuffer_copyout(input, data, len); I won't explain it in detail, but the method calls can be found at http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html

Essentially you should know that evbuffer_copyout copies data AND DOESN'T remove the data. The function evbuffer_remove_buffer() copies AND removes data from the buffer.

Now that you have all of the pieces of the program lets compile it and test it

  1. gcc -Wall -levent yourprogram.c -o server
  2. ./server

Connect to your program using netcat in a second terminal- you should see output echo'd back to you & data on the serverside ;)

  1. echo -n 'test' | nc -U /tmp/mysocket1

Further reading

  • http://www.libevent.org
  • http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html

Blog tags: 

Comments

Confused

I cant use netcat for some reason and after my 1st usage of the program after i start server with ./server i get "Couldn't create listener: Address already in use" ...

Memory Leak

Hi! You have a memory leak in: static void echo_read_cb(struct bufferevent *bev, void *ctx)
data = malloc(len); is never freed...

undefined reference

Hi, Thanks for the tutorial. When I run this sample in terminal, I get the errors undefined references to buffer event_get_input, etc.. I assume its because the respective files haven't been properly linked, if thats the case, how do I link the .h files from the libevent library?

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.