日期:2014-05-16  浏览次数:20661 次

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会越界,而越界不一定每次都会造成访问错误,但是读到的结果肯定是非法的,并且是不可预测的,所以你的测试结果没有意义。