设计极其糟糕的select函数
相较Windows而言,大部分UNIX API函数设计都比较考究,但也有少数简直就是奇葩,select函数正是这些奇葩中非常灿烂的一朵。我原来一致钟情于ACE,接触的只是reactor,最近由于开始自己设计网络层的类库,被迫和select打了一些交道,被迫和这个函数打了一些交道,结果只能是看着就吐了,吐着吐着就习惯了。
UNIX下select这个API由主函数select和几个fd_set辅助函数构成。如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); //fd_set的实际结构是__kernel_fd_set typedef struct { unsigned long fds_bits [__FDSET_LONGS]; } __kernel_fd_set;
select主函数的第一个参数nfds的描述是 nfds is the highest-numbered file descriptor in any of the three sets, plus 1.也就是最大的文件句柄数值+1,这个参数的本质意图应该是加快内部处理的,避免内部处理几个fd_set的时候都不用处理次数,从而降低系统的计算销毁,但是select本来就是要处理3个fd_set,这个nfds就只能是这3个fd_set中间的最大值,而且考虑到nfds不可能一直增加,而不减少,在FD_CLR清理掉某个句柄后,找一个新的最大值,必须和3个fd_set打交道。简直是……,很多人,很多库的处理FD_CLR后,其实会偷懒不减少nfds。这个其实不是大家的错,而是select设计者的原罪。(当然你也可以像ACE那样包装一层加快实现)
改良的方案很简单,考虑到fd_set本来就只是内部的一个结构,每个结构拥有自己的最大值,是一件非常easy的事情。这样就可以取消那个讨厌的nfds参数。我们后面看Windows下的select设计时会继续讨论这个问题。
由于3个fd_set参数都是传入传出参数,所以如果是服务器程序,你老只好在每次调用select前保留一次这些句柄。
再来看看这个UNIX大部分设计中fd_set的实现,他就是一个用bit位标识文件句柄的long数组。所以他只能处理文件句柄数值小于1024的文件句柄,由于还有其他地方会占用前面的文件句柄ID,所以其实UNIX的select根本无法处理FD_SETSIZE个网络请求。甚至极端情况下你的程序一开始就打开了1024个文件,你就别想使用这个可爱的函数,他就没有这个处理能力……
Windows下的select函数基本向UNIX平台靠齐,但是由于Windows下HANDLE(SOCKET)完全不是整数(而是一个指针),Windows的select,可以实际处理FD_SETSIZE个文件句柄(Windows下这个参数默认64,可以在保护winsock2.h前面重新定义这个宏调整)。Windows下的select函数就没有使用第一个参数,其通过设计fd_set达到了同样加速的目的。
//Windows下的fd_set的定义 typedef struct fd_set { //如果UNIX的fd_set也有这个参数,那么UNIXselect函数的处理也简单了 u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;
最后我们看看FD_ISSET的设计,大部分select的例子都是使用FD_ISSET判断某个句柄是否被触发的,但是真正我们写服务器时,如果要老老实实使用FD_ISSET,方法就只能是自己先保存一组放入select的句柄参数。然后将这些句柄取出一个个和返回的fd_set用FD_ISSET进行判断,所以这个成本至少是一个O(nfds),而在Windows下,由于句柄ID不可能直接映射查找。查询效率应该是O(输入参数fd_set的数量*触发返回的fd_set的数量)。Windows下有没有快一点的法子呢,有,就是不用FD_ISSET,直接利用fd_set的结构。处理效率就只需要O(触发返回的fd_set的数量),当然这又违背了select函数FD_ISEET这类函数(宏)的设计初衷,不希望你了解fd_set的内部结构。
整体说来,UNIX下的select函数的设计是完全是结合UNIX文件句柄的设计进行的,同时考虑了部分加快速度部分的处理,虽然在那个年代,也许有他的苦衷。但无需遮掩,其整体设计是比较失败的,既没有效率,也没有考虑扩展性,而Windows下select的设计比UNIX版本高出一节。而如果把epoll的API拿出来比一下,高下立分。
【本文作者是fullsail(雁渡寒潭),本着自由的精神,你可以在无盈利的情况完整转载此文档,转载时请附上BLOG链接:http://www.cnblogs.com/fullsail,否则每字一元不讲价。对Baidu文库加价一倍】