一、在網絡編程中,經常用到selec系統調用來判斷套接字上是否存在數據可讀,或者能否向一個套接字寫入數據。其原型爲:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
其中,fd_set是一個socket集合,常用如下宏來對fd_set進行操作:
FD_CLR( s, *set)
//從set中刪除句柄s;
FD_ISSET( s, *set)
//檢查句柄s是否存在與set中;
FD_SET( s, *set )
//把句柄s添加到set中;
FD_ZERO( *set )
//把set隊列初始爲空.
二、fd_set是如何實現的呢?需要說明一點,在內核中,socket對應struct socket結構,但在返回給用戶空間之前,內核做了一個關聯:調用get_unused_fd_flags從當前進程中獲取一個可用的文件描述符fd ,將struct socket結構關聯到該fd,並返回fd給用戶空間。所以在用戶空間中,socket爲文件描述符。另外,進程可以打開的文件數是有限制的,爲1024,故socket的取值小於1024。
試想由你實現這樣一個集合,可以往裏添加任意0~1024之間的數(FD_SET操作),也可以將加入到集合中的數移除——移除一個(FD_CLR操作)或全部(FD_ZERO),你會如何實現?
一種比較好的思路是使用位圖bitmap,往集合了添加n時只需將第n個bit位置1,移除n時只需將第n個比特置爲0,移除所有數據時,只需將所有bit置爲0,可以通過memset操作來實現。fd_set的實現就是採用位圖bitmap(關於位圖可以參考《編程珠璣》第一章)。
其定義如下:
#define __NFDBITS (8 * sizeof(unsigned long)) //每個ulong型可以表示多少個bit,
#define __FD_SETSIZE 1024 //socket最大取值爲1024
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) //bitmap一共有1024個bit,共需要多少個ulong
typedef
struct
{
unsigned
long
fds_bits [__FDSET_LONGS];
//用ulong數組來表示bitmap
} __kernel_fd_set;
typedef
__kernel_fd_set fd_set;
對應的操作如下:
//每個ulong爲32位,可以表示32個bit。
//fd >> 5 即 fd / 32,找到對應的ulong下標i;fd & 31 即fd % 32,找到在ulong[i]內部的位置
#define __FD_SET(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] |= (1<<((fd) & 31))) //設置對應的bit
#define __FD_CLR(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] &= ~(1<<((fd) & 31))) //清除對應的bit
#define __FD_ISSET(fd, fdsetp) ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] & (1<<((fd) & 31))) != 0) //判斷對應的bit是否爲1
#define __FD_ZERO(fdsetp) (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp)))) //memset bitmap