SELECT模型

Select在Socket編程中還是對照主要的,然而對於初學Socket的人來說都不太愛用Select寫程式,他們只是習性寫諸如connect、accept、recv或recvfrom這么的堵塞程式(所謂堵塞方法block,望文生義,即使過程或是執行緒實行到這些函式時一定期待某個事件的產生,萬一事件沒有產生,過程或執行緒就被梗塞,函式不能馬上歸來)。

套接字模式和套接字I/O模型的區別。
套接字模式:阻塞套接字和非阻塞套接字。或者叫同步套接字和異步套接字。
套接字模型:描述如何對套接字的I/O行為進行管理。
Winsock提供的I/O模型一共有五種:
select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。
1:select模型(選擇模型)
先看一下下面的這句代碼
intiResult=recv(s,buffer,1024);
這是用來接收數據的,在默認的阻塞模式下的套接字里,recv會阻塞在那裡,直到套接字連線上有數據可讀,把數據讀到buffer里後recv函式才會返回,不然就會一直阻塞在那裡。在單執行緒的程式里出現這種情況會導致主執行緒(單執行緒程式里只有一個默認的主執行緒)被阻塞,這樣整個程式被鎖死在這裡,如果永遠沒數據傳送過來,那么程式就會被永遠鎖死。這個問題可以用多執行緒解決,但是在有多個套接字連線的情況下,這不是一個好的選擇,擴展性很差。Select模型就是為了解決這個問題而出現的。
再看代碼:

intiResult=ioctlsocket(s,FIOBIO,(unsignedlong*)&ul);
iResult=recv(s,buffer,1024);

這一次recv的調用不管套接字連線上有沒有數據可以接收都會馬上返回。原因就在於我們用ioctlsocket把套接字設定為非阻塞模式了。不過你跟蹤一下就會發現,在沒有數據的情況下,recv確實是馬上返回了,但是也返回了一個錯誤:WSAEWOULDBLOCK,意思就是請求的操作沒有成功完成。看到這裡很多人可能會說,那么就重複調用recv並檢查返回值,直到成功為止,但是這樣做效率很成問題,開銷太大。
感謝天才的微軟工程師吧,他們給我們提供了好的解決辦法。
先看看select函式
intselect(
intnfds,
fd_setFAR*readfds,
fd_setFAR*writefds,
fd_setFAR*exceptfds,
const struct timevalFAR*timeout
);
第一個參數不要管,會被系統忽略的。第二個參數是用來檢查套接字可讀性,也就說檢查套接字上是否有數據可讀,同樣,第三個參數用來檢查數據是否可以發出。最後一個是檢查是否有帶外數據可讀取。
參數詳細的意思請去看MSDN,這裡限於篇幅不詳細解釋了。
最後一個參數是用來設定select等待多久的,是個結構

struct timeval{
longtv_sec;//seconds
longtv_usec;//andmicroseconds
};
如果將這個結構設定為(0,0),那么select函式會馬上返回。
說了這么久,select的作用到底是什麼?
他的作用就是:防止在在阻塞模式的套接字里被鎖死,避免在非阻塞套接字里重複檢查WSAEWOULDBLOCK錯誤。
他的工作流程如下:
1:用FD_ZERO宏來初始化我們感興趣的fd_set,也就是select函式的第二三四個參數
2:用FD_SET宏來將套接字句柄分配給相應的fd_set。
3:調用select函式。
4:用FD_ISSET對套接字句柄進行檢查,如果我們所關注的那個套接字句柄仍然在開始分配的那個fd_set里,那么說明馬上可以進行相應的IO操作。比如一個分配給select第一個參數的套接字句柄在select返回後仍然在select第一個參數的fd_set里,那么說明當前數據已經來了,馬上可以讀取成功而不會被阻塞。

下面給出一個簡單的select模型的服務端套接字。

#include“iostream.h”
#include“winsock2.h”
#include“windows.h”

#defineInternetAddr"127.0.0.1"
#defineiPort5055

#pragmacomment(lib,"ws2_32.lib")

voidmain()
{
WSADATAwsa;
WSASTARTUP(MAKEWORD(2,2),&wsa);
SOCKETfdServer=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
structsockaddr_inserver;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(InternetAddr);
server.sin_port=htons(iPort);
intret=bind(fdServer,(sockaddr*)&server,sizeof(server));
ret=listen(fdServer,4);

SOCKETAcceptSocket;
fd_setfdread;
timevaltv;
intnSize;

while(1)
{
FD_ZERO(&fdread);//初始化fd_set
FD_SET(fdServer,&fdread);//分配套接字句柄到相應的fd_set
tv.tv_sec=2;//這裡我們打算讓select等待兩秒後返回,避免被鎖死,也避免馬上返回
tv.tv_usec=0;
select(0,&fdread,NULL,NULL,&tv);
nSize=sizeof(server);
if(FD_ISSET(fdServer,&fdread))//如果套接字句柄還在fd_set里,說明客戶端已經有connect的請求發過來了,馬上可以accept成功
{
AcceptSocket=accept(fdServer,(sockaddr*)&server,&nSize);
break;
}
else//還沒有客戶端的connect請求,我們可以去做別的事,避免像沒有用select方式的阻塞套接字程式被鎖死的情況,如果沒用select,當程式運行到accept的時候客戶端恰好沒有connect請求,那么程式就會被鎖死,做不了任何事情
{
//dosomething
::MessageBox(NULL,"waiting...","recv",MB_ICONINFORMATION);//別的事做完後,繼續去檢查是否有客戶端連線請求
}
}

charbuffer[128];
ZeroMemory(buffer,128);

ret=recv(AcceptSocket,buffer,128,0);//這裡同樣可以用select,用法和上面一樣

::MessageBox(NULL,buffer,"recv",MB_ICONINFORMATION);

closesocket(AcceptSocket);
WSACleanup();
return;

}

相關搜尋

熱門詞條

聯絡我們