Linux Socket Network Programming: fcntl and select (Handling Multiple Client Connections to a Server)
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:
-
Non-blocking I/O:
cmdcan be set toF_SETFL, andlockcan be set toO_NONBLOCK. -
Signal-driven I/O:
cmdcan be set toF_SETFL, andlockcan be set toO_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