【发布时间】: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