Sequence: A Linux server must handle three types of events: IO events, signals, and timed events. When dealing with three types of events, there are usually three issues to consider:
- Unify event sources and use IO reuse system calls to manage all events.
- Portability: Different operating systems have different I/O reuse methods.
- Support for concurrent programming in multi-process and multi-threaded environments requires consideration of how the various executants work together to process client connections, signals, and timers to avoid race conditions.
1. IO framework library Overview
Each framework library implementation principle is basically similar, either Reactor model implementation, Proactor model implementation, or both. For example, the IO framework implemented based on the Reactor pattern consists of the following components: Handle, EventDemultiplexer, EventHandler and ConcreteEventHandler, Reactor.
These components are explained below:
- Handle: Objects to be handled by the IO framework library, namely IO events, signals, and timing events, are collectively called event sources. An event source is usually bound to a handle. But when the kernel detects a ready event, it notifies the application of it through a handle. In Linux, the handle of AN IO event is a file descriptor, and the signal event corresponds to a signal value.
- Event multiplexer: Events arrive randomly and asynchronously. We cannot predict when the application will receive a connection request from a customer or a pause signal. So the program needs to wait and process events in a loop, which is called an event loop. In event loops, waiting events are usually implemented by IO multiplexing. IO framework libraries generally encapsulate various IO reuse system calls supported by the system into a unified interface, called event multiplexer. The demultiplex method of the event multiplexer is the core function of waiting for events, which calls select, poll, epoll_wait and other functions internally.
- Event handler and concrete event handler: The event handler executes the business logic corresponding to the event. It usually contains one or more handle_EVENT callbacks, which are executed in the event loop. Event processor is usually in the form of interface in the framework, which needs to inherit the interface to achieve specific event processing and ensure scalability.
- Reator: Reator is the core of the IO framework library. It provides the following main approaches:
- Handle_events (): Executes the event loop.
- Register_handler () : Registers events in the multiplexer.
- Remove_handler () : Removes an event from the multiplexer.
2. Libevent Event flow
When an application registers an event with Libevent, the event is handled as follows:
- First, the application prepares and initializes the event, setting the event type and the callback function.
- Add the event to libevent. For timed events, libevent uses a small root heap with a timeout key. For signal and IO events, libevent puts them into the wait list (bidirectional list).
3. The program calls event_base_dispatch() series functions to wait for events in a loop. Take select() as an example. When select() returns, timeout events are checked first, then I/O events; Place ready events in the activation list; The callback function is then invoked to perform event handling for the events in the activation list.
3. Refer to the manual for Libevent interpretation
1. Create the event_base context
Before you can use libevent’s functionality, you need to create one or more event_base contexts. Each event_base context tracks a sequence of events and reports back which events were fired. (Locking is used in event_base by default to ensure the safety of multiple threads.)
Event_base currently supports the following IO multiplexing methods:
- select
- poll
- epoll
- kqueue
- devpoll
- evport
- win32
Corresponding source code:
/* Array of backends in order of preference. */
static const struct eventop *eventops[] = {
#ifdef EVENT__HAVE_EVENT_PORTS
&evportops,
#endif
#ifdef EVENT__HAVE_WORKING_KQUEUE
&kqops,
#endif
#ifdef EVENT__HAVE_EPOLL
&epollops,
#endif
#ifdef EVENT__HAVE_DEVPOLL
&devpollops,
#endif
#ifdef EVENT__HAVE_POLL
&pollops,
#endif
#ifdef EVENT__HAVE_SELECT
&selectops,
#endif
#ifdef _WIN32
&win32ops,
#endif
NULL
};
Copy the code
Tips: You can turn off any of the above methods using event_CONFIG_AVOID_method ().
For most programs, create a default context
struct event_base *event_base_new(void);
Copy the code
This function is declared in “event2/event.h”.
Configuration event_base:
struct event_config *event_config_new(void); Struct event_base *event_base_new_with_config(conststruct event_config *cfg); \\ Create a custom event_base configurationvoidevent_config_free(struct event_config *cfg); \\ Frees configuration context spaceCopy the code
Event_config configuration:
int event_config_avoid_method(struct event_config *cfg, const char *method);
enum event_method_feature {
EV_FEATURE_ET = 0x01, \\ Edge trigger EV_FEATURE_O1 =0x02, \\ Increase the complexity of delete events O(1)
EV_FEATURE_FDS = 0x04, \\ let libevent support both file reading and writing}; int event_config_require_features(struct event_config *cfg, enum event_method_feature feature); enum event_base_config_flag { EVENT_BASE_FLAG_NOLOCK =0x01, \\ single-threaded program event_base lockless EVENT_BASE_FLAG_IGNORE_ENV =0x02, \\ Ignore the environment variable EVENT_BASE_FLAG_STARTUP_IOCP =0x04, \\ Windows enable IOCP EVENT_BASE_FLAG_NO_CACHE_TIME =0x08EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST =0x10,\\ on can prevent the same event handle from being triggered multiple times when multiple events occur at the same time. Only epoll model is supported. EVENT_BASE_FLAG_PRECISE_TIMER =0x20Provide finer granularity timing}; int event_config_set_flag(struct event_config *cfg, enum event_base_config_flag flag);Copy the code
Other configurations: Specify the number of available cpus under Window
int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)
Copy the code
Limit the maximum event interval and maximum number of events handled by the callback handler for low-priority events to prevent priority flipping.
int event_config_set_max_dispatch_interval(struct event_config *cfg,
const struct timeval *max_interval, int max_callbacks,
int min_priority);
Copy the code
Sample code:
\\Example: Preferring edge-triggered backends
struct event_config *cfg;
struct event_base *base;
int i;
/* My program wants to use edge-triggered events if at all possible. So I'll try to get a base twice: Once insisting on edge-triggered IO, and once not. */
for (i=0; i<2; ++i) {
cfg = event_config_new();
/* I don't like select. */
event_config_avoid_method(cfg, "select");
if (i == 0)
event_config_require_features(cfg, EV_FEATURE_ET);
base = event_base_new_with_config(cfg);
event_config_free(cfg);
if (base)
break;
/* If we get here, event_base_new_with_config() returned NULL. If this is the first time around the loop, we'll try again without setting EV_FEATURE_ET. If this is the second time around the loop, we'll give up. */
}
Copy the code
Example: Avoiding priority-inversion
struct event_config *cfg;
struct event_base *base;
cfg = event_config_new();
if(! cfg)/* Handle error */;
/* I'm going to have events running at two priorities. I expect that some of my priority-1 events are going to have pretty slow callbacks, so I don't want more than 100 msec to elapse (or 5 callbacks) before checking for priority-0 events. */
struct timeval msec_100 = { 0.100*1000 };
event_config_set_max_dispatch_interval(cfg, &msec_100, 5.1);
base = event_base_new_with_config(cfg);
if(! base)/* Handle error */;
event_base_priority_init(base, 2);
Copy the code
Check the IO reuse methods supported by event_base:
const char **event_get_supported_methods(void);
Copy the code
Tip: The last element of the array is NULL
Sample code:
int i;
const char **methods = event_get_supported_methods();
printf("Starting Libevent %s. Available methods are:\n",
event_get_version());
for (i=0; methods[i] ! = NULL; ++i) { printf(" %s\n", methods[i]);
}
Copy the code
Check the UI reuse methods currently in use and the features supported:
struct event_base *base;
enum event_method_feature f;
base = event_base_new();
if(! base) { puts("Couldn't get an event_base!");
} else {
printf("Using Libevent with backend method %s.",
event_base_get_method(base));
f = event_base_get_features(base);
if ((f & EV_FEATURE_ET))
printf(" Edge-triggered events are supported.");
if ((f & EV_FEATURE_O1))
printf(" O(1) event notification is supported.");
if ((f & EV_FEATURE_FDS))
printf(" All FD types are supported.");
puts("");
}
Copy the code
Free event_base space:
void event_base_free(struct event_base *base);
Copy the code
Set the event priority level :(by default, all events are of the same level)
int event_base_priority_init(struct event_base *base, int n_priorities);
Copy the code
Reinitialize event_base() after process fork() :
int event_reinit(struct event_base *base);
Copy the code
Sample code:
Example
struct event_base *base = event_base_new();
/ *... add some events to the event_base ... * /
if (fork()) {
/* In parent */
continue_running_parent(base); / *... * /
} else {
/* In child */
event_reinit(base);
continue_running_child(base); / *... * /
}
Copy the code