一、需求
把https://www.cnblogs.com/soldierback/p/10673345.html中的TCP回射服务器程序重写成使用select来处理任意个客户的单进程
程序,而不是为每个进程派生一个子进程
二、分析
(1)服务器有单个监听描述符
(2)服务器只维护一个读描述符集;假设服务器是在前台启动的,那么描述符0、1、2将分别被设置为标准输入、标准输出和标准错误输出;可见监听
套接字的第一个可用描述符是3
(3)服务器维护一个名为clients的整型数组,它包含每个客户的已连接套接字描述符,该数组的所有元素都被初始化为-1
(4)当第一个客户与服务器建立连接时,监听描述符变为可读,服务器于是调用accept
(5)假设由accept返回的描述符为4,则clients数组和读描述符集如下所示
(6)当第二个客户与服务器建立连接时,假设由accept返回的描述符为5,则clients和都描述符集如下所示
(7)假设第一个客户终止它的连接;该客户的TCP发送一个FIN,使得服务器的描述符4变为可读;当服务器读这个已连接套接字时,read将
返回0,服务器于是关闭该套接字并相应地更新数据结构:把clients[0]的值置为-1,把描述符集中描述符4的位设置为0;注意:maxfd的
值没有改变
三、源代码
#include <stdio.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <strings.h> #define LISTENQ 1024 #define MAXLINE 4096 #define SERV_PORT 9999 #define SA struct sockaddr ssize_t writen(int, const void*, size_t); char *sock_ntop(const struct sockaddr*, socklen_t); int main(int argc, char *argv[]) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, clients[FD_SETSIZE]; ssize_t n; fd_set rset, allset; char buf[MAXLINE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); listen(listenfd, LISTENQ); maxfd = listenfd; /* initialize */ maxi = -1; /* index into clients[] array */ for (i = 0; i < FD_SETSIZE; i++) { clients[i] = -1; /* -1 indicates available entry */ } FD_ZERO(&allset); FD_SET(listenfd, &allset); for ( ; ; ) { rset = allset; /* structure assignment */ nready = select(maxfd+1, &rset, NULL, NULL, NULL); if (FD_ISSET(listenfd, &rset)) { /* new client connection */ clilen = sizeof(cliaddr); connfd = accept(listenfd, (SA *) &cliaddr, &clilen); for (i = 0; i < FD_SETSIZE; i++) { if (clients[i] < 0) { clients[i] = connfd; /* save descriptor */ printf("new client %d: [%s] ", i, sock_ntop((SA *)&cliaddr, sizeof(cliaddr))); break; } } if (i == FD_SETSIZE) { printf("too many clients "); goto ifAnyDescriptorReadable; } FD_SET(connfd, &allset); /* add new descriptor to set */ if (connfd > maxfd) maxfd = connfd; /* for select */ if (i > maxi) maxi = i; /* max index in client[] array */ ifAnyDescriptorReadable: if (--nready <= 0) continue; /* no more readable descriptors */ } for (i = 0; i <= maxi; i++) { /* check all clients for data */ if ( (sockfd = clients[i]) < 0) { continue; } if (FD_ISSET(sockfd, &rset)) { if ( (n = read(sockfd, buf, MAXLINE)) == 0) { /* connection closed by client */ close(sockfd); FD_CLR(sockfd, &allset); clients[i] = -1; printf("client [%d] quit ", i); } else { writen(sockfd, buf, n); } if (--nready <= 0) { break; /* no more readable descriptors */ } } } } }
注:sock_ntop和writen两个函数在分类为《UNIX网络编程》的其他随笔中有
存在的问题:某个客户建立连接后不断发送数据,此时会导致服务器拒绝为其他客户服务
解决方法:让每个客户由单独的进程或线程提供服务