select模式
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
select模式
在很多⽐较各种⽹络模型的⽂章中,但凡提到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的偏⽅⼿段,那么也许第⼆种⽅式要好些。
其实我就是想看看select模式到底是啥,不知libeventwindows下⽤的是select还是iocp,select模式怎么突破64啊。