程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 突破select的FD_SETSIZE限制

突破select的FD_SETSIZE限制

編輯:關於C++

前言:

在很多比較各種網絡模型的文章中,但凡提到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的偏方手段,那麼也許第二種方式要好些。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved