【问题标题】:FD_ISSET always true even if there is no new data?即使没有新数据,FD_ISSET 也始终为真?
【发布时间】:2015-09-08 17:16:49
【问题描述】:

我正在尝试检查客户端是否发送了一些新数据。这实际上告诉我,我总是有新数据:

bool ClientHandle::hasData()
{
    fd_set temp;
    FD_ZERO(&temp);
    FD_SET(m_sock, &temp);

    //setup the timeout to 1000ms
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 1000;
    //temp.fd_count possible?
    if (select(m_sock+1, &temp, nullptr, nullptr, &tv) == -1)
    {
        return false;
    }

    if (FD_ISSET(m_sock, &temp)) 
        return true;

    return false;
}

我正在与一个 java 客户端连接并发送一条我在 ctor 内部读取的“连接”消息:

ClientHandle::ClientHandle(SOCKET s) : m_sock(s)
{
    while (!hasData())
    {
    }
    char buffer[5];
    recv(m_sock, buffer, 4, NULL);
    auto i = atoi(buffer);
    LOG_INFO << "Byte to receive: " << i;
    auto dataBuffer = new char[i + 1]{'\0'};
    recv(m_sock, dataBuffer, i, NULL);
    LOG_INFO << dataBuffer;
    //clean up
    delete[] dataBuffer;
}

这似乎工作正常。之后,我会继续检查是否有新数据,即使 java 客户端没有发送任何新数据。

这里是java客户端。不要评判我,这只是为了检查连接。将大小信息作为 char[] 发送不会一直这样。

public static void main(String[] args) throws UnknownHostException,
        IOException {
    Socket soc = null;

    soc = new Socket("localhost", 6060);
    PrintWriter out = new PrintWriter(soc.getOutputStream(), true);
    BufferedReader in = new BufferedReader(new InputStreamReader(
            soc.getInputStream()));

    if (soc != null)
        System.out.println("Connected");
    out.write("10\0");
    out.flush();
    out.write("newCon\0");
    out.flush();    
    out.close();
    in.close();
    soc.close();
}

那么 hasData FD_ISSET 方法有什么问题呢?

【问题讨论】:

    标签: c++ recv


    【解决方案1】:

    那么 hasData FD_ISSET 方法有什么问题呢?

    其实没有。你使用recv()有问题。

    recv() 将在客户端断开连接时返回 0 并将返回此直到您 close 套接字(服务器端)。您可以找到此信息in the manual。 即使recv() 返回0,它也会“触发”select()

    知道了这一点,就很容易找出问题所在:您从不检查recv() 的返回值,因此您无法判断客户端是否仍处于连接状态。不过,你还是用FD_SET!

    #include <sys/types.h> // for ssize_t
    #include <stdio.h> // for perror()
    ClientHandle::ClientHandle(SOCKET s) : m_sock(s)
    {
        while (!hasData())
        {
        }
        char buffer[5];
        ssize_t ret = recv(m_sock, buffer, 4, NULL);
        if (ret == -1) // error
        {
            perror("recv");
            return ;
        }
        else if (ret == 0) // m_sock disconnects
        {
           close(m_sock);
           // DO NOT FD_SET m_sock since the socket is now closed 
        }
        else
        {
            auto i = atoi(buffer);
            LOG_INFO << "Byte to receive: " << i;
            auto dataBuffer = new char[i + 1]{'\0'};
            recv(m_sock, dataBuffer, i, NULL);
            LOG_INFO << dataBuffer;
            //clean up
            delete[] dataBuffer;
        }
    }
    

    【讨论】:

    • 所以,有数据很好,但正如我从其他答案中看到的那样,它也会在不同的条件下返回 true。 (不仅仅是我认为它包含新数据)所以 hasdata 方法是不正确的。所以我需要以某种方式添加它。
    • @BennX hasData() 几乎是正确的:您不应该在断开连接的套接字上使用FD_SETrecv() 获取数据返回正数,0 客户端断开连接,-1 出现错误。我更新了答案以添加更多解释。
    • 所以要检查我是否有数据,我只需接收数据并在有新数据时处理它?在我尝试选择数据之前,我想我可以用 select 来检查它。
    • @BennX recv() 只是接收数据。 select() 允许您在适当的时候使用recv()Beej's Guide To Network Programming 是一个有用的资源,你应该看看它。
    • 我已经知道该指南,但我想我需要再次深入阅读它。我只是试图在我接收之前检查是否有来自客户端的数据,但我发现它没有按我的预期工作。如果可能的话,你是否可以添加一个“正确的”hasData?我想我需要将它组合成“getData”之类的东西,如果不是这样,它会自动检查并返回 0。
    【解决方案2】:

    来自 Steven 的书 UNIX Networking Programming:

    如果以下四个条件中的任何一个为真,则套接字已准备好读取:

    • 套接字接收缓冲区中的数据字节数大于或等于套接字接收缓冲区的低水位标记的当前大小。套接字上的读取操作不会阻塞,并且会返回一个大于 0 的值(即准备好读取的数据)。我们可以使用 SO_RCVLOWAT 套接字选项设置这个低水位线。 TCP 和 UDP 套接字默认为 1。

    • 连接的读取部分已关闭(即,已收到 FIN 的 TCP 连接)。对套接字的读操作不会阻塞,会返回 0(即 EOF)。

    • 套接字是侦听套接字,已完成的连接数非零。监听套接字上的接受通常不会阻塞,尽管我们将在第 16.6 节中描述接受可以阻塞的时间条件。

    • 一个套接字错误未决。套接字上的读取操作不会阻塞,并将返回错误 (–1),并将 errno 设置为特定的错误条件。也可以通过调用 getsockopt 并指定 SO_ERROR 套接字选项来获取和清除这些未决错误。

    ISSET 在上述所有情况下都将返回 true。在您的 Java 客户端关闭连接后,套接字将准备好在服务器中读取。

    在 ClientHandle::ClientHandle 中,您没有检查 recv 的返回值以及是否返回任何数据。

    在第二次调用 recv 时是否阻塞?

    【讨论】:

    • 我明白了,所以 hasData 实际上是不正确的。您是否可以发布检查客户是否有新数据的正确方法?它应该是阻塞的,但由于我在这些调用之间刷新它不会阻塞并且工作正常。
    • @BennX,只需检查第一个 recv 的返回值是否有错误或 0(零)数据。如果返回值为 0 并且它读取 0 数据,则连接已关闭。如果返回值为 != 0,请检查错误。如果第一个成功读取 >0 字节,则仅调用第二个 recv
    【解决方案3】:

    您没有检查recv 的返回值,并且您没有处理接收的字节数比您要求的少。那么当连接关闭时,您期望会发生什么?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-04
      • 2019-10-05
      • 1970-01-01
      • 1970-01-01
      • 2023-02-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多