【问题标题】:Asynchronous, Non-Blocking Socket Behaviour - WSAEWOULDBLOCK异步、非阻塞套接字行为 - WSAEWOULDBLOCK
【发布时间】:2019-08-02 23:34:37
【问题描述】:

我继承了两个应用程序,一个在 Windows 7 PC 上运行的测试工具(客户端)和一个在 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-&gt;GetLastError()返回WSAEWOULDBLOCK

SocketTools 强调

当非阻塞(异步)套接字尝试执行无法立即执行的操作时,将返回错误 10035。此错误不是致命错误,应用程序应将其视为建议性错误。此错误代码对应于 Windows 套接字错误 WSAEWOULDBLOCK。

从非阻塞套接字读取数据时,如果此时没有更多数据可供读取,将返回此错误。在这种情况下,应用程序应该等待 OnRead 事件触发,这表明有更多数据可供读取。 IsReadable 属性可用于确定是否有数据可以从套接字读取。

向非阻塞套接字写入数据时,如果在等待远程主机读取部分数据时本地套接字缓冲区已满,则会返回此错误。当缓冲区空间可用时,将触发 OnWrite 事件,指示可以写入更多数据。 IsWritable 属性可用于确定是否可以将数据写入套接字。

需要注意的是,应用程序不会知道在一次写入操作中可以发送多少数据,因此如果客户端尝试发送太多数据太快,可能会多次返回此错误.如果在发送数据时经常出现此错误,则可能表明网络延迟较高或远程主机无法足够快地读取数据。

一直收到此错误并且无法在套接字上接收任何内容。

使用Wireshark,与此处显示的源、目标和 TCP 位标志发生以下通信:

事件:通过 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;
    }
}

希望添加的一些代码可以证明是有见地的。如果需要,我应该可以添加更多。

谢谢

【问题讨论】:

  • 只想提一下:很好的描述。
  • 您说您“一直”收到此错误。然后你会等待再试一次吗?你确定错误是在接听电话上吗?客户端是否有可能没有发送整个请求?能不能用 ncat 来模拟你的客户端发送的同一个请求,看看服务器是如何响应的?
  • 是的,我等了一会儿,然后再试一次,套接字似乎仍被标记为相同的阻塞状态。在我通过重新启动两个应用程序来刷新服务器和客户端之前,它似乎一直保持这种状态。这是一个很好的观点。错误是否在接收呼叫上。我会尝试使用ncat 并更新我的结果
  • 重新。 @ewindes - 使用 ncat,这个问题是不可重复的。一切似乎都按预期运行。

标签: c++ sockets tcp mfc


【解决方案1】:

WSAEWOULDBLOCK 错误并不意味着套接字被标记为阻塞。这意味着套接字被标记为非阻塞,此时没有要读取的数据。

WSAEWOULDBLOCK 表示如果套接字已被标记为阻塞,则套接字将阻塞等待数据的调用线程。

要知道非阻塞套接字何时有数据等待读取,请使用 Winsock 的select() 函数,或CClientSocket::AsyncSelect() 方法请求FD_READ 通知,或其他等效方法。在有东西要读之前不要尝试阅读。

在您的分析中,您看到客户端向服务器发送数据,但服务器并未向客户端发送数据。因此,您的代码中显然存在逻辑错误,您需要找到并修复它。客户端没有正确终止其请求,或者服务器没有正确接收/处理/回复它。但是由于您没有显示您的实际代码,我们无法告诉您它到底有什么问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 2014-06-24
    • 2010-10-31
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多