为何调用发送1个UDP包时程序实际多发?如何控制发包数量?
UDP发包异常:单次调用send_to却多发数据包的问题及解决思路
问题描述
我编写的简单UDP程序本应只发送1个UDP包,但WireShark显示有3个相同的数据包突发发送,接收端仅收到2个。
需要解决两个问题:
- 如何让它只发送1个?应用程序理应能控制发包数量,并非所有程序都会出现这种情况,我忽略了什么?
- 这种特性在某些场景下可能有用:有没有办法让它主动发送2个或4个?
测试代码
// // How many messages does this send out? // // g++ -std=c++20 howmany.cpp -o howmany.exe -lws2_32 // // nc -u -p 15000 -l // #include <iostream> #include <boost/asio.hpp> using namespace std; namespace asio = boost::asio; using udp = asio::ip::udp; static constexpr string localBindAddr{"10.0.0.53"}; void HowManyMsgs() { asio::io_context ioCtxt; udp::endpoint localEP{ asio::ip::address::from_string(localBindAddr), 15000}; udp::socket skt{ioCtxt, localEP}; //udp_socket_await skt{ioCtxt, localEP}; skt.set_option(udp::socket::reuse_address(true)); //skt.set_option(asio::ip::unicast::hops(7)); // TTL udp::endpoint dest{ asio::ip::address::from_string("10.0.0.140"), 15000}; string hello{"Hello there destination.\n"}; size_t nSent = skt.send_to(asio::const_buffer{hello.data(), hello.size()}, dest); cout << "Sent once: " << nSent << " bytes to " << dest; sleep(1); } int main() { HowManyMsgs(); return 0; }
抓包与接收结果
Wireshark抓包
No. Time Source Destination Protocol Length Info 65 1.537746 10.0.0.53 10.0.0.140 UDP 67 15000 → 15000 Len=25 66 1.537755 10.0.0.53 10.0.0.140 UDP 67 15000 → 15000 Len=25 67 1.537772 10.0.0.53 10.0.0.140 UDP 67 15000 → 15000 Len=25
接收端(nc命令)输出
# nc -u -p 15000 -l Hello there destination. Hello there destination.
运行环境
基于Win32的boost::asio(msys2.org /mingw64)
更新:问题根源定位
这似乎是VMWare Workstation网络栈导致的异常现象。测试场景:
- 在VM宿主机运行程序,通过桥接网络向本机VM虚拟机发送消息时,出现多发数据包;
- 关闭VM虚拟机后,向局域网内另一台机器发送,仅发送1个数据包;
- 重新开启VM虚拟机后,向局域网内其他机器发送时会出现4个相同的数据包。
可见VMWare网络适配器是关键因素,该问题属于适配器配置范畴。
问题分析与解决思路
多发数据包的原因
你的代码逻辑完全正确,单次调用send_to只会触发一次系统调用,发送一个UDP包。多发是VMWare虚拟网络适配器的特殊转发机制导致的,当宿主机与虚拟机通过桥接网络通信时,虚拟网络栈可能会复制数据包进行内部转发,从而出现重复发包的情况。确保只发送1个数据包的方法
- 切换网络环境:在物理机之间通信,或使用VMWare的NAT网络模式替代桥接模式;
- 调整VMWare网络配置:打开VMWare虚拟网络编辑器,重置虚拟网络设置,或关闭可能导致数据包复制的优化选项;
- 无需修改代码:代码本身没有问题,问题出在底层网络栈。
主动控制发送指定数量的数据包
如果需要发送2个、4个等指定数量的UDP包,直接在代码中多次调用send_to即可,这是可靠且可控的方式,示例代码如下:void HowManyMsgs() { asio::io_context ioCtxt; udp::endpoint localEP{ asio::ip::address::from_string(localBindAddr), 15000}; udp::socket skt{ioCtxt, localEP}; skt.set_option(udp::socket::reuse_address(true)); udp::endpoint dest{ asio::ip::address::from_string("10.0.0.140"), 15000}; string hello{"Hello there destination.\n"}; // 主动发送2个数据包 const int sendCount = 2; for (int i = 0; i < sendCount; ++i) { size_t nSent = skt.send_to(asio::const_buffer{hello.data(), hello.size()}, dest); cout << "Sent " << (i+1) << ": " << nSent << " bytes to " << dest << endl; // 可选:添加微延迟避免数据包被网络栈合并 std::this_thread::sleep_for(std::chrono::milliseconds(10)); } sleep(1); }不要依赖底层网络栈的异常多发行为,这种行为不可控且受环境影响极大。
内容的提问来源于stack exchange,提问作者JimB
相关产品推荐
相关产品推荐

