Back to Blog

Linux Socket Network Programming: fcntl and select (Handling Multiple Client Connections to a Server)

#Socket#Server#Linux#Programming#Network#Struct

I. Introduction

In practical scenarios, people often encounter situations where multiple clients connect to a server. Since previously introduced functions like connect, recv, and send are blocking functions, if resources are not fully prepared, the process calling these functions will enter a sleep state, making it impossible to handle I/O multiplexing.

This article presents two methods for I/O multiplexing: fcntl() and select(). As can be seen, treating sockets as special file descriptors in Linux greatly simplifies user handling.

II. fcntl

The fcntl() function has the following characteristics:

  1. Non-blocking I/O: cmd can be set to F_SETFL, and lock can be set to O_NONBLOCK.

  2. Signal-driven I/O: cmd can be set to F_SETFL, and lock can be set to O_ASYNC.

Example:

#include <sys/types.h>#include <sys/socket.h>#include <sys/wait.h>#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <sys/un.h>#include <sys/time.h>#include <sys/ioctl.h>#include <unistd.h>#include <netinet/in.h>#include <fcntl.h>#include <unistd.h> #define SERVPORT 3333#define BACKLOG 10#define MAX_CONNECTED_NO 10#define MAXDATASIZE 100 int main(){	struct sockaddr_in server_sockaddr,client_sockaddr;	int sin_size,recvbytes,flags;	int sockfd,client_fd;	char buf[MAXDATASIZE];/*创建socket*/ // Create socket	if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){		perror("socket");		exit(1);	}	printf("socket success!,sockfd=%d\n",sockfd); /*设置sockaddr结构*/ // Set sockaddr structure	server_sockaddr.sin_family=AF_INET;	server_sockaddr.sin_port=htons(SERVPORT);	server_sockaddr.sin_addr.s_addr=INADDR_ANY;	bzero(&(server_sockaddr.sin_zero),8); /*将本地ip地址绑定端口号*/ // Bind local IP address to port number	if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){		perror("bind");		exit(1);	}	printf("bind success!\n"); /*监听*/ // Listen	if(listen(sockfd,BACKLOG)==-1){		perror("listen");		exit(1);	}	printf("listening....\n"); /*fcntl()函数,处理多路复用I/O*/ // fcntl() function, handles multiplexed I/O	if((flags=fcntl( sockfd, F_SETFL, 0))<0)			perror("fcntl F_SETFL");		flags |= O_NONBLOCK;		if(fcntl( sockfd, F_SETFL,flags)<0)			perror("fcntl");	while(1){		sin_size=sizeof(struct sockaddr_in);		if((client_fd=accept(sockfd,(struct sockaddr*)&client_sockaddr,&sin_size))==-1){  //服务器接受客户端的请求,返回一个新的文件描述符 // Server accepts client request, returns a new file descriptor			perror("accept");			exit(1);		}		if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){			perror("recv");			exit(1);		}		if(read(client_fd,buf,MAXDATASIZE)<0){			perror("read");			exit(1);		}		printf("received a connection :%s",buf); /*关闭连接*/ // Close connection	close(client_fd);	exit(1);	}/*while*/}

Run this program:

[root@localhost net]# ./fcntlsocket success!,sockfd=3bind success!listening....accept: Resource temporarily unavailable

As you can see, when accept resources are unavailable, the program returns automatically.

If the red bolded code is replaced with:

	if((flags=fcntl( sockfd, F_SETFL, 0))<0)			perror("fcntl F_SETFL");		flags |= O_ASYNC;		if(fcntl( sockfd, F_SETFL,flags)<0)			perror("fcntl");

The running result is as follows:

[root@localhost net]# ./fcntl1socket success!,sockfd = 3bind success!listening...  

As you can see, the process remains in a waiting state until another relevant signal drives it.

III. select

#include <sys/types.h>#include <sys/socket.h>#include <sys/wait.h>#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <sys/un.h>#include <sys/time.h>#include <sys/ioctl.h>#include <unistd.h>#include <netinet/in.h>#define SERVPORT 3333#define BACKLOG 10#define MAX_CONNECTED_NO 10#define MAXDATASIZE 100int main(){	struct sockaddr_in server_sockaddr,client_sockaddr;	int sin_size,recvbytes;	fd_set readfd;	fd_set writefd;	int sockfd,client_fd;	char buf[MAXDATASIZE];/*创建socket*/ // Create socket	if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){		perror("socket");		exit(1);	}	printf("socket success!,sockfd=%d\n",sockfd);/*设置sockaddr结构*/ // Set sockaddr structure	server_sockaddr.sin_family=AF_INET;	server_sockaddr.sin_port=htons(SERVPORT);	server_sockaddr.sin_addr.s_addr=INADDR_ANY;	bzero(&(server_sockaddr.sin_zero),8);/*将本地ip地址绑定端口号*/ // Bind local IP address to port number	if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){		perror("bind");		exit(1);	}	printf("bind success!\n");/*监听*/ // Listen	if(listen(sockfd,BACKLOG)==-1){		perror("listen");		exit(1);	}	printf("listening....\n");/*select*/	FD_ZERO(&readfd);              // 将readfd 清空 // Clear readfd FD_SET(sockfd,&readfd);         //将sockfd加入到readfd集合中 // Add sockfd to the readfd set	while(1){	sin_size=sizeof(struct sockaddr_in);	if(select(MAX_CONNECTED_NO,&readfd,NULL,NULL,(struct timeval *)0)>0){  //第一个参数是0和sockfd的最大值加1,第二个参数是读集,第三、四个参数是写集                                                                                //和异常集 // The first parameter is the maximum value of 0 and sockfd plus 1, the second parameter is the read set, and the third and fourth parameters are the write set and exception set.		if(FD_ISSET(sockfd,&readfd)>0){         // FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符。从 sockfd 中读入, 输出到标准输出上去. // The FD_ISSET macro checks if sockfd belongs to the set of readable file descriptors. Data is read from sockfd and output to standard output.			if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1){   //client_sockaddr:客户端地址 // client_sockaddr: client address				perror("accept");				exit(1);			}			if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){				perror("recv");				exit(1);			}			if(read(client_fd,buf,MAXDATASIZE)<0){				perror("read");				exit(1);			}			printf("received a connection :%s",buf);		}/*if*/		close(client_fd);		}/*select*/	}/*while*/}运行结果如下:[root@localhost net]#  gcc select1.c -o select1[root@localhost net]# ./select1socket create success!bind success!listening...

IV. Reflection