[I/O multiplexing] poll system call

Article Directory

[1] Preface

poll is essentially the same as select, it copies the user's incoming array into the kernel space, then queries each fd for the corresponding device state, adds an entry to the device waiting queue if the device is ready to traverse, and continues traversing if no ready device is found after traversing all fds, suspends the current process until the device is readyA thread or active timeout occurs and it traverses the fd again after being waked up.

[2] poll system call function prototype

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

fds parameter: An array of pollfd structure types that specifies all readable, writable, and exception events that occur on all file descriptors that we want to listen to.

nfds parameter: Specifies the size of the set of listened events fds

Timeout parameter: Specifies the timeout value of poll in milliseconds.
When timeout is -1: the poll call will always block until an event occurs
When timeout is 0: the poll call returns immediately

 typedef  unsigned  long int  nfds_t;

The pollfd structure is defined as follows:

struct pollfd 
{
	int fd;         	/* File Descriptor */
	short events;       /* Registered Events */
	short revents;      /* What actually happened, populated by the kernel*/
}; 

Where the fd member specifies the file descriptor;
The events member tells poll to listen for events on the fd that he is in place or is a series of events;
revents members are modified by the kernel to notify the application of what events actually occurred on the fd.
If an event on an event occurs, r events will place itself at 1 for the event

The time types supported by poll are as follows:

Event describe
POLLIN Readable with data
POLLRDNORM Readable with normal data
POLLRDBAND Readable with priority data
POLLPRI There is urgent data to read
POLLOUT Writing data does not cause blocking
POLLWRNORM Writing normal data does not cause blocking
POLLWRBAND Write-first data does not cause blocking
POLLMSGSIGPOLL Message Available
... ...

The following events may be returned in the revents domain:

Event describe
POLLER An error occurred with the specified file descriptor
POLLHUP Specified file descriptor pending event
POLLNVAL Illegal file descriptor specified

Return value

  • Greater than 0: indicates that the state of the socket descriptor in the array fds has changed, or that it can be read, written, or erroneous.And the returned value represents the total number of socket descriptors whose states have changed; at this point, the fds array can be traversed to find those socket descriptors that are not empty for revents, and then determine what events are inside to read the data.
  • Equal to 0: indicates that no socket descriptor has a state change and the call timed out.
  • Less than 0: Error occurs when the global variable errno holds the error code.

[3] Program example

The following is a program for TCP-based multi-client server interaction:

[1] If the client does not send data after three connections, the server will poll the traversed fds array within five seconds of the time out for the client to send data. The time out time is set to five seconds. If no data is sent to the server within five seconds, a "time out" will be printed.
[2] Until the client sends the data, the kernel modifies the event's fds[i].revevts, and poll processes the event by traversing the FDS array to find the fds[i].revents exclusive or readable POLLIN true.
[3] There are two situations at this time. If fds[i].fd == sockfd indicates that there is a connection pending in the listening queue, use accept to pull out a connection.Otherwise, there is no new connection, the client sends data, we use recv directly to receive the client data, and print the received data.
server.c

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

#define MAXFD 10
int create_sockfd();

