【问题标题】:will tcp socket recv return "resource temporarily unavailable" when recv buf already have data当 recv buf 已经有数据时,tcp socket recv 会返回“资源暂时不可用”吗
【发布时间】:2018-02-16 09:24:27
【问题描述】:

我使用epoll实现TCP socket通信来监控所有客户端事件,一个for循环中只有一个线程处理所有客户端。每个套接字都是非阻塞的。

现在我遇到了一个问题,当客户端发送的数据超过MTU时,意味着有几个分片数据包,服务器总是无法完全读取所有数据。 如下所示,我先读取头部,从头部获取 pdu len,然后读取 pdu 部分。

问题是虽然我成功读取了头部,紧跟在 pdu recv() 之后,但它总是返回 EAGAIN 几次。所以我的重试会中断。 因为服务器需要处理上千个客户端事件,所以我认为让重试一直持续下去是一个很大的性能消耗,这是不能容忍的。

我使用 tcpdump 捕获来自客户端的数据包,每个数据包都是分段到最大 1448 字节的数据,但头部只有 5 字节,为什么我可以成功读取头部,但是下面的数据 recv() 操作会返回 EAGAIN?当数据已经到达recv缓冲区时,recv是否可能返回EAGAIN?在我看来,我可以读取前 5 个字节,因此必须在 recv 缓冲区中读取更多数据。

可能与 tcp/ip 堆栈中的 assemble 过程有关。 代码如下,每个pdu recv,需要重试10次以上,可能成功。

...
#define HDR_LEN    5
n = epoll(epfd, events, 1000, -1)
for(i =0; i < n; i++)
{
    uint8 pHdr[HDR_LEN] = {0};
    uint16 pdulen = 0, offset =0;
    infd = events[i].fd;
    nRead = recv(infd, pHdr, HDR_LEN);   // read the data head first
    pdulen = ntohs(*(uint16 *)(pHdr+2));   // get the pdu len from the head
    uint8 *pbuf = malloc(pdulen+HDR_LEN);

    memcpy(pbuf, pHdr, HDR_LEN);            // move the head to buf
    while(offset != pdulen)                 // then read the pdu data
    {
        nRead = recv(infd, pbuf+HDR_LEN+offset, pdulen-offset);
        if (nRead <=0)
        {
            if (nRead == -1 && errno == EAGAIN) // resource temporarily unavailable 
            {
                if (retry < 5)
                {
                    usleep(500);
                    retry++;
                    continue;
                }
                else
                     break;  // already try 5 times, should always continue?
            }
            else
                break;
        }
        else
        {
             offset += nRead;
             retry = 0;
        }
    }
    if (offset == pdulen)
        process(pbuf, pdulen+HDR_LEN); // process the complete data
    ...
}
...

【问题讨论】:

  • 请帮忙,解决这个问题!
  • 你的套接字是否处于非阻塞模式?如果是这样,那么只要接收缓冲区中没有更多数据要检索,就可以预期 EAGAIN。为了同时处理多个套接字的非阻塞 I/O,您需要实现一个状态机,以便在收到部分响应后可以返回正常的 poll() 调用。然后当 poll() 指示在您的套接字上要读取更多数据时,然后(并且只有在那时)您应该返回并 recv() 来自套接字的附加数据,将其附加到您之前从该套接字获得的数据中,直到你已经得到了整个 PDU。
  • '当recv buf已经有数据时,tcp socket recv会返回“资源暂时不可用”吗?'没有。

标签: linux sockets unix tcp recv


【解决方案1】:

epoll 会告诉你是否可以recv 不阻塞,一次​​。如果recv 消耗了套接字中的所有数据,那么下一个recv 将阻塞直到有更多数据,或者返回 EAGAIN(如果是非阻塞套接字)。

一个常见的模式是:

  1. 使用select/poll/epoll 检测何时可以读取套接字。
  2. 在就绪套接字上调用一次recv 并将接收到的数据附加到缓冲区。
  3. 检查缓冲区是否包含足够的数据进行处理。如果是,则处理。否则,让select/poll/epoll 告诉您何时可以阅读更多内容。

【讨论】:

    猜你喜欢
    • 2015-03-09
    • 2018-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-14
    • 2018-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多