简介:这份资源面向Windows平台下学习C++网络编程的开发者,聚焦MFC框架中基于TCP协议的短连接通信实现,适合已具备一定C++与MFC基础、希望掌握客户端-服务器通信机制的中级学习者。压缩包共80个文件,约6.46MB,以h头文件、cpp源文件、obj编译中间文件、pdb调试符号、vcproj工程文件及exe可执行程序为主,同时包含rc资源脚本、ico图标与ReadMe说明,完整保留了Sever服务端与Client客户端两套工程,可直接编译运行并对照调试。资源围绕CSocket与CAsyncSocket类展开,涵盖创建套接字、绑定监听、Connect发起连接、OnAccept处理请求、Send/Receive收发数据以及Close释放连接等短连接核心环节,并配有示例代码与教程文档,便于读者理解三次握手、连接建立与断开流程。目前已有238人学习下载,适合作为MFC网络通信入门与短连接项目实践的参考材料。
1. 拆开 TCP.rar:一份 MFC 短连接通信的完整双端工程
手上这份TCP.rar不是零散代码片段,而是一套能直接编译运行的 MFC TCP 网络通信双端工程。解压后你会看到Sever(服务端)和Client(客户端)两个独立目录,各自带.dsw、.sln、.vcproj工程文件,说明它同时兼容 VC6 和较新的 Visual Studio 版本。服务端核心是SockL.cpp/h,客户端核心是SockC.cpp/h,配合SeverDlg.cpp、ClientDlg.cpp两个对话框类,构成典型的 MFC 对话框程序结构。它解决的是 Windows 桌面环境下用 C++ 快速搭一套 TCP 短连接通信骨架的问题——每次请求建立连接、收发数据、立即关闭,适合指令下发、状态查询这类一次性交互场景。如果你正在做设备控制、工控上位机或者课程设计,这份工程能省掉从零封装 Socket 的时间。
2. 短连接在 MFC 里的落点:CSocket 封装与三次握手时序
2.1 为什么这套工程选 CSocket 而不是原生 Winsock
MFC 对网络通信的封装有两层:底层是CAsyncSocket,直接映射 Windows 消息机制;上层是CSocket,在CAsyncSocket基础上做了阻塞式同步化处理,内部靠消息泵驱动。这份工程的服务端SockL和客户端SockC都继承自CSocket,原因很实际——短连接场景下,每次通信的数据量小、交互轮次少,用同步阻塞模型写起来最直观,不需要自己维护状态机。
原生 Winsock 的socket()、bind()、listen()、accept()这套 API 当然能用,但放到 MFC 对话框程序里,你得自己处理消息循环和线程同步。CSocket把这些包了一层,OnAccept、OnReceive、OnClose这些虚函数直接对应网络事件,代码结构清晰得多。常见做法是:服务端在OnAccept里Accept出新连接,客户端在Connect返回后直接Send,收完就Close。
2.2 服务端初始化:绑定、监听与 OnAccept 的触发链
服务端启动流程集中在SeverDlg::OnInitDialog或一个独立的初始化函数里。典型写法如下:
// SeverDlg.cpp 片段:服务端启动监听 BOOL CSeverDlg::InitServer() { // 创建监听套接字,SockL 继承自 CSocket if (!m_serverSock.Create(m_nPort)) // m_nPort 默认 6000 { AfxMessageBox(_T("创建套接字失败")); return FALSE; } // 开始监听,第二个参数是等待队列长度 if (!m_serverSock.Listen(5)) { AfxMessageBox(_T("监听失败")); return FALSE; } return TRUE; } // SockL.cpp:有新连接进来时触发 void CSockL::OnAccept(int nErrorCode) { CSocket::OnAccept(nErrorCode); // 把新连接交给一个临时 CSocket 对象处理 if (m_pDlg->m_serverSock.Accept(m_clientSock)) { // 短连接:收完数据就关,不保持长连接 m_clientSock.Close(); } }Create的参数是端口号,Listen的5是 backlog,表示内核允许排队的未完成连接数。OnAccept里调Accept会填充一个新的CSocket对象,这个对象代表与特定客户端的连接。短连接的关键就在最后那行Close()——数据交互完成后立刻释放,不留给后续请求复用。
2.3 客户端连接与数据收发:Connect 到 Close 的完整闭环
客户端侧SockC的调用顺序更简单:Create→Connect→Send→Receive→Close。注意Connect在CSocket里是阻塞的,如果目标地址不可达,会卡住直到超时。工程里通常会在调用前设置超时或者放到工作线程里。
// ClientDlg.cpp 片段:发起一次短连接请求 void CClientDlg::OnBtnSend() { CSockC sock; // 创建套接字,不指定端口则用系统分配 if (!sock.Create()) { AfxMessageBox(_T("创建失败")); return; } // 连接服务端,IP 和端口从界面控件读取 if (!sock.Connect(m_strIP, m_nPort)) { AfxMessageBox(_T("连接失败")); sock.Close(); return; } // 发送数据 CString strSend = _T("HELLO"); sock.Send(strSend, strSend.GetLength() * sizeof(TCHAR)); // 接收回包,缓冲区大小按实际协议定 char buf[512] = {0}; int nRecv = sock.Receive(buf, sizeof(buf) - 1); if (nRecv > 0) { buf[nRecv] = '\0'; m_strRecv = buf; UpdateData(FALSE); } // 短连接核心:用完即关 sock.Close(); }Send的第二个参数是字节数,Receive返回实际收到的字节数,-1表示出错。短连接模式下,每次按钮点击都走一遍完整流程,连接建立和断开的开销被限制在单次交互内。如果服务端和客户端在同一台机器上测试,IP 填127.0.0.1即可。
2.4 短连接与长连接的选型边界
短连接不是万能方案。它的优势在于服务端不需要维护连接状态表,每个请求独立处理,客户端崩溃不会拖垮服务端。代价是每次通信都要经历三次握手和四次挥手,延迟比长连接高一个量级。常见做法是:指令下发、参数查询、单次文件传输用短连接;实时数据推送、心跳保活、大文件分片用长连接。这份工程选短连接,说明它的目标场景是低频、一次性的控制类通信,比如工控设备的状态轮询。
3. 编译与运行:从 .dsw 到可执行文件的环境配置
3.1 工程文件版本差异与打开方式
Sever和Client目录下同时存在.dsw和.sln,前者是 VC6 的工程文件,后者是 VS2005 及以后版本的解决方案文件。如果你用 VS2010 以上版本打开,直接双击.sln;如果只有 VC6,双击.dsw。注意.vcproj.PC-200912312313.Administrator.user这类文件是用户特定配置,换机器后可能路径失效,删掉让 IDE 重新生成即可。
打开后先检查字符集设置。MFC 工程默认可能是多字节字符集,而Send/Receive里如果混用CString和char,会出现编码问题。在项目属性 → 配置属性 → 常规 → 字符集中,统一设为“使用多字节字符集”或“使用 Unicode 字符集”,两端保持一致。
3.2 编译报错的常见来源
编译时最容易撞上的错误是f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\dumpcont.cpp(23) : atltracegeneral这类 MFC 内部断言。这通常不是代码问题,而是运行库链接方式不匹配。检查项目属性 → C/C++ → 代码生成 → 运行时库,Debug 配置用/MDd,Release 用/MD,不要混用/MT。
另一个高频问题是error: listen tcp 127.0.0.1:6000: bind: only one usage of each socket address,意思是端口被占用。服务端启动前先用netstat -ano | findstr 6000查一下,如果已有进程监听,换个端口或者结束占用进程。
3.3 双端联调步骤
先启动服务端,确认界面显示监听成功;再启动客户端,填入服务端 IP 和端口,点击发送。如果客户端报连接失败,按以下顺序排查:服务端防火墙是否放行该端口、IP 是否填错、服务端是否真的在监听。Windows 防火墙默认会拦截入站连接,测试阶段可以临时关闭或者添加例外规则。
提示:如果服务端和客户端在同一台机器上跑,用
127.0.0.1可以绕过防火墙;跨机器测试时,服务端必须用实际网卡 IP,不能用127.0.0.1。
4. 避坑与排查:短连接工程里最容易翻车的五个点
4.1 现象:客户端 Send 成功但服务端收不到数据
原因:CSocket的Send返回成功只代表数据写入了发送缓冲区,不代表对端已收到。短连接场景下,如果客户端Send后立刻Close,而服务端还没来得及Receive,TCP 的FIN可能先于数据到达,导致服务端读到空。
解决:客户端Send之后加一个短延时或者等待服务端确认再Close。更稳妥的做法是让服务端先回一个 ACK,客户端收到后再关闭。工程里如果没做这个握手,测试时可以在Send后加Sleep(100)验证。
4.2 现象:服务端 OnAccept 不触发
原因:CSocket的事件驱动依赖消息泵。如果服务端在OnInitDialog里用while循环等待连接,消息泵被阻塞,OnAccept永远不会被调用。
解决:不要在 UI 线程里写阻塞循环。Listen之后直接返回,让 MFC 的消息循环去驱动OnAccept。如果必须等待,把逻辑放到工作线程里,用PostMessage通知 UI。
4.3 现象:Receive 返回 -1,错误码 WSAEWOULDBLOCK
原因:CSocket默认是阻塞模式,但在某些 MFC 版本里,Receive可能因为底层CAsyncSocket的非阻塞特性返回WSAEWOULDBLOCK,表示当前没有数据可读,不是真正的错误。
解决:判断GetLastError(),如果是WSAEWOULDBLOCK,等待OnReceive回调再读,不要当成失败处理。短连接下更简单的做法是确保Receive调用时数据已经到达,比如服务端收到请求后立刻回包。
4.4 现象:端口释放慢,重启服务端报 bind 失败
原因:TCP 短连接频繁关闭后,主动关闭方会进入TIME_WAIT状态,默认持续 2MSL(约 4 分钟),期间端口不能被重新绑定。
解决:在Create之前设置SO_REUSEADDR选项。MFC 的CSocket没有直接暴露这个选项,需要调用SetSockOpt:
int nReuse = 1; m_serverSock.SetSockOpt(SO_REUSEADDR, &nReuse, sizeof(nReuse));这样即使端口处于TIME_WAIT,也能立即重新监听。
4.5 现象:中文数据乱码
原因:CString在 Unicode 配置下是宽字符,直接Send出去的是 UTF-16 字节流,而服务端按char数组解析,自然乱码。
解决:两端统一字符集,或者在发送前用WideCharToMultiByte转成 UTF-8,接收端再转回来。短连接场景下数据量小,转换开销可以忽略。
5. 进阶技巧:用 Wireshark 验证短连接的三次握手与四次挥手
调通之后,我习惯用 Wireshark 抓一次完整交互,确认短连接的行为符合预期。过滤条件写tcp.port == 6000,然后点一次客户端发送按钮,你会看到完整的SYN → SYN/ACK → ACK三次握手,接着是PSH数据包,最后是FIN → ACK → FIN → ACK四次挥手。如果只看到握手没有挥手,说明Close没被调用;如果挥手只有两次,说明有一端用了RST强制关闭。
这里有个细节值得注意:短连接下,主动关闭方通常是客户端。客户端Close后进入FIN_WAIT_1,服务端回ACK后进入CLOSE_WAIT,服务端再Close发FIN,客户端回ACK后进入TIME_WAIT。如果你在 Wireshark 里看到大量TIME_WAIT状态的连接,说明短连接频率太高,可以考虑改成连接池或者长连接。
另一个验证点是tcp三次握手的序列号变化。Wireshark 默认显示相对序列号,你可以在 TCP 协议首选项里关掉“Relative sequence numbers”,看真实的 ISN(初始序列号)。短连接每次的 ISN 都是随机生成的,这是 TCP 安全机制的一部分。
从那以后我每次交付 MFC 网络工程前,都强制用 Wireshark 抓一次完整会话,确认握手、数据传输、挥手三个阶段没有异常包。这个习惯帮我提前发现过好几次Close漏调导致的连接泄漏。希望帮到你。
本文还有配套的精品资源,点击获取