选择(select)模型是Winsock中最常见的 I/O模型。核心便是利用 select 函数,实现对 I/O的管理!利用 select 函数来判断某Socket上是否有数据可读,或者能否向一个套接字写入数据,防止程序在Socket处于阻塞模式中时,在一次 I/O 调用(如send或recv、accept等)过程中,被迫进入“锁定”状态;同时防止在套接字处于非阻塞模式中时,产生WSAEWOULDBLOCK错误。
select 函数原型:
int select(
int nfds, //传入参数,忽略
fd_set FAR * readfds, //检查可读性
fd_set FAR * writefds, //检查可写性
fd_set FAR * exceptfds, //例外数据
const struct timeval FAR * timeout //本次select调用最长的等待时间
);
函数返回值,select()函数调用后,返回处于就绪状态并且已经包含在fd_set结构中的套接口描述符,也就是说,它要修改集合,删除那些不能进行指定操作的套接口。但如果超时则返回0;如果发生错误,则返回SOCKET_ERROR,应用程序可通过WSAGetLastError()获取错误代码。
其中fd_set是一个结构类型说明符,代表着一系列特定套接口的集合,它的定义如下:
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[ FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
timeval是一个结构类型,它的定义如下:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
若将超时值设置为(0 , 0),表明 select 会立即返回,出于对性能方面的考虑,应避免这样的设置。
以下为测试select()函数的程序,一个服务器端两个客户端
下面是服务器端程序:
#define FD_SETSIZE 500
#include < WINSOCK2.H>
#pragma comment ( lib, "ws2_32" )
#include < stdio.h>
int main()
{
printf( "服务器端程序....\n" );
//------①加载----------
WSADATA wsaData;
if ( WSAStartup( MAKEWORD( 2 ,2 ),& wsaData)!= 0 )
{
printf( "WSAStartup Failed,Error=【%d】\n" , WSAGetLastError());
return 1 ;
}
else
printf( "①加载成功\n" );
//-------②创建流式套接字------------
SOCKET s= socket( AF_INET, SOCK_STREAM, 0 );
if ( s== INVALID_SOCKET)
{
printf( "socket() Failed,Error=【%d】\n" , WSAGetLastError());
return 1 ;
}
else
printf( "②已创建监听套接口:【%d】\n" , s);
//将套接口s置于”非阻塞模式“
u_long u1= 1 ;
ioctlsocket( s, FIONBIO,( u_long*)& u1);
//-----------③绑定本地地址---------------------
struct sockaddr_in Sadd;
Sadd.sin_family= AF_INET;
Sadd.sin_port= htons( 5555 );
Sadd.sin_addr.S_un.S_addr= inet_addr( "192.168.31.1" );
if ( bind( s,( sockaddr*)& Sadd, sizeof ( Sadd))== SOCKET_ERROR)
{
printf( "bind() Failed,Error=【%d】\n" , WSAGetLastError());
return 1 ;
}
else
printf( "③绑定成功,本地IP地址:【%s】,端口号:【%d】\n" , inet_ntoa( Sadd.sin_addr), ntohs( Sadd.sin_port));
//--------------④进入监听状态-----------------
if ( listen( s, 3 )== SOCKET_ERROR)
{
printf( "listen Failed,Error=【%d】\n" , WSAGetLastError());
return 1 ;
}
else
printf( "④进入监听状态\n" );
//--------------⑤select-------------------
//准备工作
int x= 1 ;
timeval tv;
tv.tv_sec= 20 ;
tv.tv_usec= 0 ;
fd_set socket_jh01;
FD_ZERO(& socket_jh01);
FD_SET( s,& socket_jh01);
fd_set socket_jh02;
FD_ZERO(& socket_jh02);
while ( TRUE )
{
socket_jh02= socket_jh01;
int sock_sum= select( 0 ,& socket_jh02, NULL , NULL ,& tv);
//------情况一 成功
if ( sock_sum> 0 )
{
for ( int i= 0 ;i<( int ) socket_jh02.fd_count; i++)
{
if ( socket_jh02.fd_array[ i]== s)
{
if ( socket_jh01.fd_count< FD_SETSIZE)
{
sockaddr_in Cadd;
int Cadd_len= sizeof ( Cadd);
SOCKET sNew= accept( s,( sockaddr*)& Cadd,& Cadd_len);
FD_SET( sNew,& socket_jh01);
printf( "接受一个客户端连接,对方地址:【%s】,端口号:【%d】\n" , inet_ntoa( Cadd.sin_addr), ntohs( Cadd.sin_port));
printf( "分配给该客户端的套接口为:%d\n\n" , sNew);
}
else
{
printf( "连接数量太多\n" );
continue ;
}
}
else
{
char cbuf[ 256 ];
memset( cbuf, 0 ,256 );
int cRecv;
cRecv= recv( socket_jh02.fd_array[ i], cbuf, 256 ,0 );
if ( cRecv== SOCKET_ERROR)
{
printf( "可能客户端%d非法关闭!!" , socket_jh02.fd_array[ i]);
printf( "或者调用recv() Failed,Error=【%d】\n" , WSAGetLastError());
closesocket( socket_jh02.fd_array[ i]);
FD_CLR( socket_jh02.fd_array[ i],& socket_jh01);
}
else if ( cRecv> 0 )
{
printf( "接收到来至【%d】的数据:%s\n" , socket_jh02.fd_array[ i], cbuf);
int isend;
char Sbuf[]= "Hello client!I am server" ;
isend= send( socket_jh02.fd_array[ i], Sbuf, sizeof ( Sbuf), 0 );
if ( isend== SOCKET_ERROR)
{
printf( "send() Failed,Error=【%d】\n" , WSAGetLastError());
break ;
}
else if ( isend<= 0 )
{
printf( "消息发送失败!!\n" );
break ;
}
else
printf( "给客户【%d】信息已发送,信息长度%d字节\n\n" , socket_jh02.fd_array[ i], isend);
}
else
{
printf( "客户端【%d】不再发送数据,正常关闭连接,为客户端连接创建的套接口将关闭!!\n" , socket_jh02.fd_array[ i]);
closesocket( socket_jh02.fd_array[ i]);
FD_CLR( socket_jh02.fd_array[ i],& socket_jh01);
}
}
} //end for
} //end sock_sum
//------------情况二 超时
else if ( sock_sum== 0 )
{
printf( "第【%d】次超时\n" , x);
if ( x< 3 )
{
x++;
continue ;
}
else
{
printf( "超过等待限制,退出程序\n" );
break ;
}
}
//--------------情况三 失败
else
{
printf( "select() Failed,Error=【%d】\n" , WSAGetLastError());
break ;
}
} //while end
closesocket( s);
printf( "退出" );
WSACleanup();
return 0 ;
}
客户端程序省去。