news 2026/10/1 14:35:45

VC++ UDP Demo 实战:从 Winsock 编程到丢包排查与性能优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VC++ UDP Demo 实战:从 Winsock 编程到丢包排查与性能优化

简介:这是一份面向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 Demo,VC++ 的优势是离系统 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_RCVBUF:

int 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 里的每一行错误处理都当成正式代码来写,后面能省下大量返工时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 14:35:41

CrossFormer实战:跨尺度注意力图像分类微调指南

简介&#xff1a;这份资源面向希望上手视觉Transformer的开发者与图像分类学习者&#xff0c;围绕CrossFormer这一引入跨尺度注意力机制的新型架构&#xff0c;提供从模型实现到分类任务落地的完整实战素材。压缩包共2000个文件&#xff0c;约835.34MB&#xff0c;其中1986个pn…

作者头像 李华
网站建设 2026/10/1 14:34:56

2026届毕业生实测:五大AI科研网站+TaoToken配置避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 14:34:56

微信pdf转word怎么弄?零下载免费方法,新手10秒搞定

日常办公、学习中&#xff0c;大家经常会在微信收到PDF文件。想修改文字、编辑内容、调整排版&#xff0c;却发现PDF无法直接编辑&#xff0c;重新打字又费时费力。很多人不知道&#xff0c;微信不用下载任何APP、不用电脑&#xff0c;就能直接把PDF转成可编辑的Word文档&#…

作者头像 李华
网站建设 2026/10/1 14:33:42

PDF转Word免费的软件!电脑+手机全覆盖,无水印好用不踩坑

日常办公、学习中&#xff0c;经常会遇到PDF文件无法编辑的问题&#xff0c;想要修改文字、调整表格格式&#xff0c;最便捷的方式就是把PDF转换成Word。市面上PDF转换工具五花八门&#xff0c;很多工具要么收费、要么带水印、捆绑广告&#xff0c;一不小心就踩坑。今天给大家整…

作者头像 李华