//Add the file descriptor fd to the fds array to specify events of interest
void fds_add(struct pollfd fds[],int fd)
{
	int i=0;
	for(;i<MAXFD;++i)
	{
		if(fds[i].fd==-1)
		{
			fds[i].fd=fd;
			fds[i].events=POLLIN;
			fds[i].revents=0;
			break;
		}
	}

}
//Deletes the specified file descriptor fd and its associated event information from the fds array
void fds_del(struct pollfd fds[],int fd)
{
	int i=0;
	for(;i<MAXFD;++i)
	{
		if(fds[i].fd==fd)
		{
			fds[i].fd=-1;
			fds[i].events=0;
			fds[i].revents=0;
			break;
		}
	}
}
//Initialize the fds array with the file descriptor fd set to -1 and the rest set to 0
void fds_init(struct pollfd fds[])
{
	int i=0;
	for(;i<MAXFD;++i)
	{
		fds[i].fd=-1;
		fds[i].events=0;
		fds[i].revents=0;
	}
}
int main()
{
	int sockfd=create_sockfd();
	assert(sockfd!=-1);
	
	//Initialize pollfd type struct array fds
	struct pollfd fds[MAXFD];

	//Initialize fds array
    fds_init(fds);
	
	//Add sockfd to fds array
	fds_add(fds,sockfd);

	while(1)
	{
		//Use poll system call polling to test for readiness
		int n=poll(fds,MAXFD,5000);
		if(n==-1)
		{
			perror("poll error");
		}
		else if(n==0)	//Return 0 indicates timeout, no ready
		{
			printf("time out\n");
		}
		else	//File descriptor ready for fds array
		{
			int i=0;
			for(;i<MAXFD;++i)
			{
				if(fds[i].fd==-1)
				{
					continue;
				}
				
				//Which file descriptor is available and the member revents in fds[i] are modified by the system kernel
				if(fds[i].revents &POLLIN)
				{
					//There are two situations at this time.
					//If fds[i].fd == sockfd indicates that there are connections pending processing in the listening queue
					//Then use accept to receive the connection
					//Otherwise, no new connection will be made and data will be sent from an existing client.
					//We use recv directly to receive client data and print the data we receive
					if(fds[i].fd==sockfd)
					{
						struct sockaddr_in caddr;
						int len=sizeof(caddr);
						
						//Receive a socket established connection to get the connection socket c
						int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
						if(c<0)
						{
							continue;
						}
						printf("accept c=%d\n",c);
						//Add a new connection socket c to the array fds of pollfd structure type
						fds_add(fds,c);//join
					}
					else
					{
						char buff[128]={0};
						
						//Receive client data using recv
						int res=recv(fds[i].fd,buff,127,0);
						
						//The data on the receiving server side is zero, that is, n returns 0, indicating that the client has been shut down
						if(res<=0)
						{
							//Close file descriptor fds[i].fd
							close(fds[i].fd);
							//Remove this file descriptor from the fds array 
							fds_del(fds,fds[i].fd);
							printf("one client over\n");
						}
						else
						{
							//n is not zero, that is, when data is received, print the data and reply to the client with a message
							printf("recv(%d)=%s\n",fds[i].fd,buff);
							send(fds[i].fd,"ok",2,0);
						}
					}
				}
			}
		}
	}
}

int create_sockfd()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd==-1)
	{
		return -1;
	}

	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

	int res=bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
	if(res==-1)
	{
		return -1;
	}

	listen(sockfd,5);

	return sockfd;
}



client.c

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

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1 );

	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//Link to server
	assert(res != -1);

	while(1)
	{
		char buff[128] = {0};
		printf("Please Input:");
		fgets(buff,128,stdin);
		if(strncmp(buff,"end",3) ==0 )
		{
			break;
		}
		send(sockfd,buff,strlen(buff),0);
		memset(buff,0,128);
		recv(sockfd,buff,127,0);
		printf("RecvBuff:%s\n",buff);
        printf("\n");
	}
	close(sockfd);
}

The results are as follows:
When the program has no input, the time out will be printed every 5 seconds.

[4] Advantages and disadvantages of poll

Advantages of poll:

  • poll() does not require developers to calculate the size of the maximum file descriptor plus one.
  • poll() is faster than select when dealing with a large number of file descriptors
  • It has no limit on the maximum number of connections because it is stored based on a chain table.

Disadvantages of poll:

  • As with select, pollfd needs to be polled to get ready descriptors after poll returns, which can degrade performance.

  • Although poll breaks the limit of listening for a maximum of 1024 file descriptors, it has the disadvantage that when there are only a few active connections on a large number of connections, it also uses a polling mechanism to traverse the entire array at a time

Keywords: socket less REST

Added by Alk3m1st on Sat, 20 Jul 2019 04:08:53 +0300