您正在使用 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 \"%s\". Closing connection (fd:%d)",
strerror(errno), fd);
return;
}
ret = recvfrom(fd, buf, len, flags);
return ret;
}
它告诉如果有数据准备好,通常, read() 应该返回您指定的最大字节数,这可能包括零字节(这实际上是一个有效的事情发生!),但是它不应该在之前报告就绪后阻塞。
在 Linux 下,select() 可能会将套接字文件描述符报告为“准备就绪”
用于阅读”,而随后的读取块。这可以
例如,当数据到达但经过检查时发生
校验和错误并被丢弃。可能还有其他情况
文件描述符被虚假报告为准备就绪。因此它可能
在不应阻塞的套接字上使用 O_NONBLOCK 会更安全。