1

我目前正在编写一个 UDP 客户端/服务器(在 GNU/Linux 上)。我用于在尚未绑定到端口sendto()的套接字上发送消息。SOCK_DGRAM

send(2) 联机帮助页指出:

成功时,这些调用返回发送的字符数。出错时,返回 -1,并适当设置 errno。

但是,sendto 总是返回它的长度参数,表示成功。对于大于 65507 ( 0xFFE3) 字节的消息,它会返回Message too long错误。

对于大于 1500 字节的 MTU 的消息,服务器总是(通过recvfrom())接收正好 1500 字节的消息,只是简单地截断消息,无需另行通知。

(为什么)这种行为是有意为之的,有没有办法在出现问题时得到通知?

我目前能想到的唯一解决方法是简单地假设 MTU 为 1500 字节并且从不发送更大的数据包。

这是相关的方法:

int udp_send(uint32_t dst, uint16_t port, char *msg, unsigned len) {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0) {
            perror("Could not open socket");
            return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(dst);

    int count = sendto(sock, msg, len, 0,
            (struct sockaddr *) &addr, sizeof(addr));
    printf("bytes sent: %d\n", count);

    if(count < 0) {
            perror("Could not send message");
            return -3;
    }

    close(sock);
    return count;
}

发生所描述的不良行为的调用将是 udp_send(0x7F000001, 1337, bigbuf, 1501); 仅发送 1500 个字节时将返回 1501。

4

4 回答 4

3

事实证明,正如 nos 在他的评论中所建议的那样,我的发送代码实际上非常好;我的接收代码有一个简单的错误:在某些时候,我将它的缓冲区定义为只有 1500 字节大小,后来忘记了。

此外,我还误以为 GNU/Linux 只能将 UDP 数据包发送到接口的 MTU,这是不正确的。即使标准只保证 576 字节,至少在我的情况下,它似乎可以发送(和接收)高达 65507 字节的 UDP 数据包,之后调用Message too long返回错误。sendto()UDP 数据包由内核的 IP 层自动分段,并由接收器重新组装——我不认为 UDP 可以使用这种高级技术。

于 2012-09-01T15:07:00.837 回答
2

UDP不是面向连接的,你永远不能说两个数据包属于彼此,你可以用getsockopt()它来确定MTU并保持在它之下,手动准备数据包并使用适当的偏移量。

原始套接字的 udp 数据包碎片对此有一些解释。

另一方面,您可以尝试使用 MTU 发现。基本上,在发送大于 MTU 的 UDP 数据包时,如果您的平台支持但并非所有平台都支持,则可能会发生 IP 分段。中间的一些网络设备会丢弃碎片数据包,有些会通过它们,您无法保证在交付时重新组装。

这是一些背景信息: http: //michael.toren.net/mirrors/sock-faq/unix-socket-faq-5.html

于 2012-08-04T15:35:17.583 回答
2

从 Linux udp(7) 手册页:

默认情况下,Linux UDP 会进行路径 MTU(最大传输单元)发现。这意味着内核将跟踪 MTU 到特定的目标 IP 地址,并在 UDP 数据包写入超过它时返回 EMSGSIZE。发生这种情况时,应用程序应减小数据包大小。也可以使用 IP_MTU_DISCOVER 套接字选项或 /proc/sys/net/ipv4/ip_no_pmtu_disc 文件关闭路径 MTU 发现;有关详细信息,请参见 ip(7)。关闭时,UDP 将对超出接口 MTU 的传出 UDP 数据包进行分段。但是,出于性能和可靠性的原因,不建议禁用它。

因此,这意味着任何sendto大小大于 MTU(对您来说约为 1500 字节?)都应该返回EMSGSIZE. 如果这不是您所看到的,我完全不确定发生了什么。

于 2012-08-04T18:59:39.103 回答
0

(为什么)这种行为是有意为之的,有没有办法在出现问题时得到通知?

--> UDP就是这样(无连接、不可靠、数据报协议),使用UDP传输数据时需要检查每一个错误

我目前能想到的唯一解决方法是简单地假设 MTU 为 1500 字节

--> 在我们这个时代,99% 的流量是通过以太网发送的,而在以太网上,MTU 是 1500 字节

发生所描述的不良行为的调用将是 udp_send(0x7F000001, 1337, bigbuf, 1501); 仅发送 1500 个字节时将返回 1501。

--> 默认情况下,Linux UDP 会进行路径 MTU 发现。这意味着内核将跟踪 MTU 到特定的目标 IP 地址,并在 UDP 数据包写入超过它时返回 EMSGSIZE。

这是 krenel 内部的默认行为,您可以修改它并获得分段数据包(对于 performanes,这是一个坏主意)。更多信息在这里:http ://www.kernel.org/doc/man-pages/online/pages/man7/udp.7.html

于 2012-08-04T19:04:47.257 回答