阻塞和非阻塞

阻塞函數在完成其指定的任務以前不允許程序調用另一個函數。例如,程序執行一個讀數據的函數調用時,在此函數完成讀操作以前將不會執行下一程序語句。當服務器運行到accept語句時,而沒有客戶連接服務請求到來,服務器就會停止在accept語句上等待連接服務請求的到來。這種情況稱爲阻塞(blocking)。而非阻塞操作則可以立即完成。比如,如果你希望服務器僅僅注意檢查是否有客戶在等待連接,有就接受連接,否則就繼續做其他事情,則可以通過將Socket設置爲非阻塞方式來實現。非阻塞socket在沒有客戶在等待時就使accept調用立即返回。
  #include
  #include
  ……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……
  通過設置socket爲非阻塞方式,可以實現"輪詢"若干Socket。當企圖從一個沒有數據等待處理的非阻塞Socket讀入數據時,函數將立即返回,返回值爲-1,並置errno值爲EWOULDBLOCK。但是這種"輪詢"會使CPU處於忙等待方式,從而降低性能,浪費系統資源。而調用select()會有效地解決這個問題,它允許你把進程本身掛起來,而同時使系統內核監聽所要求的一組文件描述符的任何活動,只要確認在任何被監控的文件描述符上出現活動,select()調用將返回指示該文件描述符已準備好的信息,從而實現了爲進程選出隨機的變化,而不必由進程本身對輸入進行測試而浪費CPU開銷。Select函數原型爲:
int select(int numfds,fd_set *readfds,fd_set *writefds,
fd_set *exceptfds,struct timeval *timeout);
  其中readfds、writefds、exceptfds分別是被select()監視的讀、寫和異常處理的文件描述符集合。如果你希望確定是否可以從標準輸入和某個socket描述符讀取數據,你只需要將標準輸入的文件描述符0和相應的sockdtfd加入到readfds集合中;numfds的值是需要檢查的號碼最高的文件描述符加1,這個例子中numfds的值應爲sockfd+1;當select返回時,readfds將被修改,指示某個文件描述符已經準備被讀取,你可以通過FD_ISSSET()來測試。爲了實現fd_set中對應的文件描述符的設置、復位和測試,它提供了一組宏:
  FD_ZERO(fd_set *set)----清除一個文件描述符集;
  FD_SET(int fd,fd_set *set)----將一個文件描述符加入文件描述符集中;
  FD_CLR(int fd,fd_set *set)----將一個文件描述符從文件描述符集中清除;
  FD_ISSET(int fd,fd_set *set)----試判斷是否文件描述符被置位。
  Timeout參數是一個指向struct timeval類型的指針,它可以使select()在等待timeout長時間後沒有文件描述符準備好即返回。struct timeval數據結構爲:
  struct timeval {
   int tv_sec; /* seconds */
   int tv_usec; /* microseconds */

};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

怎樣能使accept函數立即返回?
可以使用ioctlsocket。
用 selece,如果返回偵聽套接字可讀,說明有連接請求要處理

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

關於socket的阻塞與非阻塞模式以及它們之間的優缺點,這已經沒什麼可言的;我打個很簡單的比方,如果你調用socket send函數時;

如果是阻塞模式下:

send先比較待發送數據的長度len和套接字s的發送緩衝的長度,如果len大於s的發送緩衝區的長度,該函數返回SOCKET_ERROR;如果len小於或者等於s的發送緩衝區的長度,那麼send先檢查協議是否正在發送s的發送緩衝中的數據,如果是就等待協議把數據發送完,如果協議還沒有開始發送s的發送緩衝中的數據或者s的發送緩衝中沒有數據,那麼 send就比較s的發送緩衝區的剩餘空間和len,如果len大於剩餘空間大小,send就一直等待協議把s的發送緩衝中的數據發送完,如果len小於剩餘空間大小send就僅僅把buf中的數據copy到剩餘空間裏

如果是非阻塞模式下:

在調用socket send函數時,如果能寫到socket緩衝區時,就寫數據並返回實際寫的字節數目,當然這個返回的實際值可能比你所要寫的數據長度要小些(On nonblocking stream oriented sockets, the number of bytes written can be between 1 and the requested length, depending on buffer availability on both the client and server computers),如果不可寫的話,就直接返回SOCKET_ERROR了,所以沒有等待的過程。。

經過上面的介紹後,下面介紹如何設置socket的非阻塞模式:

http://www.cnblogs.com/dawen/archive/2011/05/18/2050330.html

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

非阻塞recvfrom的設置


  int iMode = 1; //0:阻塞
  ioctlsocket(socketc,FIONBIO, (u_long FAR*) &iMode);//非阻塞設置

  rs=recvfrom(socketc,rbuf,sizeof(rbuf),0,(SOCKADDR*)&addr,&len);

int ioctlsocket (
  SOCKET
s
,        
  long
cmd
,        
  u_long FAR*
argp 
);

