Ways to improve network application performance using Libevent and Libev _linux

Source: Internet
Author: User
Tags benchmark fread http request int size wrapper wrappers root access ibm server
There are many solutions, but the Libevent library and the Libev library can greatly improve performance and event handling capabilities. In this article, we 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 IBM Cloud or Amazon EC2 environments that need to support a large number of concurrent clients or operations.
Brief Introduction
One of the biggest problems facing many server deployments, especially Web server deployments, is the need to be able to handle a large number of connections. Whether you're building cloud-based services to handle network traffic, distributing applications to the IBM Amazon EC instance, or delivering high-performance components to your Web site, you need to be able to handle a large number of concurrent connections.
A good example is that Web applications have become more and more dynamic recently, especially in applications that use AJAX technology. If the system being deployed allows thousands of of clients to update information directly on a Web page, such as a system that provides real-time monitoring of events or problems, the speed at which information is provided is very important. In a grid or cloud environment, there may be persistent connections from thousands of clients that are open at the same time, and must be able to handle each client's request and respond.
Before we discuss how libevent and Libev handle multiple network connections, let's briefly review the traditional solutions for dealing with such connections.

working with multiple clients

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

The main methods used are as follows:

Loops: Early systems use a simple loop-selection solution that loops through the list of open Network connections to determine if there is data to read. This approach is slow (especially as the number of connections increases) and inefficient (because other connections may be sending requests and waiting for a response while the current connection is being processed). When the system loops through each connection, the other connections have to wait. If you have 100 connections, and only one of them has data, you still have to process the other 99 connections before you can turn to the ones that really need to be handled.
Poll, Epoll, and variants: This is an improvement on the loop method, which uses a structure to hold an array of each connection to monitor, and when data is found on a network socket, the handler function is invoked through the callback mechanism. The problem with poll is that this structure can be very large, and modifying the structure increases the load and affects performance when adding new network connections to the list.
Select: the Select () function call uses a static structure that is hard-coded into a fairly small number (1024 connections) and therefore does not apply to very large deployments.
There are other implementations on a variety of platforms (such as the kqueue on Solaris/dev/poll or FREEBSD/NETBSD), which may perform better on their respective OS, but are not portable and do not necessarily address the high level of processing requests.
All of the above solutions use a simple loop to wait and process the request, and then assign the request to another function to handle the actual network interaction. The key is that loops and network sockets require a lot of management code to monitor, update, and control different connections and interfaces.
Another way to handle many connections is to use multithreading in the modern kernel to support listening and processing connections, starting a new thread for each connection. This takes responsibility directly to the operating system, but adds considerable overhead to RAM and CPU because each thread needs its own execution space. Also, if each thread is busy working on a network connection, the context switches between threads are frequent. Finally, many kernels are not suitable for handling such a large number of active threads.

Libevent method

The Libevent library has virtually no basis for replacing Select (), poll (), or other mechanisms. Instead, it uses the previous wrapper for the implementation of the most efficient high-performance solution for each platform.
To actually process each request, the Libevent library provides an event mechanism that acts as a wrapper over the back end of the underlying network. Event systems make it easy to add processing functions to a connection, while reducing underlying I/O complexity. This is the core of the libevent system.
Other components of the Libevent library provide additional functionality, including buffered event systems for buffering data sent to or from clients, and core implementations of HTTP, DNS, and RPC systems.
The basic way to create a libevent server is to register a function that should be executed when an action occurs, such as accepting a connection from a 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 the event and the function that will be invoked, the event system begins to become autonomous; When the application runs, you can add (register) or delete (unregister) events in the event queue. Event registration is a convenient time to build a flexible network processing system by adding new events to handle newly opened connections.
For example, you can open a listening socket and register a callback function that calls this callback function whenever you need to invoke the accept () function to open a new connection, creating a network server. The code snippet shown in Listing 1 illustrates the basic process:

