Linux epoll introduction and program example
1. Where is epoll sacred?
Epoll is currently a hot candidate for developing large-scale concurrent network programs in Linux. epoll is officially introduced in the linux2.6 kernel, similar to select, in fact bothI/OMultiplexing TechnologyAnd there is nothing mysterious.
In fact, there is always no way to design concurrent network programs in Linux, such as the typical Apache model (process per connection (PPC) and TPC (thread per connection) models, as well as the select model and poll model, why should we introduce epoll? That's something to talk about...
2. disadvantages of common models
If the disadvantages of other models are not shown, how can we compare the advantages of epoll.
2.1 PPC/TPC Model
These two models share the same idea:Let every incoming connection work on its own. Don't bother me anymore.. Only PPC opens a process for it, while TPC opens a thread. But don't bother me. There is a price. It requires time and space. When there are too many connections, so many processes/threads will switch, and this overhead will come up; therefore, the maximum number of connections that this type of model can accept is not high, generally about several hundred.
2.2 select model
1. maximum number of concurrent threads, because the FD opened by a process (file descriptor) is limited, which is set by fd_setsize. The default value is 1024/2048, therefore, the maximum concurrency of the select model is limited accordingly. Modify fd_setsize by yourself? Although the idea is good, let's take a look at the following...
2. efficiency problems: each select call will linearly scan all FD sets, which will result in a linear decline in efficiency. The consequence of increasing the fd_setsize is that everyone is coming slowly. What? All timeout ??!!
3. How does the kernel notify the user of the FD message when copying the kernel/user space memory? On this issue, select adopts the memory copy method.
2.3 poll Model
Basically, the efficiency is the same as that of select. The two and three disadvantages of select are not modified.
3. epoll Improvement
I criticized other models one by one. Let's take a look at epoll's improvements. In fact, the disadvantages of select are the advantages of epoll.
3.1. epoll does not have the maximum number of concurrent connections. The maximum number is the maximum number of files that can be opened. This number is generally greater than 2048,Generally, this number has a great relationship with the system memory.The number can be viewed in CAT/proc/sys/fs/file-max.
3.2. The biggest advantage of epoll is its efficiency improvementOnly your "active" ConnectionsAnd has nothing to do with the total number of connections. Therefore, in the actual network environment, epoll is much more efficient than select and poll.
3.3. memory copy, epoll uses"Shared Memory", This memory copy is also omitted.
4. Why is epoll efficient?
The efficiency of epoll is inseparable from the design of its data structure, which will be mentioned below.
First, let's recall the select model. When an I/O event arrives, select notifies the application that an event has been processed quickly, and the application must Round-Robin all FD sets, test whether an event occurs in each FD and process the event. The Code is as follows:
Int res = select (maxfd + 1, & readfds, null, null, and 120 );
If (RES> 0)
{
For (INT I = 0; I <max_connection; I ++)
{
If (fd_isset (allconnection [I], & readfds ))
{
Handleevent (allconnection [I]);
}
}
}
// If (RES = 0) handle timeout, Res <0 handle error
Epoll not only tells the application that there is an I/0 event, but also tells the application-related information, which is filled by the application, therefore, based on this information, the application can directly locate the event without traversing the entire FD set.
Int res = epoll_wait (epfd, events, 20,120 );
For (INT I = 0; I <res; I ++)
{
Handleevent (events [N]);
}
5. epoll Key Data Structure
As mentioned above, epoll is fast and closely related to its data structure. Its key data structure is:
Struct epoll_event {
_ Uint32_t events; // epoll events
Epoll_data_t data; // user data variable
};
Typedef Union epoll_data {
Void * PTR;
Int FD;
_ Uint32_t u32;
_ Uint64_t u64;
} Epoll_data_t;
It can be seen that epoll_data is a Union struct, which can store many types of information such as FD and pointer. With it, the application can directly locate the target.
6. Use epoll
Since epoll is better than select, how can it be used? Will it be complicated... Let's take a look at the three functions below to see how easy epoll is to use.
Int epoll_create (INT size );
Generating an epoll-specific file descriptor is actually applying for a kernel space to store whether or not the socket FD you want to pay attention to occurs and what events have occurred. Size is the maximum number of socket FD you can pay attention to on this epoll FD. The size is customized as long as the memory is sufficient.
Int epoll_ctl (INT epfd, int op, int FD, struct epoll_event * event );
Controls events on an epoll file descriptor: Registration, modification, and deletion. The epfd parameter is the file descriptor used to create epoll for epoll_create. Compared with the fd_set and fd_clr macros in the select model.
Int epoll_wait (INT epfd, struct epoll_event * events, int maxevents, int timeout );
Wait for the occurrence of an I/O event. parameter description:
Epfd:Epoll_create ()The generated epoll-specific file descriptor;
Epoll_event: the array used to return the events to be processed;
Maxevents: Number of events that can be processed each time;
Timeout: the timeout value for waiting for an I/O event;
Number of events returned.
Compared with the select function in the select model.
7. Example Program
The following is an example of a simple echo server program. Although it is small and dirty, it also contains a simple timeout check mechanism. For the sake of simplicity, no error handling is performed.
[CPP]View plaincopy
- //
- // A simple echo server using epoll in Linux
- //
- // 11-011-
- // By sparkling
- //
- # Include <sys/socket. h>
- # Include <sys/epoll. h>
- # Include <netinet/in. h>
- # Include <ARPA/inet. h>
- # Include <fcntl. h>
- # Include <unistd. h>
- # Include <stdio. h>
- # Include <errno. h>
- # Include <iostream>
- Using namespace STD;
- # Define max_events 500
- Struct myevent_s
- {
- Int FD;
- Void (* call_back) (int fd, int events, void * Arg );
- Int events;
- Void * ARG;
- Int status; // 1: In epoll wait list, 0 not in
- Char buff [128]; // Recv Data Buffer
- Int Len;
- Long last_active; // last active time
- };
- // Set event
- Void eventset (myevent_s * eV, int FD, void (* call_back) (INT, Int, void *), void * Arg)
- {
- Ev-> FD = FD;
- Ev-> call_back = call_back;
- Ev-> events = 0;
- Ev-> Arg = ARG;
- Ev-> Status = 0;
- Ev-> last_active = Time (null );
- }
- // Add/MoD an event to epoll
- Void eventadd (INT epollfd, int events, myevent_s * eV)
- {
- Struct epoll_event EPV = {0, {0 }};
- Int op;
- EPV. Data. PTR = EV;
- EPV. Events = ev-> events = events;
- If (ev-> Status = 1 ){
- OP = epoll_ctl_mod;
- }
- Else {
- OP = epoll_ctl_add;
- Ev-> Status = 1;
- }
- If (epoll_ctl (epollfd, op, ev-> FD, & EPV) <0)
- Printf ("event add failed [FD = % d]/n", ev-> FD );
- Else
- Printf ("event add OK [FD = % d]/n", ev-> FD );
- }
- // Delete an event from epoll
- Void eventdel (INT epollfd, myevent_s * eV)
- {
- Struct epoll_event EPV = {0, {0 }};
- If (ev-> status! = 1) return;
- EPV. Data. PTR = EV;
- Ev-> Status = 0;
- Epoll_ctl (epollfd, epoll_ctl_del, ev-> FD, & EPV );
- }
- Int g_epollfd;
- Myevent_s g_events [max_events + 1]; // g_events [max_events] is used by listen FD
- Void recvdata (int fd, int events, void * Arg );
- Void senddata (int fd, int events, void * Arg );
- // Accept new connections from clients
- Void acceptconn (int fd, int events, void * Arg)
- {
- Struct sockaddr_in sin;
- Socklen_t Len = sizeof (struct sockaddr_in );
- Int NFD, I;
- // Accept
- If (NFD = accept (FD, (struct sockaddr *) & sin, & Len) =-1)
- {
- If (errno! = Eagain & errno! = Eintr)
- {
- Printf ("% s: Bad accept", _ FUNC __);
- }
- Return;
- }
- Do
- {
- For (I = 0; I <max_events; I ++)
- {
- If (g_events [I]. Status = 0)
- {
- Break;
- }
- }
- If (I = max_events)
- {
- Printf ("% s: Max connection limit [% d].", _ FUNC __, max_events );
- Break;
- }
- // Set nonblocking
- If (fcntl (NFD, f_setfl, o_nonblock) <0) break;
- // Add a read event for receive data
- Eventset (& g_events [I], NFD, recvdata, & g_events [I]);
- Eventadd (g_epollfd, epollin | epollet, & g_events [I]);
- Printf ("New conn [% s: % d] [Time: % d]/n", inet_ntoa (sin. sin_addr), ntohs (sin. sin_port), g_events [I]. last_active );
- } While (0 );
- }
- // Receive data
- Void recvdata (int fd, int events, void * Arg)
- {
- Struct myevent_s * EV = (struct myevent_s *) ARG;
- Int Len;
- // Receive data
- Len = Recv (FD, ev-> buff, sizeof (ev-> buff)-1, 0 );
- Eventdel (g_epollfd, Ev );
- If (LEN> 0)
- {
- Ev-> Len = Len;
- Ev-> buff [Len] = '/0 ';
- Printf ("C [% d]: % s/n", FD, ev-> buff );
- // Change to send event
- Eventset (EV, FD, senddata, Ev );
- Eventadd (g_epollfd, epollout | epollet, Ev );
- }
- Else if (LEN = 0)
- {
- Close (ev-> FD );
- Printf ("[FD = % d] closed gracefully./N", FD );
- }
- Else
- {
- Close (ev-> FD );
- Printf ("Recv [FD = % d] error [% d]: % s/n", FD, errno, strerror (errno ));
- }
- }
- // Send data
- Void senddata (int fd, int events, void * Arg)
- {
- Struct myevent_s * EV = (struct myevent_s *) ARG;
- Int Len;
- // Send data
- Len = Send (FD, ev-> buff, ev-> Len, 0 );
- Ev-> Len = 0;
- Eventdel (g_epollfd, Ev );
- If (LEN> 0)
- {
- // Change to receive event
- Eventset (EV, FD, recvdata, Ev );
- Eventadd (g_epollfd, epollin | epollet, Ev );
- }
- Else
- {
- Close (ev-> FD );
- Printf ("Recv [FD = % d] error [% d]/n", FD, errno );
- }
- }
- Void initlistensocket (INT epollfd, short port)
- {
- Int listenfd = socket (af_inet, sock_stream, 0 );
- Fcntl (listenfd, f_setfl, o_nonblock); // set non-blocking
- Printf ("server listen FD = % d/N", listenfd );
- Eventset (& g_events [max_events], listenfd, acceptconn, & g_events [max_events]);
- // Add listen socket
- Eventadd (epollfd, epollin | epollet, & g_events [max_events]);
- // Bind & listen
- Sockaddr_in sin;
- Bzero (& sin, sizeof (SIN ));
- Sin. sin_family = af_inet;
- Sin. sin_addr.s_addr = inaddr_any;
- Sin. sin_port = htons (port );
- BIND (listenfd, (const sockaddr *) & sin, sizeof (SIN ));
- Listen (listenfd, 5 );
- }
- Int main (INT argc, char ** argv)
- {
- Short Port = 12345; // default port
- If (argc = 2 ){
- Port = atoi (argv [1]);
- }
- // Create epoll
- G_epollfd = epoll_create (max_events );
- If (g_epollfd <= 0) printf ("create epoll failed. % d/N", g_epollfd );
- // Create & bind listen socket, and add to epoll, set non-blocking
- Initlistensocket (g_epollfd, Port );
- // Event Loop
- Struct epoll_event events [max_events];
- Printf ("server running: Port [% d]/n", Port );
- Int checkpos = 0;
- While (1 ){
- // A simple timeout check here, every time 100, better to use a mini-heap, and add timer event
- Long Now = Time (null );
- For (INT I = 0; I <100; I ++, checkpos ++) // doesn' t check listen FD
- {
- If (checkpos = max_events) checkpos = 0; // Recycle
- If (g_events [checkpos]. status! = 1) continue;
- Long Duration = now-g_events [checkpos]. last_active;
- If (duration> = 60) // 60 s timeout
- {
- Close (g_events [checkpos]. FD );
- Printf ("[FD = % d] timeout [% d -- % d]./N", g_events [checkpos]. FD, g_events [checkpos]. last_active, now );
- Eventdel (g_epollfd, & g_events [checkpos]);
- }
- }
- // Wait for events to happen
- Int FDS = epoll_wait (g_epollfd, events, max_events, 1000 );
- If (FDS <0 ){
- Printf ("epoll_wait error, exit/N ");
- Break;
- }
- For (INT I = 0; I <FDS; I ++ ){
- Myevent_s * EV = (struct myevent_s *) events [I]. Data. PTR;
- If (events [I]. Events & epollin) & (ev-> events & epollin) // read event
- {
- Ev-> call_back (ev-> FD, events [I]. Events, ev-> Arg );
- }
- If (events [I]. Events & epollout) & (ev-> events & epollout) // write event
- {
- Ev-> call_back (ev-> FD, events [I]. Events, ev-> Arg );
- }
- }
- }
- // Free resource
- Return 0;
- }