設置非阻塞的套接字Socket
當使用socket()函數和WSASocket()函數創建套接字時,默認都是阻塞的。在創建套接字之後,通過調用ioctlsocket()函數,將該套接字設置為非阻塞模式。函數的第一個參數是套接字,第二個參數設置為FIONBIO,第三個參數設置為unsigned long類型的非零值。下面代碼清單演示了如何用ioctlsocket()函數,設置套接字為非阻塞模式。
SOCKET s; //套接字
unsigned long ul = 1; //設置套接字選項
int nRet; //返回值
s = socket(AF_INET, SOCK_STREAM, 0); //創建套接字
nRet = ioctlsocket(s, FIONBIO, (unsigned long*)&ul); //設置套接字非阻塞模式
if (nRet == SOCKET_ERROR)
{
//設置套接字非阻塞模式,失敗處理
}
套接字設置為非阻塞模式後,在調用Windows Sockets API函數時,調用函數會立即返回。大多數情況下,這些函數調用都會調用“失敗”,並返回WSAEWOULDBLOCK錯誤代碼。說明請求的操作在調用期間內沒有時間完成。通常,應用程序需要重復調用該函數,直到獲得成功返回代碼。下面程序清單示例了一個在非阻塞套接字上反復調用recv()函數,直到收到1024個字節的數據。
#define NUM_REQUIRED 1024 //需要讀入數據的大小
#define MAX_SIZE 2048 //輸入緩沖區的大小
TCHAR buff[MAX_SIZE]; //輸入緩沖區
bool close; //對方關閉了連接
SOCKET sock; //Windows sockets
void ReadData(void)
{
int nTotal = 0; //已經讀入緩沖區字節數
int nRead = 0; //在調用recv時實際讀入字節數
int nLeft = 0; //剩下數據的字節數
int nBytes = 0; //當前已讀數據在緩沖區的位置
nLeft = NUM_REQUIRED;
while (nTotal != NUM_REQUIRED)//已經讀入緩沖區的字節數不等於需要讀入的大小時
{
nRead = recv(sock, &buff[MAX_SIZE - nBytes], nLeft, 0); //接收數據
if(SOCKET_ERROR == nRead) //讀操作失敗
{
int err = WSAGetLastError();
if(WSAEWOULDBLOCK == err) //接收數據緩沖區不可用
{
continue; //接著讀取數據
}else if(WSAETIMEDOUT == err || WSAENETDOWN == err) //連接已經斷開
{
close = TRUE; //函數退出
break;
}
}
if( 0 == nRead) //客戶端關閉了連接
{
close = TRUE; //函數退出
break;
}
nTotal += nRead;
nLeft -= nRead;
nBytes += nRead;
}
return;
}
在該程序中,通過調用WSAGetLastError()函數獲得recv()函數返回的錯誤代碼。當返回WSAEWOULDBLOCK錯誤時,說明此時套接字的緩沖區還沒有準備好的數據。需要繼續調用該函數。
在該程序中,還對recv()函數返回的其他錯誤代碼進行處理。WSAETIMEDOUT和WSAENETDOWN錯誤說明,此時由於網絡系統的原因與對方的連接已經斷開了。當函數返回0時,說明對方關閉了連接。在程序中通過設置close布爾變量值為TRUE,表明與對方的連接已經斷開。調用break語句跳出while循環體,函數退出。在開發中,應該根據具體情況對函數返回的錯誤值進行具體處理。
不同的Windows Sockets API函數,在調用失敗時返回的WSAEWOULDBLOCK錯誤代碼具有不同的含義。表對幾個Windows Sockets API函數返回WSAEWOULDBLOCK錯誤的含義進行了總結。
表 WSAEWOULDBLOCK的含義
函數名 |
說明 |
accept()和WSAAcept() |
應用程序沒有收到連接請求 |
recv()、WSARecv()、recvfrom()和WSARecvfrom() |
接收緩沖區沒有收到數據 |
send()、WSASend()、sendfrom()和WSASendfrom() |
發送緩沖區此時不可用 |
connect()和WSAConnect() |
連接未能立即完成 |
closescoket() |
通常情況下意味著應用程序使用SO_LINGER選項並且設置了一個非零的超時值,調用了setsocketopt()函數 |
需要說明的是並非所有的Windows Sockets API在非阻塞模式下調用,都會返回WSAEWOULDBLOCK錯誤。例如,以非阻塞模式的套接字為參數調用bind()函數時,就不會返回該錯誤代碼。當然,在調用WSAStartup()函數時更不會返回該錯誤代碼,因為該函數是應用程序第一調用的函數,當然不會返回這樣的錯誤代碼。
要將套接字設置為非阻塞模式,除了使用ioctlsocket()函數之外,還可以使用WSAAsyncselect()和WSAEventselect()函數。當調用該函數時,套接字會自動地設置為非阻塞方式。在後續章節中,講解該函數的使用方法。
設置非阻塞的套接字Socket