2014-09-28 157 views
-1

我試圖寫一個簡單的winsock客戶端,使用無阻塞重疊 IO將數據發送到 winsock的服務器。問題是WSASend呼叫被阻止。客戶端代碼看起來像這樣Winsock的非阻塞IO重疊仍塊

// Initialize Winsock 
WSAStartup(MAKEWORD(2, 2), &wsaData); 

// Create a SOCKET for connecting to server 
ConnectSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); 

// Connect to server. 
WSAConnect(ConnectSocket, ai_addr, ai_addrlen, 0, 0, 0, 0); 

// Make the socket non-blocking 
u_long iMode = 1; 
ioctlsocket(ConnectSocket, FIONBIO, &iMode); 

// Send the data 
WSAOVERLAPPED SendOverlapped{}; 
SendOverlapped.hEvent = WSACreateEvent(); 
WSASend(ConnectSocket, &DataBuf, 1, &SendBytes, 0, &SendOverlapped, 0); 

我所做的插座通過ioctlsocket功能無阻塞,我已經提供WSA_FLAG_OVERLAPPED標誌的WSASocket功能。我還爲WSASend函數提供了lpOverlapped參數。但是,撥打WSASend仍然阻止。我在這裏錯過了什麼嗎?

請原諒在上面的代碼中缺少錯誤檢查,只是爲了這個問題的目的而保持簡單。

澄清:爲什麼在我看來呼叫阻塞的原因是這樣的 - 我在一個循環2560次叫WSASend功能,每次發送4MB的循環。所有數據傳輸到服務器後,循環在16秒內完成。如果它是非阻塞的,我會期待循環更快地完成。不出所料,WSASend功能確實返回ERROR_IO_PENDING

以下是完整的客戶端代碼

// Client.cpp 
#ifndef WIN32_LEAN_AND_MEAN 
#define WIN32_LEAN_AND_MEAN 
#endif 

#include <stdio.h> 
#include <windows.h> 
#include <winsock2.h> 
#include <ws2tcpip.h> 
#include <iostream> 
#include <ctime> 

#pragma comment (lib, "Ws2_32.lib") 
#pragma comment (lib, "Mswsock.lib") 
#pragma comment (lib, "AdvApi32.lib") 

#define DEFAULT_BUFLEN 4 * 1024 * 1024 
#define DEFAULT_PORT "27015" 

