The previous several times analyzed the libevent tail queue and the Evbuffer, today examines its event processing framework. This is a few times before parsing Evbuffer, but the idea is not very clear, because I did not use an example to test the event process. Through this time I learned to analyze the source not only to see the source code, and you want to test it this interface is how to use, or will only indefinitely.
First look at the event structure:
struct Event {tailq_entry (event) Ev_next; Registered event linked list, name ev_next,event is the type, inactive, tailq_entry in my previous blog has been analyzed, no longer repeat Tailq_entry (event) Ev_active_next; Active event linked list, called linked list or queue all line, the following meet the queue is also said that these several tailq_entry (event) Ev_signal_next; Signal event linked list unsigned int min_heap_idx; /* for managing Timeouts *//timeout event in the small root heap index struct event_base *ev_base; The event belongs to the reactor instance, which points to a event_base reactor int ev_fd; For the IO event is the file descriptor, for the signal event, is the bound signal short ev_events; The types of events concerned, such as ev_write,ev_read,ev_timeout,ev_signal,ev_presist, can be used to combine short ev_ncalls; The number of times the call Ev_callback executes, usually 1 short *ev_pncalls, when the event is ready; /* allows deletes in callback//usually point to event_ncalls, or null struct timeval; Timeout event time-out int ev_pri; /* Smaller numbers are higher priority *//Priority void (*ev_callback) (int, short, void *arg); Event callback function, the execution of incident handler, FD corresponding ev_fd,events corresponding//ev_events, arg corresponding to Ev_arg void *ev_arg; Specify int ev_res when setting event; /* result passed to event CALlback//records the type int ev_flags of the current activation event;
A field that marks the event information, indicating the current state, such as Evlist_timeout, evlist_inserted, evlist_active, and so on. };
The following is the EVENT_BASE structure:
struct Event_base {
const struct eventop *evsel; The name used to receive the I/O mechanism before the system compiles, from eventops
void *evbase; Receive I/O multiplexing op, such as Epollop int event_count, returned by Init
; /* counts number of total events ///event totals
int event_count_active; /* counts number of active events///Active incidents
int event_gotterm; /* Set to terminate loop *
/int event_break; /* Set to terminate loop immediately * * *
Active Event Management
/* struct event_list **activequeues Queues[priority] is a linked list in which each node in the list is a
//priority event
int nactivequeues with the priority level. Number of active lists
/* Signal Handling Info *
/struct evsignal_info sig; The structure of the special management signal
struct event_list eventqueue; Linked list, save all registered events event pointer
struct timeval EVENT_TV; Manage time variable
struct min_heap timeheap; A small Gan struct timeval Tv_cache used to manage timed time
; Manage time variable
};
The type of Evsel in Event_base is the Eventop type, and its structure is this:
struct Eventop {
const char *name;
void * (*init) (struct event_base *); Initializes
Int (*add) (void *, struct event *); Register Event
Int (*del) (void *, struct event *); Delete event
int (*dispatch) (struct event_base *, void *, struct timeval *); Event distribution
void (*dealloc) (struct event_base *, void *); Log off, Release resources
/* Set if we need to reinitialize the event base
/int need_reinit; Setting whether to reinitialize
};
Eventop is the event operator, we can see that it encapsulates the name of the event operation, as well as various methods that can be used to coordinate the implementation of the C language polymorphism. This will actually encapsulate the I/O multiplexing options, such as: Name=epoll, then Init,add, and so on, using the Epoll method, or Name=select, using the Select method. As for how to choose, look below.
Evsel is actually initialized in the Event_base_new function and will select I/O multiplexer based on your options before compiling, and I'll intercept the selection of the Ecent_base_new function as follows, as well as the Eventops option array.
Depending on the system configuration and compilation options, which I/O demultiplex mechanism is used
/can be seen, Libevent chooses the system's I/O demultiplex mechanism in the compile phase, and does not support the option to select again at run time based on configuration
base- >evbase = NULL;
for (i = 0; eventops[i] &&!base->evbase; i++) { //sequential traversal, as these mechanisms are by performance from large to small rows of
Base->evsel = Eventops[i ];
Base->evbase = Base->evsel->init (base);
}
The Eventops options array is as follows, Linux supports Epoll,poll,select, and the order is sorted by performance.
All supported I/O demultiplex mechanisms are stored in this global static array, and the mechanism is chosen at compile time, and the contents of the array are declared according to the order of precedence/in orders of
preference/
static const struct Eventop *eventops[] = {
#ifdef have_event_ports
&evportops,
#endif
#ifdef have_ Working_kqueue
&kqops,
#endif
#ifdef have_epoll
&epollops,
#endif
#ifdef Have_devpoll
&devpollops,
#endif
#ifdef have_poll
&pollops,
#endif
#ifdef Have_select
&selectops,
#endif
#ifdef WIN32
&win32ops,
#endif
NULL
} ;
After the analysis of several key structures, we will take a look at the diagram of these structures (Fig is not original, the source is forgotten):
We sign up for an event, this event has a pointer inside, pointing to a reactor event_base. There are registered event queues, active event queues, and the smallest heap of timed events in the event_base. When we call the event, we choose which queue or min_heap to register according to that type, and Event_base has a series of properties for the reactor, such as the time cache, the total event counter, and the I/O multiplexer method.
The flowchart is probably the following:
Next, we'll dissect the whole process according to the flowchart.
The entire process uses a section of the socket code to illustrate the following, showing only the main function, and a complete example below.
int main ()
{
int sockfd = socket_packaging (server_ip, server_port);
Base = Event_base_new ();
struct event Listen_ev;
Event_set (&listen_ev, SOCKFD, ev_read| Ev_persist, On_accept, NULL);
Event_base_set (base, &listen_ev);
Event_add (&listen_ev, NULL);
Event_base_dispatch (base);
return 0;
}
Then we will analyze the concrete implementation inside the library.
First call the Event_init () function, before calling this function we need to know that there is a global variable in the file:
struct Event_base *current_base = NULL;
Initialized in the INIT function, which performs the function to initialize an instance of the reactor.
struct Event_base *
event_init (void)
{
struct event_base *base = event_base_new ();
if (base!= NULL)
current_base = base; When initialized, the current event_base is assigned to the value of new, and the reactor return
(base) is shared globally.
The client then invokes the Event_set () function, which sets the event events that we want to generate.
void event_set (struct event *ev, int fd, short events, void (*callback) (int, short, void *), void *arg {/* Take the current Base-caller needs to set the real base later * * * ev->ev_base = current_base;
The INIT function has been assigned a value of current_base, so it is not empty ev->ev_callback = callback;
Ev->ev_arg = arg;
EV->EV_FD = FD; Ev->ev_events = events; Event type of concern ev->ev_res = 0; Record the current activation time type ev->ev_flags = Evlist_init; A field that marks the event information, indicating the current state, such as Evlist_timeout, evlist_inserted, ev->ev_ncalls = 0; The number of times the callback function executes Ev->ev_pncalls = NULL When the time is ready; Point to Ncalls min_heap_elem_init (EV); Minimum heap Subscript Initial-1/By default, we put new events into the middle priority/if (current_base) Ev->ev_pri = Current_b ase->nactivequeues/2; The default is to set the priority to active list intermediate values, the active list of the subscript is priority}
The event function acts as an initialization event, such as a callback function to be executed when an event occurs, but we are going to add the event to the reactor sequence, so we first call the following Event_base_set () function, which is to associate them. (I was puzzled, why not put the event and event_base associated with the action to the library to execute.) In fact, this is the responsibility of the client, if a program has multiple event_base instances, the event can be associated with different reactors.
int
event_base_set (struct event_base *base, struct event *ev)
{/
* Only innocent events is assigned to a D Ifferent Base */
if (ev->ev_flags!= evlist_init) //Only innocent events can be assigned to a different base return
( -1);
Ev->ev_base = base;
Ev->ev_pri = base->nactivequeues/2; Priority half return
(0);
}
Once connected, the event should be put into the event_base corresponding event queue to invoke the Event_add () function, but the Event_add () function can be used not only in this case, it may have been added, but also to set the timer , or to change the timing, add or rejoin the timer min_heap, and call this function.
int Event_add (struct event *ev, const struct Timeval *tv) {struct Event_base *base = ev->ev_base;
To register to the event_base const struct Eventop *evsel = base->evsel; void *evbase = base->evbase;
System I/O policies used by base int res = 0; Event_debug (("Event_add:event:%p,%s%s%scall%p", Ev, Ev->ev_events & Ev_read?) "Ev_read": ", Ev->ev_events & Ev_write?" Ev_write ":" ", TV?"
Ev_timeout ":" ", Ev->ev_callback)); ASSERT (!) ( Ev->ev_flags & ~evlist_all)); Assertion ensures that there is a flag bit * * Prepare for timeout insertion further below, if we got a * failure on no step, we should don't change
Any state. ///If TV is not NULL, registers the timed event at the same time, otherwise inserts the event into the linked list//new timer event, invokes the timer heap interface to reserve a position on the heap//This ensures that the atomic//to the system I/O mechanism registration may fail, and when reserved on the heap
After work//?? The addition of timed events will certainly not fail//and the possible result of the reservation is heap expansion, but the internal element does not change the IF (TV!= NULL &&! Ev->ev_flags & Evlist_timeout)) {if Min_heap_reserve (&base->timeheap, 1 + min_heap_size (&base-> ; timeheap)) = =-1) return (-1); /* Enomem = = errno/////If the event is not registered or active in the list, the Evbase registration event is invoked. If and tv!=null, adding only timed events//This step is the possible failure mentioned previously (Ev->ev_events & ev_read| ev_write| ev_signal) &&! (Ev->ev_flags & (evlist_inserted| evlist_active))) {res = Evsel->add (evbase, Ev); Plus I/O operations, such as Epoll_add, have been analyzed above if (res!=-1)///registered successfully, insert event into registered list Event_queue_insert (base, Ev, Evlist_i
nserted);
}/* We should change the Timout state only if the previous event * addition succeeded.
*//Prepare to add timed event if (res!=-1 && TV!= NULL) {struct timeval now;
/* We already reserved memory above for the case where we * are not replacing a exisiting.
* *//evlist_timeout indicates that the event is already in the timer heap and deletes the old. if (Ev->ev_flags & evlist_timeout) Event_queue_remove (base, Ev, evlist_timeout); Delete old, new Timer/* Check If it is active due to a timeout. Rescheduling * This timeout before the callback can executeD * removes it from the active list. ////If the event is already in a ready state, remove from the activation list because the ready state cannot be modified//This is the event may have been add over, and active, active can not be changed, first delete and then change if (Ev->ev_flags & evlist_active)
&& (Ev->ev_res & Ev_timeout)) {//???
/* The If we are just active executing this * event in a loop *////Set the number of Ev_callback calls to 0???
if (ev->ev_ncalls && ev->ev_pncalls) {/* Abort loop * * *ev->ev_pncalls = 0; } event_queue_remove (base, Ev, evlist_active);
Remove the calculated time from the corresponding list and insert it into the timer small root heap gettime (base, &now); Evutil_timeradd (&now, TV, &ev->ev_timeout);
This function analyzes Event_debug next time (("Event_add:timeout in%ld seconds, call%p", Tv->tv_sec, Ev->ev_callback)); Event_queue_insert (base, Ev, evlist_timeout);
Inserts an event into the corresponding list, where part of the situation is inserted into the queue or heap, and is not analyzed for the time being.
} return (res); }
In the above function, we inserted the event into the reactor location and selected the corresponding I/O multiplexer, the callback function to be executed when the event occurred we have already set in the Event_set () function, now there is nothing to do, a big loop, Wait for the event to occur, and the action of the callback function will do. The loop is responsible for the Event_base_loop () function:
int Event_base_loop (struct event_base *base, int flags) {const struct Eventop *evsel = base->evsel;
void *evbase = base->evbase;
struct Timeval TV;
struct Timeval *tv_p;
int res, done;
Clear Time Cache */base->tv_cache.tv_sec = 0;
Evsignal_base is a global variable that, when processing signal, indicates that the event_base instance to which signal belongs is an if (base->sig.ev_signal_added) evsignal_base = base;
Done = 0; while (!done) {//See if you need to jump out of the loop, the program can call EVENT_LOOPEXIT_CB () set event_gotterm tag//Call Event_base_loopbreak () set Event_break tag/
* Terminate The loop if we have been asked to/if (base->event_gotterm) {base->event_gotterm = 0;
Break
} if (base->event_break) {base->event_break = 0;
Break
//correction system time, if the system is using a monotonic time, the user may adjust the system time//In the TIMEOUT_CORRECCT function, compare the last waiting time and current times, if the current time is less than the last
Indicates that there is a problem with the time, when the timeout for all timed events in Timer_heap timeout_correct (base, &TV) needs to be updated;
Calculates system I/o demultiplexer tv_p = &tv; According to the minimum timeout in the timer heap if (!base->eVent_count_active &&! (Flags & Evloop_nonblock))
{ //???
Timeout_next (base, &tv_p); else {//////////////* If we have active events, we just poll new events * Withou based on timer time * * Evsel-dispatch
T waiting.
* * Evutil_timerclear (&TV); //If there is no current registration event, exit//If we have no events, we just exit/if (!event_haveevents (base)) {Event_debug ("%s:no
Events registered. ", __func__));
return (1);
//Update last wait time/* Update last Old time/gettime (base, &BASE->EVENT_TV);
Empty time Cache/* Clear time cache */base->tv_cache.tv_sec = 0;
Calling system I/O demultplexer waiting for ready I/O events, possibly epoll_wait or SELECT, etc.//in Evsel->dispatch (), inserts the ready signal event, I/O event into the activation list
res = Evsel->dispatch (base, Evbase, tv_p);
if (res = = 1) return (-1);
Assign the time cache to the current system gettime (base, &base->tv_cache);
Check the timer events in heap to remove the timer event ready from the heap and insert the activation list timeout_process (base); Call Event_process_active() handles the event that is ready in the activation list, call its callback function to perform event handling//The function will look for the highest priority (the higher the priority value) The Activation Event List//and then handle all the ready events in the list//So low-priority readiness events may not be processed in a timely manner if (base->event_count_active)
{event_process_active (base);
if (!base->event_count_active && (Flags & evloop_once)) done = 1;
else if (Flags & evloop_nonblock) done = 1; }/* Clear time cache */base->tv_cache.tv_sec = 0;
Loop end, clear cache Event_debug (("%s:asked to terminate loop.", __func__));
return (0); }
The loop function caches time by cache, which eliminates the need for each cycle to be systemcall first, increasing efficiency.
The whole process is like this, some detail functions may not be analyzed for the moment, the follow-up will be detailed analysis.
Here is an example of a echoserver, which is the completion code of the program that the main function belongs to, which can be referenced to help understand the event handling process.
#include <stdio.h> #include <event.h> #include <assert.h> #include <unistd.h> #include < string.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet /in.h> #include <arpa/inet.h> #define SERVER_IP "127.0.0.1" #define SERVER_PORT 6666 #define BACKLOG 5 #de
Fine max_size 1024 struct event_base = NULL;
struct sock_ev{struct event* Read_ev;
struct event* Write_ev;
Char *buffer;
};
int socket_packaging (const char* IP, const unsigned int port);
void On_write (int sockfd, short event, void* Arg);
void on_accept (int sock, short event, void *arg);
int main () {int sockfd = socket_packaging (server_ip, Server_port);
Base = Event_base_new ();
struct event Listen_ev; Event_set (&listen_ev, SOCKFD, ev_read|
Ev_persist, On_accept, NULL);
Event_base_set (base, &listen_ev);
Event_add (&listen_ev, NULL);
Event_base_dispatch (base); Return 0;
} void Release_sock_event (struct sock_ev* ev) {Event_del (Ev->read_ev);
Free (Ev->read_ev);
Free (Ev->write_ev);
Free (ev->buffer);
Free (EV);
} void On_write (int sockfd, short event, void* Arg) {char *buffer = (char *) arg;
Send (SOCKFD, buffer, strlen (buffer) +1, 0);
Free (buffer);
} void On_read (int sockfd, short event, void* Arg) {struct event* Write_ev;
int size;
struct sock_ev* ev = (struct sock_ev*) arg;
Ev->buffer = (char *) malloc (max_size);
memset (ev->buffer, 0, sizeof (ev->buffer));
Size = recv (SOCKFD, Ev->buffer, max_size, 0);
printf ("Receive data:%s, size:%d\n", ev->buffer, size);
if (size = = 0) {release_sock_event (EV);
Close (SOCKFD);
return;
} event_set (Ev->write_ev, SOCKFD, Ev_write, On_write, Ev->buffer);
Event_base_set (base, Ev->write_ev);
Event_add (Ev->write_ev, NULL); } void on_accept (int sock, short event, void *arg) {sTruct sockaddr_in cli_addr;
int NEWFD;
struct sock_ev* ev = (struct sock_ev*) malloc (sizeof (struct Sock_ev));
Ev->read_ev = (struct event*) malloc (sizeof (struct event));
Ev->write_ev = (struct event*) malloc (sizeof (struct event));
socklen_t sin_size = sizeof (struct sockaddr_in);
NEWFD = Accept (sock, (struct sockaddr *) &cli_addr, &sin_size); Event_set (Ev->read_ev, NEWFD, ev_read|
Ev_persist, On_read, Ev);
Event_base_set (base, Ev->read_ev);
Event_add (Ev->read_ev, NULL);
int socket_packaging (const char* IP, const unsigned int port) {int SOCKFD = socket (af_inet, sock_stream, 0);
ASSERT (SOCKFD!=-1);
struct sockaddr_in servaddr;
memset (&servaddr, 0, sizeof (SERVADDR));
servaddr.sin_family = af_inet;
Servaddr.sin_port = htons (port);
SERVADDR.SIN_ADDR.S_ADDR = inet_addr (IP);
int on = 1;
int ret = setsockopt (SOCKFD, Sol_socket, so_reuseaddr, &on, sizeof (on));
ASSERT (Ret!=-1);ret = bind (SOCKFD, (struct sockaddr*) &servaddr, sizeof (SERVADDR));
ASSERT (Ret!=-1);
RET = Listen (SOCKFD, BACKLOG);
ASSERT (Ret!=-1);
return SOCKFD; }
The client's program is not written, I believe everyone can easily write out.
This blog also has a lot of details not analyzed, follow-up blog will be more perfect.
(Freeelinux's blog: http://blog.csdn.net/freeelinux/article/details/52812857)