s
[in] A descriptor identifying a socket.
cmd
[in] The command to perform on the socket s.
argp
[in/out] A pointer to a parameter for cmd.

不知道大家有沒有遇到過這種情況,當socket進行TCP連接的時候(也就是調用connect時),一旦網絡不通,或者是ip地址無效,就可能使整個線程阻塞。一般爲30秒(我測的是20秒)。如果設置爲非阻塞模式,能很好的解決這個問題,我們可以這樣來設置非阻塞模式:調用ioctlsocket函數:
unsigned long flag=1;
if (ioctlsocket(sock,FIONBIO,&flag)!=0)
{
closesocket(sock);
return false;
}
以下是對ioctlsocket函數的相關解釋:

int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp);
s:一個標識套接口的描述字。
cmd:對套接口s的操作命令。
argp:指向cmd命令所帶參數的指針。

註釋:
本函數可用於任一狀態的任一套接口。它用於獲取與套接口相關的操作參數,而與具體協議或通訊子系統無關。支持下列命令:
FIONBIO:允許或禁止套接口s的非阻塞模式。argp指向一個無符號長整型。如允許非阻塞模式則非零,如禁止非阻塞模式則爲零。當創建一個套接口時,它就處於阻塞模式(也就是說非阻塞模式被禁止)。這與BSD套接口是一致的。WSAAsynSelect()函數將套接口自動設置爲非阻塞模式。如果已對一個套接口進行了WSAAsynSelect() 操作,則任何用ioctlsocket()來把套接口重新設置成阻塞模式的試圖將以WSAEINVAL失敗。爲了把套接口重新設置成阻塞模式,應用程序必須首先用WSAAsynSelect()調用(IEvent參數置爲0)來禁至WSAAsynSelect()。 



FIONREAD:確定套接口s自動讀入的數據量。argp指向一個無符號長整型,其中存有ioctlsocket()的返回值。如果s是SOCKET_STREAM類型,則FIONREAD返回在一次recv()中所接收的所有數據量。這通常與套接口中排隊的數據總量相同。如果S是SOCK_DGRAM 型,則FIONREAD返回套接口上排隊的第一個數據報大小。
SIOCATMARK:確實是否所有的帶外數據都已被讀入。這個命令僅適用於SOCK_STREAM類型的套接口,且該套接口已被設置爲可以在線接收帶外數據(SO_OOBINLINE)。如無帶外數據等待讀入,則該操作返回TRUE真。否則的話返回FALSE假,下一個recv()或recvfrom()操作將檢索“標記”前一些或所有數據。應用程序可用SIOCATMARK操作來確定是否有數據剩下。如果在“緊急”(帶外)數據前有常規數據,則按序接收這些數據(請注意,recv()和recvfrom()操作不會在一次調用中混淆常規數據與帶外數據)。argp指向一個BOOL型數,ioctlsocket()在其中存入返回值。
此時已經設置非阻塞模式,但是並沒有設置connect的連接時間,我們可以通過調用select語句來實現這個功能。以下代碼設定了是連接時間爲5秒,如果還未能連上,則直接返回。
struct timeval timeout ;

fd_set r;

int ret;
connect( sock, (LPSOCKADDR)sockAddr, sockAddr.Size());
FD_ZERO(&r);

FD_SET(sock,&r);

timeout.tv_sec = 5;

timeout.tv_usec =0;

ret = select(0,0,&r,0,&timeout);

if ( ret <= 0 )

{

closesocket(sock);

return false;

}

以下是對select函數的解釋:
int select (
int nfds,
fd_set FAR * readfds,
fd_set FAR * writefds,
fd_set FAR * exceptfds,
const struct timeval FAR * timeout
);
第一個參數nfds沒有用,僅僅爲與伯克利Socket兼容而提供。
readfds指定一個Socket數組(應該是一個,但這裏主要是表現爲一個Socket數組),select檢查該數組中的所有Socket。如果成功返回,則readfds中存放的是符合‘可讀性’條件的數組成員(如緩衝區中有可讀的數據)。
writefds指定一個Socket數組,select檢查該數組中的所有Socket。如果成功返回,則writefds中存放的是符合‘可寫性’條件的數組成員(如連接成功)。
exceptfds指定一個Socket數組,select檢查該數組中的所有Socket。如果成功返回,則cxceptfds中存放的是符合‘有異常’條件的數組成員(如連接接失敗)。
timeout指定select執行的最長時間,如果在timeout限定的時間內,readfds、writefds、exceptfds中指定的Socket沒有一個符合要求,就返回0。

如果對 Connect 進行非阻塞調用,則可讀意味着已經成功連接,連接不成功則不可讀。所以通過這樣的設定,我們就能夠實現對connect連接時間的修改。但是,應該注意,這樣的設置並不能保證在限定時間內連接不上就說明網絡不通。比如我們設的時間是5秒,但是由於種種原因,可能第6秒就能連接上,但是函數在5秒後就返回了。


http://blog.chinaunix.net/space.php?uid=20743151&do=blog&id=326257
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章