int __cdecl main(int argc, char **argv) 
{ 
    // Initialize Winsock 
    WSADATA wsaData; 
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 
    if (iResult != 0) { 
     printf("WSAStartup failed with error: %d\n", iResult); 
     return 1; 
    } 

    // Resolve the server address and port 
    addrinfo *result = NULL; 
    addrinfo hints{}; 
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 
    hints.ai_protocol = IPPROTO_TCP; 
    iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result); 
    if (iResult != 0) { 
     printf("getaddrinfo failed with error: %d\n", iResult); 
     WSACleanup(); 
     return 1; 
    } 

    // Create a SOCKET for connecting to server 
    SOCKET ConnectSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); 
    if (ConnectSocket == INVALID_SOCKET) { 
     printf("socket failed with error: %ld\n", WSAGetLastError()); 
     WSACleanup(); 
     return 1; 
    } 

    // Connect to server. 
    iResult = WSAConnect(ConnectSocket, result->ai_addr, result->ai_addrlen, 0, 0, 0, 0); 
    freeaddrinfo(result); 
    if (ConnectSocket == INVALID_SOCKET) { 
     printf("Unable to connect to server!\n"); 
     WSACleanup(); 
     return 1; 
    } 

    // Make the socket non-blocking 
    u_long iMode = 1; 
    iResult = ioctlsocket(ConnectSocket, FIONBIO, &iMode); 
    if (iResult != NO_ERROR) { 
     printf("ioctlsocket failed with error: %ld\n", iResult); 
     WSACleanup(); 
     return 1; 
    } 

    // Prepare the buffer 
    char *sendbuf = new char[DEFAULT_BUFLEN]; 
    for (int i = 0; i < DEFAULT_BUFLEN; ++i) 
     sendbuf[i] = 'a'; 
    WSABUF DataBuf; 
    DWORD SendBytes = 0; 
    DataBuf.buf = sendbuf; 
    DataBuf.len = DEFAULT_BUFLEN; 

    // Send the buffer in a loop 
    int loopCount = 2560; 
    WSAOVERLAPPED* SendOverlapped = (WSAOVERLAPPED*)calloc(loopCount, sizeof(WSAOVERLAPPED)); 
    clock_t start = clock(); 
    for (int i = 0; i < loopCount; ++i) 
    { 
     SendOverlapped[i].hEvent = WSACreateEvent(); 
     iResult = WSASend(ConnectSocket, &DataBuf, 1, &SendBytes, 0, SendOverlapped + i, 0); 
     if (iResult == SOCKET_ERROR) 
     { 
      if (ERROR_IO_PENDING == WSAGetLastError()) 
      { 
       continue; 
      }   

      printf("send failed with error: %d\n", WSAGetLastError()); 
      closesocket(ConnectSocket); 
      WSACleanup(); 
      return 1; 
     } 
    } 

    std::cout << "initiating send data took " << clock() - start << " ms" << std::endl; 

    // Wait for all the events to be signalled 
    for (int i = 0; i < loopCount; ++i) 
    { 
     iResult = WSAWaitForMultipleEvents(1, &SendOverlapped[i].hEvent, TRUE, INFINITE, TRUE); 
     if (iResult == WSA_WAIT_FAILED) { 
      printf("WSAWaitForMultipleEvents failed with error: %d\n", WSAGetLastError()); 
      closesocket(ConnectSocket); 
      WSACleanup(); 
      return 1; 
     } 

     DWORD Flags = 0; 
     BOOL result = WSAGetOverlappedResult(ConnectSocket, SendOverlapped + i, &SendBytes, FALSE, &Flags); 
     if (result == FALSE) { 
      printf("WSASend failed with error: %d\n", WSAGetLastError()); 
      break; 
     } 
    } 

    std::cout << "actual send data took " << clock() - start << " ms"; 

    // shutdown the connection since no more data will be sent 
    iResult = shutdown(ConnectSocket, SD_SEND); 
    if (iResult == SOCKET_ERROR) { 
     printf("shutdown failed with error: %d\n", WSAGetLastError()); 
     closesocket(ConnectSocket); 
     WSACleanup(); 
     return 1; 
    } 

    // cleanup 
    closesocket(ConnectSocket); 
    WSACleanup(); 
    ////free(SendOverlapped); 
    return 0; 
} 

這裏是服務器端代碼

// Server.cpp 
#ifndef WIN32_LEAN_AND_MEAN 
#define WIN32_LEAN_AND_MEAN 
#endif 

#include <stdio.h> 
#include <windows.h> 
#include <winsock2.h> 
#include <ws2tcpip.h> 
#include <iostream> 

#pragma comment (lib, "Ws2_32.lib") 

#define DEFAULT_BUFLEN 4 * 1024 * 1024 
#define DEFAULT_PORT "27015" 

