選擇(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;
}