簡體   English   中英

異步,非阻塞套接字行為 - WSAEWOULDBLOCK

[英]Asynchronous, Non-Blocking Socket Behaviour - WSAEWOULDBLOCK

我繼承了兩個應用程序,一個在Windows 7 PC上運行的Test Harness(一個客戶端)和一個在Windows 10 PC上運行的一個服務器應用程序。 我試圖使用TCP / IP套接字在兩者之間進行通信。 客戶端將請求(對於XML形式的數據)發送到服務器,然后服務器將請求的數據(也是XML)發送回客戶端。

設置如下:

       Client                                    Server
--------------------                      --------------------  
|                  |    Sends Requests    |                  |
|   Client Socket  |  ----------------->  |   Server Socket  |
|                  |  <-----------------  |                  |
|                  |      Sends Data      |                  |
--------------------                      --------------------

此過程始終適用於初始連接(即剛剛啟動的客戶端和服務器應用程序)。 客戶端能夠斷開與服務器的連接,從而觸發套接字的清理。 重新連接后,我幾乎總是(它並不總是發生,但大部分時間都會)收到以下錯誤:

"Receive() - The socket is marked as nonblocking and the receive operation would block"

此錯誤顯示在客戶端,並且所討論的套接字是異步的非阻塞套接字。

導致此SOCKET_ERROR的行是:

numBytesReceived = theSocket->Receive(theReceiveBuffer, 10000));

where:
- numBytesReceived is an integer (int)
- theSocket is a pointer to a class called CClientSocket which is a specialisation of CASyncSocket, which is part of the MFC C++ Library.  This defines the socket object which is embedded within the client.  It is an asynchonous, non-blocking socket.
- Receive() is a virtual function within the CASyncSocket object
- theReceiveBuffer is a char array (10000 elements)

在執行上面描述的行時,從函數返回SOCKET_ERROR並調用theSocket->GetLastError()返回WSAEWOULDBLOCK

SocketTools強調了這一點

當非阻塞(異步)套接字嘗試執行無法立即執行的操作時,將返回錯誤10035。 此錯誤不是致命錯誤,應該被應用程序視為建議。 此錯誤代碼對應於Windows套接字錯誤WSAEWOULDBLOCK。

從非阻塞套接字讀取數據時,如果此時沒有更多數據可供讀取,則會返回此錯誤。 在這種情況下,應用程序應等待OnRead事件觸發,這表示有更多數據可供讀取。 IsReadable屬性可用於確定是否有可從套接字讀取的數據。

將數據寫入非阻塞套接字時,如果在等待遠程主機讀取某些數據時填充本地套接字緩沖區,則會返回此錯誤。 當緩沖區空間可用時,將觸發OnWrite事件,表示可以寫入更多數據。 IsWritable屬性可用於確定是否可以將數據寫入套接字。

重要的是要注意應用程序將不知道在單個寫操作中可以發送多少數據,因此如果客戶端嘗試過快地發送太多數據,則可能會多次返回此錯誤。 如果在發送數據時頻繁發生此錯誤,則可能表示網絡延遲較高或遠程主機無法足夠快地讀取數據。

一直收到此錯誤,並且無法在套接字上收到任何內容。

使用Wireshark ,下面的通信發生在這里提供的source,destinaton和TCP Bit Flags:

事件:通過TCP / IP將測試工具連接到服務器

Client --> Server: SYN
Server --> Client: SYN, ACK
Client --> Server: ACK

This appears to be correct and represents the Three-Way Handshake of connecting.

SocketSniff confirms that a Socket is closed on the client side.  It was not possible to get SocketSniff to work with the Windows 10 Server application.

事件:從測試工具發送數據請求

Client --> Server: PSH, ACK
Server --> Client: PSH, ACK
Client --> Server: ACK

Both request data and received data is confirmed to be exchanged successfully

事件:從服務器斷開測試工具

Client --> Server: FIN, ACK
Server --> Client: ACK
Server --> Client: FIN, ACK
Client --> Server: ACK

This appears to be correct and represents the Four-Way handshake of connection closure.

SocketSniff confirms that a Socket is closed on the client side.  It was not possible to get SocketSniff to work with the Windows 10 Server application.

事件:通過TCP / IP將測試工具重新連接到服務器

Client --> Server: SYN
Server --> Client: SYN, ACK
Client --> Server: ACK

This appears to be correct and represents the Three-Way Handshake of connecting.

SocketSniff confirms that a new Socket is opened on the client side.  It was not possible to get SocketSniff to work with the Windows 10 Server application.

事件:從測試工具發送數據請求

Client --> Server: PSH, ACK
Server --> Client: ACK

We see no data being pushed (PSH) back to the client, yet we do see an acknowledgement.  

有沒有人有任何想法可能會發生在這里? 我知道你很難在沒有看到源代碼的情況下進行診斷,但是我希望其他人可能有過這個錯誤的經驗,並且可能指出我需要調查的具體路線。

更多信息:

服務器初始化監聽線程並綁定到0.0.0.0:49720。 'WSAStartup()','bind()'和'listen()'函數都返回'0',表示成功。 此線程在服務器應用程序的整個生命周期中持續存在。

服務器初始化兩個線程,即讀取和寫入線程。 讀線程負責從其套接字讀取請求數據,並使用名為Connection的類初始化如下:

