How to Use libevent and libev to improve network application performance

Source: Internet
Author: User
Tags root access ibm server

There are many solutions, but the libevent library and libev library can greatly improve performance and event processing capabilities. In this article, we will discuss the basic structures and methods used to use and deploy these solutions in UNIX applications. Both libev and libevent can be used in high-performance applications, including applications deployed in the IBM Cloud or Amazon EC2 environment that need to support a large number of concurrent clients or operations.
Introduction
One of the biggest problems faced by many server deployment (especially web Server deployment) is that it must be able to handle a large number of connections. Whether to build cloud-based services to process network communication streams, distribute applications on IBM Amazon EC instances, or provide high-performance components for websites, you must be able to process a large number of concurrent connections.
A good example is that web applications are becoming increasingly dynamic recently, especially those using AJAX technology. If the system to be deployed allows thousands of clients to update information directly on the webpage, such as a system that provides real-time monitoring of events or problems, the speed of providing information is very important. In a grid or cloud environment, persistent connections from thousands of clients may be opened at the same time. Each client must be able to process and respond to requests.
Before discussing how libevent and libev handle multiple network connections, let's briefly review the traditional solutions for handling such connections.

Process multiple clients

There are many different traditional methods to process multiple connections, but they often cause problems when dealing with a large number of connections, because they use too much memory or CPU, or have reached an operating system limit.

The main method is as follows:

Loop: In the early stage, the system used a simple loop to select a solution, that is, to traverse the list of open network connections and determine whether there is data to be read. This method is both slow (especially as the number of connections increases) and inefficient (because other connections may be sending requests and waiting for responses when processing the current connection ). When the system cyclically traverses each connection, other connections have to wait. If there are 100 connections, and only one of them has data, it is still necessary to process 99 other connections in order to be able to process the connections that really need to be processed.
Poll, epoll, and variants: this is an improvement to the loop method. It uses a structure to save the array of each connection to be monitored. when data is found on a network socket, call the processing function through the callback mechanism. The problem with poll is that this structure will be very large. When a new network connection is added to the List, modifying the structure will increase the load and affect the performance.
Select: The select () function call uses a static structure, which is hardcoded into a relatively small number (1024 connections) in advance, so it is not applicable to very large deployments.
There are other implementations on various platforms (such as/dev/poll on Solaris or kqueue on FreeBSD/NetBSD). They may have better performance on their respective operating systems, but cannot be transplanted, it may not be able to solve the high-level problem of processing requests.
All the above solutions use a simple loop to wait and process requests, and then assign the requests to another function to process the actual network interaction. The key is that loop and network socket require a large amount of Management Code so that different connections and interfaces can be monitored, updated, and controlled.
Another way to process many connections is to use the multi-thread support in the modern kernel to listen and process connections and start a new thread for each connection. This puts the responsibility directly to the operating system, but will increase the overhead in RAM and CPU, because each thread needs its own execution space. In addition, if each thread is busy processing network connections, context switching between threads will be frequent. Finally, many kernels are not suitable for processing such a large number of active threads.

Libevent Method

The libevent library does not actually replace the foundation of select (), poll (), or other mechanisms. Instead, a package is added to the implementation using the most efficient high-performance solution for each platform.
To process each request, the libevent Library provides an event mechanism, which acts as the package at the underlying network backend. The event system makes it easy to add processing functions for connections and reduces the underlying I/O complexity. This is the core of the libevent system.
Other components of the libevent library provide other functions, including buffered Event Systems (used to buffer data sent to/received from clients) and core implementations of HTTP, DNS, and RPC systems.
The basic method for creating a libevent server is to register the function that should be executed when an operation occurs (such as accepting a connection from the client), and then call the main event loop event_dispatch (). The control of the execution process is now handled by the libevent system. After registering an event and calling a function, the event system becomes autonomous. When an application is running, you can add (Register) or delete (cancel registration) Events in the event queue. Event registration is very convenient. You can use it to add new events to process new connections and build a flexible network processing system.
For example, you can open a listening socket and register a callback function. Every time you call the accept () function to open a new connection, you can call this callback function to create a network server. The code snippet shown in Listing 1 describes the basic process:

