2

我正在开发一个应用程序,我需要将多个 UDP 数据包中的大数据发送到客户端,如何以编程方式确定我的 UDP 套接字的 MTU?

我需要能够在 Windows 和 linux 上执行此操作。

4

3 回答 3

5

您可以在 IP 标头中设置“不分片”标志。然后,您可能会收到有关所需分片的 icmp 响应。

于 2012-10-25T17:16:03.127 回答
1

这是我的eventdispatcher 库中的两个函数(有关更多详细信息,请查看udp_base.cpp)。

第一个返回实际的 MTU 大小。通常 1500 用于计算机间通信。在 locahost 上它可以更大(大约 64K)。

MTU 大小的问题在于它包含您的数据以外的数据。换句话说,您可用的大小不是 MTU。相反,您必须找出 MSS 是什么。这是下面的第二个功能。

到目前为止一切顺利,它可以在 Linux 下运行,我想 MS-Windows 可能有类似的调用,只是 API 不同。

/** \brief Retrieve the size of the MTU on that connection.
 *
 * Linux offers a ioctl() function to retrieve the MTU's size. This
 * function uses that and returns the result. If the call fails,
 * then the function returns -1.
 *
 * The function returns the MTU's size of the socket on this side.
 * If you want to communicate effectively with another system, you
 * want to also ask about the MTU on the other side of the socket.
 *
 * \note
 * MTU stands for Maximum Transmission Unit.
 *
 * \note
 * PMTUD stands for Path Maximum Transmission Unit Discovery.
 *
 * \note
 * PLPMTU stands for Packetization Layer Path Maximum Transmission Unit
 * Discovery.
 *
 * \todo
 * We need to support the possibly dynamically changing MTU size
 * that the Internet may generate (or even a LAN if you let people
 * tweak their MTU "randomly".) This is done by preventing
 * defragmentation (see IP_NODEFRAG in `man 7 ip`) and also by
 * asking for MTU size discovery (IP_MTU_DISCOVER). The size
 * discovery changes over time as devices on the MTU path (the
 * route taken by the packets) changes over time. The idea is
 * to find the smallest MTU size of the MTU path and use that
 * to send packets of that size at the most. Note that packets
 * are otherwise automatically broken in smaller chunks and
 * rebuilt on the other side, but that is not efficient if you
 * expect to lose quite a few packets. The limit for chunked
 * packets is a little under 64Kb.
 *
 * \note
 * errno is either EBADF or set by ioctl().
 *
 * \sa
 * See `man 7 netdevice`
 *
 * \return -1 if the MTU could not be retrieved, the MTU's size otherwise.
 */
int udp_base::get_mtu_size() const
{
    if(f_socket != nullptr
    && f_mtu_size == 0)
    {
        addr::addr a;
        switch(f_addrinfo->ai_family)
        {
        case AF_INET:
            a.set_ipv4(*reinterpret_cast<struct sockaddr_in *>(f_addrinfo->ai_addr));
            break;

        case AF_INET6:
            a.set_ipv6(*reinterpret_cast<struct sockaddr_in6 *>(f_addrinfo->ai_addr));
            break;

        default:
            f_mtu_size = -1;
            errno = EBADF;
            break;

        }
        if(f_mtu_size == 0)
        {
            std::string iface_name;
            addr::iface::pointer_t i(find_addr_interface(a));
            if(i != nullptr)
            {
                iface_name = i->get_name();
            }

            if(iface_name.empty())
            {
                f_mtu_size = -1;
                errno = EBADF;
            }
            else
            {
                ifreq ifr;
                memset(&ifr, 0, sizeof(ifr));
                strncpy(ifr.ifr_name, iface_name.c_str(), sizeof(ifr.ifr_name));
                if(ioctl(f_socket.get(), SIOCGIFMTU, &ifr) == 0)
                {
                    f_mtu_size = ifr.ifr_mtu;
                }
                else
                {
                    f_mtu_size = -1;
                    // errno -- defined by ioctl()
                }
            }
        }
    }

    return f_mtu_size;
}


/** \brief Determine the size of the data buffer we can use.
 *
 * This function gets the MTU of the connection (i.e. not the PMTUD
 * or PLPMTUD yet...) and subtract the space necessary for the IP and
 * UDP headers. This is called the Maximum Segment Size (MSS).
 *
 * \todo
 * If the IP address (in f_addr) is an IPv6, then we need to switch to
 * the corresponding IPv6 subtractions.
 *
 * \todo
 * Look into the the IP options because some options add to the size
 * of the IP header. It's incredible that we have to take care of that
 * on our end!
 *
 * \todo
 * For congetion control, read more as described on ietf.org:
 * https://tools.ietf.org/html/rfc8085
 *
 * \todo
 * The sizes that will always work (as long as all the components of the
 * path are working as per the UDP RFC) are (1) for IPv4, 576 bytes, and
 * (2) for IPv6, 1280 bytes. This size is called EMTU_S which stands for
 * "Effective Maximum Transmission Unit for Sending."
 *
 * \return The size of the MMU, which is the MTU minus IP and UDP headers.
 */
int udp_base::get_mss_size() const
{
    // where these structures are defined
    //
    // ether_header -- /usr/include/net/ethernet.h
    // iphdr -- /usr/include/netinet/ip.h
    // udphdr -- /usr/include/netinet/udp.h
    //
    int const mtu(get_mtu_size()
            //- sizeof(ether_header)    // WARNING: this is for IPv4 only -- this is "transparent" to the MTU (i.e. it wraps the 1,500 bytes)
            //- ETHER_CRC_LEN           // this is the CRC for the ethernet which appears at the end of the packet
            - sizeof(iphdr)             // WARNING: this is for IPv4 only
            //- ...                     // the IP protocol accepts options!
            - sizeof(udphdr)
        );

    return mtu <= 0 ? -1 : mtu;
}
于 2020-10-12T16:04:58.847 回答
0

发现最佳 MTU 的最常见方法是使用命令“ping”

ping -M do -s MTU_N www.google.com  # Linux
ping www.google.com -f -l MTU_N     # Windows

其中 MTU_N 是您要测试的 MTU。这样,您可以将 MTU_N 从 500 迭代到 1900,直到获得最佳速度。

但是最好的 MTU 将取决于几件事:

  • 您的网络设置
  • 你在VPN下吗?
  • 局域网还是无线?

因此,每次这些因素发生变化时,您都需要重新测试。

于 2020-04-21T16:07:44.447 回答