【问题标题】:C udp recvfrom client side来自客户端的 C udp recv
【发布时间】:2014-02-18 10:24:43
【问题描述】:

我正在编写 udp 服务器/客户端应用程序,其中服务器发送数据和客户端 正在接收。当数据包丢失时,客户端应该向服务器发送 nack。我将套接字设置为 O_NONBLOCK 这样我就可以注意到客户端是否没有收到数据包

if (( bytes = recvfrom (....)) != -1 ) {
  do something
}else{
  send nack
}

我的问题是,如果服务器没有开始发送数据包,客户端的行为就像 数据包丢失并开始向服务器发送 nack。 (当没有数据可用时,recvfrom 失败)我想要一些建议,如果服务器没有开始发送数据包并且如果它发送,但数据包真的丢失了,我该如何区分这些情况

【问题讨论】:

  • 记录“数据包丢失”和“数据包尚未到达”的区别,并实施。
  • 在这两种情况下 recvfrom 都返回 -1 ,所以我不确定如何有所作为
  • 你不能怪recvfrom,它不能展望未来。也许数据包会到达,也许不会。您应该决定何时停止等待。

标签: c sockets udp


【解决方案1】:

您正在使用 UDP。对于这个协议,如果需要的话,丢弃数据包是完全可以的。因此,就“发送的将到达”而言,它是不可靠的。您在客户端中要做的是检查您需要的所有数据包是否到达,如果没有,请礼貌地与您的服务器交谈以重新发送您没有收到的数据包。实现这些东西并不容易,

如果您必须使用 UDP 来传输较大的数据块,那么请设计一个小的应用程序级协议来处理可能的数据包丢失和重新排序(这是 TCP 为您所做的一部分)。我会选择这样的:

数据报小于 MTU(加上 IP 和 UDP 报头)的大小(比如 1024 字节)以避免 IP 碎片。 每个数据报的固定长度标头包括数据长度和序列号,因此您可以将数据重新拼接在一起,并检测丢失、重复和重新排序的部分。 接收方对已成功接收和整理的内容的确认。 当这些 ack 没有在适当的时间内到达时,发送端会超时并重新传输。

您有一个循环调用 select()poll() 来确定数据是否已到达 - 如果是,则调用 recvfrom() 来读取数据。

您可以按如下方式设置接收数据的超时时间

ssize_t recv_timeout(int fd, void *buf, size_t len, int flags) {

ssize_t ret;

struct timeval tv;
fd_set rset;

// init set
FD_ZERO(&rset);
// add to set
FD_SET(fd, &rset);

 // this is set to 60 seconds
tv.tv_sec =
    config.idletimeout;
tv.tv_usec = 0; 

    // NEVER returns before the timeout value.
ret = select(fd, &rset, NULL, NULL, &tv);

if (ret == 0) {
    log_message(LOG_INFO,
                "Idle Timeout (after select)");
    return 0;
} else if (ret < 0) {
    log_message(LOG_ERR,
            "recv_timeout: select() error \"&#37;s\". Closing connection (fd:%d)",
            strerror(errno), fd);
    return;
} 

ret = recvfrom(fd, buf, len, flags);
return ret;

}

它告诉如果有数据准备好,通常, read() 应该返回您指定的最大字节数,这可能包括零字节(这实际上是一个有效的事情发生!),但是它不应该在之前报告就绪后阻塞。

在 Linux 下,select() 可能会将套接字文件描述符报告为“准备就绪” 用于阅读”,而随后的读取块。这可以 例如,当数据到达但经过检查时发生 校验和错误并被丢弃。可能还有其他情况 文件描述符被虚假报告为准备就绪。因此它可能 在不应阻塞的套接字上使用 O_NONBLOCK 会更安全。

【讨论】:

  • 亲爱的 JKB,我正在编写实时应用程序,所以我将延迟发送几秒钟以便有时间重新发送丢失的数据包,....我可以使用非阻塞套接字而不是 select 或轮询?有什么不同?谢谢
  • @user3119422 你可以使用select()的非阻塞套接字。请看我更新的帖子
【解决方案2】:

查找滑动窗口协议here

这个想法是将有效负载分成适合物理 udp 数据包的数据包,然后对它们进行编号。您可以将缓冲区可视化为一圈插槽,以某种方式按顺序编号,例如顺时针。

然后您从 12 点开始发送到 1,2,3... 在此过程中,您可能(也可能不会)从服务器接收到包含您发送的数据包的插槽号的 ACK 数据包。

如果您收到 ACK,则可以从环中删除该数据包,并将下一个未发送的数据包放置在该环中。

如果您收到您发送的数据包的 NAK,这意味着该数据包已被服务器接收到数据损坏,然后您从 NAK 中报告的环槽重新发送它。

此协议类允许通过有数据或丢包的通道进行传输(如 RS232、UDP 等)。如果您的底层数据传输协议不提供校验和,那么您需要为您发送的每个环数据包添加校验和,以便服务器可以检查其完整性,并向您报告。

来自服务器的 ACK 和 NAK 数据包也可能丢失。要处理这个问题,您需要将一个计时器与每个环槽关联,如果当计时器达到您设置的超时限制时,您没有收到一个槽的 ACK 或 NAK,那么您重新传输数据包并重置计时器。

最后,为了检测致命的连接丢失(即服务器宕机),您可以为环中的所有数据包建立一个最大超时值。要对此进行评估,您只需计算单个插槽的连续超时次数。如果这个值超过了你设置的最大值,那么你可以认为连接丢失了。

显然,此协​​议类需要根据数据包编号在双方进行数据集组装,因为数据包可能不会按顺序发送或接收。 “环”对此有所帮助,因为只有在成功传输后才会删除数据包,而在接收端,只有当先前的数据包编号已被删除并附加到不断增长的数据集时。但是,这只是一种策略,还有其他策略。

希望有帮助。

【讨论】:

  • 亲爱的DNT,我不确定这个算法是否太复杂和效率低下,因为我的应用程序是实时的,它必须非常简单,我想用nack来避免拥塞,但主要问题将是计时器我将延迟发送几秒钟,以便有时间重新发送丢失的数据包并订购它们......而且我对环形缓冲区不太了解,我想使用哈希表并查找数据包通过他们的ID
  • 环形缓冲区只是数据缓冲区的循环列表。例如,一个 std::vector mydata(number_of_buffers);然后你使用 n = (n+1) % number_of_buffers;以循环方式处理它。我的描述只是对滑动窗口的一个总结,实现起来还是比较容易的。实时也是相对的:由于您需要确保正确接收数据包,因此您至少需要重传、超时和数据集组装以避免停止等待的情况。
猜你喜欢
  • 2014-12-12
  • 2012-08-05
  • 2012-02-03
  • 1970-01-01
  • 2014-01-09
  • 2015-10-24
  • 2013-04-21
  • 2011-09-30
  • 1970-01-01
相关资源
最近更新 更多