在C语言中处理UDP套接字编程中的数据包丢失

6
我希望发送一个字节数组X次,直到达到缓冲区大小。我的客户端会发送字节数组,直到缓冲区大小满为止。但问题在于数据包丢失,服务器只收到缓冲区大小的8/10左右。

客户端:

while(writeSize < bufferSize)
{
    bytes_sent += sendto(servSocket, package, sizeof(package), 0, (struct sockaddr *)&server, sizeof(server));
    writeSize+= sizeof(package);    
}

服务器:

while(packetSize < bufferSize)
{
    bytes_recv += recvfrom(servSocket, package, sizeof(package), 0, (struct sockaddr*)&client, &size);
    packetSize += sizeof(package);
    printf("Bytes recieved: %d\n", bytes_recv);
}

我无法真正解决这个问题。当客户端发送了所有包并且服务器遭受了包丢失时,while循环不会结束,你有什么想法可以解决这个问题吗?
非常感谢。

5
TCP是传输控制协议的缩写,它可以解决这个问题。 - davmac
7
如果可靠性协议很重要,使用TCP。否则,您将不得不在应用层上实现某种形式的重传处理(超过UDP)。前者比后者简单得多... - Nim
1
任务是实现一个UDP服务器/客户端,很抱歉在帖子中没有包含。我已经完成了TCP服务器/客户端。 - bungieqdf
1
你确定你正在丢失数据包吗?你的服务器代码不太对 - 在循环的每次迭代中,packetSize 应该增加 bytes_recv - simonc
您在系统调用上缺少错误检查。如果recvfrom()sendto()失败,您的字节计数将会出现问题。 - alk
显示剩余4条评论
4个回答

3

哼。您正在使用UDP协议。对于这个协议,如果需要的话,抛弃数据包是完全可以接受的。所以在“发出的内容是否能够到达”方面,它并不可靠。在您的客户端中,您需要检查是否所有所需数据包都已到达,如果没有,礼貌地与服务器通信以重新发送您没有收到的数据包。实现这些内容并不容易,您最终可能会得到一个比仅使用传统的TCP套接字更低效的解决方案,因此我的建议是首先切换到TCP连接。


我忘了提到我的任务是构建一个UDP服务器/客户端,所以改用TCP不是一个选项。如果没有所有的数据包到达也没关系,我想要一种方法在服务器端没有达到缓冲区大小时退出while循环。 - bungieqdf

3
如果您必须使用UDP传输大块数据,那么请设计一个小型的应用层协议来处理可能的数据包丢失和重排序(这是TCP为您完成的部分)。我建议采用以下方案:
  • 将数据报大小控制在MTU(加上IP和UDP头)以下,大小为1024字节,以避免IP分片。
  • 为每个数据报设计固定长度的标头,其中包括数据长度和序列号,以便您可以将数据拼接在一起,并检测缺失、重复和重新排序的部分。
  • 从接收端发送确认已成功接收并组装的数据。
  • 当这些确认信息未在适当的时间内到达时,在发送端进行超时和重传。

以上就是方案。我们又开始构建TCP了……


1
当然TCP会是更好的选择,但如果你只有UDP选项,我会将数据包装成一个带有序列和buf_length字段的结构体。
这绝对不是最优解决方案...它只是关注序列号以确保数据完全到达并按正确顺序排列。如果没有,就停止接收数据(它可以做一些更智能的事情,比如要求发送方重新发送片段并重建数据,尽管顺序错误,但这将变得非常复杂)。
struct udp_pkt_s {
    int     seq;
    size_t  buf_len;
    char    buf[BUFSIZE];
};

发送方:
struct udp_pkt_s udp_pkt;

udp_pkt.seq = 0;

while(writeSize < bufferSize)
{
    // switch to the next package block
    // ...

    memcpy(udp_pkt.buf, package);
    udp_pkt.buf_len = sizeof(package);
    udp_pkt.seq++;

    bytes_sent += sendto(servSocket, &udp_pkt, sizeof(udp_pkt_s), 0, (struct sockaddr *)&server, sizeof(server));
    writeSize += sizeof(package);    
}

接收方:

last_seq = 0;

while(packetSize < bufferSize)
{
    bytes_recv += recvfrom(servSocket, &udp_pkt, sizeof(udp_pkt_s), 0, (struct sockaddr*)&client, &size);

    // break it if there's a hole
    if (udp_pkt.seq != (last_seq + 1))
        break;

    last_seq = udp_pkt.seq;

    packetSize += udp_pkt.buf_len;

    printf("Bytes recieved: %d\n", udp_pkt.buf_len);
}

0

在调用 sendto 后,你可以等待 sendto 缓冲区降为零,之后你可以在每次 sendto 调用之后使用以下 API。

void waitForSendBuffer(void)
{
int outstanding = 0;
while(1)
{
  ioctl(socketFd_, SIOCOUTQ, &outstanding);
  if(outstanding == 0)
    break;
  printf("going for sleep\n");
  usleep(1000);
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接