Listing 1. Open the listening socket, register a callback function (call it whenever you need to call the accept () function to open a new connection), and then create a network serverCopy codeThe Code is as follows: int main (int argc, char ** argv)
{
...
Ev_init ();

/* Setup listening socket */

Event_set (& ev_accept, listen_fd, EV_READ | EV_PERSIST, on_accept, NULL );
Event_add (& ev_accept, NULL );

/* Start the event loop .*/
Event_dispatch ();
}

The event_set () function creates a new event structure. event_add () adds events to the event queue mechanism. Then, event_dispatch () starts the event queue system and starts to listen (and accept) requests.

Listing 2 provides a more complete example to build a very simple echo server:
Listing 2. Building a simple echo serverCopy codeThe Code is as follows: # include <event. h>
# Include <sys/types. h>
# Include <sys/socket. h>
# Include <netinet/in. h>
# Include <arpa/inet. h>
# Include <string. h>
# Include <stdlib. h>
# Include <stdio. h>
# Include <fcntl. h>
# Include <unistd. h>

# Define SERVER_PORT 8080
Int debug = 0;

Struct client {
Int fd;
Struct bufferevent * buf_ev;
};

Int setnonblock (int fd)
{
Int flags;

Flags = fcntl (fd, F_GETFL );
Flags | = O_NONBLOCK;
Fcntl (fd, F_SETFL, flags );
}

Void buf_read_callback (struct bufferevent * incoming,
Void * arg)
{
Struct evbuffer * evreturn;
Char * req;

Req = evbuffer_readline (incoming-> input );
If (req = NULL)
Return;

Evreturn = evbuffer_new ();
Evbuffer_add_printf (evreturn, "You said % s \ n", req );
Bufferevent_write_buffer (incoming, evreturn );
Evbuffer_free (evreturn );
Free (req );
}

Void buf_write_callback (struct bufferevent * bev,
Void * arg)
{
}

Void buf_error_callback (struct bufferevent * bev,
Short what,
Void * arg)
{
Struct client * client = (struct client *) arg;
Bufferevent_free (client-> buf_ev );
Close (client-> fd );
Free (client );
}

Void accept_callback (int fd,
Short ev,
Void * arg)
{
Int client_fd;
Struct sockaddr_in client_addr;
Socklen_t client_len = sizeof (client_addr );
Struct client * client;

Client_fd = accept (fd,
(Struct sockaddr *) & client_addr,
& Client_len );
If (client_fd <0)
{
Warn ("Client: accept () failed ");
Return;
}

Setnonblock (client_fd );

Client = calloc (1, sizeof (* client ));
If (client = NULL)
Err (1, "malloc failed ");
Client-> fd = client_fd;

Client-> buf_ev = bufferevent_new (client_fd,
Buf_read_callback,
Buf_write_callback,
Buf_error_callback,
Client );

Bufferevent_enable (client-> buf_ev, EV_READ );
}

