select

先指出一点,select的设计存在很多问题和缺陷

select的几个参数都很诡异。

许多人经常遇到的问题是每次调用过后没有重新FD_SET一下文件描述符列表,我刚接触这个函数的时候就被这个问题困扰过。

今天又遇到了个问题。

下面这段代码在我上大学的时候就写下了,一直工作的很好
[code]
int status()
{
FD_SET(fd,&fset);
return ::select(1,&fset,NULL,NULL,&s_timeout);
}

bool avail(){
int x= status();
if (x==-1){
perror("select()");
}
// char buf[4];
// int err = recv(fd, buf, sizeof(buf), MSG_PEEK);
// if(err == 0 || (err < 0 && errno != EAGAIN))
// throw 1;

return x>0;
}
[/code]
直到今天我把它移植到mac系统上

程序可以正常的发数据包,但一直没有收到任何数据,花了一段时间折腾才定位到select一直返回为0

但是这段代码在windows下工作的很好,为什么到了OS X上就失败呢?

仔细的看了看文档

int
select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);

DESCRIPTION
     Select() examines the I/O descriptor sets whose addresses are passed in readfds, writefds, and errorfds to see if some of their descriptors are ready for reading, are ready for writing, or have an exceptional condition pending, respectively.  The first nfds descriptors are checked in each set; i.e., the descriptors from 0 through nfds-1 in the descriptor sets are examined.  (Example: If you have set two file descriptors "4" and "17", nfds should  not be "2", but rather "17 + 1" or "18".)  On return, select() replaces the given descriptor sets with subsets consist-ing consisting of those descriptors that are ready for the requested operation.
Select() returns the total number of ready descriptors in all the sets.

第一个参数nfds代表的并不是所要观察的文件描述符的个数,而是最大的文件描述符加一,上面那个函数调用应该写成
[code]::select(fd+1,&fset,NULL,NULL,&s_timeout);[/code]
才行。

那么,为什么windows下能够工作正常呢?msdn上给出的说法简单粗暴

nfds [in]
Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.

windows保留这个参数只是为了跟bsd socket兼容,直接忽略掉了。

也就是说,当初我把这个参数误认为了个数,windows也忽略了,所以一直能够正常使用。而mac系统是基于freebsd内核的,是标准的bsd实现,问题才浮出水面。

select的最后一个参数timeout是个struct,使用起来需要填充各个值,很不方便,其实直接用一个int表示毫秒数就行了。

总之,这个函数的每个参数都存在陷阱和缺陷,unix的设计哲学是简洁统一,而select是个十足的反面例子。

另一个经常被人诟病的问题是性能,在服务器端已经是很经典的问题了,由于select每次都要轮询整个列表,对于一个多人在线的服务器会造成很大的性能开销,另一个poll函数也类似,为了解决这个问题,linux在2.6版本内核引入了epoll机制,epoll_wait函数并不轮询,当数据到达的时候内核会把这些描述符放入一个完成队列,检查下它就行了。

对于客户端应用来说,select的性能问题并不存在,而且在windows下,异步socket一般需要个消息循环,所以对于一些命令行程序来讲,select就是唯一的选择了,这也是我一直在使用上面这段代码的原因。