In Linux network programming, select has been used for event triggering for a long time. In the new Linux kernel, there is a mechanism to replace it, epoll.
The biggest advantage of epoll over SELECT is that it does not decrease in efficiency as the number of FDS monitored increases. Because in the kernel select implementation, it is processed by polling, the more FD data polling, the more time.
The epoll interface has three functions
1) int epoll_create(int size);
Create an epoll handle with size to tell the kernel how big the listening data is.
It is important to note that when the epoll handle is created, it will occupy a FD value. After epoll is used, close must be called to close, otherwise the FD may be exhausted.
2) int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
Epoll registers the function. The first argument is the return value of epoll_create().
The second argument represents the action, represented by three macros
EPOLL_CTL_ADD: Registers a new FD to an EPFD
EPOLL_CTL_MOD: Modifies the listening event of a registered FD
EPOLL_CTL_DEL: Deletes a FD from an EPFD
The third parameter is the FD to listen on
The fourth parameter tells the kernel what to listen for.
Struct_event:
[cpp] view plain copy print ?
- typedef union epoll_data {
- void *ptr;
- int fd;
- __uint32_t u32;
- __uint64_t u64;
- } epoll_data_t;
- 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;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
Copy the code
\
Events can be a collection of the following macros:
EPOLLIN: indicates that the corresponding file descriptor is readable (including that the peer SOCKET is normally closed)
EPOLLOUT: indicates that the corresponding file descriptor can be written
EPOLLPRI: indicates that the corresponding file descriptor has urgent data to read
EPOLLERR Indicates that an error occurs in the corresponding file descriptor
EPOLLHUP: indicates that the corresponding file descriptor is hung up
EPOLLET: Set EPOLL to Edge Triggered mode. This is as opposed to Level Triggered
EPOLLONSHOT: only listens once. If the listener needs to listen on the socket after the event is played, the socket needs to be added to the WPOLL queue again
3) int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
Maxevents tells the kernel how big events are. The value of maxEvents cannot be larger than the size at which epoll_create() was created. Timeout is the timeout time (milliseconds) There is talk of permanent blockage. This function returns the number of events that need to be processed, with a return of 0 indicating timeout.
There are two working modes of ET.LT
The ET mode is notified only when the state sends a change, which does not include unprocessed data in the buffer. If you want to use ET mode, you need to keep reading/writing until an error occurs. Many people reflect why the ET mode only receives part of the data and then no longer gets the notification, mostly because of this.
The LT mode will keep notifying as long as there is data that is not being processed.
Epoll model
First create an epoll handle with create_epoll(int maxfds), where maxfds is the maximum number of handles supported by your epoll. This function returns a new epoll handle from which all subsequent operations will be performed. When you’re done, close the created epoll handle with close(). Then, in your network main loop, call epoll_wait(int EPfd, epoll_event Events, int Max Events, int timeout) for each frame to query all network interfaces to see which are readable and which are writable. The basic syntax is: NFDS = epoll_wait(KDPFD, events, maxEvents, -1); KDPFD is the handle created using epoll_CREATE, and events is a pointer to epoll_event*. After epoll_wait succeeds, epoll_events stores all read and write events. Max_events is the number of all socket handles that need to be listened on. The last timeout is the epoll_wait timeout. If it is 0, it will return immediately; if it is -1, it will wait until there is an event range; if it is any positive integer, it will wait that long; if there is no event, it will wait. In general, if the network main loop is a separate thread, you can use -1 to ensure some efficiency. If the main loop is in the same thread as the main logic, you can use 0 to ensure the efficiency of the main loop. The epoll_wait range should be followed by a loop that iterates through all events. \
[cpp] view plain copy print ?
- #include<iostream>
- #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>
- using namespace std;
- int main(int argc,char *argv[])
- {
- int maxi,listenfd,connfd,sockfd,epfd,nfds;
- ssize_t n;
- char line[100];
- listenfd = socket(AF_INET,SOCK_STREAM,0);
- // Declare the epoll_event structure variable,ev to register the event, and array to return the event to be processed
- struct epoll_event ev,events[20];
- epfd = epoll_create(256);
- ev.date.fd = listenfd;
- ev.events = EPOLLIN|EPOLLET;
- epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); // Register epoll events
- struct sockaddr_in serveraddr;
- bzero(&serveraddr,sizeof(serveraddr));
- Char * local_addr = “127.0.0.1”;
- inet_aton(local_addr,&(serveraddr.sin_addr));
- serveraddr.sin_port=htons(8888);
- bind(listenfd,(sockaddr*)&serveraddr,sizeof(serveraddr));
- listen(listenfd,LISTENQ);
- maxi=0;
- for(;;)
- {
- // Wait for the epoll event to occur
- NFDS = epoll_wait (epfd, events, 20500);
- // Handle all events that occur
- for(int i = 0; i
- {
- If (events[I].data.fd == listenfd)// If a new SOCKET user is detected to be connected to the bound SOCKET port, a new connection is established
- {
- struct sockaddr_in clientaddr;
- socketlen_t clilen;
- connfd = accept(listenfd,(sockaddr *)&clientaddr,&clilen);
- char *str = inet_ntoa(clientaddr.sin_addr);
- cout <<“accept a connection from “<<str<<endll;
- ev.data.fd = connfd;
- ev.events = EPOLLIN|EPOLLET;
- epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
- }
- Else if(events[I].events & EPOLLIN) // If the user is connected and data is received, then read in
- {
- sockfd = events[i].data.fd;
- n = read(sockfd,line,100);
- line[n] = ‘\0’;
- cout <<“read msg :”<<line<<endl;
- ev.data.fd = sockfd;
- ev.events = EPOLLOUT|EPOLLEN;
- epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
- }
- else if(events[i].events&EPOLLOUT)
- {
- sockfd = events[i].data.fd;
- write(sockfd,line,n);
- ev.data.fd = sockfd;
- ev.events = EPOLLIN|EPOLLET;
- epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
- }
- }
- }
- return 0;
- }
#include<iostream>
#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>
using namespace std;
int main(int argc,char *argv[])
{
int maxi,listenfd,connfd,sockfd,epfd,nfds;
ssize_t n;
char line[100];
listenfd = socket(AF_INET,SOCK_STREAM,0);
// Declare the epoll_event structure variable,ev to register the event, and array to return the event to be processed
struct epoll_event ev.events[20].
epfd = epoll_create(256);
ev.date.fd = listenfd;
ev.events = EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); // Register epoll events
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
char *local_addr = "127.0.0.1";
inet_aton(local_addr,&(serveraddr.sin_addr));
serveraddr.sin_port=htons(8888);
bind(listenfd,(sockaddr*)&serveraddr,sizeof(serveraddr));
listen(listenfd,LISTENQ);
maxi=0;
for(;;)
{
// Wait for the epoll event to occur
nfds = epoll_wait(epfd,events,20.500);
// Handle all events that occur
for(int i = 0; i <nfds; i++) {if(events[i].data.fd == listenfd) // If a new SOCKET user is detected connecting to the bound SOCKET port, establish a new connection
{
struct sockaddr_in clientaddr;
socketlen_t clilen;
connfd = accept(listenfd,(sockaddr *)&clientaddr,&clilen);
char *str = inet_ntoa(clientaddr.sin_addr);
cout <<"accept a connection from "<<str<<endll;
ev.data.fd = connfd;
ev.events = EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events & EPOLLIN) // If it is a connected user and data is received, then read in
{
sockfd = events[i].data.fd;
n = read(sockfd,line,100);
line[n] = '\ 0';
cout <<"read msg :"<<line<<endl;
ev.data.fd = sockfd;
ev.events = EPOLLOUT|EPOLLEN;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
else if(events[i].events&EPOLLOUT)
{
sockfd = events[i].data.fd;
write(sockfd,line,n);
ev.data.fd = sockfd;
ev.events = EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); }}}return 0;
}
Copy the code
\
Epoll is different from select
1. Support a process to open a large number of socket descriptors (FD)
The most intolerable thing about select is that there is a limit to the number of FDS a process can open, set by FD_SETSIZE. The default value is 2048. That’s too little for an IM server that needs to support tens of thousands of connections. At this point, you can either modify the macro and recompile the kernel, but the data also points out that this can lead to a decrease in network efficiency
2. IO efficiency does not decrease linearly with the increase of FD number
Another fatal weakness of traditional SELECT /poll is when you have a large set of sockets, but due to network latency, only some of the sockets are “active” at any one time, but select/poll scans the entire set linearly with each call, resulting in a linear decrease in efficiency. Epoll does not have this problem and only operates on “active” sockets-this is because in the kernel implementation epoll is implemented according to the callback function on each FD.
Epoll uses event-based readiness notification. In select/poll, the kernel scans all monitored file descriptors only after a certain method is called. Epoll registers a file descriptor through epoll_ctl(). Once a file descriptor is ready, the kernel uses a callback mechanism similar to callback. Activate this file descriptor quickly to be notified when the process calls epoll_wait().
3. Use Mmap to speed up kernel-user space messaging
Either SELECT, poll, or epoll requires the kernel to notify user space of FD messages. How to avoid unnecessary memory copying is important. In this case, epoll is implemented by the kernel in the same memory as user space Mmap
4. Kernel fine tuning
This is not really a strength of Epoll, but of the Linux platform as a whole.