int __cdecl main(void) 
{ 
    // Initialize Winsock 
    WSADATA wsaData; 
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 
    if (iResult != 0) { 
     printf("WSAStartup failed with error: %d\n", iResult); 
     return 1; 
    } 

    // Resolve the server address and port 
    addrinfo hints{}; 
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 
    hints.ai_protocol = IPPROTO_TCP; 
    hints.ai_flags = AI_PASSIVE; 
    addrinfo *result = NULL; 
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); 
    if (iResult != 0) { 
     printf("getaddrinfo failed with error: %d\n", iResult); 
     WSACleanup(); 
     return 1; 
    } 

    // Create a SOCKET for connecting to server 
    SOCKET ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); 
    if (ListenSocket == INVALID_SOCKET) { 
     printf("socket failed with error: %ld\n", WSAGetLastError()); 
     freeaddrinfo(result); 
     WSACleanup(); 
     return 1; 
    } 

    // Setup the TCP listening socket 
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen); 
    if (iResult == SOCKET_ERROR) { 
     printf("bind failed with error: %d\n", WSAGetLastError()); 
     freeaddrinfo(result); 
     closesocket(ListenSocket); 
     WSACleanup(); 
     return 1; 
    } 

    freeaddrinfo(result); 

    iResult = listen(ListenSocket, SOMAXCONN); 
    if (iResult == SOCKET_ERROR) { 
     printf("listen failed with error: %d\n", WSAGetLastError()); 
     closesocket(ListenSocket); 
     WSACleanup(); 
     return 1; 
    } 

    // Accept a client socket 
    SOCKET ClientSocket = accept(ListenSocket, NULL, NULL); 
    if (ClientSocket == INVALID_SOCKET) { 
     printf("accept failed with error: %d\n", WSAGetLastError()); 
     closesocket(ListenSocket); 
     WSACleanup(); 
     return 1; 
    } 

    // No longer need server socket 
    closesocket(ListenSocket); 

    // Receive until the peer shuts down the connection 
    char* recvbuf = new char[DEFAULT_BUFLEN]; 
    do { 
     iResult = recv(ClientSocket, recvbuf, DEFAULT_BUFLEN, 0); 
     if (iResult > 0) { 
      printf("Bytes received: %d\n", iResult); 
     } 
     else if (iResult == 0) 
      printf("Connection closing...\n"); 
     else { 
      printf("recv failed with error: %d\n", WSAGetLastError()); 
      closesocket(ClientSocket); 
      WSACleanup(); 
      return 1; 
     } 

    } while (iResult > 0); 

    // shutdown the connection since we're done 
    iResult = shutdown(ClientSocket, SD_SEND); 
    if (iResult == SOCKET_ERROR) { 
     printf("shutdown failed with error: %d\n", WSAGetLastError()); 
     closesocket(ClientSocket); 
     WSACleanup(); 
     return 1; 
    } 

    // cleanup 
    closesocket(ClientSocket); 
    WSACleanup(); 

    return 0; 
} 
+2

你是什麼意思,「呼叫被阻止」?即使您指定應該使用重疊的IO,API也可能在返回之前完成調用。正如文檔所說,「如果重疊操作立即完成,WSASend返回值爲零,並且lpNumberOfBytesSent參數將更新爲發送的字節數」。 – 2014-09-28 05:07:40

+0

這段代碼中絕對沒有錯誤檢查。你不能這樣寫代碼。 – EJP 2014-09-28 05:08:30

+0

@EJP,我知道沒有錯誤檢查,這只是爲了保持這個問題整潔。 – tcb 2014-09-28 05:31:17

回答

-1

WSASend呼叫阻塞,因爲它完成得太快。人們會認爲4MB傳輸足夠大,需要異步處理,但事實並非如此。當我在服務器端插入1毫秒的睡眠時,我可以看到客戶端異步處理,並且在數據傳輸之前環路就結束了。

+0

WSASend()調用根本沒有阻塞。你誤解了症狀。它是*返回。* I/O異步發生。你似乎不瞭解你自己的代碼。請參閱上面的@ AlexFarber評論。 – EJP 2014-09-28 23:55:58

+0

如果運行2560次的for循環需要16秒才能完成,對我來說就是阻塞。 – tcb 2014-09-29 00:04:17

+0

代碼在我的機器上的表現比您描述的要糟糕得多。客戶端從未完成,第二次運行後,我甚至無法殺死客戶端進程 - 我最終不得不重新啓動我的機器。我懷疑我的反惡意軟件防火牆可能會導致一些問題。其他任何人都認爲反惡意軟件會造成與其應該阻止的惡意軟件幾乎一樣多的麻煩? – 2014-09-29 00:46:53