FD_ISSET 即使没有新数据也始终为真?

FD_ISSET always true even if there is no new data?

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

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 客户端并发送一条 "connection" 消息,我在 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]{'[=12=]'};
    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[=13=]");
    out.flush();
    out.write("newCon[=13=]");
    out.flush();    
    out.close();
    in.close();
    soc.close();
}

那么hasDataFD_ISSET方法有什么问题呢?

摘自 Steven 的《UNIX 网络编程》一书:

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

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

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

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

  • 套接字错误待处理。套接字上的读取操作不会阻塞,并且会 return 错误 (–1) 并将 errno 设置为特定的错误条件。这些挂起的错误也可以通过调用 getsockopt 并指定 SO_ERROR 套接字选项来获取和清除。

在上述所有情况下,

ISSET 将 return 为真。在您的 Java 客户端关闭连接后,套接字将准备好在服务器中读取。

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

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

您没有检查 recv 的 return 值,也没有处理接收到的字节数少于您要求的字节数。那么当连接关闭时,您期望发生什么?

So what is wrong with the hasData FD_ISSET method?

实际上没有。您使用 recv().

有问题 如果客户端断开连接,

recv() 将 return 0 并将 return 直到您 close 套接字(服务器端)。您可以找到此信息 in the manual。 即使recv()returns0也会"trigger"select().

知道这一点,很容易找出问题所在:您从不检查 recv() 的 return 值,因此您无法判断客户端是否仍处于连接状态。但是,您仍然添加 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]{'[=10=]'};
        recv(m_sock, dataBuffer, i, NULL);
        LOG_INFO << dataBuffer;
        //clean up
        delete[] dataBuffer;
    }
}