select模型时,被轮训的套接字的数量收到宏FD_SETSIZE的限制(linux下默认是1024,windows是64)。可以使用技巧绕过这个限制(因为select模型的轮训特性,不推荐把轮训套接字的数量设置很大)。
1、数组长度的拓展
在C语言的一个技巧来拓展结构体中的最后一个成员数组的长度,代码如下:typedef struct _str_type
{
int _len;
char _s[1];
}str_type;
int str_len = 100;//要把_s拓展成100字节
str_type *s = (str_type*) malloc( sizeof( str_type ) + str_len - 1 );
free( s );
要求:
1)需要动态增长的成员必须位于结构体的末尾。_s恰好在结构体尾部,所以可以为其分配一段连续的空间。
2)str_type定义的变量需要分配在足够长的堆或者栈内存上。
3)结构体内不可含有c++的东西,否则不安全。实际上应该是不能包含虚表。(参考<Inside the C++ object model>)
2、select的应用例子
(1)fd_set结构
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;
(2)自定义的宏
需要使用自定义的宏来加入数组。因为c库版本的添加描述符到数组的宏如下,是包含库的宏FD_SETSIZE
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)
写一个自己版本的添加描述符到数组的宏
#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 )
(3)加入描述符到数组
unsigned int count = 2000;//修改成了自己指定的2000个套接字的限制了(个数可以自定定)
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 );
(4)select实例代码
1)轮询readint r = select( 0, &read_set, 0, 0, &timeout );
if( r < 0 )
{
// select error
}
else if( r > 0 )
{
for( each sockets )
{
if( FD_ISSET( now_socket, &read_set ) )
{
// this socket can read data
}
}
}
2)轮询write 和read
// read_set, write_set使用了上文所述技巧的fd_set类型的指针
int r = select( 0, read_set, write_set, 0, &timeout );
if( r < 0 )
{
// select error
}
for( int i = 0; i < read_set->fd_count; ++ i )
{
// 轮询socket(不使用FD_ISSET)
if(read_set->fd_array[i] == now_socket)
{
//do reading
}
}
for( int i = 0; i < write_set->fd_count; ++ i )
{
// 轮询socket
if(write_set->fd_array[i] == now_socket)
{
//do writing
}
}