On select function (C language)

Posted by unerd.co.uk on Sat, 09 Oct 2021 06:34:59 +0200

1. Introduction

Select is still important in Socket programming, but for beginners of Socket, they don't like to write programs with select. They are just used to writing blocking programs such as connect, accept, recv or recvfrom (the so-called blocking method block, as the name suggests, means that a process or thread must wait for an event to occur when executing these functions. If the event does not occur, the process or thread will be blocked and the function cannot return immediately). However, using select can complete non blocking (the so-called non block method means that the process or thread does not have to wait for the event to occur when executing the function. Once the function is executed, it returns positive, and the return value is different to reflect the execution of the function. If the event occurs, it is the same as the blocking method. If the event does not occur, it returns a code to inform that the event does not occur, and the process or thread continues to execute, so it is effective It can monitor the changes of file descriptors we need to monitor - read, write or exceptions.

Select function format:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);

Two structures:
First, struct fd_set can be understood as a set in which file descriptors are stored , that is, file handle, which can be what we call a file in the ordinary sense. Of course, any device, pipeline, FIFO, etc. under Unix are all in the form of files, so there is no doubt that a socket is a file and a socket handle is a file descriptor. Fd_set can be manually operated through some macros, such as emptying the set FD_ZERO(fd_set *) , add a given file descriptor to fd_set (int, fd_set *), delete FD_CLR(int,fd_set *) from the set, and check whether the specified file descriptor in the set can read and write fd_isset (int, fd_set *).

Second, struct timeout is a commonly used structure to represent the time value. It has two members, one is the number of seconds and the other is the number of microseconds.

Specifically explain the parameters of select:
int maxfdp is an integer value. It refers to the range of all file descriptors in the collection, that is, the maximum value of all file descriptors plus 1. It can't be wrong! In Windows, the value of this parameter doesn't matter and can be set incorrectly.

fd_set * readfds is a pointer to the fd_set structure. This set should include file descriptors. We want to monitor the reading changes of these file descriptors, that is, we are concerned about whether we can read data from these files. If there is a file readable in this set, select will return a value greater than 0, indicating that the file is readable; if there is no readable file File, then judge whether to timeout according to the timeout parameter. If the timeout time is exceeded, select returns 0. If an error occurs, it returns a negative value. You can pass in a NULL value, indicating that you don't care about any file reading changes.

fd_set * writefds is a pointer to the fd_set structure. This set should include file descriptors. We want to monitor the write changes of these file descriptors, that is, we are concerned about whether we can write data to these files. If there is a file writable in this set, select will return a value greater than 0, indicating that there is a file writable. If there is no writable file If the timeout is exceeded, select returns 0. If an error occurs, it returns a negative value. You can pass in a NULL value to indicate that you don't care about any file write changes.

fd_set * errorfds is the same as the above two parameters. It is used to monitor file errors and exceptions.

Struct timeout * timeout is the timeout of the select. This parameter is very important. It can make the select in three states: * * first, * * if NULL is passed in as a formal parameter, that is, the time structure is not passed in, that is, the select is placed in the blocking state until a file descriptor in the monitored file descriptor set changes; * * second, * * if the time value is set to 0 seconds and 0 millisecond Seconds, it becomes a pure non blocking function. No matter whether the file descriptor changes or not, it immediately returns to continue execution. If the file does not change, it returns 0, and if there is a change, it returns a positive value; * * third, the value of * * timeout is greater than 0, which is the waiting timeout time, that is, select blocks within the timeout time, and returns when an event arrives within the timeout time, otherwise it will not be returned after the timeout The return value is the same as above.

Return value: returns the total number of descriptors whose status has changed.
Negative value: select error

Positive: some files are read-write or error

0: wait timeout, no read / write or wrong file

2. Routine

Routine 1

Read the keyboard input value, and output the number of characters entered by the user after a timeout interval of 2.5 seconds.

#include <sys/types.h> 
#include <sys/time.h> 
#include <stdio.h> 
#include <fcntl.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 

