First give official documents: http://libevent.org, Home has a programming with libevent, inside is a section of the introduction libevent, but feel the information is too large, but also the English-. -(Of course, if you want to use libevent, to see if it is necessary), there is also a reference, roughly the version of the libevent use Doxgen generated documents, used to check function prototypes and basic usage of what.
The following assumes that you have learned basic socket programming (SOCKET,BIND,LISTEN,ACCEPT,CONNECT,RECV,SEND,CLOSE) and have a basic understanding of asynchronous/callback.
Basic socket programming is blocking/synchronizing, each operation is returned unless it is completed or an error occurs, so that for each request to be processed using a thread or a separate process, the system resources cannot support a large number of requests (so-called c10k problem?), For example, Memory: By default, each thread needs to occupy 2~8m of stack space. POSIX defines a select system call that can be used asynchronously, but because it uses polling to determine whether an FD becomes active, it is inefficient [O (n)], and the number of connections is not enough. Therefore, each system proposes a system call based on asynchronous/callback, such as the IOCP of Linux Epoll,bsd kqueue,windows. Because of the support at the kernel level, it is possible to find active FD with the efficiency of O (1). Basically, Libevent is the encapsulation of these high-efficiency IO, which provides a unified API and simplifies development.
Libevent is probably the case:
By default it is single-threaded (can be configured as multiple threads, if necessary), each thread has only one event_base, and corresponds to a struct event_base struct (and the event manager attached to it). Used to schedule a series of event hosting to it, can be compared with the operating system process management analogy, of course, to be simpler. When an event occurs, Event_base will call the function that is bound to the event at the appropriate time (not necessarily immediately) (passing in some predefined arguments, as well as a parameter specified in the binding) until the function finishes executing and returns schedule other events.
Create a Event_base
struct Event_base *base = event_base_new ();
ASSERT (Base! = NULL);
There is a loop inside the event_base that loops through the system calls such as Epoll/kqueue until there is a/some event that occurs and then handles these events. Of course, these events are bound to be on this event_base. Each event corresponds to a struct event, which can be used to listen for a FD or POSIX semaphore (only FD is spoken here, others see manual bar). The struct event uses event_new to create and bind, using Event_add to enable:
Create and bind an event
struct event *listen_event;
Parameters: Event_base, listen for FD, event type and attribute, binding callback function, parameter to callback function
Listen_event = Event_new (base, Listener, ev_read| Ev_persist, Callback_func, (void*) base);
Parameters: Event, Time-out (struct timeval * type,null means no timeout setting)
Event_add (listen_event, NULL);
Note: The events and attributes supported by Libevent include (using the Bitfield implementation, so use | To make them fit)
(a) Ev_timeout: timeout
(b) Ev_read: As long as there is data in the network buffer, the callback function is triggered
(c) Ev_write: The callback function is triggered whenever the data that is plugged into the network buffer is written out
(d) ev_signal:posix signal volume, refer to Manual bar
(e) Ev_persist: If this attribute is not specified, the event will be deleted after the callback function is triggered
(f) Ev_et:edge-trigger edge trigger, reference Epoll_et
You then need to start the event_base loop so that you can start processing the events that occur. The loop starts with Event_base_dispatch, and the loop continues until there are no more events to follow, or the Event_loopbreak ()/event_loopexit () function is encountered.
Start the event loop
Event_base_dispatch (base);
Next, focus on the callback function bound to the event Callback_func: It is passed to a socket FD, an event type and attribute Bit_field, and the last parameter passed to Event_new (go to the above lines to review the event _base to come in, in fact, more is to allocate a structure, the relevant data are put in, and then lost to event_new, here can be achieved. The prototype is:
typedef VOID (* event_callback_fn) (evutil_socket_t sockfd, short event_type, void *arg)
For a server, the above process is probably a combination of the following:
1. Listener = socket (), bind (), listen (), set nonblocking (the POSIX system can use the Fcntl settings, Windows does not need to be set up, actually libevent provides a unified wrapper Evutil_ make_socket_nonblocking)
2. Create a Event_base
3. Create an event, host the socket to Event_base, specify the type of event to listen on, and bind the corresponding callback function (and the parameters needed to it). For listener sockets, you only need to listen for ev_read| Ev_persist
4. Enable the event
5. Enter the event loop
---------------
6. (async) When a client initiates a request, the callback function is called for processing.
Question: Why not call accept immediately after listen, get the client connection and then throw it to event_base? Think about this question first.
What does the callback function do? Of course it is the request to handle the client. First, accept, get a sockfd that can communicate with the client, and then ... Call Recv/send? Wrong! Wrong! If the recv/send is called directly, the thread will be blocked in this place, if the client is very sinister (such as never send messages, or bad network, always drop packets), libevent can only wait for it, unable to handle other requests-- Therefore, a new event should be created to host this sockfd.
In the old version of the implementation of libevent, compared wordy [if you do not want to learn more, see the next part].
For situations where the server wants to get data from the client first, the general process is this:
1. Set this SOCKFD to nonblocking
2. Create 2 of the event:
Event_read, tie the SOCKFD ev_read|. Ev_persist, set callback functions and parameters (the struct mentioned later)
Event_write, tie the SOCKFD ev_write|. Ev_persist, set callback functions and parameters (the struct mentioned later)
3. Enable the Event_read event
------
4. (asynchronously) waits for the Event_read event to occur, invoking the corresponding callback function. Here is the trouble: the callback function with recv read data, can not be sent directly to SOCKFD with send-because SOCKFD is nonblocking, and throw it, can not guarantee the right (for what? )。 So you need a cache of your own to hold the data in the read (create a struct after accept, as ARG for the 2nd step callback), enable the Event_write event at the appropriate time (such as encountering a newline character) "Event_add (Event_ Write, NULL) ", Waiting for the Ev_write event to be triggered
------
5. (async) When the callback function of the Event_write event is called, write the data to SOCKFD, and then delete the Event_write event "Event_del (Event_write)", waiting for the next execution of the Event_read event.
The above steps are obscure, the specific code can refer to the official document "Example:a low-level ROT13 server with Libevent"
Because of the need to manage buffers themselves, and the process is obscure and incompatible with Windows IOCP, Libevent2 began by providing bufferevent, a more elegant and easy-to-use API. The struct bufferevent built two event (read/write) and corresponding buffer "struct Evbuffer *input, *output", The corresponding function is provided to manipulate the buffer (or to manipulate the bufferevent directly). The READ_CB function is called whenever the data is read into input, and WRITE_CB is called whenever output is finished, and ERROR_CB is called when there is an error in the network IO operation (connection interrupt, timeout, other error). The steps in the previous section were simplified to:
1. Set SOCKFD to nonblocking
2. Use bufferevent_socket_new to create a struct bufferevent *bev, associate the SOCKFD, and host to Event_base
3. Use BUFFEREVENT_SETCB (Bev, READ_CB, WRITE_CB, ERROR_CB, (void *) arg) to ev_read/ev_write the corresponding function
4. Using Bufferevent_enable (Bev, ev_read| ev_write| Ev_persist) to enable the Read/write event
------
5. (async)
Reads data from input in READ_CB, and then plugs into output (automatically written to SOCKFD)
Inside the WRITE_CB (what do you need to do?) READ_CB is sufficient for an echo server)
Handle the errors encountered in ERROR_CB
*. You can use Bufferevent_set_timeouts (Bev, struct timeval *read, struct timeval *write) to set a read-write timeout and handle timeouts in ERROR_CB.
*. The prototypes of READ_CB and WRITE_CB are
void Read_or_write_callback (struct bufferevent *bev, void *arg)
The prototype of ERROR_CB is
void Error_cb (struct bufferevent *bev, short error, void *arg)//This is the standard callback function prototype for event
Can extract event_base, SOCKFD, input/output and other related data from Bev with Libevent API, details rtfm~
The code then simplifies to READ_CB and ERROR_CB functions that require only a few lines:
void Read_cb (struct bufferevent *bev, void *arg) {
Char line[256];
int n;
evutil_socket_t FD = BUFFEREVENT_GETFD (BEV);
while (n = bufferevent_read (Bev, line, up), n > 0)
Bufferevent_write (Bev, line, N);
}
void Error_cb (struct bufferevent *bev, short event, void *arg) {
Bufferevent_free (BEV);
}
So an echo server that supports large amounts of concurrency is formed! The following is a non-annotated echo server source code, 110 lines, more than a few times, it can be fully understood! For more complex examples, see "example:a simpler ROT13 server with Libevent" in the official documentation
#Include <stdio.h>
#Include <stdlib.h>
#Include <errno.h>
#Include <assert.h>
#Include <event2/event.h>
#Include <event2/bufferevent.h>
#Define Listen_port 9999
#Define Listen_backlog 32
void Do_accept (evutil_socket_t listener,Short event,void *arg);
void Read_cb (struct Bufferevent *bev,void *arg);
void Error_cb (struct Bufferevent *bev,Short event,void *arg);
void Write_cb (struct Bufferevent *bev,void *arg);
int main (int argc,Char *argv[])
{
int ret;
EVUTIL_SOCKET_T Listener;
Listener = socket (af_inet, sock_stream, 0);
ASSERT (Listener > 0);
Evutil_make_listen_socket_reuseable (listener);
struct sockaddr_in sin;
sin.sin_family = af_inet;
sin.sin_addr.s_addr = 0;
Sin.sin_port = htons (Listen_port);
if (Bind (Listener, (struct sockaddr *) &sin,sizeof (SIN)) < 0) {
Perror ("bind");
return 1;
}
if (Listen (listener, Listen_backlog) < 0) {
Perror ("Listen");
return 1;
}
printf ("listening...\n");
Evutil_make_socket_nonblocking (listener);
struct Event_base *base = event_base_new ();
ASSERT (Base! =NULL);
struct event *listen_event;
Listen_event = Event_new (base, Listener, ev_read| Ev_persist, Do_accept, (void*) (base);
Event_add (Listen_event,NULL);
Event_base_dispatch (base);
printf ("The End.");
return 0;
}
void Do_accept (evutil_socket_t listener,Short event,void *arg)
{
struct Event_base *base = (struct event_base *) arg;
evutil_socket_t FD;
struct sockaddr_in sin;
Socklen_t Slen;
FD = Accept (Listener, (struct sockaddr *) &sin, &slen);
if (FD < 0) {
Perror ("accept");
Return
}
if (fd > Fd_setsize) {
Perror ("FD > fd_setsize\n");
Return
}
printf ("ACCEPT:FD =%u\n", FD);
struct Bufferevent *bev = bufferevent_socket_new (base, FD, Bev_opt_close_on_free);
BUFFEREVENT_SETCB (Bev, READ_CB,NULL, ERROR_CB, ARG);
Bufferevent_enable (Bev, ev_read| ev_write| Ev_persist);
}
void Read_cb (struct Bufferevent *bev,void *arg)
{
#Define Max_line 256
Char line[max_line+1];
int n;
evutil_socket_t FD = BUFFEREVENT_GETFD (BEV);
while (n = bufferevent_read (Bev, Line, Max_line), n > 0) {
Line[n] = ' + ';
printf ("fd=%u, read line:%s\n", FD, line);
Bufferevent_write (Bev, line, N);
}
}
void Write_cb (struct Bufferevent *bev,void *arg) {}
void Error_cb (Struct bufferevent *bev, short event, void *arg)
{
evutil_socket_t FD = BUFFEREVENT_GETFD (BEV);
printf ("FD =%u,", FD);
if (event & Bev_event_timeout) {
< span class= "kw" >printf ("Timed out\n"); if bufferevent_set_timeouts () called
}
else if (event & bev_event_eof) {
printf ("Connection closed\n");
else if (Event & Bev_event_ ERROR) {
printf ("some other error\n");
Bufferevent_free (BEV);
}
--
Libevent Getting Started