我正在一个需要将多个UDP数据包中的大数据发送到客户端的应用程序上,如何以编程方式确定UDP套接字的MTU?
我需要能够在Windows和Linux上执行此操作。
最佳答案
这是我的eventdispatcher library中的两个函数(有关更多详细信息,请参见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;
}