loop_in_codes

低调做技术__欢迎移步我的独立博客 codemaro.com 微博 kevinlynx

突破select的FD_SETSIZE限制

Author : Kevin Lynx 

前言:

在很多比较各种网络模型的文章中,但凡提到select模型时,都会说select受限于轮询的套接字数量,这个
数量也就是系统头文件中定义的FD_SETSIZE值(例如64)。但事实上这个算不上真的限制。

C语言的偏方:

在C语言的世界里存在一个关于结构体的偏门技巧,例如:

 

typedef struct _str_type
{
   
int _len;
   
char _s[1];
}
str_type;

 

str_type用于保存字符串(我只是举例,事实上这个结构体没什么用处),乍看上去str_type只能保存长度为
1的字符串('\0')。但是,通过写下如下的代码,你将突破这个限制:

int str_len = 5;
str_type
*s = (str_type*) malloc( sizeof( str_type ) + str_len - 1 );
//
free( s );


这个技巧原理很简单,因为_s恰好在结构体尾部,所以可以为其分配一段连续的空间,只要注意指针的使用,
这个就算不上代码上的罪恶。但是这个技巧有个限制,str_type定义的变量必须是被分配在堆上,否则会破
坏堆栈。另外,需要动态增长的成员需要位于结构体的末尾。最后,一个忠告就是,这个是C语言里的技巧,
如果你的结构体包含了C++的东西,这个技巧将不再安全(<Inside the C++ object model>)。

其实select也可以这样做:

事实上,因为select涉及到的fd_set是一个完全满足上述要求的结构体:

winsock2.h :

typedef
struct fd_set {
        u_int fd_count;              
/* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];  
/* an array of SOCKETs */
}
fd_set;


但是,如果使用了以上技巧来增加fd_array的数量(也就是保存的套接字数量),那么关于fd_set的那些宏可
能就无法使用了,例如FD_SET。

winsock2.h :

#define FD_SET(fd, set) do { \
    u_int __i; \
   
for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
       
if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \
           
break; \
        }
\
    }
\
   
if (__i == ((fd_set FAR *)(set))->fd_count) { \
       
if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \
            ((fd_set FAR
*)(set))->fd_array[__i] = (fd); \
            ((fd_set FAR
*)(set))->fd_count++; \
        }
\
    }
\
}
while(0)


有点让人眼花缭乱,我鼓励你仔细看,其实很简单。这里有个小技巧,就是他把这些代码放到一个do...while(0)
里,为什么要这样做,我觉得应该是防止名字污染,也就是防止那个__i变量与你的代码相冲突。可以看出,
FD_SET会将fd_count与FD_SETSIZE相比较,这里主要是防止往fd_array的非法位置写数据。

因为这个宏原理不过如此,所以我们完全可以自己写一个新的版本。例如:

#define MY_FD_SET( fd, set, size ) do { \
    unsigned
int i = 0; \
   
for( i = 0; i < ((fd_set*) set)->fd_count; ++ i ) { \
       
if( ((fd_set*)set)->fd_array[i] == (fd) ) { \
           
break; \
        }
\
    }
\
   
if( i == ((fd_set*)set)->fd_count ) { \
       
if( ((fd_set*)set)->fd_count < (size) ) { \
            ((fd_set
*)set)->fd_array[i] = (fd); \
            ((fd_set
*)set)->fd_count ++; \
        }
\
    }
\
}
while( 0 )


没什么变化,只是为FD_SET加入一个fd_array的长度参数,宏体也只是将FD_SETSIZE换成这个长度参数。
于是,现在你可以写下这样的代码:

unsigned int count = 100;
fd_set
*read_set = (fd_set*) malloc( sizeof( fd_set ) + sizeof(SOCKET) * (count - FD_SETSIZE ) );
SOCKET s
= socket( AF_INET, SOCK_STREAM, 0 );
//
MY_FD_SET( s, read_set, count );
//
free( read_set );
closesocket( s );


小提下select模型:

这里我不会具体讲select模型,我只稍微提一下。一个典型的select轮询模型为:

int r = select( 0, &read_set, 0, 0, &timeout );
if( r < 0 )
{
   
// select error
}
 

