io multiplexing select, poll, epoll model

1. Select model
1. Meaning of function parameters
int select(int maxfd,fd_set *rset,fd_set *wset,fd_set *eset,struct timeval *timeout);
Copy the fd_set set (parameters 2, 3, and 4) that need to be detected to the kernel. If there is io ready, clear the fd_set set, reset the fd_set set, and then copy it to the user layer.

Parameter 1: Determine the value of the maximum fd to facilitate the internal loop to determine whether the fd is io ready.
Parameter 2: The file descriptor is readable
Parameter 3: The file descriptor is writable
Parameter 4: File descriptor exception
Parameter 5: How often to poll NULL: Blocking 0: Return immediately without blocking the process
Return value: greater than 0 indicates that there is an io ready event
Code:

 int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {<!-- -->
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(2048);
    if(-1==bind(sockfd,(struct sockaddr*) & amp;seraddr,sizeof(struct sockaddr)))
    {<!-- -->
        perror("bind");
        return -1;
    }
    printf("----bind\\
");
    listen(sockfd,10);

    fd_set rfds,rset; // One setting and one reading judgment
    FD_ZERO( & amp;rfds);
    FD_SET(sockfd, & amp;rfds);
    int maxfd = sockfd;
    while(1)
    {<!-- -->
        rset = rfds;
        int nready = select(maxfd + 1, & amp;rset,NULL,NULL,NULL);//The fifth parameter is empty, which means waiting forever
        if(FD_ISSET(sockfd, & amp;rset))//Determine whether there is a connection
        {<!-- -->
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int clientfd = accept(sockfd,(struct sockaddr*) & amp;clientaddr, & amp;len);
            printf("accept\\
");
            FD_SET(clientfd, & amp;rfds);
            if (clientfd > maxfd) maxfd = clientfd;
        }
        int i=0;
        for(i=sockfd + 1;i<=maxfd;i + + )
        {<!-- -->
            if(FD_ISSET(i, & amp;rset))//Read event
            {<!-- -->
                char buffer[128] = {<!-- -->0};
                int count = recv(i,buffer,sizeof(buffer),0);
                if(count == 0){<!-- -->
                FD_CLR(i, & amp;rfds);
                close(i);
                }
                if(count > 0)
                    printf("sockfd: %d,count: %d,buffer:%s\\
",sockfd,count,buffer);
            }
        }
    }

selectDisadvantages
1. Too many parameters are not conducive to maintenance and understanding.
2. There is a limit to the number of IOs: 1024 for Linux and 64 for Windows.
3. You need to copy the io collection to be tested every time
4. Every time you need to traverse the io collection, return the ready collection

2. Poll model
1. Meaning of function parameters
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
Parameter one: struct pollfd{
int fd; file description
short events;//Parameters brought in POLLIN \ POLLOUT…
short revents;//Returned information POLLIN \ POLLOUT…
}
Parameter 2: The value of the maximum fd is convenient for the internal loop to determine whether the fd is io ready.
Parameter 3: How often to poll. NULL: blocking 0: return immediately without blocking the process

/*struct pollfd{
int fd;
short events;//parameters brought in
short revents;//returned information
}*/
int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {<!-- -->
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(2048);
    if(-1==bind(sockfd,(struct sockaddr*) & amp;seraddr,sizeof(struct sockaddr)))
    {<!-- -->
        perror("bind");
        return -1;
    }
    printf("----bind\\
");
    listen(sockfd,10);
    
    struct pollfd fds[1024] = {<!-- -->0};
    fds[sockfd].fd=sockfd;
    fds[sockfd].events = POLLIN;
    int maxfd = sockfd;//The number of traversals is smaller, fd is incremented
    while(1)
    {<!-- -->
        int nready = poll(fds,maxfd + 1,-1);
        if(fds[sockfd].revents & amp; POLLIN)
        {<!-- -->
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int clientfd = accept(sockfd,(struct sockaddr*) & amp;clientaddr, & amp;len);
            printf("accept\\
");
            fds[clientfd].fd = clientfd;
            fds[clientfd].events = POLLIN;
            if (clientfd > maxfd) maxfd = clientfd;
        }
        int i = 0;
        for (i = sockfd + 1;i <= maxfd;i + + ) {<!-- -->
            if (fds[i].revents & amp; POLLIN) {<!-- -->
                char buffer[128] = {<!-- -->0};
                int count = recv(i,buffer,sizeof(buffer),0);
                if (count == 0) {<!-- -->
                    fds[i].fd = -1;
                    fds[i].events = 0;
                    close(i);
                    break;
                }
                if(count > 0)
                    printf("sockfd: %d,count: %d,buffer:%s\\
",sockfd,count,buffer);
            }
        }
    }

Poll is similar to select and uses polling. Poll has no limit of 1024 file descriptors and uses a linked list to store fd.

3. epoll model
1. Function meaning
int epoll_create(int size);//The parameters are meaningless, just be greater than 0
Creating a new epoll instance and returning a file descriptor referencing the instance will create a red-black tree and io ready queue.
epoll_crl(int epfd,int op,int fd,struct epoll_event *event);
Add modifications and delete specific file descriptors to the epoll set (red-black tree).
Parameter 1: fd return value created by epoll_create
Parameter two: EPOLL_CTL_ADD adds fd to the red-black tree
EPOLL_CTL_DEL deletes the value of fd on the red-black tree
EPOLL_CTL_MOD modifies the value of fd on the red-black tree
Parameter three: specific network fd, scokfd, clientfd
Parameter four: typedef union epoll_data {
voidptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;//Save data related to a file descriptor that triggered the event
struct epoll_event {
__uint32_t events; /
epoll event / EPOLLIN read EPOLLOUT write EPOLLLT level trigger EPOLLET edge trigger
epoll_data_t data; /
User data variable */
};
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
Waits for an I/O event, blocking the calling thread if no event is currently available.
Parameter 1: fd return value created by epoll_create
Parameter 2: The return parameter of the interface, epoll copies the collection of events that occurred from the kernel to the events array
Parameter three: The size of the pre-allocated array is equal to parameter two.
Parameter four: Indicates the maximum waiting time when no event is detected, timeout time (>=0), the unit is milliseconds, -1 means blocking, 0 means no blocking
Return value: Successfully returns the number of events that need to be processed. Returns 0 on failure, indicating that the wait has timed out.

//IO-oriented processing: accept through listenfd and read and write through clientfd
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <fcntl.h>
#include <pthread.h>

#include <sys/poll.h>
#include <sys/epoll.h>
int main()
{<!-- -->
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {<!-- -->
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(2048);
    if(-1==bind(sockfd,(struct sockaddr*) & amp;seraddr,sizeof(struct sockaddr)))
    {<!-- -->
        perror("bind");
        return -1;
    }
    printf("----bind\\
");
    listen(sockfd,10);

    int epfd = epoll_create(1);
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd, & amp;ev);
    struct epoll_event events[1024] = {<!-- -->0};
    while(1)
    {<!-- -->
        int nready = epoll_wait(epfd,events,1024,-1);
        if (nready < 0) continue;
        int i = 0;
        for(i = 0;i < nready;i + + )
        {<!-- -->
            int connfd = events[i].data.fd;
            if(sockfd == connfd) //process listenfd for io classification
            {<!-- -->
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                int clientfd = accept(sockfd,(struct sockaddr*) & amp;clientaddr, & amp;len);
                printf("accept\\
");
                ev.events = EPOLLIN;
                ev.data.fd = clientfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd, & amp;ev);
            }
            else if(events[i].events & amp; EPOLLIN) //Process clientfd
            {<!-- -->
                char buffer[128] = {<!-- -->0};
                int count = recv(connfd,buffer,sizeof(buffer),0);
                if(count == 0)
                {<!-- -->
                    epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
                    close(i);
                }
                if(count > 0)
                    printf("sockfd: %d,count: %d,buffer:%s\\
",sockfd,count,buffer);
                continue;
            }
        }
    }
}

2. epoll horizontal trigger LT (default trigger method): It will always trigger when the io buffer has data or space to write, and will continue to return through epoll_wait()
Test: The server receives 10 bytes each time and the client sends 32 bytes each time

while(1)
    {<!-- -->
        int nready = epoll_wait(epfd,events,1024,-1);
        printf("epoll_wait\\
");
        if (nready < 0) continue;
        int i = 0;
        for(i = 0;i < nready;i + + )
        {<!-- -->
            int connfd = events[i].data.fd;
            if(sockfd == connfd) //process listenfd for io classification
            {<!-- -->
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                int clientfd = accept(sockfd,(struct sockaddr*) & amp;clientaddr, & amp;len);
                printf("accept\\
");
                ev.events = EPOLLIN;
                ev.data.fd = clientfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd, & amp;ev);
            }
            else if(events[i].events & amp; EPOLLIN) //Process clientfd
            {<!-- -->
                char buffer[10] = {<!-- -->0};//Accept 10 data each time
                int count = recv(connfd,buffer,sizeof(buffer),0);
                if(count == 0)
                {<!-- -->
                    epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
                    close(i);
                }
                if(count > 0)
                    printf("sockfd: %d,count: %d,buffer:%s\\
",sockfd,count,buffer);
                continue;
            }
        }
    }


3. epoll edge triggers ET: it will be triggered when the io buffer status changes. Read: it will be triggered once when data comes in. Write: it will be triggered once when the write buffer is full and there is space.
Test: The server receives 10 bytes each time and the client sends 32 bytes each time

while(1)
    {<!-- -->
        int nready = epoll_wait(epfd,events,1024,-1);
        if (nready < 0) continue;
        int i = 0;
        for(i = 0;i < nready;i + + )
        {<!-- -->
            int connfd = events[i].data.fd;
            if(sockfd == connfd)
            {<!-- -->
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                int clientfd = accept(sockfd,(struct sockaddr*) & amp;clientaddr, & amp;len);
                printf("accept\\
");
                ev.events = EPOLLIN | EPOLLET;//Set edge trigger
                ev.data.fd = clientfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd, & amp;ev);
            }
            else if(events[i].events & amp; EPOLLIN)
            {<!-- -->
                char buffer[10] = {<!-- -->0};
                int count = recv(connfd,buffer,sizeof(buffer),0);
                if(count == 0)
                {<!-- -->
                    epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
                    close(i);
                }
                if(count > 0)
                    printf("sockfd: %d,count: %d,buffer:%s\\
",sockfd,count,buffer);
                continue;
            }
        }
    }


Click Send again to continue reading 10 bytes

Solve incomplete reading situation through loop reading

 while((count = recv(connfd,buffer,sizeof(buffer),0)) > 0)
 {<!-- -->
     if(count == 0)
     {<!-- -->
         epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
         close(i);
     }
     else if(count < 0)
     {<!-- -->
          if (errno == EAGAIN) {<!-- -->
              break;
          }
          epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
          close(i);
      }
      else if(count > 0)
      {<!-- -->
      printf("sockfd: %d,count: %d,buffer:%s\\
",sockfd,count,buffer);
          memset(buffer,0,sizeof(buffer));
      }
      continue;
 }

4.LT and ET application scenarios
The TCP protocol has a packet sticking process, usually adding a length or separator (redis) to the header of the business data packet.
Take defining the packet length as an example, read it twice, first read the length, and then read the content
short length = 0;
recv(fd, & length ,2,0);
length = ntohs(length);
recv(fd,buffer,length,0);

The redis protocol format is as follows:
*3\r\\

$3\r\\
//Length
SET\r\\
//Content
$6\r\\
//Length
mykey\r\\

$5\r\\
//Length
12345\r\\

Horizontal trigger: suitable for the above situations
Edge trigger: when the data cannot be read in one go, such as sending a large file

5. Send one large file at a time (exceeding the read and write buffer, both read and write buffers are set to 16384 bytes)

#Write buffer size
$ sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096 16384 4194304
#Change write buffer size
$ sysctl -w net.ipv4.tcp_wmem="4096 16384 8388608"
#read buffer size
sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096 131072 6291456
#Change send buffer size
$ sysctl -w net.ipv4.tcp_rmem="4096 16384 8388608"


In ET mode, 47411 bytes of data are sent and epoll_wait is called only once.

In LT mode, sending 47411 bytes of data will call epoll_wait multiple times.