VC++ UDP Demo 实战:从 Winsock 编程到丢包排查与性能优化
发布时间:2026/10/1 14:35:49来源:尧图网络
简介这是一份面向VC初学者与网络编程入门者的UDP通信演示工程围绕Windows平台Winsock套接字展开帮助读者理解无连接传输协议的基本用法。资源以客户端与服务器双端示例为核心覆盖WSAStartup初始化、socket创建、sockaddr_in地址结构、bind绑定、sendto发送与recvfrom接收等关键环节并涉及错误码处理与缓冲区管理等常见问题。压缩包共15个文件约11KB包含6个cpp源文件、3个头文件、3个dsp工程文件、2个dsw工作区文件及1个positions文件分别承担代码实现、声明定义与工程配置等职责可直接用VC打开编译运行。目前已有131人学习下载适合希望快速跑通UDP收发流程、对照双端代码理解通信机制的开发者参考也可作为课程实验或小型项目的起步模板。1. 从一份 VC UDP Demo 说起为什么它至今仍是网络编程的必修课很多人第一次接触网络编程都是从一份 VC 的 UDP Demo 开始的。标题里的UDP.rar_DEMO_UDP_VC UDP_udp demo_vc UDP说白了就是一份在 Visual C 环境下编译运行的 UDP 通信示例工程通常包含一个发送端和一个接收端用 Winsock 接口把数据报从一个进程扔到另一个进程。它解决的问题很具体让你在 Windows 上亲眼看到无连接通信到底长什么样而不是停留在「TCP 可靠、UDP 快」这种口头结论上。适合谁适合刚学完 C 语法、想碰一碰 socket 的在校生也适合工作几年但一直调 RESTful API、没直接摸过传输层的数据链路工程师。这份 Demo 的价值不在于代码多复杂而在于它把 bind、sendto、recvfrom 这几个关键动作串成了一条能跑通的链路让你在调试器里看到数据真的从一块网卡飞到了另一块网卡。2. 把 UDP Demo 跑起来从工程配置到第一个数据报2.1 为什么选 VC 而不是别的语言写 UDP Demo在 Windows 平台上写 UDP DemoVC 的优势是离系统 API 最近。C# 的UdpClient封装太厚Python 的socket模块跨平台但看不到 Winsock 的初始化细节而 VC 直接调WSAStartup、socket、bind、sendto、recvfrom每一步的返回值都能拿到出错时WSAGetLastError给出的错误码足够定位问题。常见做法是建一个 Win32 控制台工程链接Ws2_32.lib然后写两个独立的可执行文件一个负责发一个负责收。这样调试时能分别下断点观察发送缓冲区和接收缓冲区的状态。选 VC 还有一个现实原因很多工控现场的上位机就是 MFC 或纯 Win32 程序UDP 用来和 PLC、传感器、板卡通信用 VC 写 Demo 能直接移植进正式工程不用再折腾语言绑定。2.2 最小可运行工程两个 cpp 文件加一条链接指令先建一个空的控制台项目添加udp_sender.cpp和udp_receiver.cpp分别编译成两个 exe。接收端先跑绑定端口发送端后跑往目标地址发字符串。下面是接收端的核心代码// udp_receiver.cpp #include winsock2.h #include ws2tcpip.h #include cstdio #pragma comment(lib, Ws2_32.lib) // 让链接器自动找到 Winsock 库 int main() { WSADATA wsaData; // 请求 Winsock 2.2 版本MAKEWORD 把主次版本号打包成一个 WORD if (WSAStartup(MAKEWORD(2, 2), wsaData) ! 0) { printf(WSAStartup failed: %d\n, WSAGetLastError()); return 1; } SOCKET recvSock socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (recvSock INVALID_SOCKET) { printf(socket failed: %d\n, WSAGetLastError()); WSACleanup(); return 1; } sockaddr_in bindAddr{}; bindAddr.sin_family AF_INET; bindAddr.sin_port htons(8888); // 监听 8888 端口htons 转网络字节序 bindAddr.sin_addr.s_addr INADDR_ANY; // 绑定本机所有网卡地址 if (bind(recvSock, (sockaddr*)bindAddr, sizeof(bindAddr)) SOCKET_ERROR) { printf(bind failed: %d\n, WSAGetLastError()); closesocket(recvSock); WSACleanup(); return 1; } char buf[1024]; sockaddr_in fromAddr{}; int fromLen sizeof(fromAddr); while (true) { // recvfrom 会阻塞直到有数据报到达或出错 int ret recvfrom(recvSock, buf, sizeof(buf) - 1, 0, (sockaddr*)fromAddr, fromLen); if (ret SOCKET_ERROR) { printf(recvfrom failed: %d\n, WSAGetLastError()); break; } buf[ret] \0; char ipStr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, fromAddr.sin_addr, ipStr, sizeof(ipStr)); printf(recv from %s:%d - %s\n, ipStr, ntohs(fromAddr.sin_port), buf); } closesocket(recvSock); WSACleanup(); return 0; }发送端更简单不需要 bind直接 sendto// udp_sender.cpp #include winsock2.h #include ws2tcpip.h #include cstdio #pragma comment(lib, Ws2_32.lib) int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), wsaData); SOCKET sendSock socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); sockaddr_in destAddr{}; destAddr.sin_family AF_INET; destAddr.sin_port htons(8888); inet_pton(AF_INET, 127.0.0.1, destAddr.sin_addr); // 本机回环测试 const char* msg hello udp demo; int sent sendto(sendSock, msg, (int)strlen(msg), 0, (sockaddr*)destAddr, sizeof(destAddr)); if (sent SOCKET_ERROR) { printf(sendto failed: %d\n, WSAGetLastError()); } else { printf(sent %d bytes\n, sent); } closesocket(sendSock); WSACleanup(); return 0; }逻辑说明接收端先WSAStartup初始化 Winsock 环境再创建SOCK_DGRAM类型的套接字bind到 8888 端口后进入阻塞循环每次recvfrom拿到数据的同时也拿到发送方的地址和端口。发送端不需要bind系统会自动分配一个临时端口sendto指定目标地址即可。参数上htons和ntohs负责主机字节序与网络字节序的转换端口号必须转IP 地址用inet_pton和inet_ntop处理。INADDR_ANY表示绑定所有本地接口如果只想监听特定网卡换成对应 IP 的inet_pton结果即可。2.3 编译参数与运行顺序两个容易忽略的细节编译时字符集选「使用多字节字符集」还是「Unicode」会影响inet_ntop的调用方式Demo 里用多字节更省事。运行顺序必须是先收后发否则发送端发出的数据报会因为目标端口没有绑定而被系统丢弃发送端却返回成功这是 UDP 无连接特性带来的第一个「玄学」现象。另外如果接收端和发送端在同一台机器上测试目标 IP 写127.0.0.1走回环不经过物理网卡能排除防火墙和交换机的影响。确认回环通了之后再把 IP 换成另一台机器的实际地址这时才需要检查 Windows 防火墙的入站规则。3. UDP 协议栈在 Demo 背后的真实行为从数据报分片到缓冲区3.1 数据报边界与 IP 分片为什么 recvfrom 一次只收一个包UDP 是面向数据报的协议发送端一次sendto对应接收端一次recvfrom边界不会像 TCP 那样被合并或拆分。但这里有个隐藏层IP 协议。当 UDP 数据报长度超过链路 MTU以太网通常是 1500 字节时IP 层会把它分成多个分片接收端 IP 层再重组然后才交给 UDP。这意味着如果发送 3000 字节接收端recvfrom的缓冲区必须至少 3000 字节否则多余部分会被截断丢弃而且recvfrom返回的字节数会小于实际发送量。常见做法是把单次发送控制在 1400 字节以内避开分片减少丢包重组的概率。如果业务必须发大包就要在应用层自己做分包和组包给每个包加序号和总包数接收端按序号拼装。3.2 接收缓冲区与丢包UDP 没有后悔药UDP 套接字有一个接收缓冲区默认大小在 Windows 上通常是 8KB 左右。如果发送端发得太快接收端应用层来不及recvfrom缓冲区满了之后新到的数据报会被直接丢弃发送端完全不知道。这就是为什么用 UDP 做视频流或高频采集时必须调大SO_RCVBUFint bufSize 1024 * 1024; // 1MB setsockopt(recvSock, SOL_SOCKET, SO_RCVBUF, (const char*)bufSize, sizeof(bufSize));参数说明SO_RCVBUF设置接收缓冲区大小实际生效值可能被系统上限截断可以用getsockopt回读确认。调大缓冲区只能缓解突发流量不能根治丢包。真正的丢包排查要看网卡统计、交换机端口计数和应用层收包速率。如果接收端处理一条数据要 10ms发送端每 1ms 发一条那缓冲区再大也会溢出这时候要么加多线程消费要么在协议层加反馈让发送端降速。3.3 用 iperf3 打 UDP 流验证链路真实带宽Demo 跑通之后下一步是确认链路能承受多大的 UDP 流量。iperf3 支持 UDP 模式命令如下# 服务端 iperf3 -s # 客户端UDP 模式带宽 100M时长 30 秒 iperf3 -c 192.168.1.100 -u -b 100M -t 30输出里重点看丢包率和抖动。如果丢包率超过 1%说明链路或接收端处理能力有瓶颈。把-b逐步调大找到丢包率开始上升的拐点这个值就是当前环境下 UDP 的实际上限。注意 iperf3 的 UDP 测试结果里发送端统计的是「发出多少」接收端统计的是「收到多少」两端都要看。这个方法和 VC Demo 是互补的Demo 验证代码逻辑iperf3 验证网络承载能力。4. 避坑与排查VC UDP Demo 最常见的五个翻车现场4.1 bind 返回 10048端口已被占用现象接收端启动时bind失败WSAGetLastError返回 10048。原因8888 端口已经被另一个进程占用可能是上一次调试的接收端没退干净也可能是别的软件在用。解决用netstat -ano | findstr 8888找到占用进程的 PID在任务管理器里结束它或者把 Demo 的端口改成 8889 再试。调试阶段建议把端口号写成命令行参数避免每次改代码重新编译。4.2 sendto 返回成功但接收端没收到现象发送端打印sent 17 bytes接收端毫无反应。原因目标 IP 或端口写错或者接收端根本没启动。UDP 的sendto成功只代表数据报交给了本机 IP 层不代表对方收到了。解决先用127.0.0.1在本机验证确认收发逻辑没问题再检查目标机器的防火墙是否放行了对应端口最后用 Wireshark 在发送端抓包看数据报是否真的离开了网卡。4.3 recvfrom 返回 10054连接被重置现象接收端循环里突然recvfrom返回 SOCKET_ERROR错误码 10054。原因之前收到的某个数据报的发送方已经关闭了套接字接收端尝试回复 ICMP 端口不可达Winsock 把这个 ICMP 错误映射成了 10054。解决在recvfrom出错时不要直接退出循环判断错误码如果是 10054 就继续下一轮接收。这个错误在 UDP 里很常见尤其是对端是短生命周期的测试程序时。4.4 中文乱码发送端和接收端编码不一致现象发送端发「你好」接收端打印出乱码。原因VC 源文件默认编码可能是 GBK而接收端按 UTF-8 解释或者反过来。解决统一用 UTF-8 编码保存源文件发送前把字符串转成 UTF-8 字节流接收端按 UTF-8 解码。如果工程必须用多字节字符集就在发送端和接收端约定同一种编码不要一边 GBK 一边 UTF-8。4.5 防火墙拦截本机通、跨机不通现象两台机器互相 ping 得通但 UDP Demo 跨机收发失败。原因Windows 防火墙默认阻止入站 UDP 流量。解决在「高级安全 Windows Defender 防火墙」里新建入站规则允许对应端口的 UDP 流量。测试阶段可以临时关闭防火墙验证但正式部署时必须用规则放行不要直接关防火墙。5. 从 Demo 到可用把 UDP 通信做成能进现场的工具Demo 跑通只是起点真正进现场要解决三件事心跳、重传和日志。心跳是发送端定期发一个短包接收端超过一定时间没收到就报警用来判断链路是否还活着。重传是在应用层给关键数据加序号和确认接收端收到后回一个 ACK发送端超时没收到 ACK 就重发这是把 UDP 当可靠传输用的常见做法。日志是把每次收发的字节数、时间戳、对端地址写进文件出问题时能回溯。我自己的习惯是任何 UDP 工具在交付前先用 iperf3 打满带宽跑 10 分钟观察丢包率是否稳定再用 Demo 程序连续收发 24 小时看内存和句柄有没有泄漏最后把日志按天切分保留最近 7 天。这套流程帮我提前发现过接收缓冲区溢出和端口未释放的问题比在现场被叫起来排查要轻松得多。UDP 编程没有 TCP 那么多状态机要照顾但正因为简单边界条件才更容易被忽略。把 Demo 里的每一行错误处理都当成正式代码来写后面能省下大量返工时间。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网