Int main (int argc,
Char ** argv)
{
Int socketlisten;
Struct sockaddr_in addresslisten;
Struct event accept_event;
Int reuse = 1;

Event_init ();

Socketlisten = socket (AF_INET, SOCK_STREAM, 0 );

If (socketlisten <0)
{
Fprintf (stderr, "Failed to create listen socket ");
Return 1;
}

Memset (& addresslisten, 0, sizeof (addresslisten ));

Addresslisten. sin_family = AF_INET;
Addresslisten. sin_addr.s_addr = INADDR_ANY;
Addresslisten. sin_port = htons (SERVER_PORT );

If (bind (socketlisten,
(Struct sockaddr *) & addresslisten,
Sizeof (addresslisten) <0)
{
Fprintf (stderr, "Failed to bind ");
Return 1;
}

If (listen (socketlisten, 5) <0)
{
Fprintf (stderr, "Failed to listen to socket ");
Return 1;
}

Setsockopt (socketlisten,
SOL_SOCKET,
SO_REUSEADDR,
& Reuse,
Sizeof (reuse ));

Setnonblock (socketlisten );

Event_set (& accept_event,
Socketlisten,
EV_READ | EV_PERSIST,
Accept_callback,
NULL );

Event_add (& accept_event,
NULL );

Event_dispatch ();

Close (socketlisten );

Return 0;
}

The following describes the functions and their operations:

Main (): The main function is used to create a socket for listening to the connection, and then the accept () callback function is created to process each connection through the event processing function.
Accept_callback (): When the connection is accepted, the event system calls this function. This function accepts the connection to the client. It adds the client socket information and a bufferevent structure. In the event structure, it adds a callback function for the read/write/error event on the client socket; transmits the client structure (and embedded eventbuffer and client socket) as parameters ). Call the corresponding callback function whenever the corresponding client socket contains read, write, or error operations.
Buf_read_callback (): it is called when the client socket has data to be read. As the echo service, this function writes "you said..." back to the client. The socket is still open and can accept new requests.
Buf_write_callback (): Call it when there is data to be written. In this simple service, this function is not required, so the definition is empty.
Buf_error_callback (): Call it when an error occurs. This includes client disconnection. In all scenarios where an error occurs, close the client socket, delete the event entries of the client socket from the event list, and release the memory of the client structure.
Setnonblock (): Set the network socket to open I/O.
When the client is connected, add new events in the event queue to process client connections. when the client is disconnected, delete the events. Behind the scenes, libevent processes network sockets, identifies the clients that require services, and CALLS corresponding functions respectively.

To build this application, you need to compile the C source code and add the libevent Library: $ gcc-o basic. c-levent.

From the client perspective, this server only sends back any text sent to it (see listing 3 ).

Listing 3. The server sends the text sent to it backCopy codeThe Code is as follows: $ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!
You said Hello!

Such network applications are suitable for large-scale distributed deployment that requires processing multiple connections, such as the IBM Cloud system.

It is difficult to observe the handling of a large number of concurrent connections and performance improvement through a simple solution. You can use embedded HTTP to help you understand scalability.

Use the built-in HTTP Server

If you want to build a local application, you can use a general network-based libevent interface. However, an increasingly common scenario is to develop HTTP-based applications, and web pages that load or dynamically reload information. If you use any AJAX library, the client requires HTTP, even if the information you return is XML or JSON.

The HTTP implementation in libevent is not a substitute for the Apache HTTP Server, but a practical solution suitable for large-scale dynamic content associated with the cloud and web environment. For example, you can deploy libevent-based interfaces in IBM Cloud or other solutions. Because HTTP can be used for communication, the server can be integrated with other components.

To use the libevent service, you need to use the same basic structure as the main network event model, but you must also process network interfaces. the HTTP package will handle this for you. This makes the entire process into four function calls (initialize, start the HTTP server, set the HTTP callback function, and enter the event loop), plus the callback function that sends the returned data. Listing 4 provides a very simple example:

Listing 4. Simple example of using libevent ServiceCopy codeThe Code is as follows: # include <sys/types. h>

# Include <stdio. h>
# Include <stdlib. h>
# Include <unistd. h>

# Include <event. h>
# Include <evhttp. h>

Void generic_request_handler (struct evhttp_request * req, void * arg)
{
Struct evbuffer * returnbuffer = evbuffer_new ();

Evbuffer_add_printf (returnbuffer, "Thanks for the request! ");
Evhttp_send_reply (req, HTTP_ OK, "Client", returnbuffer );
Evbuffer_free (returnbuffer );
Return;
}

Int main (int argc, char ** argv)
{
Short http_port = 8081;
Char * http_addr = "192.168.0.22 ";
Struct evhttp * http_server = NULL;

Event_init ();
Http_server = evhttp_start (http_addr, http_port );
Evhttp_set_gencb (http_server, generic_request_handler, NULL );

Fprintf (stderr, "Server started on port % d \ n", http_port );
Event_dispatch ();

Return (0 );
}

The preceding example shows the basic structure of the Code, which does not need to be explained. The main elements are the evhttp_set_gencb () function (which sets the callback function to be used when an HTTP request is received) and generic_request_handler () the callback function itself (it fills the Response Buffer with a simple message indicating success ).

