socket编程中的FD_ISSET的越界问题(描述府如1024,2048,3096)
学习select的用法构建一个多路并发服务器的时候遇到问题,写了一个小程序来测试我的想法,发现很有趣的结果,列出来跟大家共享。
从select的文档和各方面资料(Unix环境高级编程 和Unix网络编程以及Google的若干篇文章)都提到FD_SETSIZE这个概念,在我用的Redhat linux AS4上我验证这个值为1024,我理解这个值表达的是在一个fd_set中最多允许的描述符的个数,而不是最大描述符的值,即一个fd_set中,可以包括1,2,3,4,5,6这样的小于1024的描述符,也可以包括如1023,1025,1026,2048这样的描述符,只是总体的个数不能超过FD_SET_SIZE.否则select就只能值处理小于1024的描述符,这个限制未免太大了一点,我未在任何资料中证实这一点。
基于以上的理解,我的测试程序如下:
#include <stdio.h>
#include <sys/socket.h>
int main(void) {
int fds[] = {1, 2, 3, 4, 5, 6, 1024,2048, 3096};
fd_set fdset;
int i;
int size;
int ret = 0;
size = sizeof(fds) /sizeof(int);
FD_ZERO(&fdset);
for(i = 0; i < size; i++) {
if( (ret = FD_ISSET(fds[i], &fdset))!= 0) {
printf( "fd %d has been in set, return code:%d\n ", fds[i], ret);
continue;
}
}
}
相当简单,置空一个fd_set,然后依次查看序列中每个描述符是否在这个fd_set中。
当数据序列fds是:1, 2, 3, 4, 5, 6, 1024,2048时,得到的输出时
fd 1024 has been in set, return code:1
fd 2048 has been in set, return code:1
当数据序列fds是:1, 2, 3, 4, 5, 6, 1023, 1024,2048, (这里多了一个1023), 得到的输出为:
fd 1024 has been in set, return code:1
当数据序列fds是:1, 2, 3, 4, 5, 6, 1024,2048,3096 (这里较第一次多了一个3096), 得到的输出为:
fd 1024 has been in set, return code:1
fd 3096 has been in set, return code:1
我找到了FD_ISSET的实现代码,是一段汇编代码,我大学的时候学过IBM PC汇编,但一是没精通,二是多年不用,三是Linux的汇编采用AT&T格式,很多东西需要从头学,所以想在这里偷一下懒,看看有没有人能帮我解释一下这个奇怪的现象。
我这么做的目的是测试fd_set到底能不能存储大的描述符,因为网络上有人说fd_set用的是位映射的方式。那如果存入一个描述符如65535,那是不是说fd_set就需要65535位也就是用8000多个字节来存一个描述符,如果这样除了一位有用外,其他的都是没有意义的,这样子启不是很浪费?
另外,章节末尾有一道题: select的实现方式,窃以为是用信号的方式实现的,不知道各位以为如何。
------解决方案--------------------早期的select用int的位来映射fd,这样只有32个fd可被监控,现在的select可以支持最多1024个fd,这可以从FD_SETSIZE的定义看出:
#define __FD_SETSIZE 1024
1024个fd通过fd_set的位来映射,而fd_set实际上是若干个long int,fd_set的定义等价于:
typedef struct
{
long int __fds_bits[1024 / (8 * sizeof(unsigned long))];
} fd_set;
可以看出,实际上一个fd_set最多包含1024个fd。
而FD_ISSET的定义等价于(汇编的定义确实难懂):
# define __FD_ISSET(d, set) (__FDS_BITS (set)[__FDELT (d)] & __FDMASK (d))
即
# define __FD_ISSET(d, set) (set)-> __fds_bits[d / __NFDBITS] & (1 < < ((d) % __NFDBITS)))
其中__NFDBITS=8 * sizeof(unsigned long),也就是一个long能包含的位数。
不难看出,当d大于1024时,__FD_ISSET会越界,而越界不一定每次都会造成访问错误,但是读到的结果肯定是非法的,并且是不可预测的,所以你的测试结果没有意义。