2011-03-14 41 views
0

希望您度過美好的一天。另一個套接字問題,另一天:)Winsock accept()返回WSAENOTSOCK(代碼10038)

我終於得到MicroSoft Visual C++(MSVC++)IDE安裝,再加上Platform SDK,所以我可以編譯winsock應用程序。

在這裏錯過了一大堆東西。在ServerSocket :: accept()函數中,它創建一個ClientSocket的新實例,並將它的套接字文件描述符設置爲accept()ed的那個,我也在那裏檢查過,並且它認識到描述符在那裏也是有效的。在我的ClientSocket :: recv()函數中,我顯然調用了winsock庫中的recv()函數。我遇到的問題是,我正在使用的套接字描述符被recv()識別爲無效,但僅在從我的ServerSocket :: accept()返回的服務器端ClientSocket實例上 - 客戶端ClientSocket實例沒有問題。我插入了多個調試語句,描述符是有效的。

最奇怪的是,如果我編譯這個完全相同的代碼與Windows上的MinGW gcc/g ++,它運行良好!它只使用MSVC++發生此問題。

string ClientSocket::recv(int bufsize) { 
    if (!isConnected()) throw SocketException("Not connected."); 

    cout << "SocketRecv: " << (sockfd == INVALID_SOCKET) << " " << sockfd << endl; 
    vector<char> buffer(bufsize+1, 0); 
    cout << "SocketRecv1: " << (sockfd == INVALID_SOCKET) << " " << sockfd << endl; 
    int ret = ::recv(sockfd, &buffer[0], bufsize, 0); 
    cout << "SocketRecv2: " << (sockfd == INVALID_SOCKET) << " " << sockfd << endl; 

    // ret is apparently -1 because of "invalid" socket descriptor, but the 
    // above statements print zero (false) on the (sockfd == INVALID_SOCKET) ! :\ 
    if (ret < 0) { 
     #ifdef _WIN32 
     switch((ret = WSAGetLastError())) { 
     #else 
     switch(errno) { 
     #endif 
      case DECONNREFUSED: // The 'd' prefix means _I_ defined it, i.e. from windows it's 
           // set to 'WSAECONNREFUSED', but from linux it's set to 'ECONNREFUSED' 
       throw SocketException("Connection refused on recover."); 
       break; 
      case DENOTCONN: 
       throw SocketException("Not connected."); 
       break; 
      case DECONNABORTED: 
       throw SocketException("Software caused connection abort."); 
       break; 
      case DECONNRESET: 
       throw SocketException("Connection reset by peer."); 
       break; 
      default: 
       //usually this itoa() and char/string stuff isn't here... needed it in 
       //order to find out what the heck the problem was. 
       char tmp[21]; 
       string tmp4 = "Unknown error reading socket. "; 
       string tmp3 = tmp4 + itoa(ret, tmp, 10); 
       //this throw keeps throwing "Unknown error reading socket. 10038" 
       throw SocketException(tmp3); 
       break; 
     } 
    } else if (ret == 0) { 
     connected = false; 
     return ""; 
    } 

    return &buffer[0]; 
} 

附加信息:套接字處於阻止模式,即尚未設置爲非阻塞。我已成功調用WSAStartup()。這發生在服務器端,在從我的ServerSocket :: accept()返回的ClientSocket實例上(是的,我也在那裏檢查了描述符 - 這很好)。客戶端聲明'WSAECONNRESET(10054)'或'WSAECONNABORTED(10053)'。

我想不出任何其他可能是錯誤的。最糟糕的部分是,在windows和linux上都使用MinGW gcc/g ++都可以正常工作。

如果你想看到整個庫,它粘貼在:(注意:600+線)
Socket.cxx - http://paste.pocoo.org/show/353725/
Socket.hxx - http://paste.pocoo.org/show/353726/

謝謝!

更新 - 按照本公司的解決方案,我現在使用:void ServerSocket::accept(ClientSocket& sock);,並作爲實施:ClientSocket mysock; server.accept(mysock);

太謝謝你了!

回答

4

看起來你沒有跟隨Rule of Three。任何時候你有一個析構函數,你都需要寫或禁用複製構造函數和賦值運算符。

在您的使用例子:

ClientSocket client = server.accept(); 

變量client是從返回值拷貝構造。然後析構函數在臨時變量上運行,關閉套接字。

在C++ 0x中,您可以添加移動構造函數並解決此問題。現在,你應該實現swap並使用它:

ClientSocket client; 
server.accept().swap(client); 

或者通過client作爲server.accept參數:

ClientSocket client; 
server.accept(client); 

你可以寫一個移動的拷貝構造函數ClientSocket,在auto_ptr風格,但我不會推薦。人們不希望複製構造函數竊取資源。

+0

那麼,你會如何建議從ServerSocket :: accept()返回ClientSocket?我寧願不要強制使用指針:\ – FurryHead 2011-03-14 22:48:37

+0

非常感謝。我現在正在編譯它,我確實希望它能解決這個問題。 – FurryHead 2011-03-14 22:53:04

+0

HALELUJAH!它的工作原理是使用'無效的ServerSocket :: accept(ClientSocket&sock);'和:'ClientSocket mysock; server.accept(mysock);'謝謝!你不知道這一直困擾着我多久。 – FurryHead 2011-03-14 22:55:19

0

僅僅因爲您的套接字變量未設置爲INVALID_SOCKET並不表示套接字描述符從WinSock的角度來看是有效的。顯然,它不是,否則WinSock不會抱怨它。您可以調用recv()之前關閉套接字(這一點在客戶端出現錯誤時也很明顯)。

這是因爲ServerSocket::accept()按值返回一個新的ClientSocket實例。編譯器必須爲返回值分配對象的第二個副本,但ClientSocket類不定義任何拷貝構造函數。原始套接字描述符將從第一個ClientSocket實例複製到第二個實例,然後在退出時釋放原始實例,在第二個實例可以使用之前關閉套接字。您需要定義一個複製構造函數,該構造函數接受原始套接字描述符的所有權,並將原始實例的描述符設置爲INVALID_SOCKET,以便其析構函數不能再關閉套接字。

與此相關的是,ClientSocket類中存在句柄泄漏。您正在ClientSocket構造函數中調用WSAStartup()socket()(這不是這些調用中的最佳位置)。當ServerSocket::accept()接受新的客戶端時,用新的套接字描述符調用ClientSocket::setFd(),該套接字描述符替換在ClientSocket構造函數中分配的原始套接字描述符,但未正確釋放它。您應該定義第二個ClientSocket構造函數,該構造函數接受現有套接字描述作爲輸入,然後讓該構造函數調用setFd()而不是socket()。這將消除泄漏,然後複製構造函數可以在需要時獲取這個單個分配的套接字描述符的所有權。

+0

爲什麼不先setFd()先調用舊變量的WSACleanup()和closesocket()?真的,我傾向於和貝恩一起回答。 – FurryHead 2011-03-14 23:05:54

+0

按照我的建議,執行'swap'可以同時解決泄漏問題。 – 2011-03-14 23:25:34

+0

最好不要首先分配一個未使用的套接字描述符。這是對系統資源的浪費。並且在'setFd()'內部調用'WSACleanup()'是一個壞主意,因爲代碼將不得不再次調用'WSAStartup()'。應用程序不應該像這樣反覆調用'WAStartup()'和'WSACleanup()'。在程序啓動時調用'WSAStartup()',然後根據需要開始使用其他API函數,然後在程序關閉期間調用'WSACleanup()'。 – 2011-03-15 01:35:07