首页 > 解决方案 > 是否可以遍历 recv / read 以从套接字读取所有数据

问题描述

我正在通过 TCP 构建一个多客户端<->服务器消息传递应用程序。我使用 epoll 创建了一个非阻塞服务器来多路复用 linux 文件描述符。
当 fd 接收到数据时,我将 read() /or/ recv() 读入 buf。我知道我需要在传输开始时指定数据长度*,或者在传输结束时使用分隔符**来分隔消息。

*使用数据长度:

char *buffer_ptr = buffer;
        do {
            switch (recvd_bytes = recv(new_socket, buffer_ptr, rem_bytes, 0)) {
                  case -1: return SOCKET_ERR;
                  case 0: return CLOSE_SOCKET;
                  default: break;
            }
            buffer_ptr += recvd_bytes;
            rem_bytes -= recvd_bytes;
        } while (rem_bytes != 0);

**使用分隔符:

void get_all_buf(int sock, std::string & inStr)
{
    int n = 1, total = 0, found = 0;
    char c;
    char temp[1024*1024]; 
    // Keep reading up to a '\n'
    while (!found) {
        n = recv(sock, &temp[total], sizeof(temp) - total - 1, 0);
        if (n == -1) {
            /* Error, check 'errno' for more details */
            break;
        }
        total += n;
        temp[total] = '\0';
        found = (strchr(temp, '\n') != 0);
    }
    inStr = temp;
}

我的问题:是否可以遍历 recv() 直到满足其中一个条件?如果客户端发送虚假消息长度或没有分隔符或有数据包丢失怎么办?我不会永远被困在我的程序中循环 recv() 吗?

标签: clinuxsockets

解决方案


是否可以循环 recv() 直到满足其中一个条件?

可能不是,至少对于生产质量的代码来说不是。正如您所建议的那样,在您获得完整消息之前循环的问题在于它让您的线程任由客户端摆布 - 如果客户端决定只发送部分消息然后等待很长时间(甚至永远) 如果不发送最后一部分,那么您的线程将被无限期地阻塞(或循环)并且无法用于任何其他目的——通常不是您想要的。

如果客户端发送虚假消息长度怎么办

那么你就有麻烦了(尽管如果你选择了最大消息大小,你可以检测到明显大于该大小的虚假消息长度,并通过例如强行关闭连接来保护自己)

还是有丢包?

如果丢包的数量相当少,TCP 层将自动重新传输数据,因此您的程序不会注意到差异(除了消息正式“到达”比其他情况要晚一点)。如果数据包丢失非常严重(例如,有人将以太网电缆从墙上拔出 5 分钟),那么消息的其余部分可能会延迟几分钟或更长时间(直到连接恢复,或者 TCP 层放弃并关闭TCP 连接),将您的线程困在循环中。

那么对于这种困境的工业级、恶意客户端和糟糕的网络防护解决方案是什么,以便您的服务器可以保持对其他客户端的响应,即使特定客户端本身没有行为呢?

答案是这样的:不要依赖于一次接收整个消息。相反,您需要为每个客户端设置一个简单的状态机,以便您可以recv()在任何特定时间从该客户端的 TCP 套接字发送尽可能多(或尽可能少)的字节,并将这些字节保存到与该客户端关联的本地(每个客户端)缓冲区,然后即使您尚未收到整个消息,也可以返回正常的事件循环。仔细跟踪您当前从每个客户端收到的有效数据字节数,并在每个 recv() 调用返回后,检查关联的每个客户端传入数据缓冲区是否包含整个消息,或者不是——如果是,解析消息,对其采取行动,然后将其从缓冲区中删除。起泡、冲洗并重复。


推荐阅读