int main() 
{ 
    char buffer[128]; 
    int result, nread; 
    fd_set inputs, testfds; 
    struct timeval timeout; 
    FD_ZERO(&inputs);//Clear the set before using the select function  
     FD_SET(0,&inputs);//Add the handle to be detected - standard input (0) to the set.
     while(1) 
    { 
       testfds = inputs; 
       timeout.tv_sec = 2; 
       timeout.tv_usec = 500000; 
       result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, &timeout); 
       switch(result) 
       { 
       case 0: 
           printf("timeout/n"); 
	       break;
	   case -1: 
           perror("select"); 
           exit(1); 
       default: 
           if(FD_ISSET(0,&testfds)) 
           { 
               ioctl(0,FIONREAD,&nread);//Gets the number of characters entered from the keyboard, including carriage return. 
               if(nread == 0) 
               { 
                  printf("keyboard done/n"); 
                  exit(0); 
               } 
               nread = read(0,buffer,nread); 
               buffer[nread] = 0; 
               printf("read %d from keyboard: %s", nread, buffer); 
         } 
         break; 
      } 
   } 
   return 0;
} 

Routine 2

Server side

#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <sys/time.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 

int main() 
{ 
	int server_sockfd, client_sockfd; 
	int server_len, client_len; 
	struct sockaddr_in server_address; 
	struct sockaddr_in client_address; 
	int result; 
	fd_set readfds, testfds; 
	server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//Establish server socket 
	server_address.sin_family = AF_INET; 
	server_address.sin_addr.s_addr = htonl(INADDR_ANY); 
	server_address.sin_port = htons(9734); 
	server_len = sizeof(server_address); 
	bind(server_sockfd, (struct sockaddr *)&server_address, server_len); 
	listen(server_sockfd, 5); 
	FD_ZERO(&readfds); 
	FD_SET(server_sockfd, &readfds);//Add the server-side socket to the collection
	while(1) 
	{
		char ch;                
		int fd; 
		int nread; 
		testfds = readfds; 
		printf("server waiting/n"); 

		/*Block indefinitely and test file descriptor changes */
		result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); 
		if(result < 1) 
		{ 
			perror("server5"); 
			exit(1); 
		} 

		/*Scan all file descriptors*/
		for(fd = 0; fd < FD_SETSIZE; fd++) 
		{
			/*Find related file descriptor*/
			if(FD_ISSET(fd,&testfds)) 
			{ 
		     	        /*Judge whether it is a server socket. If yes, it means that the client requests a connection.*/
				if(fd == server_sockfd) 
				{ 
					client_len = sizeof(client_address); 
					client_sockfd = accept(server_sockfd, 
					(struct sockaddr *)&client_address, &client_len); 
					FD_SET(client_sockfd, &readfds);//Add the client socket to the collection
					printf("adding client on fd %d/n", client_sockfd); 
				} 

				/*When there is a data request in the client socket*/
				else 
				{                                          
					ioctl(fd, FIONREAD, &nread);//Get the data volume and give it to nread
					
					/*After the client data request is completed, close the socket and clear the corresponding descriptor from the collection */
					if(nread == 0) 
					{ 
						close(fd); 
						FD_CLR(fd, &readfds); 
						printf("removing client on fd %d/n", fd); 
					} 

					/*Processing customer data requests*/
					else 
					{ 
						read(fd, &ch, 1); 
						sleep(5); 
						printf("serving client on fd %d/n", fd); 
						ch++; 
						write(fd, &ch, 1); 
					} 
				} 
			} 
		} 
	} 
} 

client

#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h>                                                                    
#include <arpa/inet.h> 
#include <unistd.h> 

int main() 
{ 
    int client_sockfd; 
    int len; 
    struct sockaddr_in address;//Server side network address structure                                           
     int result; 
    char ch = 'A'; 
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//Establish client socket                               
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = inet_addr("127.0.0.1");             
    address.sin_port = 9734; 
    len = sizeof(address); 
    result = connect(client_sockfd, (struct sockaddr *)&address, len); 
    if(result == -1) 
    { 
         perror("oops: client2"); 
         exit(1); 
    } 
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("char from server = %c/n", ch); 
    close(client_sockfd); 
    zexit(0); 
}                      

Topics: C Windows TCP/IP