Five IO models

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,&copy,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,&copy))//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;
}

Keywords: Linux

Added by Maugrim_The_Reaper on Mon, 24 Jan 2022 23:58:29 +0200