The HTTP package provides many other functions. For example, a request parser extracts query parameters from a typical request (just like processing CGI requests ). You can also set the handler to be triggered in different request paths. By setting different callback and processing functions, you can use the path '/db/' to provide interfaces to the database, or use the '/memc' to provide interfaces to memcached.

Another feature of the libevent toolkit is the support for universal timers. Events can be triggered after a specified time period. By combining the timer and HTTP, You can provide lightweight services to automatically provide the file content and update the returned data when modifying the file content. For example, in the past, to provide instant update services during frequent news events, front-end web applications need to reload press releases on a regular basis, and now content can be easily provided. The entire application (and web Service) is in the memory, so the response is very fast.

This is the main purpose of the example in listing 5:

Listing 5. Use a timer to provide instant update during frequent news activitiesCopy codeThe Code is as follows: # include <sys/types. h>
# Include <stdio. h>
# Include <stdlib. h>
# Include <string. h>
# Include <unistd. h>
# Include <sys/stat. h>
# Include <event. h>
# Include <evhttp. h>

# Define RELOAD_TIMEOUT 5
# Define DEFAULT_FILE "sample.html"

Char * filedata;
Time_t lasttime = 0;
Char filename [80];
Int counter = 0;

Void read_file ()
{
Int size = 0;
Char * data;
Struct stat buf;

Stat (filename, & buf );

If (buf. st_mtime> lasttime)
{
If (counter ++)
Fprintf (stderr, "Reloading file: % s", filename );
Else
Fprintf (stderr, "Loading file: % s", filename );

FILE * f = fopen (filename, "rb ");
If (f = NULL)
{
Fprintf (stderr, "Couldn't open file \ n ");
Exit (1 );
}

Fseek (f, 0, SEEK_END );
Size = ftell (f );
Fseek (f, 0, SEEK_SET );
Data = (char *) malloc (size + 1 );
Fread (data, sizeof (char), size, f );
Filedata = (char *) malloc (size + 1 );
Strcpy (filedata, data );
Fclose (f );

Fprintf (stderr, "(% d bytes) \ n", size );
Lasttime = buf. st_mtime;
}
}

Void load_file ()
{
Struct event * loadfile_event;
Struct timeval TV;

Read_file ();

TV. TV _sec = RELOAD_TIMEOUT;
TV. TV _usec = 0;

Loadfile_event = malloc (sizeof (struct event ));

Evtimer_set (loadfile_event,
Load_file,
Loadfile_event );

Evtimer_add (loadfile_event,
& TV );
}

Void generic_request_handler (struct evhttp_request * req, void * arg)
{
Struct evbuffer * evb = evbuffer_new ();

Evbuffer_add_printf (evb, "% s", filedata );
Evhttp_send_reply (req, HTTP_ OK, "Client", evb );
Evbuffer_free (evb );
}

Int main (int argc, char * argv [])
{
Short http_port = 8081;
Char * http_addr = "192.168.0.22 ";
Struct evhttp * http_server = NULL;

If (argc> 1)
{
Strcpy (filename, argv [1]);
Printf ("Using % s \ n", filename );
}
Else
{
Strcpy (filename, DEFAULT_FILE );
}

Event_init ();

Load_file ();

Http_server = evhttp_start (http_addr, http_port );
Evhttp_set_gencb (http_server, generic_request_handler, NULL );

Fprintf (stderr, "Server started on port % d \ n", http_port );
Event_dispatch ();
}

The basic principle of this server is the same as the previous example. First, the script sets an HTTP server, which only responds to requests that combine the basic URL host/port (do not process the request URI ). The first step is to load the file (read_file ()). Use this function when loading the initial file and when the timer triggers the callback.

The read_file () function uses the stat () function to call and check the file modification time. The read_file () function reads the file content again only when the file is modified after the last loading. This function loads file data by calling fread (), copies the data to another structure, and transfers the data from the loaded string to the global string using strcpy.

