选择(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]; /* anarray 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("WSAStartupFailed,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("listenFailed,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!Iam 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;
}