【问题标题】:Why is select() returning 1 but recv() returning 0?为什么 select() 返回 1 而 recv() 返回 0?
【发布时间】:2016-02-19 21:31:26
【问题描述】:

我可以清楚地看到 recvbuf 拥有我期望的所有数据,但 select() 一直返回 1。

现在它被困在else if (iBuffer == 0) {} 的边缘。

SOCKET m_ConnectSocket;
/* The socket setup is done elsewhere but just adding this for clarity
   This socket is responsible for sending from the client to the server
   and also receives anything the server sends back.

   This socket is doing the connect() & initial send()
*/

fd_set set;
struct timeval timeout;

// Set up the file descriptor set.
FD_ZERO(&set);
FD_SET(m_ConnectSocket, &set);

// Set up the struct timeval for the timeout.
timeout.tv_sec  = RECV_DELAY_SEC;
timeout.tv_usec = RECV_DELAY_USEC;

int iBuffer = 0;

do
{
    iResult = select(m_ConnectSocket, &set, NULL, NULL, &timeout);
    if (iResult > 0)
    {
        iBuffer = recv(m_ConnectSocket, recvbuf, DEFAULT_BUFLEN, 0);
        if (iBuffer > 0)
        {
            string sRecv(recvbuf);

            STrace = String::Format("Bytes Received: {0}", iBuffer);
            Trace(STrace, TRACE_INFO);

            STrace = String::Format("Data Received: [{0}]", gcnew String(sRecv.c_str()));
            Trace(STrace, TRACE_INFO);
        }
        else if (iBuffer == 0)
        {
            STrace = String::Format("iBuffer empty");
            Trace(STrace, TRACE_INFO);
        }
        else
        {
            STrace = String::Format("recv failed: {0}", WSAGetLastError());
            Trace(STrace, TRACE_ERROR);
        }
    }
    else if (iResult == 0)
    {
        STrace = String::Format("No data left in buffer");
        Trace(STrace, TRACE_INFO);

        pMessage->Data(recvbuf);
        if (iSentType != pMessage->Type())
        {
            STrace = String::Format("Message type mismatch: {0} | Expected: {1}", (int)pMessage->Type(), (int)iSentType);
            Trace(STrace, TRACE_WARNING);
        }
    }
    else if (iResult == -1)
    {
        STrace = String::Format("select() error");
        Trace(STrace, TRACE_ERROR);
    }
} while (iResult > 0);

【问题讨论】:

  • select 在循环中的第一次或后续时间是否返回 1?
  • 这是 UDP 连接吗? DEFAULT_BUFLEN 的值是多少?你为什么要通过m_connectSocket 而不是1 来选择? (select的第一个参数应该是要检查的文件描述符的数量)。
  • 我认为您需要在每次通话前重置settimeout。也许错了
  • @Enigma 我错了 - 根据 msdn,该参数被忽略。 (msdn.microsoft.com/en-us/library/windows/desktop/…) 但在 berkeley 套接字中,它应该是最高套接字 + 1 (linux.die.net/man/2/select)
  • @EdHeal 你是绝对正确的; select 改变了 fdset,这就是为什么它将它们作为指针,所以你可以知道哪些套接字亮了,可以这么说。

标签: c++ windows select networking winsock2


【解决方案1】:

到目前为止,他们所说的关于重置 FD 集的所有答案都是正确的,但他们都没有真正确定潜在的问题。

如果recv() 返回零,则表示对等方已断开连接,您必须关闭套接字。如果你不这样做,因为你不是,你将继续选择套接字为可读并继续接收零。

它确实不是意味着“缓冲区为空”。

【讨论】:

    【解决方案2】:

    由于select 将其参数作为指针传递,并且这些数据结构被select put 改变

    fd_set set;
    struct timeval timeout;
    
    // Set up the file descriptor set.
    FD_ZERO(&set);
    FD_SET(m_ConnectSocket, &set);
    
    // Set up the struct timeval for the timeout.
    timeout.tv_sec  = RECV_DELAY_SEC;
    timeout.tv_usec = RECV_DELAY_USEC;
    

    就在select 语句之前,即在循环内。

    【讨论】:

    • 所以我这样做了,但我仍然偶尔会进入永远 1 的情况。我现在想知道它是否与对面套接字的状态有关。
    • 这种情况很少发生,而且(我认为)只有当我在流程中遇到断点时才会发生。
    • 当它返回 1 时,recv 可能返回 -1。因此,该套接字已关闭。
    • @EdHeal:如果连接优雅地关闭,select() 将报告可读性,recv() 将返回 0,而不是 -1。
    【解决方案3】:

    您误用了select()。请阅读documentation

    参数

    nfds [in]
    忽略。包含 nfds 参数只是为了与 Berkeley 套接字兼容。

    ...

    返回时,结构会更新以反映这些套接字中满足指定条件的子集。

    因此,每次在循环中调用 select() 时,您必须重置 fd_set 结构。

    此外,看起来当select() 超时时,您正在尝试解析接收到的任何内容,但您只解析最后一个成功的recv() 返回的最后一个缓冲区(如果有的话)。如果在数据超时之前必须多次调用recv(),则需要收集每个返回的缓冲区,然后将它们作为一个整体解析。

    此外,您的错误处理通常也需要一些改进。

    尝试类似的方法:

    fd_set set;
    struct timeval timeout;
    
    string sBuffer;
    int iBuffer;
    
    do
    {
        // Set up the file descriptor set.
        FD_ZERO(&set);
        FD_SET(m_ConnectSocket, &set);
    
        // Set up the struct timeval for the timeout.
        timeout.tv_sec  = RECV_DELAY_SEC;
        timeout.tv_usec = RECV_DELAY_USEC;
    
        iResult = select(0, &set, NULL, NULL, &timeout);
        if (iResult > 0)
        {
            iBuffer = recv(m_ConnectSocket, recvbuf, DEFAULT_BUFLEN, 0);
            if (iBuffer > 0)
            {
                string sRecv(recvbuf, iBuffer);
    
                STrace = String::Format("Bytes Received: {0}", iBuffer);
                Trace(STrace, TRACE_INFO);
    
                STrace = String::Format("Data Received: [{0}]", gcnew String(sRecv.c_str()));
                Trace(STrace, TRACE_INFO);
    
                sBuffer += sRecv;
            }
            else
            {
                if (iBuffer == 0)
                {
                    STrace = String::Format("Connection closed");
                    Trace(STrace, TRACE_INFO);
                }
                else
                {
                    STrace = String::Format("recv failed: {0}", WSAGetLastError());
                    Trace(STrace, TRACE_ERROR);
                }
                break;
            }
        }
        else if (iResult == 0)
        {
            STrace = String::Format("No data left in buffer");
            Trace(STrace, TRACE_INFO);
    
            pMessage->Data(sBuffer.c_str());
            if (iSentType != pMessage->Type())
            {
                STrace = String::Format("Message type mismatch: {0} | Expected: {1}", (int)pMessage->Type(), (int)iSentType);
                Trace(STrace, TRACE_WARNING);
            }
        }
        else
        {
            STrace = String::Format("select failed: {0}", WSAGetLastError());
            Trace(STrace, TRACE_ERROR);
        }
    }
    while (iResult > 0);
    

    【讨论】:

    • 不能识别实际问题,也不能正确处理数据或流结束,并且根本不能处理recv() 上的错误。 select() 返回零并不意味着“缓冲区中没有数据”,并且假设读取超时等于消息结束是无效的。
    • @user207421 "无法确定实际问题" - 问题之一。 “没有正确处理数据” - 以什么方式? "or end of stream" - 循环在 EOS 上中断,带有日志记录。 “根本不处理recv() 上的错误” - 循环因错误而中断,带有日志记录。 “select() 返回零并不意味着'没有数据留在缓冲区'” - 代码假定超时时消息结束,因为数据没有被帧化。一些协议以这种方式工作,但在大多数情况下,框架是最好的。 “假设读取超时等于消息结束无效。” - 取决于协议和超时长度
    猜你喜欢
    • 2015-06-21
    • 1970-01-01
    • 2017-07-27
    • 2016-10-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多