Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Introduction to Libevent, Linux High Performance I/O Framework Library


Jun 01, 2021 Article blog


Table of contents


This article focuses on the Libevent library and, by the way, introduces I/O库 as a whole.

There are three types of events that Linux server programs must handle:

  • I/O event
  • signal
  • Timing events

When dealing with these three types of events, we usually need to consider the following three issues:

  • Unify the event source. Obviously, dealing with these three types of events in undone can make the code easy to understand while avoiding some potential logical errors.
  • Portability. Different operating systems have different I/O multiplexing methods, such as Solaris dev/poll file, FressBSD kqueue mechanism, Linux epoll system calls
  • With support for concurrent programming, in a multi-process and multithreaded environment, we need to consider how each execution entity can work together on customer connections, signals, and timers to avoid race conditions.

Fortunately, the open source community provides a number of excellent I/O framework libraries that not only solve these problems, allowing developers to focus entirely on the logic of the program, but also on stability, performance, and more. Libevent is one of the relatively lightweight framework libraries.

Overview of the I/O framework library

I/O framework library encapsulates lower-level system calls in the form of library functions, providing applications with a more user-friendly set of interfaces. T hese library functions tend to be more reasonable, efficient, and robust than the same functions that programmers implement themselves. Because they have stood the test of high voltage in real network environment, as well as the test of time.

The implementation principles of the various I/O framework libraries are basically similar, either in Reactor mode or Procator mode (high-performance server program framework - two efficient event handling modes), or both. For example, the Reactor I/O framework library contains several components:

  • Handle handle
  • Event Multi-Distributor EventDemultiplexer
  • Event processor Eventhandler
  • ConcreteEventHandler the specific event processor
  • Reactor

(Recommended tutorial: Linux tutorial)

The relationship between these components is illustrated below:

 Introduction to Libevent, Linux High Performance I/O Framework Library1

  1. Handles: Objects to be processed by I/O framework library, i.e. I/O events, signals, and timing events, are collectively referred to as event sources. A n event source is usually bound to a handle. T he purpose of a handle is that when the kernel detects a ready event, it notifies the application of the event through the handle. In Linux environment, the handle for I/O event is a file descriptor, and the handle for a signal event is a signal value.
  2. Event Multiple Distributor: The arrival of an event is random and asynchronous. W e cannot predict when the program will receive a customer connection request and also receive a pause signal. S o the program needs to wait and process events in a loop, and that's the event loop. I n an event loop, waiting events are typically implemented using I/O reuse techniques. I/O framework library typically encapsulates the various I/O multiplexing system calls supported by the system as a unified interface, called an event multiplexer. T he event multiplexer's demultiplex method is the core function of waiting for an event, which select such as select, poll epoll_wait In addition, the event multiple distributor implements register_event and remove_event methods for callers to add events to the event multiple distributor and remove events from the event multiple distributor.
  3. Event processor and time-specific processor: The event processor executes the business logic corresponding to the event. I t typically contains one or more handle_event callback functions that are executed in an event loop. T he event processor provided by I/O framework library is usually an interface that users need to inherit to implement their own event processor, the specific event processor. T herefore, callback functions in the event processor are generally declared as required functions to support the user's extension. I n addition, the event processor typically provides a get_handle method that returns the handle associated with the event processor. S o what does the event processor have to do with the handle? W hen the time multiple distributor detects an event, it notifies the application through a handle. Therefore, we must bind the event processor to the handle in order to get the correct event processor when the event occurs.
  4. Reactor: Reactor is at the heart of the I/O framework. I t provides several main methods:
    • handle_events This method executes an event loop. It repeats the process of waiting for an event and then processing the event processor for all ready events in turn.
    • register_handler This method calls the event multiple distributor's register_event method to register an event in the event multiple distributor. - remove_handler This method calls the remove_event method of the event multiple distributor to register an event in the event multiple distributor.

The working sequence of the I/O framework library is as follows:

 Introduction to Libevent, Linux High Performance I/O Framework Library2

Libevent source analysis

Libevent is a high-performance I/O framework library for the open source community with the following features:

  • Cross-platform support
  • Unify the event source
  • Thread-safe
  • Implementation based on the Reactor pattern

(Recommended micro-class: Linux micro-class)

An instance

Here's a “Hello World” program implemented with the Libevent library.

include <sys/signal.h>

#include <event2/event.h>


void signal_cb(int fd, short event, void *argc)
{
    struct event_base* base = (event_base*)argc;
    struct timeval delay = {2, 0};
    printf("Caught an interrupt signal; exiting cleanly in two seconds....\n");
    event_base_loopexit(base, &delay);
}


void timeout_cb(int fd, short event, void* argc)
{
    printf("timeout\n");
}


int main(int argc, char const *argv[])
{
    struct event_base* base = event_base_new();
    struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, base);
    event_add(signal_event, NULL);


    timeval tv = {1, 0};
    struct event* timeout_event = evtimer_new(base, timeout_cb, NULL);
    event_add(timeout_event, &tv);


    event_base_dispatch(base);


    event_free(timeout_event);
    event_free(signal_event);
    event_base_free(base);


    return 0;
}

The above code, while simple, basically describes the main logic of Libevent library:

  1. Call event_base_new function to create a event_base object. A event_base is equivalent to one Reactor instance.
  2. Create specific event processors and set the Reactor instances to which they belong. evsignal_new and evtimer_new are used to create signal transaction processors and timing event processors, respectively. They are defined as follows:

 Introduction to Libevent, Linux High Performance I/O Framework Library3

define evsignal_new(b, x, cb, arg) \

    event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
#define evtimer_new(b, cb, arg)     event_new((b), -1, 0, (cb), (arg))

It can be seen that their unified portal is event_new function, the function used to create a common event processor, as defined as follows:

event_new (struct event_base base, evutil_socket_t fd, short events, void (evutil_socket_t, short, void), void arg), where the base parameter specifies the row

thereinto:

  • base parameter specifies Reactor to which the newly created event processor belongs.
  • fd parameter specifies the handle associated with the event processor. When you create an I/O event processor, you should pass a file descriptor to the fd parameter, when you create a signal event processor, you should pass a signal value to fd parameter, such as SIGINT in the previous instance code, and when you create a timed event processor, you should pass -1 to fd parameter.
  • events parameter specifies the event type, as defined below:

    #define EV_TIMEOUT  0x01   /*定时事件*/
    #define EV_READ     0x02         /*可读事件*/
    #define EV_WRITE    0x04        /*可写事件*/
    #define EV_SIGNAL   0x08       /*信号事件*/
    #define EV_PERSIST  0x10     /*永久事件*/
    /*边缘触发事件,需要I/O复用系统调用支持,比如epoll */
    #define EV_ET       0x20

In the code above, EV_PERSIST is to automatically call the event_add function again to this event when the event is triggered.

  • cb parameter specifies the callback function corresponding to the target event, which is equivalent to the event processor handle_event method.
  • arg is the argument Reactor passes to the callback function.

event_new function event an object of the event type, the event processor of Libevent when it succeeds. Libevent uses the word “event” to describe the event processor, not the event, so the convention is as follows:

  • An event is an event bound on a handle, such as a readable event on file descriptor 0
  • Event processors, which are event of the event structure lift type, have many other members, such as callback functions, in addition to the two features (handles and event types) that the event must have
  • Events are managed by event multiple distributors, and event processors are managed by event queues, which include a variety of event queues, such as registered event queues in event_base
  • An event loop that handles an activated event (ready event) refers to a callback function in the event processor that executes the event.

  1. Call event_add function, add the event processor to the registered event queue, and add the event corresponding to the event processor to the event multiple distributor. even_add function is equivalent to register_handler method in Reactor
  2. Call event_base_dispatch function to execute the event loop
  3. At the end of the event cycle, release the system resources using *_free of _free

(Recommended course: That's what Linux should learn)

The source code organization

  • github address: https://github.com/libevent/libevent
  • Header file directory include/event2 The directory was introduced after Libevent motherboard was upgraded to 2.0 and is available to applications such as event.h header files as core functions, http.h header files with HTTP protocol-related services, rpc.h header files with remote procedure call support.
  • The header file under the source root directory. These header files fall into two categories:
  • One category is the wrapper of some header files under the include/event2 directory
  • The other category is the auxiliary header files for internal use in Libevent all of which have file names in the form of *-internal.h
  • Universal Data Catalog compat/sys T here is only one file in the directory ---- queue.h It encapsulates the underlying data structure across platforms, including one-way lists, bidirectional lists, queues, tail queues, and loop queues.
  • sample directory. Some sample code is provided
  • test directory. Provide a one-time amount test code
  • WIN32-Code Provides some specialized code on the Windows platform.
  • event.c file. The overall framework of the file time Libevent is primarily related to the operations of the two structures, event and event_base
  • debpoll.c kqueue.c evport.c select.c win32select.c poll.c and epoll.c files. T hey encapsulate the following I/O reuse mechanisms: /dev/poll kqueue event ports POSIX select Windows select poll and epoll The main contents of these files are similar and are specific implementations of interface functions defined by the structure eventop
  • minheap-internal.h This file implements a heap of events to provide support for timed events.
  • signal.c Provides support for signals. It is also a concrete implementation of interface functions defined by the structure eventop
  • evmap.c file: It maintains a mapping relationship between a handle (file descriptor or signal) and a time processor
  • event_tagging.c Provides a function to add tag data to the buffer, such as a positive number, and to read tag data from the buffer
  • event_iocp files: Support for Windows IOCP (Input/Output Finish Port, Input and Output Completion Port) is available
  • buffer*.c files: Provides control over network I/O buffering, including input and output data filtering, transfer rate limiting, protection of application data using SSL Sockets Layer protocol, and zero-copy file transfer.
  • evthread*.c file: Provides support for multithreaded
  • listener.c Encapsulates the operation of listening to socket including listening for connections and accepting connections
  • logs.c file. It is Libevent log file system
  • evutil.c evutil_rand.c strlcpy.c and arc4random.c files: provide basic operations such as generating random numbers, getting socket address information, reading files, setting socket properties, and so on
  • evdns.c http.c and evrpc.c address information: support for DNS protocols, HTTP protocols, and RPC (Remote Procddure Call, Remote Procedure Call), respectively
  • epoll_sub.c file, which is not in use

Of the entire source code, four event-internal.h include/event2/event_struct.h event.c and evmap.c are the most important. They define event and event_base structures and implement the operations associated with both structures.

Here's a look at Libevent the high-performance I/O framework library in Linux and hopefully it'll help.