Author : Kevin Lynx
前言:
在很多比較各種網(wǎng)絡(luò)模型的文章中,但凡提到select模型時(shí),都會(huì)說(shuō)select受限于輪詢的套接字?jǐn)?shù)量,這個(gè)
數(shù)量也就是系統(tǒng)頭文件中定義的FD_SETSIZE值(例如64)。但事實(shí)上這個(gè)算不上真的限制。
C語(yǔ)言的偏方:
在C語(yǔ)言的世界里存在一個(gè)關(guān)于結(jié)構(gòu)體的偏門技巧,例如:
typedef struct _str_type


{
int _len;
char _s[1];
}str_type;
str_type用于保存字符串(我只是舉例,事實(shí)上這個(gè)結(jié)構(gòu)體沒(méi)什么用處),乍看上去str_type只能保存長(zhǎng)度為
1的字符串('\0')。但是,通過(guò)寫(xiě)下如下的代碼,你將突破這個(gè)限制:
int str_len = 5;
str_type *s = (str_type*) malloc( sizeof( str_type ) + str_len - 1 );
//
free( s );


這個(gè)技巧原理很簡(jiǎn)單,因?yàn)開(kāi)s恰好在結(jié)構(gòu)體尾部,所以可以為其分配一段連續(xù)的空間,只要注意指針的使用,
這個(gè)就算不上代碼上的罪惡。但是這個(gè)技巧有個(gè)限制,str_type定義的變量必須是被分配在堆上,否則會(huì)破
壞堆棧。另外,需要?jiǎng)討B(tài)增長(zhǎng)的成員需要位于結(jié)構(gòu)體的末尾。最后,一個(gè)忠告就是,這個(gè)是C語(yǔ)言里的技巧,
如果你的結(jié)構(gòu)體包含了C++的東西,這個(gè)技巧將不再安全(<Inside the C++ object model>)。
其實(shí)select也可以這樣做:
事實(shí)上,因?yàn)閟elect涉及到的fd_set是一個(gè)完全滿足上述要求的結(jié)構(gòu)體:
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;


但是,如果使用了以上技巧來(lái)增加fd_array的數(shù)量(也就是保存的套接字?jǐn)?shù)量),那么關(guān)于fd_set的那些宏可
能就無(wú)法使用了,例如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)


有點(diǎn)讓人眼花繚亂,我鼓勵(lì)你仔細(xì)看,其實(shí)很簡(jiǎn)單。這里有個(gè)小技巧,就是他把這些代碼放到一個(gè)do...while(0)
里,為什么要這樣做,我覺(jué)得應(yīng)該是防止名字污染,也就是防止那個(gè)__i變量與你的代碼相沖突??梢钥闯觯?br>FD_SET會(huì)將fd_count與FD_SETSIZE相比較,這里主要是防止往fd_array的非法位置寫(xiě)數(shù)據(jù)。
因?yàn)檫@個(gè)宏原理不過(guò)如此,所以我們完全可以自己寫(xiě)一個(gè)新的版本。例如:
#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 )


沒(méi)什么變化,只是為FD_SET加入一個(gè)fd_array的長(zhǎng)度參數(shù),宏體也只是將FD_SETSIZE換成這個(gè)長(zhǎng)度參數(shù)。
于是,現(xiàn)在你可以寫(xiě)下這樣的代碼:
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模型:
這里我不會(huì)具體講select模型,我只稍微提一下。一個(gè)典型的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時(shí)也差不多。在Etwork(一個(gè)超小型的基本用于練習(xí)網(wǎng)絡(luò)編程的網(wǎng)絡(luò)庫(kù),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,判斷方式同上
}


兩種方式的效率從代碼上看去似乎都差不多,關(guān)鍵在于,F(xiàn)D_ISSET干了什么?這個(gè)宏實(shí)際上使用了__WSAFDIsSet
函數(shù),而__WSAFDIsSet做了什么則不知道。也許它會(huì)依賴于FD_SETSIZE宏,那么這在我們這里將是不安全的,
所以相比之下,如果我們使用了這個(gè)突破FD_SETSIZE的偏方手段,那么也許第二種方式要好些。
相關(guān)下載(5.21.2008)
隨便寫(xiě)了一個(gè)改進(jìn)的select模型的echo服務(wù)器,放上源碼。