IO multiplexing -- select system call

Keywords: socket network Linux

IO multiplexing

  • The client program processes multiple socket s. (e.g. non blocking connect)
  • The client program processes both user input and network connections. (e.g. chat program)
  • The server should handle listening socket and connecting socket at the same time.
  • The server processes both TCP and UDP requests.
  • The server should listen to multiple ports or handle multiple services at the same time.

select API

The purpose of the select system call is to poll and listen for events such as readable, writable and abnormal events on the file descriptors of interest to users for a specified period of time.

  • Polling: looping over and over from 0 to the maximum of these file descriptors we record.
  • fdset: it is a structure that contains only one integer array. Each bit of each element of the integer array represents a file descriptor. Therefore, the maximum number of file descriptors that can be processed by select at the same time is determined by the total number of bits of the array. The maximum value is a constant FD_SETSIZE defined in the < sys / select. C > header file.

Archetype:

#include<sys/select.h>
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timecal *timeout);

1. The nfd parameter specifies the total number of file descriptors to be monitored, usually the maximum descriptor number of readfd, writefd, and exceptfd descriptors plus 1, because the file descriptors start from 0.
2. * * readfd, writefd and exceptfd respectively point to the file descriptor set corresponding to readable, writable and exception events. **When the application calls, polling waits for events to occur for these descriptors, and when the select call returns, the kernel modifies them to inform the application which file descriptors are ready. For these three parameters, the pointer type of FD set structure is as follows (in the header file < sys / select. H >):

/* Maximum number of file descriptors in `fd_set'.  */
#define FD_SETSIZE	 1024

/* The fd_set member is required to be an array of longs.  */
typedef long int __fd_mask;  //Long integer

/* Some versions of <linux/posix_typeshygfdfsdgfhjkl.h> define these macros.  */
#undef	__NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
#Define [nfdbits (8 * (int) sizeof ([FD] mask)) / / how many bits does a long integer data have

/* fd_set for select and pselect.  */
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];  
    //Divide the digits of a long integer element by 1024 bits, that is, how many long integer data elements are there
#define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
#define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;

It can be seen from the above definition that the fd_set structure only contains an integer array. Each bit of each element of the array identifies a file descriptor. For example, if you need 1024, you only need 1024/(8*sizeof(long int))=32. You only need an array with 32 elements, which greatly saves space.

Because the file descriptor is too cumbersome, we should use the following series of macros to access the bits in the FD set structure:

void FD_ZERO(fd_set *set);//Clear all bits of fdset
void FD_SET(int fd,fd_set *set);//Set bit fd of fdset
void FD_CLR(int fd,fd_set *set);//Clear bit fd of fdset
int FD_ISSET(int fd,fd_set *set);//Test whether bit fd of fdset is set

3. The timeout parameter is used to set the timeout of the select function and identify how long select is willing to wait. It is a pointer of the structure type of time Val, but we can't fully trust the timeout value after the select call. For example, the timeout value is uncertain when the call fails. The definition of time Val structure is as follows:

struct timeval{
    long    tv_sec;     //Seconds
    long    tv_usec;    //Microsecond

};

As can be seen from the above definition, select provides us with a microsecond level timing mode.

  1. If the long-term value of timeval is assigned to 0, select returns immediately.
  2. If timeout is NULL, select will block until a file descriptor is ready.

Select returns the total number of ready file descriptors successfully. If no descriptors are ready within the timeout period, select fails to return - 1 and sets errno. If the program receives a signal during the select wait, select immediately returns - 1 and sets errno to EINTR

File descriptor ready condition

socket readable:

1. The number of bytes in the socket kernel receive buffer is greater than or equal to its local tax table and so ﹣ rcvlowat. At this time, you can read the socket without blocking and read the operation
2. The socket informs the other party to close the connection, and the read operation of the socket will return 0.
3. Listen for new connection requests on the socket.
4. There is an unhandled error on the socket. At this point, we can use getsockopt to read and clear the error.

socket can write:

1. The number of bytes available in the send buffer of the socket kernel is greater than or equal to its low level mark so ﹣ sndlowat. At this time, we can write the socket without blocking, and the number of bytes returned by the write operation is greater than 0.
2.socket write is turned off. Writing to a socket whose write operation is turned off triggers a SIGPIPE signal.
3. After socket uses non blocking connect connection successfully or fails (timeout).
4. There is an unhandled error on the socket. At this point, we can use get 'sockopt to read and clear errors.

Program example

The standard input descriptor is monitored through select detection. If there is data, it will be read. If there is no data, it will continue to monitor and set the timeout.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/select.h>

/* Standard input: file descriptor is 0 */
#define STDIN 0 

