1.IO process
Any IO process must include two steps, the first is waiting and the second is copying Moreover, in the actual application scenario, the waiting time is often much higher than the copy time The core way to make IO more efficient is to minimize the waiting time
Wait for IO ready: it means that the resources you want to obtain are ready for operation
Read recv/recvfrom: wait for data in the receive buffer (wait for IO process) and data in the receive buffer (wait for IO ready)
Write send/sendto: there is space in the send buffer (waiting for IO process) and space in the send buffer (waiting for IO ready)
Copy data to buffer:
Read: recv/recvfrom(sockfd,buff,size,0): copy the data in the receive buffer to the space (buff) prepared by yourself
Write: send/sendto: copy the application layer data to the send buffer
2. Typical IO model
2.1 blocking IO
When resources are unavailable, IO requests are blocked until resources are available, which is called blocking io. All sockets are blocked by default. Blocking IO is the most common IO model
Implementation process:
characteristic:
1. After initiating an IO call, the waiting time depends on the kernel
2. During the waiting process, the execution flow is suspended, and the CPU utilization is very low
3. Between IO readiness and copying data, the real-time performance is very high (if a fish bites, immediately lift the bar and hook)
2.2 non blocking IO
When the resource is unavailable, the IO request will not be blocked, but will be returned directly. The current resource is unavailable and the EWOULDBLOCK error code will be returned
If the current resource is unavailable, the return of the IO request indicates that the IO request has not been completed. Therefore, to complete the IO request, non blocking needs to be combined with recycling until the IO request is completed
Nonblocking IO often requires programmers to repeatedly try to read and write file descriptors in a circular manner. This process is called polling This is a great waste of CPU, which is generally used only in specific scenarios
Implementation process:
characteristic:
1. Non blocking IO has higher CPU utilization than blocking io
2. The code is complex and the process control is complex because of the need for circulation
3. It needs to be combined with cyclic call until the IO request is completed
4. The time between IO readiness and copying data is not real-time enough
while{ Non blocking IO call //Resource is not ready, error code returned At this time, the resource is ready, but it needs to point to the following code, so the real-time performance is poor //Code 1 //Code 2 }
Distinguish between blocking IO and non blocking IO: you only need to care about whether the IO call returns immediately. If there is no immediate return, it indicates that it is blocked. If it is returned directly, it indicates that it is non blocking
2.3 signal driven IO
Implementation process:
1. Customize an IO signal (SIGIO) processing function and initiate IO call in the processing function
2. When the kernel prepares the data, it uses the SIGIO signal to notify the application for IO operation
3. When the program receives an IO signal, the kernel will call the custom processing function. The kernel calls the custom processing function and initiates the IO call in the custom processing function
characteristic:
1. The real-time performance is enhanced from Io readiness to copying data
2. The code is more complex and the process control is more difficult because of the introduction of signals
3. The advantage is that there is no need to repeatedly initiate IO calls, but the logic of custom signals needs to be added to the code
2.4 asynchronous IO
The kernel notifies the application when the data copy is completed (and the signal driver tells the application when it can start copying data)
Implementation process:
1. User defined signal processing function - > notify data copy completion
2. Initiate an asynchronous IO call, and the asynchronous IO call returns directly
3. After the asynchronous IO call returns, the execution stream can execute user code, and the operating system kernel waits for IO readiness and data copy
4. After the data copy is completed, the kernel notifies the caller by signal
3. Multiplexer IO model
Function: IO multiplexing can monitor a large number of file descriptors, including readable events, writable events and abnormal events
Monitor file descriptors: when a file descriptor is ready, which file descriptor will be processed
3.1 select model
The select system call is used to let our program monitor the state changes of multiple file descriptors
The program will stop at select and wait until one or more of the monitored file descriptors have changed state
Implementation process:
1. Copy the file descriptors concerned by the user to the kernel for monitoring
2. If the kernel monitors that a file descriptor is ready, it returns the descriptor
3. The user operates on the returned descriptor
Interface:
Code demonstration 1: no timeout
int main() 7 { 8 //Set event collection 9 fd_set readfds;// 10 11 FD_ZERO(&readfds);//Empty collection 12 FD_SET(0,&readfds);//Add file descriptor No. 0 13 14 while(1) 15 { 16 int ret=select(1,&readfds,NULL,NULL,NULL);//Blocking monitoring 17 if(ret < 0) 18 { 19 cerr<<"select error"<<endl;//Monitoring error 20 exit(1); 21 } 22 23 if(FD_ISSET(0,&readfds)!=0)//Judge whether the file descriptor No. 0 is ready 24 { 25 char buff[100]={0}; 26 read(0,buff,sizeof(buff)-1); 27 printf("echo:%s",buff); 28 } 29 } 30 31 return 0; 32 }
Code demonstration 2: with timeout
int main() { //Set event collection fd_set readfds;// FD_ZERO(&readfds);//Empty collection FD_SET(0,&readfds);//Add file descriptor No. 0 while(1) { //Set timeout struct timeval tv; tv.tv_sec=3;//The timeout is set to 3 seconds tv.tv_usec=0; int ret=select(1,&readfds,NULL,NULL,&tv);//Blocking monitoring if(ret < 0) { cerr<<"select error"<<endl;//Monitoring error exit(1); } if(ret==0)//Timeout { cout<<"time out"<<endl; if(FD_ISSET(0,&readfds)==0)//When the timeout expires, select will remove the file descriptor that is not ready cout<<" 0 fd is not in readfds"<<endl; FD_SET(0,&readfds);//From new settings continue; } if(FD_ISSET(0,&readfds)!=0)//Determine whether the file descriptor No. 0 is ready { char buff[100]={0}; read(0,buff,sizeof(buff)-1); printf("echo:%s",buff); } } return 0; }
Advantages and disadvantages of select:
advantage:
1. It follows posix standard, that is, it can be used across platforms
2.select timeout can be accurate to microseconds
Disadvantages:
1.select adopts polling traversal, and the efficiency of monitoring will decrease with the increase of file descriptors
2. The file descriptors that select can monitor are limited (1024), depending on the FD of the kernel_ SetSize macro value
3. When select monitors the file descriptor, it is necessary to copy the set to the kernel. When select finds that an event is ready (the return value is greater than 0, indicating discovery), it is also necessary to copy the event from the kernel to the user space, which will also affect the efficiency
4.select will remove the file descriptors that are not ready from the collection when it returns. As a result, if you need to monitor the removed file descriptors in the next monitoring, you need to add them again
5.select does not directly tell the programmer which file descriptor is ready. The programmer needs to judge in the returned event set
Constructing tcp server with select
Build process:
Build code:
#pragma once #include <sys/select.h> #include <iostream> using namespace std; #include <math.h> #include <vector> class Select { public: Select() { //Initialize event set, maximum file descriptor FD_ZERO(&readfds); _maxfd=-1; } void AddSet(int fd)//Add file descriptor to monitor { _maxfd=fmax(_maxfd,fd);//Update maximum file descriptor FD_SET(fd,&readfds); } void DeleteSet(int fd)//Delete monitored file descriptor { FD_CLR(fd,&readfds); //Update maximum file descriptor if(fd==_maxfd) { for(int i=fd;i>=0;i--)//Look from back to front. The first one is the biggest one { if(FD_ISSET(i,&readfds)) { _maxfd=i; break; } } } } bool SelectWait(vector<int>& arr)//Monitoring interface { //Set delay time struct timeval tv; tv.tv_sec=2; tv.tv_usec=0; fd_set copy = readfds;//Save a copy and restore it after returning int ret=select(_maxfd+1,©,NULL,NULL,NULL); if(ret < 0)//Monitoring error { cerr<<"select error"<<endl; return false; } else if(ret==0)//The wait timed out { cout<<"time out"<<endl; return false; } //Multiple file descriptors are monitored, but it is not known which file descriptor is ready for(int i=0;i<=_maxfd;i++) { if(FD_ISSET(i,©))//The file descriptor for i is ready arr.push_back(i);//Add to the ready collection } return true; } ~Select() {} private: int _maxfd;//Maximum file descriptor fd_set readfds;//Readable event collection };
#ifndef _SERVER_HPP_ #define _SERVER_HPP_ #include <iostream> using namespace std; #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #endif #define BACKLOCK 5 class Server { private: int port; int lsock;//listening socket public: Server(int _port) :port(_port) ,lsock(-1) {} void Init()//initialize server { lsock=socket(AF_INET,SOCK_STREAM,0); if(lsock<0) { cerr<<"socket fail"<<endl; exit(2); } struct sockaddr_in local; local.sin_family=AF_INET;//Filling protocol local.sin_port=htons(port);//Fill the port and convert it into network byte order local.sin_addr.s_addr=INADDR_ANY;//Fill in ip if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)//Bind { cerr<<"bind error"<<endl; exit(3); } if(listen(lsock,BACKLOCK) < 0) { cerr<<"listen error"<<endl; exit(4); } } int Task(int sock)//Use linked sockets to perform tasks { char buff[200]; size_t size=recv (sock,buff,sizeof(buff)-1,0); if(size>0)//Read it { buff[size]='\0';//Add at end \ 0 cout<<"client:"<<buff<<endl; string str="ser echo:"; str+=buff; send(sock,str.c_str(),str.size(),0);//No need for + 1, \ 0 is the standard of language, and the file is unknown return 0; } else return -1;//It means that the other party has closed } int Stat()//Use socket to get link { sockaddr_in end_point;//Get customer structure information, which is the same as udp socklen_t len=sizeof(end_point); int sock=accept(lsock,(struct sockaddr*)&end_point,&len); if(sock < 0) { cout<<"accept error"<<endl; return -1; } return sock; } int GetPid()//Return listening socket { return lsock; } ~Server() { close(lsock); } };
#include "select.hpp" #include "server.hpp" int main(int argc,char *argv[]) { if(argc!=2) { cout<<"please enter your port"<<endl; exit(0); } Server se(atoi(argv[1])); se.Init(); Select st; st.AddSet(se.GetPid()); while(1) { vector<int> arr; bool ret=st.SelectWait(arr);//Monitor if(ret==false) continue; for(size_t i=0;i < arr.size();i++) { if(arr[i]==se.GetPid()) { int sock=se.Stat();//Get link if(sock>0) { st.AddSet(sock);//Add the acquired link to the monitor cout<<"get a new link"<<endl; } } else { int ret=se.Task(arr[i]); if(ret==-1)//The other party has turned it off { st.DeleteSet(arr[i]); cout<<"link end"<<endl; } } } } return 0; }
3.2 poll model
Interface:
characteristic:
1. Compared with poll and select, the cross platform portability is not as good as select, and the monitoring efficiency is not as good as epoll
2. Improvements over select:
a. There is no limit to the number of file descriptors
b. Compared with the event collection method before select, it is improved into an event structure. The event structure tells us what the concerned file descriptor is and what the concerned file descriptor event is
Code validation:
int main() { struct pollfd arr[10]; arr[0].fd=0;//File descriptor No. 0 arr[0].events=POLLIN;//Care about readable events while(1) { int ret=poll(arr,1,2000);//1 valid element with timeout of 2000 ms if(ret==0)//Wait timeout { cout<<"time out"<<endl; continue; } else if(ret < 0) { cerr<<"poll error"<<endl;//poll failed exit(0); } else { char buff[100]; for(int i=0;i<ret;i++) { if(arr[i].events==POLLIN) { int size=read(arr[i].fd,buff,sizeof(buff)-1); buff[size-1]=0;//Line breaks are also read in cout<<"echo:"<<buff<<endl; } } } } return 0; }
Advantages and disadvantages:
advantage:
1. The event structure is adopted to simplify the coding
2. The number of file descriptors is not limited
3. There is no need to re add the file descriptor during secondary monitoring
Disadvantages:
1. By polling and traversing the event structure array, the performance decreases with the increase of file descriptors
2. Cross platform is not supported
3. The user is not told which specific file descriptor is ready, which requires the programmer's information traversal judgment
4. You also need to copy the event structure to the kernel, and then copy the user space from the kernel
3.3 epoll model
3.3.1 introduction to epoll
At present, it is recognized that under the linux operating system, the monitoring performance is the highest
Interface:
epoll_create:
epoll_ctl:
epoll_wait:
epoll principle:
Using epoll to build tcp server
Construction process:
Build code:
#pragma once #include <iostream> using namespace std; #include <unistd.h> #include <sys/epoll.h> #include <vector> class EpollSvr { public: EpollSvr() :epfd(-1) {} void EpollIinit() { //Create and return the operation handle epfd=epoll_create(10); if(epfd < 0) { cout<<"EpollINIT Error"<<endl; exit(0); } } void EpollAdd(int fd) { //Create event structure struct epoll_event ep; ep.events=EPOLLIN;//Events of interest (readable) ep.data.fd=fd;//Tell epoll about the file descriptor of interest int ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ep);//Add file name descriptor fd of interest if(ret < 0)//Add failed { cout<<"epoll add error"<<endl; exit(1); } } void EpollDelete(int fd) { int ret=epoll_ctl(epfd,EPOLL_CTL_DEL,fd,nullptr);//Delete file descriptor if(ret < 0)//Deletion failed { cout<<"epoll delete error"<<endl; exit(2); } } bool EpollWait(vector<int> &out) { struct epoll_event arr[10];//Define an array of event structures int size=sizeof(arr)/sizeof(arr[0]); int ret=epoll_wait(epfd,arr,size,-1);//The timeout setting is less than 0, which is blocking waiting if(ret < 0) return false; if(ret > size)//Determine whether the event exceeds the array size ret = size; for(int i=0;i < ret; i++) { out.push_back(arr[i].data.fd);//Returns the ready file descriptor } return true; } ~EpollSvr() {} private: int epfd; };
#include <iostream> using namespace std; #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #endif #define BACKLOCK 5 class Server { private: int port; int lsock;//listening socket public: Server(int _port) :port(_port) ,lsock(-1) {} void Init()//initialize server { lsock=socket(AF_INET,SOCK_STREAM,0); if(lsock < 0) { cerr<<"socket fail"<<endl; exit(2); } struct sockaddr_in local; local.sin_family=AF_INET;//Filling protocol local.sin_port=htons(port);//Fill the port and convert it into network byte order local.sin_addr.s_addr=INADDR_ANY;//Fill in ip if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)//Bind { cerr<<"bind error"<<endl; exit(3); } if(listen(lsock,BACKLOCK) < 0) { cerr<<"listen error"<<endl; exit(4); } } int Task(int sock)//Use linked sockets to perform tasks { char buff[200]; size_t size=recv (sock,buff,sizeof(buff)-1,0); if(size>0)//Read it { buff[size]='\0';//Add at end \ 0 cout<<"client:"<<buff<<endl; string str="ser echo:"; str+=buff; send(sock,str.c_str(),str.size(),0);//No need to + 1, \ 0 is the language standard, and the file is unknown return 0; } else { //close(sock); return -1;//It means that the other party has closed } } int Stat()//Use socket to get link { sockaddr_in end_point;//Get customer structure information, which is the same as udp socklen_t len=sizeof(end_point); int sock=accept(lsock,(struct sockaddr*)&end_point,&len); if(sock < 0) { cout<<"accept error"<<endl; return -1; } return sock; } int GetPid()//Return listening socket { return lsock; } ~Server() { close(lsock); } };
#include "EpollSvr.hpp" #include "server.hpp" int main(int argc,char *argv[]) { if(argc!=2) { cout<<"please enter your port"<<endl; exit(0); } Server se(atoi(argv[1])); se.Init(); EpollSvr es; es.EpollIinit();//initialization es.EpollAdd(se.GetPid());//Add listening socket while(1) { vector<int> arr; bool ret= es.EpollWait(arr);//Monitor if(ret==false) continue; for(size_t i=0;i < arr.size();i++) { if(arr[i]==se.GetPid()) { int sock=se.Stat();//Get link if(sock>0) { es.EpollAdd(sock);//Add the acquired link to the monitor cout<<"get a new link"<<endl; } } else { int ret=se.Task(arr[i]); if(ret==-1)//The other party has turned it off { es.EpollDelete(arr[i]);//Delete file descriptor cout<<"link end"<<endl; close(arr[i]); } } } } return 0; }
3.3.2 how poll triggers the file descriptor ready event
3.3.2.1 horizontal trigger EPOLLLT
If you meet the conditions, it will always trigger - > for example, when you are playing a game, your mother asks you to go to dinner. If you don't go, she will come and call you again
Epolllt - > the default working mode of epoll. Both select and poll are triggered horizontally
Readable events:
As long as the data in the receive buffer is greater than the low water mark (1 byte), the readable event will be triggered until there is no data readable in the receive buffer (the data in the receive buffer is lower than the low water mark)
Writable events:
As long as the space in the send buffer is greater than the low watermark (1 byte), a writable event will be triggered until there is no space to write in the send buffer (the space in the send buffer is lower than the low watermark)
3.3.2.1 edge triggered EPOLLET
After meeting the conditions, it will only trigger once - > for example, if you are playing a game and your father asks you to eat, he will only call you once
Epollet - > owned only by epoll
set up:
When setting the event structure corresponding to the file descriptor, you only need to press bit or EPOLLET in the event variable in the event structure
struct epoll_event et; ev.events = EPOLLIN|EPOLLET;
Readable events:
The readability is triggered only when new data arrives, otherwise it will not be notified after one notification - > each time a new data arrives, it will only be notified once. If the application does not finish reading the data in the receiving buffer (the unfinished data is left in the buffer, and the next trigger will start here), it will not be notified again until the new data arrives, Can trigger readable events, so you need to read the data as much as possible
Writable events:
A writable event is triggered only when the remaining space in the send buffer is changed from non writable to writable
For ET mode, if a ready event occurs, you must grasp the opportunity to read the data for readable events and write the data for writable events
The ET mode combines the cycle to read and send data, and does not notify frequently, so the efficiency is relatively high
Modify the code using ET mode:
Construction details:
1. How to judge whether the data has been read:
Set size = bytes expected to be read and ret as bytes actually read
RET < size indicates that there must be no data in the buffer - > finished reading
ret==size
At this time, there may be data, and there may be no - > data, which needs to be read again
Reading again may enter blocking, so it needs to be changed to non blocking state
In the non blocking state, when reading null, an error will be reported and returned (EAGAIN/EWOULDBLOCK). Therefore, special handling of exceptions is required
2. Send data:
It is also necessary to build a loop to send. When the buffer has no capacity, it will send circularly until the buffer has capacity
3. Setting the descriptor as a non blocking interface introduction:
int fcntl(int fd, int cmd, ... /* arg */ );
1.fd: file descriptor to be set
2.cmd: operation mode
F_GETFL gets the properties of the current file descriptor
F_SETFL sets the non blocking attribute to the attribute of the file descriptor (O_NONBLOCK)
4. Code
Modification details: 1.The default method is adopted when monitoring the listening socket 2.When monitoring linked sockets ET pattern 3.Because it is ET Mode, all data needs to be read or sent when it should be notified, so it needs to be processed by loop 4.In the loop processing, if the transmission is blocked, the last processing will fall into the blocking state, so it needs to be changed to the non blocking state 5.In the non blocking state, when the return value is less than 0, exceptions may be reported, so special handling is required
#pragma once #include <iostream> using namespace std; #include <unistd.h> #include <sys/epoll.h> #include <vector> class EpollSvr { public: EpollSvr() :epfd(-1) {} void EpollIinit() { //Create and return the operation handle epfd=epoll_create(10); if(epfd < 0) { cout<<"EpollINIT Error"<<endl; exit(0); } } void EpollAdd(int fd,bool is_et=false) { //Create event structure struct epoll_event ep; if(is_et)//Set to ET mode ep.events=EPOLLIN|EPOLLET;//Events of interest (readable) else ep.events=EPOLLIN; ep.data.fd=fd;//Tell epoll about the file descriptor of interest int ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ep);//Add file name descriptor fd of interest if(ret < 0)//Add failed { cout<<"epoll add error"<<endl; exit(1); } } void EpollDelete(int fd) { int ret=epoll_ctl(epfd,EPOLL_CTL_DEL,fd,nullptr);//Delete file descriptor if(ret < 0)//Deletion failed { cout<<"epoll delete error"<<endl; exit(2); } } bool EpollWait(vector<int> &out) { struct epoll_event arr[10];//Define an array of event structures int size=sizeof(arr)/sizeof(arr[0]); int ret=epoll_wait(epfd,arr,size,-1);//The timeout setting is less than 0, which is blocking waiting if(ret < 0) return false; if(ret > size)//Determine whether the event exceeds the array size ret = size; for(int i=0;i < ret; i++) { out.push_back(arr[i].data.fd);//Returns the ready file descriptor } return true; } ~EpollSvr() {} private: int epfd; };
#ifndef _SERVER_HPP_ #define _SERVER_HPP_ #include <iostream> using namespace std; #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <fcntl.h> #endif #define BACKLOCK 5 class Server { private: int port; int lsock;//listening socket public: Server(int _port) :port(_port) ,lsock(-1) {} void Init()//initialize server { lsock=socket(AF_INET,SOCK_STREAM,0); if(lsock < 0) { cerr<<"socket fail"<<endl; exit(2); } struct sockaddr_in local; local.sin_family=AF_INET;//Filling protocol local.sin_port=htons(port);//Fill the port and convert it into network byte order local.sin_addr.s_addr=INADDR_ANY;//Fill in ip if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)//Bind { cerr<<"bind error"<<endl; exit(3); } if(listen(lsock,BACKLOCK) < 0) { cerr<<"listen error"<<endl; exit(4); } } void SetNonBlock(int sock)//Set to non blocking state { int flag=fcntl(sock,F_GETFL);//Gets the properties of the current file descriptor fcntl(sock,F_SETFL,flag | O_NONBLOCK);//Set file descriptor to non blocking } int Task(int sock)//Use linked sockets to perform tasks { string message; while(1) { //Read data //Set read buffer char buff[3]={0}; int num=recv(sock,buff,sizeof(buff)-1,0);//How much data is actually read if(num < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK)//There is no data, and an error occurs when reading again break; cerr<<"recv error"<<endl; return -1; } else if(num==0)//The opposite end closed the link { return -1; } message+=buff;//Accumulate the last result if(num < sizeof(buff)-1)//Read all break; } cout<<"client:"<<message; //Send data //Echo string string send_str("echo:"); send_str+=message; size_t pos=0;//Send location size_t send_size=send_str.size();//Number of characters remaining to be sent while(1) { size_t size=send(sock,send_str.c_str()+pos,send_size,0);//How much data is sent from where if(size < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK)//Space is full, continue sending continue; //There was an error. The other party closed the link cerr<<"send error"<<endl; return -1; } //Update the location of the next data transmission and the remaining data to be transmitted pos+=size; send_size-=size; if(send_size <= 0)//Sending is complete break; } return 1; } int Stat()//Use socket to get link { sockaddr_in end_point;//Get customer structure information, which is the same as udp socklen_t len=sizeof(end_point); int sock=accept(lsock,(struct sockaddr*)&end_point,&len); if(sock < 0) { cout<<"accept error"<<endl; return -1; } return sock; } int GetPid()//Return listening socket { return lsock; } ~Server() { close(lsock); } };
#include "EpollSvr.hpp" #include "server.hpp" int main(int argc,char *argv[]) { if(argc!=2) { cout<<"please enter your port"<<endl; exit(0); } Server se(atoi(argv[1])); se.Init(); EpollSvr es; es.EpollIinit();//initialization es.EpollAdd(se.GetPid());//Add listening socket while(1) { vector<int> arr; bool ret= es.EpollWait(arr);//Monitor if(ret==false) continue; for(size_t i=0;i < arr.size();i++) { if(arr[i]==se.GetPid()) { int sock=se.Stat();//Get link if(sock>0) { //se.SetNonBlock(sock);// Set the socket to a non blocking state es.EpollAdd(sock,true);//The obtained link is added to the monitoring and is in ET mode cout<<"get a new link"<<endl; } } else { se.SetNonBlock(arr[i]);//Set the socket to a non blocking state int ret=se.Task(arr[i]); if(ret==-1)//The other party has turned it off { es.EpollDelete(arr[i]);//Delete file descriptor cout<<"link end"<<endl; close(arr[i]); } } } } return 0; }