HANDLE theConnectionReadThread 
           = CreateThread(NULL,                                    // Security Attributes
                          0,                                       // Default Stacksize
                          Connection::connectionReadThreadHandler, // Callback
                          (LPVOID)this,                            // Parameter to pass to thread
                          CREATE_SUSPENDED,                        // Don't start yet
                          NULL);                                   // Don't Save Thread ID

寫線程以類似的方式初始化。

在每種情況下,CreateThread()函數返回一個合適的HANDLE,例如

theConnectionReadThread  = 00000570
theConnectionWriteThread = 00000574  

線程實際上是在以下函數中啟動的:

void Connection::startThreads()
{
    ResumeThread(theConnectionReadThread);
    ResumeThread(theConnectionWriteThread);
}                                   

此函數在另一個名為ConnectionManager類中調用,該類管理與服務器的所有可能連接。 在這種情況下,為簡單起見,我只關注單個連接。

將文本輸出添加到服務器應用程序后發現,在觀察到錯誤行為之前,我可以多次成功connect/disconnect客戶端和服務器。 例如,在connectionReadThreadHandler()connectionWriteThreadHandler()函數中,我一執行就將文本輸出到日志文件中。

如果觀察到正確的行為,則會將以下行輸出到日志文件:

Connection::ResumeThread(theConnectionReadThread) returned 1
Connection::ResumeThread(theConnectionWriteThread) returned 1
ConnectionReadThreadHandler() Beginning
ConnectionWriteThreadHandler() Beginning

當觀察到錯誤行為時,以下行將輸出到日志文件:

Connection::ResumeThread(theConnectionReadThread) returned 1
Connection::ResumeThread(theConnectionWriteThread) returned 1

回調函數似乎沒有被調用。

此時,錯誤顯示在客戶端上,表明:

"Receive() - The socket is marked as nonblocking and the receive operation would block"

在客戶端,我有一個名為CClientDoc的類,它包含客戶端套接字代碼。 它首先初始化theSocket ,它是嵌入客戶端的套接字對象:

private:
    CClientSocket* theSocket = new CClientSocket;

當在客戶端和服務器之間初始化連接時,該類調用一個名為CreateSocket()的函數,其中一部分包含在下面,以及它調用的輔助函數:

void CClientDoc::CreateSocket()
{
    AfxSocketInit();
    int lastError;
    theSocket->Init(this);

    if (theSocket->Create()) // Calls CAyncSocket::Create() (part of afxsock.h)
    {
        theErrorMessage = "Socket Creation Successful"; // this is a CString
        theSocket->SetSocketStatus(WAITING);             
    }
    else
    {
        // We don't fall in here
    }
}

void CClientDoc::Init(CClientDoc* pDoc)
{
    pClient = pDoc; // pClient is a pointer to a CClientDoc
}

void CClientDoc::SetSocketStatus(SOCKET_STATUS sock_stat)
{
    theSocketStatus = sock_stat; // theSocketStatus is a private member of CClientSocket of type SOCKET_STATUS
}

緊接着CreateSocket() SetupSocket()被調用這也是在這里提供:

void CClientDoc::SetupSocket()
{
    theSocket->AsyncSelect(); // Function within afxsock.h
}

斷開客戶端與服務器的連接后,

void CClientDoc::OnClienDisconnect()
{
    theSocket->ShutDown(2); // Inline function within afxsock.inl
    delete theSocket;
    theSocket = new CClientSocket;
    CreateSocket();
    SetupSocket();        
}

因此,我們刪除當前套接字,然后創建一個可供使用的新套接字,它似乎按預期工作。

錯誤正在DoReceive()函數中寫入客戶端。 此函數調用套接字嘗試讀取消息。

CClientDoc::DoReceive()
{
    int lastError;
    switch (numBytesReceived = theSocket->Receive(theReceiveBuffer, 10000))
    {
    case 0:
        // We don't fall in here
        break;
    case SOCKET_ERROR: // We come in here when the faulty behaviour occurs
        if (lastError = theSocket->GetLastError() == WSAEWOULDBLOCK)
        {
            theErrorMessage = "Receive() - The socket is marked as nonblocking and the receive operation would block";
        }
        else
        {
            // We don't fall in here
        }
        break;
    default:
        // When connection works, we come in here
        break;
    }
}

希望添加一些代碼證明是有見地的。 如果需要,我應該能夠添加更多。

謝謝

WSAEWOULDBLOCK錯誤並不意味着套接字被標記為阻塞。 這意味着套接字被標記為非阻塞,並且當時沒有數據要讀取。

WSAEWOULDBLOCK表示如果套接字HAD BEEN標記為阻塞,則套接字WOULD已阻止等待數據的調用線程。

要知道非阻塞套接字何時等待讀取數據,請使用Winsock的select()函數或CClientSocket::AsyncSelect()方法來請求FD_READ通知或其他等效通知。 在有東西要讀之前不要試着讀。

在分析中,您會看到客戶端將數據發送到服務器,但服務器未向客戶端發送數據。 所以你的代碼中明顯存在邏輯錯誤,你需要找到並修復它。 客戶端未正確終止其請求,或者服務器未正確接收/處理/回復它。 但由於您沒有顯示您的實際代碼,我們無法告訴您它實際上有什么問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM