18.5 select函数文件描述符上限是1024?

512 阅读2分钟

我们先在源码中看看fd_set的定义

typedef long int __fd_mask; // 位掩码数据类型,用于描述一组文件描述符

#undef	__NFDBITS // 如果前面有宏定义则取消它
// 定义一个 __fd_mask 变量可以保存多少个文件描述符的状态信息
// 由于我是64位机,所以sizeof得到8,再乘8即得到一个__fd_mask保存64个文件描述符的状态信息
#define __NFDBITS	(8 * (int) sizeof (__fd_mask)) 
#define	__FD_ELT(d)	((d) / __NFDBITS)
#define	__FD_MASK(d)	((__fd_mask) 1 << ((d) % __NFDBITS))

/* fd_set for select and pselect.  */
typedef struct
  {
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
// 两个只是命名的区别
#else 
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;

__FD_SETSIZE是一个宏定义,为1024,NFDBITS之前求过是64,所以我们的fds_bits数组的个数为16个

我们做个实验:

#include <stdio.h>
#include<sys/select.h>
int main(int argc, char **argv)
{
    int j;
	unsigned char gap = 0x12; // 该值作为锚点,到时候set越界时gap的最低位被位运算覆盖。
	fd_set readfds;
	int sd[2500];
	unsigned long dist;

	printf("gap value is :0x%x\n", gap);
	// dist是readfds和附近gap之间的空间大小,即readfds最大的可用空间。每台机器可能不同,不过都在1024左右
	dist = (unsigned long)&gap - (unsigned long)&readfds;
	FD_ZERO(&readfds);
	// dist*8 + 1即让readfds越界1个bit。
	// 由于gap为0x12,二进制10010,越界1个bit,可以预期FD_SET会置位0x12的最低位。
	// 结果就是0x13
	for (j = 0; j < dist*8 + 1; j++) {
		sd[j] = j;
		FD_SET(sd[j], &readfds);
	}
	printf("j %d .", j);
	printf("after FD_SET. gap value is :0x%01x   bytes space:%d\n", gap, dist);
}

结果为

gap value is :0x12
dist is 135
j 1081 .after FD_SET. gap value is :0x13   bytes space:135

这意味着,实际上 FD_SET宏根本不管是否越界以及越界的后果,fd_set也并非严格限制在1024. 但你要是越界使用很可能付出代价。

那我们再看看越界后的fd_set还能正常使用吗?

#include <stdio.h>
#include <netdb.h>
#include<sys/select.h>
#include <sys/socket.h>

#define SIZE 1200
// 全局变量分配在全局数据区而不是栈上,以防被覆盖。
int i = 1001, j;
int sd[SIZE];
struct sockaddr_in serveraddr;
int main(int argc, char **argv)
{
	// 使readfds在第一个,覆盖掉我们不再care about的内存.
	fd_set readfds;
	int childfd;

	FD_ZERO(&readfds);
	for (j = 0; j < SIZE; j++) {
		sd[j] = socket(AF_INET, SOCK_STREAM, 0);
		serveraddr.sin_family = AF_INET;
		serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
		serveraddr.sin_port = htons(i++);
		bind(sd[j], (struct sockaddr *) &serveraddr, sizeof(serveraddr));
		listen(sd[j], 5);
		FD_SET(sd[j], &readfds);
	}

	while (1) {
		// select 超过1024的...
		if (select(1200, &readfds, 0, 0, 0) < 0) {
			perror("ERROR in select");
		}
		for (j = 0; j < SIZE; j++) {
			if (FD_ISSET(sd[j], &readfds)) {
      			childfd = accept(sd[j], NULL, NULL);
				printf("#### %d\n", j);
      			close(childfd);
			}
		}
	}
}

成功连接,事实说明,文件描述符超过了1024依然OK:
image.png

那么如何突破1024的限制呢?

使用malloc动态分配内存咯

fd_set *readfds;
readfds = (fd_set *)malloc(8000/8);

这下readfds就能容纳8000个文件描述符了