Introduction
“In computer network programming, multi-channel IO technology is a very common technology. Among them, the Poll function and the Epoll function are the two most commonly used multi-channel IO technologies. These two technologies can help the server handle the processing of multiple clients. Concurrent requests improve server performance. This article will introduce the use of Poll and Epoll functions, and discuss the processes and precautions for using these two technologies in server development.”
Introduction to poll function
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(man poll call)
Function Description: Similar to select, it entrusts the kernel to monitor readable, writable, and abnormal events
Function parameters:
fds: The first address of a struct pollfd structure array
struct pollfd {
int fd;//The file descriptor to be monitored, if fd is -1, it means the kernel will no longer monitor
short events; //Input parameters, indicating events to be monitored by the kernel, read events, write events, exception events
short revents;//Output parameters, indicating that the kernel tells the application which file descriptors have events occurred
};
events/revents:
POLLIN: readable event, you need to write this to let the kernel monitor read events
POLLOUT: Writable event, the buffer can be written before it is full
POLLERR: Exception event
nfds: tells the kernel the scope of monitoring, specifically: the maximum value of the array subscript + 1
timeout:
=0: No blocking, return immediately
-1: means blocking until an event occurs
>0: Indicates the blocking duration. If an event occurs within the duration, it will return immediately;
If the time is exceeded, it will return immediately
Function return value:
>0: The number of changed file descriptors
=0: No file descriptors changed
-1: Indicates exception
poll function development process
1 Create socket and get the listening file descriptor, lfd —– socket();
2 Set port reuse———-setsockopt()
3 Binding —— bind()
4
struct pollfd client[1024]; client[0].fd = lfd; // You can put it anywhere, put it at the end for easy use client[0].events = POLLIN; //Monitor read events. If you also want it to monitor writable events, use or // Set fd to -1, indicating that the kernel is not monitoring. This is an initialization int maxi = 0; // Define the maximum array index for(int i = 0;i < 1024;i + + ) { client[i].fd = -1; } //Entrust the kernel to continuously monitor k= 0; while(1) { nready = poll(client,maxi + 1,-1); //abnormal situation if(nready < 0 ) { if(error == EINTR) { continue; } break; } if(client[0].revents = POLLIN) { //Accept new client connections k++; cfd = Accept(lfd,NULL,NULL); /*Continue to entrust the kernel to listen for events Find available positions in client array*/ for(i = 0;i < 1024;i + + ) { if(client[i].fd ==-1) { client.fd[i] = cfd; client.fd[i] = POLLIN; break; } } //The number of client connections reaches the maximum value if(i == 1024) { close(cfd); continue; //Exit, there may be a client connection exit to facilitate continued search } //Modify the maximum subscript value of the client array if(maxi < i ) maxi = i; if(--nready == 0 ) continue; } //The following is the situation where the client sends data for(i = 1;i <= maxi;i + + ) { //If fd in the client array is -1, it means that the kernel is no longer monitored. if(client[i].fd == -1) continue; if(client[i].revents == POLLIN) { sockfd = client[i].fd; memset(buf,0x00,sizeof(buf)); //read data n = Read(sockfd, buf,sizeof(buf)); if(n <= 0) { printf("read error or client closed,n =[%d]\ ",n); close(sockfd); client[i].fd = -1; //Tell the kernel not to monitor anymore } else { printf("read error,n == [%d],buf==[%s]\ ,"n,buf); //Send data to client Write(sockfd,buf,n); } if(--nready == 0 ) { break; } } } close(lfd); }
Multiple IO-epoll (key)
The detection of changes in file descriptors is entrusted to the kernel for processing, and then the kernel will
Events are returned to the application.
head File
#include
function
int epoll_create(int size)
Function description: Create a poll tree and return a number of root nodes
Function parameters: size: a number greater than 0 must be passed
Return value: Returns a file descriptor. This file descriptor represents the root node of the epoll tree.
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
Function description: Delete and modify the epoll tree on fd from the tree
Function parameters:
epfd: the root node of the epoll tree
op:
EPOLL_CTL_ADD: Add event nodes to the tree
EPOLL_CTL_DEL: Delete event nodes from the tree
EPOLL_CTL_MOD: Modify the corresponding event node on the treefd: the file descriptor to be operated on
event:
Commonly used event.events are:
EPOLLIN: read events
EPOLLOUT: write events
EPOLLERR: error event
EPOLLET: edge triggered modeevent.fd: the file descriptor corresponding to the event to be monitored
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
struct epoll_event{
uint32 events; / * Epoll events */
epoll_data data; /* User data variable */
};
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
Function description: Wait for the kernel return event to occur
Parameter Description:
epfd: epoll tree root
events: Outgoing parameters, actually an array of event structures
maxevents: array size
timeout:
-1: indicates permanent blocking
0: Return immediately
>0: indicates timeout waiting event
return value:
Success: Returns the number of events that occurred
Failure: If timeout=0, return if no event occurs; return -1, set errno value,
Use epoll model to develop server process
1: Create a socket and get the listening file descriptor lfd —- socket()
2: Set port reuse —– setsockopt()
3: Binding —— bind()
4: Listening ——– listen()
5. Create an epoll tree
Develop complete code
//EPOLL model testing #include "wrap.h" #include <sys/epoll.h> #include <ctype.h> int main() { int ret; int n; int nready; int lfd; int cfd; int sockfd; char buf[1024]; socklen_t socklen; struct sockaddr_in svraddr; struct epoll_event ev; struct epoll_event events[1024]; int k; int i; \t //Create socket lfd = Socket(AF_INET,SOCK_STREAM,0); \t //Set the file descriptor for port reuse int opt = 1; setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR, & amp;opt,sizeof(int)); //Bind svraddr.sin_family = AF_INET; svraddr.sin_addr.s_addr = htonl(INADDR_ANY); svraddr.sin_port = htons(8888); Bind(lfd,(struct sockaddr *) & amp;svraddr,sizeof(struct sockaddr_in)); \t //Listen Listen(lfd,128); \t //Create an epoll tree int epfd = epoll_create(1024); if(epfd < 0 ) { perror("create epoll error"); return -1; } \t ev.data.fd = lfd; ev.events = EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd, & amp;ev); //The event node corresponding to lfd is on the tree \t while(1) { nready = epoll_wait(epfd,events,1024,-1); //Waiting for the kernel to return events if(nready < 0) { perror("epoll_wait error"); if(nready == EINTR) //Determine whether an interrupt signal is received { continue; } break; } for(i = 0;i < nready;i + + ) //less than the number of events that occur { //There is a client connection sending a request sockfd = events[i].data.fd; if(sockfd == lfd) { cfd = Accept(lfd,NULL,NULL); ev.data.fd = cfd; ev.events = EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd, & amp;ev); } //A client sends data else { memset(buf,0x00,sizeof(buf)); n = Read(sockfd,buf,sizeof(buf)); if(n <= 0) { close(sockfd); epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //Delete sockfd from the epfd tree } else { for(k = 0;k < n;k + + ) { buf[k] = toupper(buf[k]); //Return to uppercase } Write(sockfd,buf,n); } } \t\t\t } } close(epfd); close(lfd); return 0; }
epoll’s two modes ET and LT mode
LT mode of epoll:
The default situation of epoll is LT mode. In this case, if the read data is not finished at one time,
If there is still readable data in the buffer, epoll_wait will notify again.
ET mode of epoll:
If epoll is set to ET mode, if the data is not read at once, epoll_wait will no longer notify
Until next time there is new data
In ET mode, in order to prevent the second client from being able to connect normally and send data, the socket needs to be set to non-blocking mode.
ET sets the non-blocking mode because it uses the edge trigger mode (EPOLLET). In edge trigger mode, when there is data to read, the EPOLLIN event will only be triggered once. If the read does not read all the data in the buffer, the EPOLLIN event will still be triggered next time. Therefore, in order to ensure that complete data is read every time, the socket needs to be set to non-blocking mode to avoid blocking when the buffer is not fully read.
Code:
//EPOLL model test ET #include "wrap.h" #include <sys/epoll.h> #include <ctype.h> #include <fcntl.h> int main() { int ret; int n; int nready; int lfd; int cfd; int sockfd; char buf[1024]; socklen_t socklen; struct sockaddr_in svraddr; struct epoll_event ev; struct epoll_event events[1024]; int k; int i; \t //Create socket lfd = Socket(AF_INET,SOCK_STREAM,0); \t //Set the file descriptor for port reuse int opt = 1; setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR, & amp;opt,sizeof(int)); //Bind svraddr.sin_family = AF_INET; svraddr.sin_addr.s_addr = htonl(INADDR_ANY); svraddr.sin_port = htons(8888); Bind(lfd,(struct sockaddr *) & amp;svraddr,sizeof(struct sockaddr_in)); \t //Listen Listen(lfd,128); \t //Create an epoll tree int epfd = epoll_create(1024); if(epfd < 0 ) { perror("create epoll error"); return -1; } \t ev.data.fd = lfd; ev.events = EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd, & amp;ev); //The event node corresponding to lfd is on the tree \t while(1) { nready = epoll_wait(epfd,events,1024,-1); //Waiting for the kernel to return events if(nready < 0) { perror("epoll_wait error"); if(nready == EINTR) //Determine whether an interrupt signal is received { continue; } break; } for(i = 0;i < nready;i + + ) //less than the number of events that occur { //There is a client connection sending a request sockfd = events[i].data.fd; if(sockfd == lfd) { cfd = Accept(lfd,NULL,NULL); ev.data.fd = cfd; ev.events = EPOLLIN | EPOLLET; // epoll_ctl(epfd,EPOLL_CTL_ADD,cfd, & amp;ev); \t\t\t\t //Set cfd to non-blocking mode int flag = fcntl(cfd, F_GETFL); flag |= O_NONBLOCK; //The O_NONBLOCK (non-blocking) flag position is 1. fcntl(cfd, F_SETFL, flag); } //A client sends data else { \t\t\t\t memset(buf,0x00,sizeof(buf)); while(1) { n = Read(sockfd,buf,sizeof(buf)); printf("n == [%d]\ ",n); \t\t\t\t\t if(n == -1) { printf("read over,n == [%d]\ ",n); break; } if(n < 0 || (n <0 & amp; & amp; n!=-1)) //The other party closes the connection, or there is an abnormal situation { printf("n == [%d],buf == [%s]\ ",n,buf); close(sockfd); epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //Delete sockfd from the epfd tree break; } else { printf("n == [%d],buf == [%s]\ ",n,buf); for(k = 0;k < n;k + + ) { buf[k] = toupper(buf[k]); //Return to uppercase } Write(sockfd,buf,n); } } \t \t } \t\t\t } } close(epfd); close(lfd); return 0; }