在網路程式設計中,select 模型常被詬病受限於 FD_SETSIZE(Windows 預設為 64),導致無法同時監控大量 Socket。然而,這並非鐵律。透過 C 語言結構體的「柔性數組」特性,我們可以動態擴展 fd_set 容量,突破這一限制。
C 語言允許結構體最後一個成員為不定長度數組(C99 標準化為柔性數組成員)。早期常見寫法:
typedef struct _str_type {
int _len;
char _s[1]; // 佔位符,實際分配時擴展
} str_type;
// 分配額外空間
str_type *s = (str_type*)malloc(sizeof(str_type) + str_len - 1);
關鍵在於 _s 位於結構體末尾,分配連續記憶體後即可安全讀寫超出原定義範圍的資料。此技巧要求:
malloc/new)Windows winsock2.h 中 fd_set 定義完美符合條件:
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
fd_array 位於結構尾部,fd_count 記錄實際 Socket 數量。但標準巨集 FD_SET 內部會檢查 FD_SETSIZE,直接使用會失效:
#define FD_SET(fd, set) do {
// ...
if (((fd_set*)(set))->fd_count < FD_SETSIZE) { ... }
} while(0)
解決方案是重寫巨集,將固定 FD_SETSIZE 替換為動態傳入的容量參數:
#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)
unsigned int count = 100; // 需監控 100 個 Socket
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); // 使用自訂巨集
// 呼叫 select 時傳入指標
int r = select(0, read_set, NULL, NULL, &timeout);
// 處理就緒 Socket:建議直接遍歷 fd_array,避免 FD_ISSET 可能依賴 FD_SETSIZE
for (int i = 0; i < read_set->fd_count; ++i) {
SOCKET ready_sock = read_set->fd_array[i];
// 處理資料...
}
free(read_set);
closesocket(s);
遍歷 fd_array 而非 FD_ISSET 可避免潛在相容性問題。但在高併發場景下,select 本身輪詢機制效率較低,建議考慮 IOCP (Windows) 或 epoll (Linux) 等可擴展模型。