Listing 1. Open a listening socket, register a callback function (call it whenever you need to call the Accept () function to open a new connection), and create a network server
Copy Code code 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 an event to the event queue mechanism. Then, Event_dispatch () starts the event queuing system and starts listening (and accepting) the request.

Listing 2 shows a more complete example that builds a very simple echo server:
Listing 2. Building a simple Echo server
Copy Code code 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-&GT;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-&GT;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 functions and their actions are discussed below:

Main (): The main function creates a socket that listens for connections, and then creates a callback function for accept () to handle each connection through the event handler function.
Accept_callback (): This function is called by the event system when the connection is accepted. This function accepts a connection to the client, adds client socket information and a bufferevent structure, adds a callback function to a read/write/Error event on the client socket in the event structure, and passes the client structure (and embedded eventbuffer and client sockets) as a parameter. Invokes the corresponding callback function whenever the corresponding client socket contains a read, write, or error operation.
Buf_read_callback (): Called when the client socket has data to read. As a echo service, this function puts "you said ..." Write back to the client. The socket is still open and you can accept the new request.
Buf_write_callback (): Call it when there is data to write. In this simple service, this function is not required, so the definition is empty.
Buf_error_callback (): Called when an error occurs. This includes a client interrupt connection. In all scenarios where the error occurred, close the client socket, remove the client socket event entry from the list of events, and free the client structure's memory.
Setnonblock (): Sets the network socket to open I/O.
When the client connects, adds a new event to the event queue to handle the client connection, and deletes the event when the client disconnects. Behind the scenes, Libevent handles network sockets, identifies the clients that need it, and calls the corresponding functions separately.

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

From the client's point of view, the server simply sends back any text sent to it (see Listing 3).


Listing 3. The server sends back the text sent to it
Copy Code code as follows:

$ telnet localhost 8080
Trying 127.0.0.1 ...
Connected to localhost.
Escape character is ' ^] '.
Hello!
You said Hello!

Such a network application is ideal for large distributed deployments that need to handle multiple connections, such as IBM Cloud systems.

It is difficult to observe a large number of concurrent connections and performance improvements with simple solutions. You can use an embedded HTTP implementation to help understand scalability.

Using the built-in HTTP server

If you want to build native applications, you can use a generic web-based libevent interface, but the increasingly common scenario is to develop applications based on HTTP protocols and Web pages that load or dynamically reload information. If you use any AJAX library, the client needs HTTP, even if the information you return is XML or JSON.

The HTTP implementation in Libevent is not a substitute for Apache HTTP servers, but rather a practical solution for large-scale dynamic content associated with the cloud and web environment. For example, a libevent interface can be deployed in an IBM Cloud or other solution. 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 primary network event model, but you must also handle the network interface, which the HTTP wrapper will handle. This turns the entire process into four function calls (initializing, starting the HTTP server, setting the HTTP callback function and entering the event loop), plus a callback function to send back the data. Listing 4 shows a very simple example:


Listing 4. A simple example of using the Libevent service
Copy Code code 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, "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);
}

You should be able to see the basic structure of the code in the previous example, and you don't need to explain. The primary element is the EVHTTP_SET_GENCB () function, which sets the callback function to use when an HTTP request is received, and the Generic_request_handler () callback function itself (it fills the response buffer with a simple message that represents success).

The HTTP wrapper provides many other features. For example, there is a request parser that extracts query parameters from a typical request (as with a CGI request). You can also set up a handler function to be triggered in a different request path. By setting different callback functions and handler functions, you can use the path '/db/' to provide interfaces to the database, or use '/MEMC ' to provide interfaces to the memcached.

Another feature of the Libevent Toolkit is that it supports universal timers. Events can be triggered after a specified period of time. You can provide lightweight services by using a combination of timers and HTTP implementations to automatically provide file content and update the returned data when modifying the contents of a file. For example, to provide an immediate updating service during a news-frequency event, a front-end Web application needs to periodically reload the newsletter, and now it's easy to provide content. The entire application (and Web service) is in memory, so the response is very fast.

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


Listing 5. Use timers to provide immediate updating services during news-prone activities
Copy Code code 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,&AMP;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,
&AMP;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 rationale for this server is the same as the previous example. First, the script sets up an HTTP server that responds only to requests for a base URL host/port combination (not processing the request URI). The first step is to load the file (Read_file ()). This function is used when the original file is loaded and when the callback is triggered by the timer.

The Read_file () function uses the stat () function call to check the modification time of a file before it re-read the contents of the file until the file was modified after the last mount. This function loads the file data by calling Fread (), copies the data into another structure, and then uses strcpy () to transfer the data from the loaded string to the global string.

The Load_file () function is the function that is invoked when the timer is triggered. It loads the content by calling Read_file (), and then uses the Reload_timeout value to set the timer as the number of seconds before the file is attempted to load. The libevent timer uses the TIMEVAL structure to allow timers to be specified in seconds and milliseconds. The timer is not cyclical, set when the timer event is triggered, and then deletes the event from the event queue.

Compile the code using the same format as the previous example: $ Gcc-o basichttpfile basichttpfile.c-levent.

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


Listing 6. Create a static file to use as data
Copy Code code as follows:

$./basichttpfile
Loading file:sample.html (8046 bytes)
Server started on port 8081


Now that the program can accept the request, the reload timer is also started. If you modify the contents of the sample.html, you should reload the file and record a message in the log. For example, the output in Listing 7 shows the initial load and two reload:
Listing 7. Output shows initial load and two reload
Copy Code code 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 that for maximum benefit, you must ensure that the environment does not limit the number of open file descriptors. You can use the Ulimit command to modify restrictions (requires appropriate permissions or root access). The specific settings depend on your OS, but you can set the number of open file descriptors (and network sockets) with the-n option on the linux®:


Listing 8. Set the number of open file descriptors with the-n option

$ ulimit-n
1024



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

Performance benchmarking applications such as the Apache Bench 2 (AB2) can be used to check server performance. You can specify the number of concurrent queries and the total number of requests. For example, to run benchmark tests with 100,000 requests, the number of concurrent requests is 1000: $ ab2-n 100000-c 1000 http://192.168.0.22:8081/.

Use the 8K file shown in the server example to run the sample system with a result of processing 11,000 requests per second. Keep in mind that this libevent server runs on a single thread, and a single client is less likely to put pressure on the server because it is also limited by the method of opening the request. However, this rate of processing is still surprising for single-threaded applications in the case of a moderate size of the document being exchanged.


--------------------------------------------------------------------------------
Back to the top of the page
Using implementations in other languages

Although the C language is well suited for many system applications, the C language is not frequently used in modern environments, and scripting languages are more flexible and practical. Luckily, most scripting languages, such as Perl and PHP, are written in C, so you can use the Libevent C library through the extension module.

For example, listing 9 gives 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 network server scripts
Copy Code code as follows:

