【发布时间】:2012-08-02 12:08:56
【问题描述】:
我正在开发一个基于 LAN 的解决方案,该解决方案带有一个必须控制多个“玩家”的“服务器” 我选择的协议是 UDP,因为它很简单,我不需要连接,我的流量只包含不时的短命令,我想使用混合的广播消息进行同步,使用单个目标消息进行玩家个人命令。
多播 TCP 将是一种替代方案,但它更复杂,不完全适合该任务,并且通常不受硬件很好的支持。
不幸的是,我遇到了一个奇怪的问题:
使用“sendto”发送到特定 ip 的第一个数据报丢失。 之后短时间发送到同一 IP 的任何数据报都会被接收。 但是如果我等待一段时间(几分钟),第一个“sendto”会再次丢失。
广播数据报始终有效。 本地发送(到同一台计算机)始终有效。
我认为操作系统或路由器/交换机有一些从 IP 到 MAC 地址的转换表,在几分钟不使用时会被遗忘,不幸的是会导致数据报丢失。 我可以通过不同的路由器/交换机硬件观察到这种行为,所以我怀疑是 Windows 网络层。
我知道 UDP 从定义上讲是“不可靠的”,但我无法相信这会发展到如此地步,即使物理连接正常并且所有内容都定义明确,数据包也会丢失。那么它就真的一文不值了。
从技术上讲,我正在打开一个 UDP 套接字, 将其绑定到端口和 INADRR_ANY。 然后我使用“sendto”和“recvfrom”。 我从不进行连接——我不想这样做,因为我有几个玩家。据我所知,UDP 应该可以在没有连接的情况下工作。
我目前的解决方法是定期向所有特定玩家 ip 发送虚拟数据报 - 这解决了问题,但不知何故“不满意”
问题:有人知道这个问题吗?它从何而来?我该如何解决?
编辑:
我将其归结为以下测试程序:
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET Sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
SOCKADDR_IN Local = {0};
Local.sin_family = AF_INET;
Local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
Local.sin_port = htons(1234);
bind(Sock, (SOCKADDR*)&Local, sizeof(Local));
printf("Press any key to send...\n");
int Ret, i = 0;
char Buf[4096];
SOCKADDR_IN Remote = {0};
Remote.sin_family = AF_INET;
Remote.sin_addr.S_un.S_addr = inet_addr("192.168.1.12"); // Replace this with a valid LAN IP which is not the hosts one
Remote.sin_port = htons(1235);
while(true) {
_getch();
sprintf(Buf, "ping %d", ++i);
printf("Multiple sending \"%s\"\n", Buf);
// Ret = connect(Sock, (SOCKADDR*)&Remote, sizeof(Remote));
// if (Ret == SOCKET_ERROR) printf("Connect Error!\n", Buf);
Ret = sendto(Sock, Buf, strlen(Buf), 0, (SOCKADDR*)&Remote, sizeof(Remote));
if (Ret != strlen(Buf)) printf("Send Error!\n", Buf);
Ret = sendto(Sock, Buf, strlen(Buf), 0, (SOCKADDR*)&Remote, sizeof(Remote));
if (Ret != strlen(Buf)) printf("Send Error!\n", Buf);
Ret = sendto(Sock, Buf, strlen(Buf), 0, (SOCKADDR*)&Remote, sizeof(Remote));
if (Ret != strlen(Buf)) printf("Send Error!\n", Buf);
}
return 0;
程序打开一个 UDP 套接字,并在每次击键时连续发送 3 个数据报到特定 IP。 通过wireshark 运行该命令,观察您的UDP 流量,按一个键,稍等片刻,然后再次按一个键。 您不需要远程 IP 上的接收器,没有区别,除了您不会收到黑色标记的“不可访问”数据包。 这就是你得到的:
如您所见,第一次发送启动了对 IP 的 ARP 搜索。虽然该搜索正在等待 3 次连续发送中的前 2 次丢失。 第二次击键(在 IP 搜索完成后)正确发送了 3 条消息。 您现在可以重复发送消息,它会一直工作,直到您等待(大约一分钟,直到地址转换再次丢失)然后您将再次看到丢失。
这意味着:发送UDP消息时没有发送缓冲区,并且有ARP请求待处理!除最后一条消息外,所有消息都会丢失。 还有“sendto”在投递成功之前不会阻塞,也没有错误返回!
嗯,这让我很吃惊,也让我有点难过,因为这意味着我必须接受我目前的解决方法,或者实现一个一次只发送一条消息然后等待回复的 ACK 系统 - 这不会再简单,也意味着许多困难。
【问题讨论】:
-
您说,即使在工作网络链接的情况下,UDP 也会丢弃数据包,这是毫无价值的。我不同意;如果你在你的协议中实现 ACK,你可以让它相当可靠,但如果你想要任何一种可靠性,你确实需要这样做。
-
“多播 TCP 将是一个替代方案”。不,它不会,没有这样的东西,所以难怪它没有“得到硬件的良好支持”。你的意思是多播UDP?
-
@icktoofay:我使用 UDP 是因为它很简单。引入 ACK 会大大增加复杂性。我必须解决诸如“重新发送命令之前要等待多长时间”之类的问题,最后玩家也遇到了同样的问题,那么如果发送了命令但没有 ACK 怎么办 - 服务器会重复命令,这会导致问题。
-
@OleDittmann:是的,添加 ACK 确实会增加复杂性。但是,如果您想要可靠性,您需要在其之上分层您自己的机制(如 ACK)来提供它。期望 UDP,一个 不可靠 协议,这样做是不合理的。正如您所说,UDP 并非“毫无价值”;它只是需要更多的工作,但它确实需要这项工作。
-
@OleDittmann:如果您不想在 UDP 之上分层自己的可靠性机制,但需要可靠性,则不能使用 UDP。对不起。
标签: c sockets windows-7 network-programming udp