The load_file () function is called when a timer is triggered. It loads content by calling read_file (), and then sets the timer using the RELOAD_TIMEOUT value as the number of seconds before trying to load the file. The libevent timer uses the timeval structure and allows the timer to be specified in seconds and milliseconds. The timer is not periodic. When a timer event is triggered, it is set and then deleted from the event queue.

Compile the code in the same format as the preceding example: $ gcc-o basichttpfile. c-levent.

Now, create a static file used as data. The default file is sample.html, but you can specify any file by using the first parameter on the command line (see Listing 6 ).

Listing 6. Create a static file used as dataCopy codeThe Code is as follows: $./basichttpfile
Loading file: sample.html (8046 bytes)
Server started on port 8081

Now, the program can accept the request, and the reload timer is started. If sample.html is modified, the file is reloaded and a message is recorded in the log. For example, the output in listing 7 shows the initial load and two reloads:
Listing 7. The output shows the initial load and two reloads.Copy codeThe Code is as follows: $./basichttpfile
Loading file: sample.html (8046 bytes)
Server started on port 8081
Reloading file: sample.html (8047 bytes)
Reloading file: sample.html (8048 bytes)

Note: To maximize the benefits, you must ensure that the environment does not limit the number of opened file descriptors. You can use the ulimit command to modify the limits (requires proper permissions or root access ). The specific settings depend on your OS, but on Linux, you can use the-n option to set the number of opened file descriptors (and network sockets:

Listing 8. Use the-n option to set the number of opened file descriptors

$ Ulimit-n
1024

Raise the limit by specifying a number: $ ulimit-n 20000.

You can use performance benchmarks such as Apache iis2 (ab2) to test the server performance. You can specify the number of concurrent queries and the total number of requests. For example, if you use 100,000 requests to run the benchmark test, the number of concurrent requests is 1000: $ ab2-n 100000-c 1000 http: // 192.168.0.22: 8081 /.

Run the example system using the 8 K file shown in the server example, and the result is about 11,000 requests per second. Keep in mind that this libevent server runs in a single thread, and a single client is unlikely to put pressure on the server because it is also limited by the method for opening requests. Even so, such a processing rate is surprising for single-threaded applications when the document size of the switch is moderate.

--------------------------------------------------------------------------------
Back to Top
Implementation in other languages

Although C language is suitable for many system applications, C language is not often used in modern environments, and the script language is more flexible and practical. Fortunately, most scripting languages such as Perl and PHP are written in C, so C libraries such as libevent can be used through the extension module.

For example, listing 9 shows the basic structure of the Perl network server script. The accept_callback () function is the same as the accept function in the core libevent example shown in Listing 1.

Listing 9. Basic Structure of Perl web server scriptCopy codeThe Code is as follows: my $ server = IO: Socket: INET-> new (
LocalAddr => 'localhost ',
LocalPort = & gt; 8081,
Proto => 'tcp ',
ReuseAddr => SO_REUSEADDR,
Listen => 1,
Blocking => 0,
) Or die $ @;

My $ accept = event_new ($ server, EV_READ | EV_PERSIST, \ & accept_callback );

$ Main-> add;

Event_mainloop ();

Libevent implementations written in these languages generally support the core of the libevent system, but do not necessarily support HTTP wrappers. Therefore, it is complicated to use these solutions for scripting applications. There are two methods: either embedding the scripting language into a C-based libevent application, or using one of the many HTTP implementations built based on the scripting language environment. For example, Python contains a highly functional HTTP server class (httplib/httplib2 ).

It should be pointed out that nothing in the scripting language can be re-implemented with C. However, considering the development time constraints, integration with existing code may be more important.

Libev Library

Like libevent, libev is also based on the event loop system. It provides an event-based Loop Based on the local implementation of poll (), select (), and other mechanisms. By the time I wrote this article, libev has lower overhead and can achieve better benchmark test results. Libev APIs are relatively primitive and do not have an HTTP wrapper. However, libev supports more built-in event types. For example, an evstat implementation can monitor attribute changes of multiple files and can be used in the HTTP file solution shown in Listing 4.

However, the basic processes of libevent and libev are the same. Create the required network listening socket, register the event to be called during execution, and then start the main event loop to let the rest of the libev process.

For example, you can use the Ruby interface to provide the echo server in a similar way as listing 1, as shown in listing 10.

Listing 10. Using the Ruby interface to provide the echo serverCopy codeThe Code is as follows: require 'rubygems'
Require 'Rev'

PORT = 8081

Class EchoServerConnection <Rev: TCPSocket
Def on_read (data)
Write 'you said: '+ data
End
End

Server = Rev: TCPServer. new ('192. 168.0.22 ', PORT, EchoServerConnection)
Server. attach (Rev: Loop. default)

Puts "Listening on localhost: # {PORT }"
Rev: Loop. default. run

Ruby is particularly well implemented because it provides wrapper for many common network solutions, including HTTP clients, OpenSSL, and DNS. Other script language implementations include fully functional Perl and Python implementations. You can give it a try.

Conclusion

Both libevent and libev provide flexible and powerful environments and support high-performance network (and other I/O) interfaces for processing server or client requests. The goal is to support thousands or even tens of thousands of connections in an efficient (low CPU/RAM usage) manner. In this article, you have seen some examples, including the built-in HTTP service in libevent, which can be used to support web applications based on IBM Cloud, EC2, or AJAX.

References

Learning

  • C10K problem gives a wonderful overview of the problem of handling 10,000 connections.

  • The IBM Cloud Computing website provides information about different Cloud implementations.
  • Read the system management toolkit: standardize your UNIX command line tool (Martin Brown, developerWorks, May 2006) and learn how to use the same command across multiple machines.
  • Working with UNIX and Linux (Martin Brown, developerWorks, April 2006) explains how to make traditional UNIX distributions work with Linux.
  • Breaking the secrets of cloud computing (Brett McLaughlin, developerWorks, March 2009): helps you select the best cloud computing platform based on your application needs.
  • Read about using Amazon Web Services for cloud computing (Prabhakar Chaganti, developerWorks, November July 2008): describes in detail how to use Amazon Web Services.
  • You can use IBM products for Amazon EC2 through the developerWorks Cloud Computing Resource Center.
  • DeveloperWorks Cloud Computing Resource Center uses IBM products for the Amazon EC2 platform.
  • Discover and share the knowledge and experience of application and service developers building their cloud deployment projects in the cloud developer resources of developerWorks.
  • AIX and UNIX zone: the "AIX and UNIX zone" on developerWorks provides a wealth of information related to all aspects of AIX system management that you can use to extend your UNIX skills.
  • Getting started with AIX and UNIX: visit the "getting started with AIX and UNIX" page to learn more about AIX and UNIX.
  • Summary of AIX and UNIX topics: the AIX and UNIX area has introduced many technical topics for you and summarized many popular knowledge points. We will continue to launch many related hot topics later. To facilitate your access, we will summarize all the topics in this area here, it makes it easier for you to find the content you need.
  • AIX and UNIX download center: here you can download IBM server software and tools that can run on AIX or UNIX systems, so that you can try out their powerful functions for free in advance.
  • IBM Systems Magazine for AIX Chinese version: This Magazine focuses more on trends and enterprise-level architecture applications, at the same time, we also have in-depth discussions on emerging technologies, products, and application methods. IBM Systems Magazine is written by very senior industry insiders, including IBM partners, IBM host engineers, and senior management personnel. Therefore, you can learn a higher level of application concepts from these content, so that you can have a better understanding when choosing and applying IBM systems.
  • Listen to interesting interviews and discussions for software developers on the developerWorks podcast.
  • DeveloperWorks technical events and network broadcasts: Stay tuned to developerWorks technical events and network broadcasts.

Obtain products and technologies

  • Obtain the libev library, including downloads and documents.

  • Obtain the libevent library.
  • Ruby libev (rev) Library and Documentation.
  • Memcached is a RAM cache used to store and process data (its core uses libevent, or other libevent servers ).
  • Use IBM trial software to improve your next open source code development project, which can be downloaded or obtained from a DVD.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.