select model for IO reuse

Keywords: Linux tcp select

IO multiplexing

IO reuse is a very important technology in linux system. I think the understanding of reuse here is that a single process or thread can process multiple IOS in a certain mode, rather than one process or thread only processes one io. Generally, when processing socket communication, a thread or process either blocks and waits there or is not blocked when processing read and write, and then check whether it is readable and writable. This will waste a lot of resources. If multiple sockets need to be processed for read and write, many threads or processes have to be opened. IO reuse technology is to solve this problem. This section explains the IO reuse model select in detail.

select model and function definition

Model mechanism

The select model is shown in the figure above. The user first adds the socket that needs IO operation to the select, then blocks and waits for the return of the select system call. When the data arrives, the socket is activated and the select function returns. The user thread formally initiates a read request, reads data and continues execution. From the perspective of process, there is not much difference between the IO request using the select function and the synchronous blocking model. There are even more monitoring sockets and additional operations calling the select function, which is more inefficient. However, the biggest advantage of using select is that users can process IO requests from multiple sockets at the same time in one thread. Users can register multiple sockets, and then continuously call select to read the activated sockets, so as to process multiple IO requests in the same thread at the same time. In the synchronous blocking model, this can only be achieved through multithreading.

API introduction

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
/
Parameter Description:
maxfdp: The total number of file descriptors listened to, which is 1 greater than the maximum value of file descriptors in all file descriptor sets, because file descriptors are counted from 0;

readfds,writefds,exceptset: Respectively point to the descriptor set corresponding to events such as readable, writable and exception.

timeout:Used to set select The timeout of the function, which tells the kernel select Give up waiting after waiting for how long. timeout == NULL Indicates an infinite waiting time

Return value: 0 for timeout;Failure Return-1;Successfully returns an integer greater than 0, which represents the number of ready descriptors.

Macro used with select function

#include <sys/select.h>   
int FD_ZERO(int fd, fd_set *fdset);   //One FD_ All bits of a variable of type set are set to 0
int FD_CLR(int fd, fd_set *fdset);  //Can be used when clearing a bit
int FD_SET(int fd, fd_set *fd_set);   //Set a position bit of the variable
int FD_ISSET(int fd, fd_set *fdset); //Test whether a bit is set

When a file descriptor set is declared, FD must be used_ Zero will zero all positions. Then, the position bits corresponding to the descriptor we are interested in are as follows:

fd_set rset;   
int fd;   
FD_ZERO(&rset);   
FD_SET(fd, &rset);   
FD_SET(stdin, &rset);

Then the select function is called to wait for the arrival of the file descriptor. If the set time is exceeded, do not wait and continue to execute.

select(fd+1, &rset, NULL, NULL,NULL);

After select returns, use FD_ISSET tests whether the given location is set:

if(FD_ISSET(fd, &rset)   
{ 
    ... 
    //do something  
}

select application example:

No more nonsense. Let's talk about the code. select io reuses the code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 1234    // the port users will be connecting to
#define BACKLOG 5     // how many pending connections queue will hold
#define BUF_SIZE 1024
int fd_A[BACKLOG];    // accepted connection fd
int conn_amount;      // current connection amount
void showclient()
{
    int i;
    printf("client amount: %d/n", conn_amount);
    for (i = 0; i < BACKLOG; i++) {
        printf("[%d]:%d  ", i, fd_A[i]);
    }
    printf("/n/n");
}
int main(void)
{
    int sock_fd, new_fd;             // listen on sock_fd, new connection on new_fd
    struct sockaddr_in server_addr;  // server address information
    struct sockaddr_in client_addr;  // connector's address information
    socklen_t sin_size;
    int yes = 1;
    char buf[BUF_SIZE];
    int ret;
    int i;
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }
    server_addr.sin_family = AF_INET;         // host byte order
    server_addr.sin_port = htons(MYPORT);     // short, network byte order
    server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
    memset(server_addr.sin_zero, '/0', sizeof(server_addr.sin_zero));
    if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }
    if (listen(sock_fd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }
    printf("listen port %d/n", MYPORT);
    fd_set fdsr;
    int maxsock;
    struct timeval tv;
    conn_amount = 0;
    sin_size = sizeof(client_addr);
    maxsock = sock_fd;
    while (1)
    {
        // initialize file descriptor set
        FD_ZERO(&fdsr);
        FD_SET(sock_fd, &fdsr);  // add fd
        // timeout setting
        tv.tv_sec = 30;
        tv.tv_usec = 0;
        // add active connection to fd set
        for (i = 0; i < BACKLOG; i++) {
            if (fd_A[i] != 0) {
                FD_SET(fd_A[i], &fdsr);
            }
        }
        ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
        if (ret < 0) {          // error
            perror("select");
            break;
        } else if (ret == 0) {  // time out
            printf("timeout/n");
            continue;
        }
        // check every fd in the set
        for (i = 0; i < conn_amount; i++)
        {
            if (FD_ISSET(fd_A[i], &fdsr)) // check which fd is ready
            {
                ret = recv(fd_A[i], buf, sizeof(buf), 0);
                if (ret <= 0)
                {        // client close
                    printf("ret : %d and client[%d] close/n", ret, i);
                    close(fd_A[i]);
                    FD_CLR(fd_A[i], &fdsr);  // delete fd
                    fd_A[i] = 0;
                    conn_amount--;
                }
                else
                {        // receive data
                    if (ret < BUF_SIZE)
                        memset(&buf[ret], '/0', 1); // add NULL('/0')
                    printf("client[%d] send:%s/n", i, buf);
                }
            }
        }
        // check whether a new connection comes
        if (FD_ISSET(sock_fd, &fdsr))  // accept new connection
        {
            new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
            if (new_fd <= 0)
            {
                perror("accept");
                continue;
            }
            // add to fd queue
            if (conn_amount < BACKLOG)
            {
                fd_A[conn_amount++] = new_fd;
                printf("new connection client[%d] %s:%d/n", conn_amount,
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                if (new_fd > maxsock)  // update the maxsock fd for select function
                    maxsock = new_fd;
            }
            else
            {
                printf("max connections arrive, exit/n");
                send(new_fd, "bye", 4, 0);
                close(new_fd);
                break;   
            }
        }
        showclient();
    }
    // close other connections
    for (i = 0; i < BACKLOG; i++)
    {
        if (fd_A[i] != 0)
        {
            close(fd_A[i]);
        }
    }
    exit(0);
}

Posted by paddycallaghan on Thu, 23 Sep 2021 17:54:44 -0700