
简介这份资源面向Windows平台网络编程初学者与C开发者聚焦基于TCP协议的C/S架构通信实现帮助读者理解Socket套接字在MFC框架下的封装与调用方式。压缩包共4个文件包含2个cpp源文件与2个h头文件分别对应客户端连接对话框与服务器端对话框的核心实现整体约6KB体积轻量便于快速阅读与移植。资源围绕CSocket类展开涉及Winsock初始化、套接字创建、绑定监听、接受连接、连接服务器以及数据收发与关闭等关键环节可作为学习MFC网络编程的入门范例。目前已有1260人学习下载适合希望掌握TCP通信流程、理解客户端与服务器交互逻辑的开发者参考也可作为课程实验或小型项目的基础代码骨架便于在此基础上扩展业务功能。1. 从零拆一个 MFC TCP 通信程序C/S 架构到底能解决什么问题很多人第一次接触 socket 网络编程是在控制台里敲socket()、bind()、listen()那一套跑通了却不知道怎么套到 Windows 窗口程序里。这份资源给的是一个基于 socket 通信、用 MFC 实现 TCP 通信的 C/S 架构程序说白了就是把 TCP 服务端和客户端都塞进 MFC 的对话框框架里用 Windows 消息机制驱动收发而不是靠while(1)死循环硬扛。它适合两类人一类是学过 TCP 三次握手、知道 TCP 和 UDP 区别但没在 MFC 里真正落地过的学生或转岗工程师另一类是要给工控上位机、设备调试工具做通信模块的从业者需要一个能直接改、能编译、能抓包验证的骨架。C/S 架构在这里不是概念而是服务端先listen等连接、客户端connect发起握手、双方各自维护一个SOCKET句柄并靠OnReceive回调收数据的完整闭环。下面按「资源是什么 → 怎么用 → 坑在哪」的顺序拆开讲。2. MFC 与 Socket 的接法为什么不用阻塞循环而用消息驱动2.1 选型理由MFC 的 CAsyncSocket 与原生 Winsock 怎么选MFC 里做 TCP 通信有两条路。一条是直接用 Winsock 的socket、connect、send、recv另一条是用 MFC 封装好的CAsyncSocket和CSocket。这份资源走的是 MFC 封装路线原因很实际MFC 的窗口线程本身要跑消息循环如果你在 UI 线程里写一个阻塞的recv窗口立刻卡死点关闭都没反应。CAsyncSocket把 socket 句柄和窗口消息绑定底层用WSAAsyncSelect注册网络事件收到数据时投递一个自定义消息由OnReceive虚函数处理UI 线程该刷新刷新、该响应响应。常见做法是服务端用CAsyncSocket派生一个CListenSocket负责Listen和OnAccept再派生一个CClientSocket负责每个连接的OnReceive、OnClose客户端直接派生一个CClientSocket调Connect。这样每个连接是一个对象生命周期清晰不会出现句柄满天飞的情况。提示CAsyncSocket的回调是在窗口线程里执行的OnReceive里不要做耗时操作否则照样卡 UI。要处理大块数据先把数据拷到缓冲区再投递到工作线程。2.2 服务端搭建从 Listen 到 OnAccept 的完整步骤服务端核心是「监听 socket 只负责接受连接通信 socket 每个连接一个」。下面是一个可抄的骨架基于对话框程序在InitDialog里启动监听。// ListenSocket.h class CListenSocket : public CAsyncSocket { public: CListenSocket() {} virtual ~CListenSocket() {} virtual void OnAccept(int nErrorCode); }; // ListenSocket.cpp void CListenSocket::OnAccept(int nErrorCode) { if (nErrorCode 0) { // 有新连接进来创建一个通信 socket CClientSocket* pClient new CClientSocket(); // Accept 把新连接的句柄交给 pClient if (Accept(*pClient)) { // 把 pClient 存进列表方便后续广播或管理 theApp.m_clientList.AddTail(pClient); } else { delete pClient; } } CAsyncSocket::OnAccept(nErrorCode); }逻辑说明OnAccept是CAsyncSocket在监听 socket 收到连接请求时自动调用的虚函数。Accept的参数是一个空的CClientSocket对象调用成功后这个对象就持有了新连接的 socket 句柄后续它的OnReceive会被触发。参数nErrorCode为 0 表示正常非 0 要查WSAGetLastError。启动监听的代码放在对话框初始化里// 在对话框 OnInitDialog 中 if (!m_listenSocket.Create(8888, SOCK_STREAM)) { AfxMessageBox(_T(监听端口创建失败)); return FALSE; } if (!m_listenSocket.Listen(5)) { AfxMessageBox(_T(Listen 失败)); return FALSE; }Create的第二个参数SOCK_STREAM指定 TCP端口 8888 可以换成任意未占用端口。Listen(5)里的 5 是等待队列长度不是最大连接数别搞混。常见做法是设 5 到 10太大没意义太小高并发时会丢连接。2.3 客户端连接与收发Connect 和 OnReceive 的配合客户端比服务端少一个监听环节直接Create再Connect。注意Connect是异步的返回FALSE且GetLastError为WSAEWOULDBLOCK是正常的真正的连接结果在OnConnect里判断。// ClientSocket.cpp void CClientSocket::OnConnect(int nErrorCode) { if (nErrorCode 0) { // 连接成功可以发数据了 CString strMsg _T(hello server); Send(strMsg, strMsg.GetLength() * sizeof(TCHAR)); } else { AfxMessageBox(_T(连接服务端失败)); } CAsyncSocket::OnConnect(nErrorCode); } void CClientSocket::OnReceive(int nErrorCode) { if (nErrorCode 0) { char buf[4096] {0}; int nRead Receive(buf, sizeof(buf) - 1); if (nRead 0) { buf[nRead] \0; // 把收到的数据显示到界面注意跨线程问题 theApp.m_pMainDlg-AppendText(CString(buf)); } else if (nRead 0) { // 对端正常关闭 Close(); } } CAsyncSocket::OnReceive(nErrorCode); }Receive返回 0 表示对端关闭了连接返回SOCKET_ERROR要查错误码WSAEWOULDBLOCK表示当前没数据可读不是错误。发送时Send的字节数要按实际数据算发CString时用GetLength() * sizeof(TCHAR)在 Unicode 工程里TCHAR是wchar_t占 2 字节这个点后面避坑章节还会提。3. 编译环境与工程配置让 MFC 和 Winsock 真正跑起来3.1 Visual Studio 里 MFC 组件的安装与工程创建MFC 不是默认安装的。Visual Studio 安装器里要勾「使用 C 的桌面开发」右侧「安装详细信息」里再勾「适用于最新 v143 生成工具的 C MFC」。离线环境的话安装器支持下载后离线布局但这一步按官方安装器走就行不展开。创建工程时选「MFC 应用」应用程序类型选「基于对话框」项目名随意。生成后你会看到App、Dlg、stdafx或pch这几个核心文件。MFC 四大类在这里对应CWinApp是应用对象CDialogEx是主窗口CAsyncSocket是网络对象CString是字符串封装。工程属性里不需要额外链接ws2_32.lib因为 MFC 的 socket 类已经处理了但如果你混用原生 Winsock API就要在链接器输入里加ws2_32.lib。3.2 端口、IP 与防火墙三个必须确认的参数参数位置常见值说明端口号Create第一个参数88881024 以下需管理员权限建议用 8000 以上IP 地址Connect第一个参数127.0.0.1 或局域网 IP本机测试用回环跨机测试用实际网卡 IP等待队列Listen参数5不是最大连接数是未 accept 的排队上限防火墙是新手最容易翻车的地方。服务端Listen成功后如果客户端在另一台机器上连不上先关掉 Windows Defender 防火墙的对应入站规则测试确认是防火墙问题后再加规则放行端口。本机127.0.0.1测试不受防火墙影响所以先用回环验证代码逻辑再上局域网。3.3 用 TCP 调试助手做交叉验证不要只信自己的客户端和服务端。常见做法是服务端跑起来后用 TCP 调试助手网上搜「tcp调试助手1.17」这类工具当客户端连上去手动发几条数据看服务端OnReceive有没有正确解析。反过来用调试助手开一个 TCP 服务端让你的 MFC 客户端去连验证Connect和Send是否正常。这样能把「代码问题」和「环境问题」分开。调试助手发十六进制和 ASCII 都支持测粘包时特别有用。4. 避坑与排查MFC TCP 通信里最容易翻车的五件事4.1 现象客户端连不上错误码 10061原因服务端没Listen成功或者端口被占用或者防火墙拦了。10061是WSAECONNREFUSED意思是目标端口没有程序在监听。解决先在服务端机器上netstat -ano | findstr 8888看端口有没有处于LISTENING。如果没有检查Create和Listen的返回值别把AfxMessageBox弹窗当装饰。端口被占用就换一个。跨机测试先临时关防火墙确认。4.2 现象OnReceive 收到的数据少一截或乱码原因TCP 是字节流没有消息边界。你发两次对面可能一次Receive全收到也可能分两次收到。另外 Unicode 工程里CString发出去是宽字符对面按char收就会乱。解决自定义一个简单的包头比如前 4 字节写长度后面跟实际数据。收的时候先收 4 字节解析出长度再循环收够为止。或者统一用char数组和sizeof(char)发送界面显示时再转CString。粘包处理是 TCP 编程的必修课别指望一次Receive就是一条完整消息。4.3 现象关闭窗口时程序崩溃或报内存泄漏原因CClientSocket对象是new出来的连接关闭后没有delete。MFC 的CAsyncSocket析构时会关闭句柄但对象本身要你管理。解决在OnClose里把对应的 socket 对象从列表移除并delete或者用CArray、CList配合智能指针。调试时看输出窗口的「Detected memory leaks」提示定位是哪个对象没释放。4.4 现象OnReceive 里更新界面没反应或直接崩原因如果你把 socket 放到工作线程里OnReceive就在工作线程执行直接调SetWindowText或操作控件会跨线程访问 UI轻则无效重则崩溃。解决用PostMessage把数据指针投递到主窗口在主窗口的自定义消息处理函数里更新界面。PostMessage是异步的不会阻塞工作线程。数据指针要用new分配接收方负责delete。4.5 现象编译报错「无法打开包括文件 afxsock.h」原因工程没有启用 MFC或者 MFC 组件没装全。解决项目属性 → 常规 → 「使用 MFC」设为「在共享 DLL 中使用 MFC」或「在静态库中使用 MFC」。如果选项是灰的说明 MFC 组件没装回安装器勾上。头文件包含顺序也有讲究afxsock.h要放在afxwin.h之后。5. 进阶技巧把通信模块从界面里剥出来写到这儿程序能跑但代码全堆在对话框里加个功能就牵一发动全身。我一般会做一件事把 socket 通信封装成一个独立的CNetManager类对话框只负责调StartServer、ConnectServer、SendData和接收回调不直接碰CAsyncSocket。这样换界面、加协议、上多线程都不用动网络层。具体做法是定义一个回调接口// 网络事件回调接口 class INetCallback { public: virtual void OnNetRecv(const char* data, int len) 0; virtual void OnNetConnect() 0; virtual void OnNetClose() 0; }; // CNetManager 持有 CAsyncSocket 派生对象把事件转发给 callback class CNetManager { public: void SetCallback(INetCallback* cb) { m_cb cb; } BOOL StartServer(UINT port); BOOL ConnectTo(LPCTSTR ip, UINT port); int SendData(const char* data, int len); private: INetCallback* m_cb nullptr; CListenSocket m_listen; CClientSocket m_client; };对话框实现INetCallback在OnNetRecv里把数据转成CString显示。这样网络层和界面层解耦测试的时候可以写一个控制台程序实现同样的接口不用开窗口就能压测收发。验证方法上我习惯用两个指标一是连续发 10000 条 1KB 数据看有没有丢包和错序TCP 本身保证不丢不乱但你的解析逻辑可能出错二是用任务管理器看句柄数反复连接断开 100 次句柄数应该回到初始值附近如果一直涨说明 socket 对象没释放干净。注意CAsyncSocket在 MFC 里不是线程安全的一个 socket 对象只能在一个线程里用。多线程方案要么每个线程独立 socket要么用CSocket配合工作线程但CSocket的阻塞特性又和 UI 线程冲突选型时要先想清楚。从那以后我每次写 MFC 网络程序都强制先把「监听、连接、收发、关闭」四个动作的返回值检查一遍再谈界面。希望帮到你。本文还有配套的精品资源点击获取