int main()
{
	/* fd Input file descriptor 0 for standard  */
	int fd = STDIN;
	fd_set fdset;
	while (1)
	{
		/* fdset Initialize to empty collection */
		FD_ZERO(&fdset); 

	/* Add the descriptor passed by fd to the collection */ 
	FD_SET(fd, &fdset); 

	/* The timeout is set to 5s, i.e. wait up to 5s on standard input stdin */
	struct timeval tv = { 5, 0 };

	/* select The return value is n, indicating that there are n descriptors with changed states */
	int n = select(fd + 1, &fdset, NULL, NULL, &tv);

	/* select Failure returns - 1 and sets errno */
	if (n == -1)
	{
		perror("select error");
	}
	/* select A value of 0 means that the file descriptors have not changed, so the timeout information will be printed out */
	else if (n == 0)
	{
		printf("time out\n");
	}
	else
	{
		/* 
		** Determine whether the file descriptor pointed to by the parameter fd is determined by the parameter
		** fdset An element in the FD set set set pointed to 
		*/
		if (FD_ISSET(fd, &fdset))
		{
			char buffer[128] = { 0 };

			/* Read data from standard input stdin to buffer */
			int res = read(fd, buffer, 127);

			/* Print data in buffer */
			printf("read(%d) = %s\n", res, buffer);
		}
	}
}

}

Do not introduce multi process and multi thread, use select to realize multi client / server in one program

Add the following descriptors to the collection, and remove them if not.
An array is used to save the collection. After processing the message collection, changes will be attached. The original descriptor collection will be recovered through the array.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/select.h>
#include<sys/time.h>


#define MAXFD 10

void fds_init(int fds[])
{

    int i=0;
    for(;i<MAXFD;i++)
    {
        fds[i]=-1;    
    }

}
void fds_add(int fds[],int fd)
{
    int i=0;
    for(;i<MAXFD;i++)
    {
        if(fds[i]==-1)
        {
            fds[i]=fd;
            break;    
        }
    }    
}
void fds_del(int fds[],int fd)
{
    int i=0;
    for(;i<MAXFD;i++)
    {
        if(fds[i]==fd)
        {
            fds[i]=-1;
            break;    
        }
    }
}
int create_socket()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
        printf("socket error\n");
        return -1;    
    }

    struct sockaddr_in ser;
    memset(&ser,0,sizeof(ser));

    ser.sin_family=AF_INET;
    ser.sin_port=htons(6000);
    ser.sin_addr.s_addr=inet_addr("127.0.0.1");
    
    int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
    if(res==-1)
    {
        printf("bind error\n");    
    }

    listen(sockfd,5);
    return sockfd;    
}

int main()
{
    int sockfd=create_socket();
    assert(sockfd!=-1);

    int fds[MAXFD];
    fds_init(fds);
    fds_add(fds,sockfd);
    
    fd_set fdset;
    while(1)
    {
    FD_ZERO(&fdset);
    int maxfd=-1;
    int i=0;
    for(;i<MAXFD;i++)
    {
        if(fds[i]==-1)
        {
            continue;    
        }
        FD_SET(fds[i],&fdset);
        if(maxfd<fds[i])
        {
            maxfd=fds[i];    
        }
    }
    
    struct timeval tv={5,0};

    int num=select(maxfd+1,&fdset,NULL,NULL,&tv);
    if(num==-1)
    {
        perror("select error\n");
        continue;    
        
    }
    else if(num==0)
    {
        printf("timeout\n");    
    }
    else
    {
        int i=0;
        for(;i<MAXFD;i++)
        {
            if(fds[i]==-1)
            {
                continue;    
            }
            if(FD_ISSET(fds[i],&fdset))
            {
                if(sockfd==fds[i])
                {
                	struct sockaddr_in cli;
   					int len=sizeof(cli);
                    int c=accept(sockfd,(struct sockaddr*)&cli,&len);
                    if(c<0)
                    {
                        continue;
                    }
                    fds_add(fds,c);
                    printf("accept c=%d\n",c); 
                }
                else
                {
                    char buff[128]={0};
                    int n=recv(fds[i],buff,127,0);
                    if(n<=0)
                    {
                        close(fds[i]);
                        printf("client(%d) is over\n",fds[i]);
                        fds_del(fds,fds[i]);    
                    }
                    else
                    {
                        printf("recv(%d)=%s\n",fds[i],buff);
                        send(fds[i],"ok",2,0);
                    }
                }
            }            
        }
    }
  }
}       
Published 28 original articles, won praise 1, visited 429
Private letter follow

Posted by RHolm on Thu, 16 Jan 2020 03:20:56 -0800