程序简介:这是一个运用select函数进行IO复用的服务器模型。它的原理是:当一个客户端连接上服务器时,服务器就将其连接的fd加入fd_set集合,等到这个连接准备好读或写的时候,就通知程序进行IO操作,与客户端进行数据通信。它是一个经典的IO复用模型,但当同时连接的客户端数量变大时,fd_set集合就变得很大,而每次要时行IO操作时,都要遍历一次这个集合,导致效率降低,所以它现在基本上被EPOLL模型代替了。
上代码:
#include "my_unp.h"
int main(void)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
pid_t childpid;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
//创建用于TCP协议的套接字
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//把socket和socket地址结构联系起来
Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
//开始监听LISTENQ端口
Listen(listenfd, LISTENQ);
maxfd = listenfd;
maxi = -1;
//初始化结构fd_set
for(i=0; i<FD_SETSIZE; i++)
client[i] = -1;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
while(1)
{
rset = allset;
//阻塞程序,等待某个事件的发生(见注解1)
//这里没有对Select函数设置超时,会导致程序有安全隐患
nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
//如果监听的套接字变得可读,就建立一个新链接
if( FD_ISSET(listenfd, &rset) )
{
clilen = sizeof(cliaddr);
//调用accept函数并更新相关的数据结构
connfd = Accept(listenfd, (SA*)&cliaddr, &clilen);
//输出客户端的IP地址与端口号,还有处理(子)进程的PID
printf("connection from %s, port %d. process with clild %d\n",
Inet_ntop(AF_INET, (void*)&cliaddr.sin_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port), childpid);
for(i=0; i<FD_SETSIZE; i++)
{
if( client[i] < 0 )
{
client[i] = connfd;
break;
}
}
//链接数已经达到上限了
if( FD_SETSIZE == i )
error_quit("too many clients");
//增加新的描述符到集合中
FD_SET(connfd, &allset);
if( connfd > maxfd )
maxfd = connfd;
if( i > maxi )
maxi = i;
//利用select的返回值来检查末就绪的描述符
if( --nready <= 0 )
continue;
}
//扫描每个现在客户链接
for(i=0; i<=maxi; i++)
{
sockfd = client[i];
if( sockfd < 0 )
continue;
if( FD_ISSET(sockfd, &rset) )
{
n = Read(sockfd, buf, MAXLINE);
//如果客户关闭了链接,就更新数据结构
if( 0 == n )
{
Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
}
//否则,就回射一段字符给客户端
else
Writen(sockfd, buf, n);
}
if( --nready <= 0 )
break;
}
}
return 0;
}