if( r > 0 )
{
   
for( each sockets )
   
{
       
if( FD_ISSET( now_socket, &read_set ) )
       
{
           
// this socket can read data
        }

    }

}
 


轮询write时也差不多。在Etwork(一个超小型的基本用于练习网络编程的网络库,google yourself)中,作者
的轮询方式则有所不同:

// read_set, write_set为采用了上文所述技巧的fd_set类型的指针
int r = select( 0, read_set, write_set, 0, &timeout );
//  error handling
for( int i = 0; i < read_set->fd_count; ++ i )
{
   
// 轮询所有socket,这里直接采用read_set->fd_array[i] == now_socket判断,而不是FD_ISSET
}
 

for( int i = 0; i < write_set->fd_count; ++ i )
{
   
// 轮询所有socket,检查其whether can write,判断方式同上
}
 


两种方式的效率从代码上看去似乎都差不多,关键在于,FD_ISSET干了什么?这个宏实际上使用了__WSAFDIsSet
函数,而__WSAFDIsSet做了什么则不知道。也许它会依赖于FD_SETSIZE宏,那么这在我们这里将是不安全的,
所以相比之下,如果我们使用了这个突破FD_SETSIZE的偏方手段,那么也许第二种方式要好些。

相关下载(5.21.2008)

随便写了一个改进的select模型的echo服务器,放上源码

posted on 2008-05-20 11:20 Kevin Lynx 阅读(22310) 评论(12)  编辑 收藏 引用 所属分类: game developnetwork

评论

# re: 突破select的FD_SETSIZE限制 2008-05-20 11:49 2nd guest

何必搞那么复杂,叫人怎么维护呢?  回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2008-05-20 13:03 Kevin Lynx

@2nd guest
我觉得这个东西不复杂,只要保持模块对外接口的简洁,维护这么小的模块不会那么复杂。  回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2008-05-20 13:16 eXile

可以参考 boost::asio中的detail/win_fd_set, 很简单  回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2008-05-20 14:24 Kevin Lynx

@eXile
谢谢提醒,win_fd_set_adapter.hpp确实是个方案。那估计是用__WSAFDIsSet也不是问题了。:)   回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2008-05-20 16:44 hsen

__WSAFDIsSet 好像是Windows的API吧。Windows下的select有这个限制吗?  回复  更多评论   

# re: 突破select的FD_SETSIZE限制[未登录] 2008-05-24 20:40 christanxw

多此一举。直接重新FD_SETSIZE不就是了,何必搞这么复杂。  回复  更多评论   

# 突破select的FD_SETSIZE限制[未登录] 2008-05-24 20:41 christanxw

多此一举。直接重新定义FD_SETSIZE不就是了,何必搞这么复杂。  回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2008-07-03 01:47 denny

@christanxw

正因为FD_SETSIZE是系统宏,所有才有这个问题,要不你重新编译内核,那么这种解决方案也不具有通用性  回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2008-10-29 11:42 itssunny

其实没必要这么费劲,只要在包含 winsock2.h 头文件的前面重新define 一下 FD_SETSIZE 的值就可以修改这个限制。但为了性能考虑这样做没什么太大意义,最好还是用重叠io或完成端口等异步模型性能好。  回复  更多评论   

# re: 突破select的FD_SETSIZE限制[未登录] 2010-05-14 23:58 ttylikl

do{}while(0)的解释错误呢
考虑这种情况:
if(****)
FD_SET(....);
else
FD_SET(....);

这个时候do {} while(0)就很重要啦。  回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2011-01-21 11:10 jmzz

直接重新定义FD_SETSIZE即可

ws2_32.dll中_WSAFDIsSet的实现

int __stdcall _WSAFDIsSet(SOCKET fd, fd_set *a2)
{
int result; // eax@1
u_int v3; // ecx@1
char *v4; // edx@2

v3 = a2->fd_count;
result = 0;
if ( a2->fd_count )
{
v4 = (char *)&a2->fd_array[v3];
do
{
v4 -= 4;
if ( *(_DWORD *)v4 == fd )
result = 1;
--v3;
}
while ( v3 );
}
return result;
}
  回复  更多评论   

# re: 突破select的FD_SETSIZE限制 2013-08-31 15:30 lkz

MSDN说了,在include头文件前重新定义FD_SETSIZE即可……  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理