1.先看一下select 函数原型:
#include
#include
int select (int maxfdp1,fd_set *readset, fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
其中,maxfdp1 是select 所要监听的最大描述符,所谓描述符就是一些 int 型的id值,比如一个socket描述符,或者i/o描述符(fileno(stdinout)等),源自Berkeley的实现已经允许任何描述符的i/o复用。SVR3最初吧i/o复用限制于对应流的设备的描述符,SVR4去除了这个限定。
第二个参数到第四个参数,这三个参数主要用来绑定fd_set 结构体,三个分别对应读操作、写操作、异常条件描述符。也就是我们关心的那个操作,比如我想看看socket 套接字啥时候可以读了,我就把这个套接字的读操作也就是第二个参数设成一个fd_set 类型的值,而其他的操作位上 全部射程NULL。
最后一个参数主要用来设置最晚等待时间的,当我们的进程进行可读操作的判断时,进程等待的时间最多时多少,一旦超过了这个时间,那么进程就不再等了。struct timeval的结构如下
struct timeval{
long tv_sec;
long tv_usec;
}
第一个成员是秒,第二个成员是微秒。也就是精确到微秒。这个参数有以下三种可能:
1〉永远等下去,直到socket 可读或者可写。我们只需将第三个参数设成NULL。
2〉等待一段固定的秒数:在一个描述符就绪是就返回,但是不超过该参数所指定的timeval的成员之和。
3〉根本不等待:检查描述符后立即返回,这称为轮询。我们只需将第三个参数设成0就行。
依次使用下面几个函数对我们的描述符进行设置监听和取消监听等操作:
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd , fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
直接上代码:
int
main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[4096];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); /*设置服务器IP地址*/
servaddr.sin_family = AF_INET; /*并且设置ipv4的各种选项*/
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9877);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); /*bind服务器的listenfd套接字*/
listen(listenfd, 5); /*listen套接字编程监听套接字*/
maxfd = listenfd;
maxi = -1;
for(i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* 设置client数组的初始值(-1)*/
FD_ZERO(&allset);
FD_SET(listenfd, &allset); /*将listenfd套接字加入fd_set结构中,设置监听服务,一旦有玩家连接上了,listenfd描述符就变成可读*/
for(;;)
{
rset = allset;
nready = select(maxfd + 1, &rset, NULL, NULL, NULL); /*将rset的监听服务开启,一旦rset中被设置的成员有一个或者多个描述符就位就返回*/
if(FD_ISSET(listenfd, &rset))
{
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
for(i = 0; i < FD_SETSIZE; i++)
{
if(client[i] < 0)
{
client[i] = connfd; /* 每次有玩家连接,就将client数组里面的第一个为-1的元素值射程该描述符的值 */
break;
}
}
if(i == FD_SETSIZE)
{
printf("too many clients\n");
exit(1);
}
FD_SET(connfd, &allset); /* 添加并且设置一个新的描述符,每有一个玩家连接上,你就得把它加入到队列,好为他们服务 */
if(connfd > maxfd) /*重新设置maxfd,好为下一次select的设置做准备,将maxfd设置成第一个参数值*/
maxfd = connfd;
if(i > maxi)
maxi = i; /*这个maxi主要用于给客户服务时,遍历到的最大的个数*/
if(--nready <= 0) /*判断已连接的描述符是不是就是刚刚来的那一个,如果里面就一个描述符,我就索性不服务了,直接accept其他人*/
continue;
}
for(i = 0; i <= maxi; i++) /* 开始为我们加入队列的玩家进行服务 */
{
if((sockfd = client[i]) < 0)
continue; /*如果client[i]值为-1,那么我们认为这个位置上面没有玩家连接,就直接continue;*/
if(FD_ISSET(sockfd, &rset))
{
if((n = read(sockfd, buf, 4096)) == 0) /*如果sockfd 准备好读了,我们的主进程就直接开始读了,如果读到了FIN,执行下面的代码*/
{
close(sockfd);
FD_CLR(sockfd, &rset);
client[i] = -1;
}
else /*如果正常读到内容,就写到sockfd里面,我们这个实例比较简单 ,就是服务器直接反射你的内容传个你*/
{
writen(sockfd, buf, n);
}
if(--nready <= 0) /* 如果就这一个人,那也就不往下检测是否还有其他玩家了,就直接重新开始accept其他玩家了。看看listenfd描述符有没有准备好*/
break;
}
} /*这就是但进程处理client连接并服务client的例子,弊端就是accept和服务client不能同时进行。先accept,在去服务clients*/
}
}