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 congested/synchronized. Each operation will be returned unless it has been completed or an error occurs. In this way, a thread or a separate process must be used to process each request, system resources cannot support a large number of requests. POSIX defines that asynchronous select system calls can be used, but it uses the round robin method to determine whether a FD is active and the efficiency is not high (n ). Therefore, various systems propose asynchronous/callback-based system calls, such as Linux epoll, BSD kqueue, and Windows iocp. With support at the kernel layer, you can find the active FD at O (1) efficiency. Basically, libevent encapsulates these efficient Io and provides a unified API to simplify development.
Libevent is probably like this:
By default, it is single-threaded (multi-threaded). Each thread has only one event_base, corresponding to a struct event_base struct (and the event manager attached to it ), A series of events managed by schedule can be compared with the Process Manager of the operating system, which is simpler. When an event occurs, event_base will call the function bound to the event at the appropriate time (not necessarily immediately) (pass in some predefined parameters, and a parameter specified during binding). After the function is executed, other schedule events are returned.
// Create an event_basestruct event_base * base = event_base_new (); Assert (base! = NULL );
Event_base has a loop in it, which is blocked by epoll, kqueue, and other system calls. It knows that one or more events occur and then processes these events. Of course, these events will be bound to event_base. Each event corresponds to a struct event, which can be a listener for a FD or POSIX semaphore. Struct event uses event_new to create and bind, and event_add to enable it.
// Create and bind an eventstruct event * listen_event; // parameter: event_base, listened FD, event type and attribute, bound callback function, listen_event = event_new (base, listener, ev_read | ev_persist, callback_func, (void *) Base) for the callback function; // parameter: event, timeout time (struct timeval * type, null indicates no timeout setting) event_add (listen_event, null );
Note: libevent supports the following events and attributes: (bitfield is used, so | is used to fit them)
(A) ev_timeout: timeout
(B) ev_read: the callback function is triggered as long as there is data in the network buffer.
(C) ev_write: the callback function is triggered when only the data buffered by the network is written.
(D) ev_signal: POSIX semaphore. Refer to manual.
(F) ev_et: edge-trigger edge trigger. For more information, see epoll_et.
Then, you need to start the event_base loop to process the events. Use event_base_dispatch to start a loop. The cycle will continue, knowing there are no more events to be concerned about, or encountering the event_loopbreak ()/event_loopexit () function.
// Start the event loop event_base_dispatch (base );
Next, pay attention to the callback function callback_func bound to the event: 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 and pass event_base to it. In fact, it is more about allocating a struct and putting all the relevant data in it, finally, it is thrown to event_new and can be obtained here ). Its prototype is:
typedef void(* event_callback_fn(evutil_socket_t sockfd, short event_type, void *arg))
For a server, the above process is probably combined as follows:
1. Listener = socket (), BIND (), listen (), set nonblocking (fcntl can be used for POSIX systems, but not for windows. In fact, libevent provides a unified packaging evutil_make_socket_nnblocking)
2. Create an event_base
3. Create an event, host the socket to event_base, specify the event type to listen to, and bind the corresponding callback function (and parameters to be provided ). For listener socket, you only need to listen to ev_read | ev_persist
4. Start the event
5. Enter the event Loop
6. (asynchronous) when a client initiates a request, this callback function is called for processing.
Q: Why don't I call accept immediately after listen and get the client connection before throwing it to event_base? I don't know if Yfs is doing this, but this requires another thread.
What do callback functions do? Of course, it is to process client requests. First, you must accept to obtain a sockfd that can communicate with the client, and then ...... Call Recv/send? Error! A big mistake! If you call Recv/send directly, this thread will be blocked in this place. If this client is very sinister (such as never sending messages, or the network is not good, always packet loss ), libevent can only wait for it and cannot process other requests-so you should create a new event to host this sockfd.
The implementation of libevent in earlier versions is cool [if you don't want to know more about it, refer to the next section].
The general process for the server to obtain data from the client is as follows:
1. Set sockfd to nonblocking
2. Create two events:
Event_read: bind the ev_read | ev_persist of sockfd and set the callback function and parameters.
Event_write: bind the ev_write of sockfd | ev_persist, and set the callback function and parameters.
3. Start the event_read event
----
4. Wait for the event_read event to occur asynchronously and call the corresponding callback function. The problem is that the data read by the callback function Recv cannot be directly sent to sockfd because sockfd is nonblocking. If it is thrown to him, it cannot be guaranteed to be correct (because it is asynchronous ...). Therefore, you need a managed cache to save the read data (create a struct after accept and upload it as the ARG of the callback function in step 1 ), enable the event_write event (event_add (event_write, null) in an appropriate event (such as a line break) and wait for the ev_write event to be triggered.
5. (asynchronous) when the callback function of the event_write event is called, write data to sockfd and delete the event_write event (event_del (event_write), waiting for the next execution of the event_read event.
For an example, refer to [Example: a low-level rot13 server with libevent] in the official document.
Because you need to manage the buffer on your own, and the process is obscure and incompatible with Windows's iocp, libevent2 provides the bufferevent artifact to provide more elegant and easy-to-use APIs. Struct bufferevent has two built-in events (read/write) and the corresponding buffer [struct evbuffer * input, * output], and provides corresponding functions for operating the buffer (or directly operating bufferevent ). When data is read into input, the read_cb function is called. When output is finished, write_cb is called; error_cb is called in case of network I/O operation errors (connection interruption, timeout, and other errors. As a result, the previous steps are simplified:
1. Set sockfd to nonblocking
2. Use bufferevent_socket_new to create a struct bufferevent * Bev, associate it with the sockfd, and host it with event_base.
3. Use bufferevent_setcb (BEV, read_cb, write_cb, error_cb, (void *) Arg) to convert the function corresponding to ev_read/ev_write
4. Use bufferevent_enable (BEV, ev_read | ev_write | ev_persist) to enable read/write events.
----
5. (asynchronous)
Read data from input in read_cb and insert it into output after processing (it will be automatically written to sockfd)
In write_cb (What do I need to do? For an echo server, read_cb is enough)
Handle errors in error_cb
You can use bufferevent_set_timeouts (BEV, struct timeval * read, struct timeval * write) to set read/write timeout, and process timeout in error_cb.
The prototype of read_cb and write_cb is
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 prototype of the standard callback function of event.
You can use libevent APIs in Bev to extract event_base, sockfd, input/output, and other data.
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, 256), n > 0) bufferevent_write(bev, line, n);}void error_cb(struct bufferevent *bev, short event, void *arg) { bufferevent_free(bev);}
# 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_sin; socklen_t slen = sizeof (SIN); FD = accept (listener, (struct sockaddr *) & sin, & slen); If (FD <0) {perror ("accept "); return;} If (FD> fd_setsize) {// This if reference the rot13 example, which seems to be an official omission, I copied it from the select-based example and forgot to change perror ("FD> fd_setsize \ n"); return;} printf ("accept: FD = % u \ n ", FD); struct bufferevent * Bev = bufferevent_socket_new (base, FD, callback); 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] = '\ 0 '; 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) {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 );}