My $server = Io::socket::inet->new (
Localaddr => ' localhost ',
LocalPort => 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 typically support the core of the libevent system, but do not necessarily support HTTP wrappers. Therefore, it is more complex to use these solutions for scripting applications. There are two approaches: either embed the scripting language in a C-based libevent application or use one of the many HTTP implementations built on a scripting locale. For example, Python contains a powerful HTTP server class (HTTPLIB/HTTPLIB2).

It should be noted that there is nothing in the scripting language that cannot be implemented in C again. However, considering the limitations of development time, and integration with existing code may be more important.

Libev Library

Like Libevent, the Libev system is also a system based on event cycling, which provides an event-based loop based on the native implementation of poll (), select () and other mechanisms. By the time I wrote this article, the Libev implementation was less expensive and was able to achieve better benchmark results. The Libev API is primitive and does not have an HTTP wrapper, but Libev supports more event types built into the implementation. For example, a EVSTAT implementation can monitor property changes for multiple files, and it can be used in the HTTP file solution shown in Listing 4.

However, the basic process of libevent and Libev is the same. Create the required network listening sockets, register the events to be invoked during execution, and then start the main event loop, allowing Libev to process the remainder of the process.

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


Listing 10. Provide echo server with Ruby interface
Copy Code code 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



The Ruby implementation is particularly good because it provides wrappers for many common network solutions, including HTTP clients, OpenSSL, and DNS. Other scripting language implementations include a full-featured Perl and Python implementation that you can try.

Conclusion

Both Libevent and Libev provide a flexible and powerful environment that enables High-performance network (and other I/O) interfaces for processing server-side or client requests. The goal is to support thousands of or even tens of thousands of connections in a highly efficient (Cpu/ram) way. In this article, you see some examples, including the HTTP services built into the libevent, that can be used to support Web applications based on IBM Cloud, EC2, or AJAX.

Resources

Learn

  • c10k problem A wonderful overview of the handling of 10,000 connections.

  • The IBM Cloud Computing Web site provides information about different cloud implementations.

  • Read the System Management Kit: Standardize your UNIX command-line tools (Martin brown,developerworks,2006 year May) to learn how to use the same commands across multiple machines.

  • Let Unix and Linux work together (Martin brown,developerworks,2006 year April) to explain how to get the traditional UNIX release and Linux working together.

  • Discover Cloud Computing (Brett mclaughlin,developerworks,2009 March): help you choose the best cloud computing platform based on your application needs.

  • Read cloud computing with Amazon Web Services (Prabhakar chaganti,developerworks,2008 July): Detailed explanation of how to use Amazon Web services.

  • IBM products that apply to the Amazon EC2 platform can be used by DeveloperWorks Cloud Computing Resource Center.

  • DeveloperWorks Cloud Computing Resource Center uses IBM products that apply to the Amazon EC2 platform.

  • Discover and share the knowledge and experience of application and service developers to build their cloud deployment projects in DeveloperWorks's cloud developer resources.

  • Aix and Unix zones: DeveloperWorks's Aix and UNIX Zone provides a wealth of information about all aspects of AIX system management that you can use to extend your UNIX skills.

  • Getting Started with Aix and UNIX beginners: Visit the "Getting Started with Aix and UNIX Beginners" page for more information on Aix and UNIX.

  • Aix and UNIX feature rollup: the Aix and UNIX zones have already launched a number of technical topics for you, summarizing a number of popular knowledge points. We will continue to launch a number of related hot topics to you, in order to facilitate your visit, we are here for you to the area of all the topics summarized, so that you more convenient to find the content you need.

  • Aix and UNIX Download Center: Here you can download the IBM Server software and tools that you can run on AIX or on UNIX systems, allowing you to try out their powerful features in advance for free.

  • IBM Systems Magazine for AIX Chinese: The magazine's content is more focused on trends and enterprise-class architecture applications, as well as on emerging technologies, products, and applications. IBM Systems Magazine's content is written by highly experienced industry insiders, including IBM's partners, IBM's mainframe engineers, and senior management staff. So, from this content, you can learn a higher level of application concepts, so that you have a better understanding of the selection and application of the IBM system.

  • Listen to interesting interviews and discussions for software developers on the DeveloperWorks podcast.

  • DeveloperWorks Technical Activities and webcasts: Keep an eye on developerWorks technical activities and webcasts.

Access to products and technology

    • Get the Libev library, including downloads and documentation.

    • Gets the Libevent library.

    • Ruby Libev (REV) library and documentation.

    • Memcached is the RAM cache that is used to store and process data (its core uses libevent, or it can use other libevent servers).

    • Use the IBM trial software to improve your next open source development project, which can be downloaded or obtained from DVDs.

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.