【问题标题】:Checking for errors before recv() called在调用 recv() 之前检查错误
【发布时间】:2026-01-31 01:45:01
【问题描述】:

遇到以下问题:

我使用 select() 创建了一个能够处理多个连接的服务器。但是 select 也会返回一个客户端(FD_SET 的索引),如果套接字刚刚收到“客户端断开连接”之类的错误或其他错误。

是否可以在不调用 recv() 的情况下检查套接字。因为要接收,我需要从我的“BufferPool

中获取一个缓冲区

示例代码:

int ret = recv(client, buffer_pool->get(), BUFFER_SIZE, 0);
if(ret == -1) ... // something went wrong

那么我必须再次释放缓冲区,这几乎浪费了我池中的一个缓冲区。 (短时间)

所以不调用 recv() 就不能检查套接字

【问题讨论】:

    标签: c++ windows sockets recv


    【解决方案1】:

    我不确定 Windows,但使用 getsockopt() 在符合 POSIX 的系统上就像一个魅力。尽管在使用它之前 - 确保从池中获取缓冲区比进行额外的系统调用更昂贵。这是一个代码sn-p:

    int my_get_socket_error(int fd)
    {
            int err_code;
            socklen_t len = sizeof(err_code);
    
            if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err_code, &len) != 0)
                    err_code = errno;
            else
                    errno = err_code;
            return err_code;
    }
    

    更新:

    根据this document的说法,Windows好像也支持。

    【讨论】:

    • 不幸的是 err_code 每次都是 0,如果 recv() 返回 0(错误)
    • @Aurus:如果它返回0,recv返回0,那么你可能得到了EOF(即对方已经关闭了连接)。
    • 但如果客户端连接仍然存在,它们也是 0。
    • @Aurus:那你就没有数据了。也许您使用电平触发 I/O 并唤醒,因为套接字可用于写入并且没有要读取的数据。真的,如果套接字没有错误 - 没有错误,那部分可以工作几十年。
    【解决方案2】:

    不,没有办法避免recv() 调用。如果select() 报告套接字是可读的,那么您必须从套接字读取以确定它的新状态。如果客户端正常断开连接,recv() 将返回 0,而不是 -1。如果你不想浪费一个pooled buffer,那么你必须先读入一个临时的local buffer,然后如果recv()返回任何数据,你可以检索一个pooled buffer并将读取的数据复制到其中。

    【讨论】:

      【解决方案3】:

      调用 recv 和类似函数不能直接用于网络设备或类似的东西。 当您发送或接收数据时,您所做的只是向操作系统询问可用数据,或者将数据放入队列中以进行发送。然后,当您的代码已经走得更远时,操作系统将完成另一项工作。 这就是为什么您在下次调用将“联系”操作系统网络层的套接字函数后收到错误的原因。

      此时出现错误是正常的,你必须处理它们。

      但是为了防止阻塞套接字和浪费缓冲区,请查看在线实现技术或准备好的库,它们为您提供异步处理套接字的方式,这样您就不需要在套接字触发接收回调函数之前定义任何内容必须做实际接收。

      同样,一次性接收大量数据也不是什么好技术,因为通过 TCP 层会遇到合并或拆分数据的问题,因为它是基于流的层。建议在您的数据包中包含标头(几个字节)并接收它们,这样您就不需要为标头拉取,但只有在标头之后您才想根据标头中提供的长度读取消息的其余部分。这只是可能的例子。

      【讨论】:

        【解决方案4】:

        经过几分钟的工作和您的帮助,我在收到全部金额之前只收到了 1 个字节:

        SOCKET client = ...;
        
        char temp = 0x00;
        int len = recv(client, &temp, 1, 0);
        if(len == 0)
        {
            // .. client error handling
            return;
        }
        
        char* buffer = m_memory_pool->Get();
        len = recv(client, buffer + 1, m_memory_pool->buffer_size() - 1, 0);
        buffer[0] = temp;
        
        // data handling
        

        【讨论】:

          【解决方案5】:

          我也尝试为 recv() 设置超时,但似乎在 Windows 下它不起作用,这是我的代码:

          ...
          long timeout_ms = 10;
          struct timeval interval = {timeout_ms / 1000, (timeout_ms % 1000) * 1000};
          if (interval.tv_sec < 0 || (interval.tv_sec == 0 && interval.tv_usec <= 0))
          {
              interval.tv_sec = 0;
              interval.tv_usec = 10000;
          }
          setsockopt(s_sktIx, SOL_SOCKET, SO_RCVTIMEO, (char *)&interval, sizeof(struct timeval));
          ...
          

          【讨论】: