简介:计算机网络课程设计完整报告文档,以基于UDP协议的聊天程序开发为主线,涵盖问题描述、设计原理、系统流程图与详细源码实现。报告重点解析UDP无连接传输特性、客户端/服务器通信模式及套接字编程核心步骤,包含Bind、ReceiveFrom、Sendto等关键API调用说明,适合高校网络课程设计、期末报告撰写及Socket编程初学者参考。资源为单个Word文档,共101KB,内容结构完整,章节清晰,可直接作为设计报告模板或实验思路借鉴。已有953人学习使用,对于需要完成网络编程类课程设计或快速理解UDP通信机制的学生具有实用价值。报告还涉及Visual C++ 6.0环境下面向对象程序设计思想的应用,以及数据丢失、乱序等问题应对策略,可帮助读者提升网络编程实践能力。
1. 这份UDP聊天程序课程设计:先弄清楚它到底能复用什么
拿到这份《计算机网络课程设计报告-基于UDP协议的聊天程序.doc》的人,一半是冲着课程设计范文来的,另一半是想把里面的程序真正跑起来。按着报告把代码敲进VC++ 6.0,大概率会卡在编译和两台机器的互通上——这不是报告写得差,而是它把Winsock初始化、链接库配置、运行顺序这些“默认为你会”的部分省略了。这份报告的核心价值在于搭出了一个完整的UDP通信骨架:创建SOCK_DGRAM套接字、服务器绑定6000端口、sendto/recvfrom一问一答、控制台交互。它解决的是局域网内两台主机用UDP互发文本消息的最小闭环,适合正在做计算机网络课程设计的学生,也适合想快速回顾Windows下UDP编程的开发者。
2. UDP选型与C/S模式:为什么聊天程序不用TCP、数据怎么流动
2.1 UDP和TCP协议的区别:从“可靠”与“不可靠”说起
聊天程序用UDP还是TCP,是计算机网络课程设计答辩时老师必问的问题,也是期末复习时最容易混的点。UDP(用户数据报协议)和TCP同处于传输层,但设计哲学完全相反。TCP是有连接的:先三次握手建立连接,传输过程中对每个报文确认、重传、排序,保证数据完整有序到达;UDP则无连接,报文发出去就不管了,不确认、不重传、不排序,对方收没收到、收到顺序对不对,发送方一概不知。代价是UDP更轻量,不需要维护连接状态,协议栈和实现都简单得多,内存占用也小。
这份报告里聊天程序选UDP,理由其实很朴素:课程设计的目标是理解套接字编程的基本流程,而UDP把连接管理、可靠性保证这些复杂度都去掉了,核心只剩“将数据打包成数据包,发往目的地,再接收别人发来的数据包查看内容”四步。局域网环境下丢包概率本来就低,即使丢一条消息也不会引发严重后果,这种实时交互场景恰恰是UDP的典型适用面——就像网络视频会议能容忍个别帧丢失,换取更低的时延。报告里提到UDP“从问世至今已经被使用了很多年”,今天它仍然在DNS查询、视频会议、实时游戏里大量存在,并不是被TCP淘汰的旧协议。
| 对比项 | TCP | UDP |
|---|---|---|
| 连接状态 | 面向连接,需三次握手 | 无连接,直接发数据报 |
| 可靠性 | 确认重传、按序到达 | 尽力而为,可能丢失、乱序 |
| 传输开销 | 首部20字节起,需维护连接状态 | 首部仅8字节,状态简单 |
| 报文顺序 | 保证发送与接收顺序一致 | 不保证,后发可能先到 |
| 适用场景 | 文件传输、网页、邮件 | 视频会议、实时游戏、语音 |
做选型时要记住一个反直觉的结论:UDP写起来“容易跑通”,但千万别把TCP那套“发了就一定到”的预期带进来。后面要处理的丢包、乱序、防火墙问题,全部源于UDP“不管不顾”的个性。课程设计里可以不用处理重传,但报告陈述或答辩时能把“UDP不提供可靠性保证,所以本设计面向局域网实时文本通信”这句话说清楚,就远比闷头贴代码得分高。
2.2 UDP报文头四个字段与端口绑定逻辑
报告把UDP报文头拆成四个域,每个域占2字节,总共8字节。源端口号(16位)标识发送方端口,目标端口号(16位)标识接收方端口,数据报长度(16位)是含报头和数据在内的总字节数,校验值(16位)用于检测数据在传输过程中的损坏。UDP协议正是靠端口号为不同应用保留各自的数据传输通道,让同一时刻多项应用可以同时收发。
端口号是UDP通信里最关键的概念。一台机器上同时跑很多网络应用,UDP靠目标端口把数据报分发给对应进程。服务器端必须明确占住一个端口,所以代码里要用bind绑定6000端口;客户端不需要bind,系统会在sendto时自动分配一个临时端口。也就是说,“服务器先bind、客户端后发送”是UDP编程的基本节奏。报告里作者记录的调试困惑是“不知道必须先运行客户端程序,一直出错”,我理解他真正遇到的现象是:先运行服务器端程序时,控制台窗口一直停在那里没有动静,误以为程序卡死。实际上服务器端在recvfrom处阻塞等待本身就是正常行为,正确做法是先启动服务器完成bind,再启动客户端发消息。
数据报长度这个字段值得多提一句。理论上UDP数据报最大65535字节,但报告也写了“实际应用往往会限制数据包的大小,有时会降低到8192字节”。原因是以太网帧的MTU通常是1500字节,超过这个大小IP层就要分片,分片再多一点,只要有一个分片丢失,整个数据报就废了。聊天程序里把收发缓冲区设成100字节、临时缓冲区设成200字节,正是为了把数据报压在一个分片内,避免传输层以下的分片问题。做网络实验时不要轻易把缓冲区改成几万字节,消息一大,丢包率会肉眼可见地上升,到时分不清是代码问题还是网络问题。
2.3 C/S模式的数据流:服务器的五个动作与客户端的三个动作
这份UDP聊天程序采用C/S模式,报告明确列出了双方职责。服务器端动作序列是:打开通信信道申请套接字,通知本地主机在保留端口接收请求;等待客户请求到达指定端口;收到请求后启动新进程处理,同时释放旧进程响应新请求;服务完成后关闭与客户的通信;继续等待下一个请求或直接关闭服务器进程。客户端要简单一些:打开通信信道并准备请求;向服务器发出请求报文,等待应答;收到应答或不再请求时关闭信道并终止进程。
对应到代码层面,两个程序各自形成一条调用链。服务器端是socket → bind → recvfrom/sendto循环 → closesocket;客户端是socket → sendto/recvfrom循环 → closesocket。注意客户端没有bind,它的地址信息在每次sendto时通过参数传出去。两个程序通过6000端口“对上暗号”:数据报从客户端的临时端口出发,到达服务器的6000端口;服务器回复时,借助recvfrom返回的addrClient结构体,把数据报送回客户端的临时端口。
有个容易被忽略的细节:服务器必须保存recvfrom带回来的客户端地址,否则不知道把回复发给谁。报告里的写法是定义SOCKADDR_IN addrClient变量,调用recvfrom时传入它的指针,函数返回后这个结构体里就装着对方的IP和端口。这个地址是“用一次、存一次”,每个请求都要重新获取,和TCP里连接建立后地址固定不复用是两回事。另外注意,服务器要回复的套接字始终是同一个sockSrv,UDP套接字不区分客户端连接,所有客户端的数据都从这个套接字收发,靠第5个参数len区分不同的对端。
3. 把代码搬进VC++ 6.0:服务器端与客户端的完整实现拆解
3.1 工程配置三步:头文件、动态库、控制台类型
先把环境准备好。报告指定的是Microsoft Visual C++ 6.0,新手最容易在第一步就翻车。新建一个Win32 Console Application空工程:File→New→Projects,选中Win32 Console Application,填工程名如UdpChatServer,在向导里选Empty Project,然后File→New→Files→C++ Source File,命名server.cpp。做完这三步,一个干净的控制台工程就建好了。
第一,在源文件顶部加#include <winsock2.h>。注意不要用老式的winsock.h,两者版本不同,混用时winsock2.h要放在最前面,否则可能因为windows.h先包含了旧头文件而出现一堆类型重定义报错。第二,链接ws2_32.lib。两种方式:在代码里写#pragma comment(lib, "ws2_32.lib")最省事,跟代码走不会丢;或者Project→Settings→Link→Object/library modules里手动填。我一般用第一种,换机器重新编译时不容易漏。第三,在主函数最开始调用WSAStartup,这是Windows套接字编程最容易忘的一步,不初始化直接调用socket、sendto,编译不报错,运行时会一直返回SOCKET_ERROR。
这三件事做完,后面所有的报错至少能少一半。很多课程设计卡在编译阶段,翻来覆去改代码,其实和业务逻辑一点关系都没有,纯粹是工程配置没到位。编译用F7,运行时用Ctrl+F5,确认是Debug控制台程序而不是MFC程序,免得出现一堆窗口类依赖。
3.2 服务器端完整代码:创建套接字、绑定6000端口、收发循环
下面是我按报告逻辑整理后的服务器端完整代码,在原基础上补了初始化、错误检查和结束条件处理,可以直接粘进VC++ 6.0编译运行。
// server.cpp - 基于UDP协议的聊天程序服务器端 #include <winsock2.h> #include <stdio.h> #include <string.h> #pragma comment(lib, "ws2_32.lib") int main() { // 1. 初始化Winsock库,版本2.2 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed\n"); return -1; } // 2. 创建UDP套接字,SOCK_DGRAM表示数据报式服务 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0); if (sockSrv == INVALID_SOCKET) { printf("socket failed\n"); WSACleanup(); return -1; } // 3. 绑定本机6000端口,INADDR_ANY表示接受本机所有网卡的请求 SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); if (bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) == SOCKET_ERROR) { printf("bind failed, port 6000 may be in use\n"); closesocket(sockSrv); WSACleanup(); return -1; } char recvBuf[100] = {0}; // 接收缓冲区 char sendBuf[100] = {0}; // 发送缓冲区 char tempBuf[200] = {0}; // 格式化输出缓冲区 SOCKADDR_IN addrClient; // 记录客户端地址,recvfrom回填 int len = sizeof(SOCKADDR); printf("Server started on port 6000, waiting for client...\n"); while (1) { // 4. 阻塞接收客户端消息,recvfrom返回实际接收字节数 int result = recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len); if (result > 0) { recvBuf[result] = '\0'; // 用完整字符串"q"判断退出,避免误触发 if (strcmp(recvBuf, "q") == 0) { sendto(sockSrv, "q", 2, 0, (SOCKADDR*)&addrClient, len); printf("Chat end!\n"); break; } sprintf(tempBuf, "%s say: %s", inet_ntoa(addrClient.sin_addr), recvBuf); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); printf("%s\n", tempBuf); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); } // 5. 服务器回复消息,地址用recvfrom回填的addrClient printf("Please input data:\n"); gets(sendBuf); sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrClient, len); } closesocket(sockSrv); WSACleanup(); return 0; }代码逻辑拆开看:WSAStartup初始化是前提,版本号MAKEWORD(2,2)对应Winsock 2.2,Windows 2000之后的系统都支持。socket第三个参数0在SOCK_DGRAM类型下等价于IPPROTO_UDP,指明使用UDP协议。bind把套接字和本机的6000端口绑定,htons的作用是字节序转换——Intel系列CPU是小端,网络传输用大端,端口号不转换就全错了。htonl(INADDR_ANY)表示绑定本机所有网卡的IP,服务器有多个IP时任何一个都能访问。
recvfrom的第二个参数100是缓冲区大小,如果对方发来超过100字节的消息会被截断,课程设计场景够用,但改成更大值时,要同步把recvBuf数组和sendto里的长度参数一起改。gets读入的字符串末尾没有长度信息,sendto第四个长度参数用strlen(sendBuf)+1,加1是为了把结尾的\0也发过去,接收方据此判断消息边界。SetConsoleTextAttribute只是控制台颜色,和网络逻辑无关,但能让服务器消息显示成绿色,区分默认色的客户端提示,报告里特意做了这个细节。
3.3 客户端完整代码:不绑定、指定服务器地址、收发循环
客户端的骨架和服务器很像,但少了bind,多了指定对端地址。原报告里客户端也将地址结构体命名为addrSrv,它表示“服务器地址”,和服务器里表示“本机地址”的addrSrv是两码事,阅读代码时不要搞混。整理后代码如下。
// client.cpp - 基于UDP协议的聊天程序客户端 #include <winsock2.h> #include <stdio.h> #include <string.h> #pragma comment(lib, "ws2_32.lib") int main() { // 1. 初始化Winsock库 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed\n"); return -1; } // 2. 创建UDP套接字 SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0); if (sockClient == INVALID_SOCKET) { printf("socket failed\n"); WSACleanup(); return -1; } // 3. 设置服务器地址:IP改成服务器机器实际IP,端口保持6000 SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); char recvBuf[100] = {0}; char sendBuf[100] = {0}; char tempBuf[200] = {0}; int len = sizeof(SOCKADDR); while (1) { printf("Please input data:\n"); gets(sendBuf); // 4. 发送消息到服务器地址 sendto(sockClient, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len); // 5. 阻塞接收服务器回复 int result = recvfrom(sockClient, recvBuf, 100, 0, (SOCKADDR*)&addrSrv, &len); if (result > 0) { recvBuf[result] = '\0'; if (strcmp(recvBuf, "q") == 0) { sendto(sockClient, "q", 2, 0, (SOCKADDR*)&addrSrv, len); printf("Chat end!\n"); break; } sprintf(tempBuf, "%s say: %s", inet_ntoa(addrSrv.sin_addr), recvBuf); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); printf("%s\n", tempBuf); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); } } closesocket(sockClient); WSACleanup(); return 0; }客户端有几个点要特别说明。第一,addrSrv里的IP是服务器地址,本机测试填127.0.0.1,换到两台机器时必须改成服务器的实际局域网IP,否则消息只会绕回本机。inet_addr把点分十进制字符串转成网络字节序的32位整数,如果IP写错格式,它返回INADDR_NONE,后者等于0xFFFFFFFF,sendto会失败返回SOCKET_ERROR。第二,客户端不bind,系统自动分配一个临时端口(通常是一段高位动态端口),服务器回复时就是发到这个临时端口上。第三,sendto和recvfrom的地址参都用addrSrv,意味着客户端只和这一个地址通信,这是单点对单点的模型,多客户端并存需要服务器端在recvfrom时区分源地址,逐个回复。
这两个程序合并起来看,就是一组最朴素的UDP通信闭环。编译顺序没有依赖,先编哪个都行,但运行时服务器必须先启动。建议把server和client分别放进两个工程,生成两个exe,不要混在一个工程里,否则调试时很容易分不清当前跑的是哪一端。
3.4 运行顺序:先服务器后客户端,本机验证再换局域网
编译通过只是第一步,运行顺序错了依然收不到消息。推荐流程是:先运行server.exe,确认控制台打印出“Server started on port 6000”这一行,再运行client.exe。客户端输入hello,如果服务器窗口显示“127.0.0.1 say: hello”,说明本机收发链路已经通了。这一步走的是环回接口,也就是虚拟回路,不经过物理网卡,和防火墙、局域网设置无关,是最干净的测试环境,任何一台装了Windows的机器都能复现。
本机测试通过后,把客户端代码里的127.0.0.1换成服务器的实际IP,比如192.168.1.100,重新编译,再到两台机器上跑。先用ping确认两台机器同子网且网络互通,再启动程序。建议每次输入消息时带上编号,比如msg1、msg2,两边窗口对着看,能立刻确认哪条消息丢了、哪条乱序了。测试消息不要超过100字节,前面说过,长消息会把UDP数据报撑过分片阈值,丢包率会明显上升,到时候分不清是程序bug还是网络问题。
| 测试步骤 | 操作 | 预期结果 |
|---|---|---|
| 本机环回测试 | 服务器、客户端都在一台机器,IP填127.0.0.1 | 两边窗口均显示对方消息 |
| 局域网真机测试 | 客户端IP改成服务器实际IP | 双向收发正常,先启动服务器 |
| 退出指令测试 | 任一端输入q | 双方都打印结束提示并退出 |
4. 避坑与排查:从编译报错到两台机器收不到消息的五个问题
4.1 编译报错:socket、bind等函数全部未定义
现象:在VC++ 6.0里编译,报一堆error C2065: 'socket' : undeclared identifier,提示socket未声明。原因:没有包含winsock2.h头文件,或者包含顺序不对——windows.h里默认引用旧版winsock.h,和winsock2.h冲突时会出现类型重定义,连带socket函数解析不出来。解决:把#include <winsock2.h>放在所有头文件的最前面,同时确认工程里没有引入其他网络库头文件。再加一句#pragma comment(lib, "ws2_32.lib"),让链接器找到实现函数。
另一个隐蔽雷区是WSAStartup缺失。函数都声明了、也链接了,但运行时socket返回INVALID_SOCKET,原因就是没初始化套接字库。检查代码开头有没有WSAStartup(MAKEWORD(2,2), &wsaData),没有就补上。这是Windows套接字编程的第一道门槛,报告里没有显式写WSAStartup,但真要跑起来,这一步躲不过去。
4.2 链接失败:sendto、recvfrom等一堆unresolved external symbol
现象:编译通过,但链接时报错,形如unresolved external symbol __imp__sendto@24,后面跟着recvfrom、bind、socket,一连串“未解析的外部符号”。原因:sendto这类API不在C运行库里,而是实现在ws2_32.dll中,链接器找不到对应的导入库ws2_32.lib。解决:在源文件里加#pragma comment(lib, "ws2_32.lib"),或者在Project→Settings→Link选项卡的Object/library modules输入框填ws2_32.lib,多个库用空格分隔。这个坑经常和4.1连在一起出现:没包含头文件是编译错,没加链接库是链接错,两个问题全部解决,代码才算真正过了编译链路。
4.3 客户端收不到服务器回复:IP填成了回环地址
现象:本机测试一切正常,两台机器上运行时,客户端能发消息,服务器能看到,但客户端收不到回复。原因:最常见的是客户端addrSrv.sin_addr填了127.0.0.1没改。回环地址只在本机内部有意义,数据包根本不会出网卡,服务器在其他机器上自然收不到。解决:在客户端所在机器上执行ipconfig,查服务器机器的IP——注意是在客户端机器上查看服务器机器的IP,不能填自己机器的IP——然后把inet_addr的字符串改掉,重新编译。还有一种情况是服务器绑定用的是INADDR_ANY,客户端访问服务器机器上绑定的多个IP之一都能通,优先填和客户端同网段的那个IP,减少路由干扰。
4.4 两台机器IP正确但消息过不去:Windows防火墙拦UDP
现象:ping通、IP没错、程序都启动了,但一发送就石沉大海,服务器窗口毫无反应。原因:Windows防火墙默认阻止外部程序对UDP端口6000的访问,尤其当server.exe在另一台机器上入站监听时,入站规则会把它拦下。解决:在服务器机器上打开“Windows Defender防火墙→高级设置→入站规则→新建规则”,选择端口,协议选UDP,特定本地端口填6000,允许连接。也可以临时关闭防火墙验证问题是否出在这里,但只建议在隔离的实验网里做,毕竟关了防火墙等于把机器裸奔在网络风险里。
提示:验证端口是否真正在收数据,可以在服务器机器上执行
netstat -an | findstr 6000,看到UDP 0.0.0.0:6000的记录说明bind成功。注意UDP不像TCP那样有LISTENING状态,只有“不显示”和“显示绑定”的区别,不要拿TCP的监听状态标准去套UDP,这是计算机网络里UDP和TCP协议的区别之一。
这个坑最容易让人怀疑人生,因为两边程序看起来都在跑,消息就是飞不过去。血泪经验是先查防火墙再查IP,顺序反了会白折腾半天。有一次我调了一个多小时,最后发现是两台机器不在同一网段,网关也不通,和防火墙半毛钱关系没有。
4.5 输入以q开头的字符串程序就退出:结束条件判断太粗暴
现象:聊天聊得好好的,对方发一句“qingdao”或“qwer”,程序立刻判定聊天结束退出。原因:原始代码用的是'q' == recvBuf[0],只判断首字符是不是q,根本没比较完整内容。解决:改成字符串比较。先recvBuf[result] = '\0'确保有结束符,再用strcmp(recvBuf, "q")==0判断是否精确等于q。如果觉得q太容易误触,干脆约定用“quit”当结束指令,两端同步改判断条件。顺带一提,gets函数在VC++ 6.0下没有越界保护,输入超过99个字符同样可能让缓冲区溢出,触发一堆奇怪的运行时错误,稳妥做法是用fgets替换gets,限制读入长度,缓冲区大于等于接收数组大小。
5. 从能跑到好用:多线程改造与真机验证的进阶用法
报告里的聊天程序本质上是一问一答:一方输入、发送、等待回复;另一方接收、回复、等待输入。循环里sendto和recvfrom是串行执行的,意味着你在等待对方回复时,键盘输入是被忽略的。课程设计演示够用,但要它像个真正的聊天软件,就得把收发拆成两个线程。
// 发送线程:只管键盘输入和sendto DWORD WINAPI SendThread(LPVOID lpParam) { SOCKET s = (SOCKET)lpParam; char buf[100]; while (1) { gets(buf); sendto(s, buf, strlen(buf) + 1, 0, (SOCKADDR*)&peerAddr, sizeof(peerAddr)); } return 0; } // 接收线程:只管recvfrom和打印 DWORD WINAPI RecvThread(LPVOID lpParam) { SOCKET s = (SOCKET)lpParam; char buf[100]; int len = sizeof(SOCKADDR); while (1) { int ret = recvfrom(s, buf, 100, 0, (SOCKADDR*)&peerAddr, &len); if (ret > 0) { buf[ret] = '\0'; printf("%s\n", buf); } } return 0; }用CreateThread分别创建这两个线程后,peerAddr要定义成全局变量或作为结构体指针传给线程,否则两个线程各自持有副本,地址不同步。UDP套接字同时被两个线程读写是安全的,sendto和recvfrom分别作用于不同方向,不会互相踩踏。退出的处理比串行版本复杂,建议设一个全局标志变量endFlag,收到退出指令时置1,两个线程在循环条件里检查它,主线程WaitForSingleObject等待线程结束后再closesocket和WSACleanup,避免套接字被关掉时线程还在用。
多线程调试有个玄学问题:你永远不知道哪个线程先崩。建议先用串行版本把数据通路验收到100%没问题,再上多线程。验证方法分三步走:第一步,串行版在本机用127.0.0.1测试收发,建立基准预期;第二步,真机环境改成真实IP,用ping确认链路,再用netstat -an确认6000端口的状态;第三步,开两个控制台窗口模拟服务器和客户端,依次输入测试消息,观察两边窗口的打印顺序和数据内容是否一致。哪一步失败就退回上一步排查,不要一上来就抓多线程的bug。
我当年做这个课程设计时,就栽在启动顺序和IP地址上:先运行了客户端,消息发出去石沉大海,又以为服务器程序出了问题,折腾了一晚上才发现是服务器没bind、客户端IP还是回环地址。后来学乖了,每写一个UDP收发程序,都强制走一遍检查顺序:先确认服务器完成bind并看到启动提示,再确认客户端填的是服务器的真实IP,最后才考虑防火墙和缓冲区大小。这份课程设计报告文档里包含了完整的服务器端、客户端源代码、调试分析和报告正文,连踩坑记录一起打包了,下载后按第三章的流程走一遍,比自己从零敲省太多事,希望帮到你。
本文还有配套的精品资源,点击获取