资讯动态

MFC FTP客户端开发实战:连接、传输、进度显示与断点续传

发布时间:2026/10/26 7:54:00 来源:尧图企业网站定制
简介基于MFC框架的简单FTP客户端项目面向C初学者和网络编程入门者演示如何利用VC环境实现登录服务器、列目录、上传下载等核心功能是理解FTP协议与MFC图形界面结合的典型实例。压缩包为rar格式共28个文件约1.83MB包含4个h头文件与3个cpp源文件可查看MFC类库调用逻辑、1个exe可执行文件直接运行体验、以及rc/res/ico等界面资源文件另有obj、pdb等编译中间文件整体结构清晰便于对照学习。目前已有217人学习下载。通过该项目可掌握CInternetSession与CFtpConnection建立连接、FTP命令交互、读写文件及UI消息映射等关键知识点同时可学习后台线程避免界面卡顿的简单实现思路。适合作为课程设计参考或入门MFC网络编程的起步素材。1. 用 MFC 写 FTP 客户端老工程里绕不开的一门手艺同事丢给你一个FTP.rar解压出来是一整套用 MFC 写的 FTP 客户端工程这种事在 Windows 桌面开发里太常见了。MFC 做 FTP 客户端听起来是上个世纪的技术选型但它至今仍然大量藏在工业上位机、气象数据落地、内网文件同步和金融柜台程序里。这篇笔记要解决的就是一个问题靠 MFC 这套老框架怎么把一个能连服务器、能翻目录、能传文件、还能在状态栏显示进度的 FTP 客户端做出来。它适合正在维护老项目的人也适合被业务方要求“不要换 QT、不要引第三方库”的开发者。FTP 虽是明文协议但在受控内网里依然管用MFC 的封装也能让一个小工具在一天内落地别一听 MFC 就摇头坑是有但都有解。2. 先搞懂 MFC 的 FTP 底座WinINet 封装与连接参数MFC 自己不带 FTP 协议栈它包的是 Windows 系统里的 WinINet 库。搞清楚这层关系后续所有代码才能立住。2.1 为什么首选 CInternetSession而不是自己造 WinSock常见做法是用 MFC 的CInternetSession配合CFtpConnection干活。这两个类把 FTP 最繁琐的指令交互封装掉了连接、登录、目录切换、文件读写全部对应成 C 方法。对 mfc 教程里常见的界面程序来说这种封装的开发效率是最高的写一个能连服务器的对话框几十行代码就够。另一条路是用 WinSock 裸写协议自己拼USER、PASS、RETR这些指令去服务端握手。这么做的好处是能控制每一个字节但代价是你得自己处理被动模式、响应码解析、二进制与文本模式切换这些细节。除非你要实现官方的CFtpConnection不支持的功能比如真正的断点续传二次开发否则我不建议从零写维护成本太高。对比项CInternetSession / CFtpConnection自己用 WinSock 实现代码量少约几十行完成连接多协议解析全部自己写学习门槛低有现成文档和示例高需要懂 FTP 状态码断点续传官方封装支持有限可以完全自定义调试难度中等排错看错误码高封包都要自己抓适合场景POC、工具类、管理后台协议定制、深度对接实际做项目时我一般先用官方封装把整条链路跑通再针对瓶颈做局部替换。这比一上来就开 Socket 要稳妥得多。// 最简单的 MFC FTP 连接代码 CInternetSession session(_T(MfcFtpClient/1.0)); // 用户代理服务器日志能看到客户端标识 CFtpConnection* pConn nullptr; try { pConn session.GetFtpConnection( _T(192.168.1.100), // FTP 服务器地址IP 或域名均可 _T(ftpuser), // 登录用户名 _T(ftpPass123), // 登录密码 INTERNET_INVALID_PORT_NUMBER); // 端口号默认 21 } catch (CInternetException* pEx) { // GetFtpConnection 内部会弹错误码但异常对象要手动释放 TCHAR szErr[256] {}; pEx-GetErrorMessage(szErr, 256); AfxMessageBox(szErr); pEx-Delete(); }这段代码做三件事创建会话、尝试登录、处理登录异常。INTERNET_INVALID_PORT_NUMBER表示使用 FTP 默认端口 21如果你在内网跑的是非标端口这里直接填数字就能覆盖。GetFtpConnection是阻塞调用网络不通时最长会等满系统默认超时所以工程化代码必须把超时参数提前设好这一点后面单独说。2.2 主动模式与被动模式连接参数决定能不能通做 FTP 客户端最典型的翻车现场是在本机能连部署到客户机器上就连不上然后客户投诉软件有问题。其中八成问题出在 FTP 的主动、被动模式上。主动模式由客户端开放端口等服务器来连被动模式由服务器开放端口等客户端去连。公司内网防火墙策略不同直接决定哪一种模式能通。在 WinINet 封装里模式切换不是一个公开的SetActiveMode之类的接口常见做法是通过SetOption在会话层设置超时和重试参数模式则依赖系统网络配置。所以我在代码里会明确写清楚连接前把INTERNET_OPTION_CONNECT_TIMEOUT和INTERNET_OPTION_DATA_TIMEOUT设置到合适值避免默认等待时间过长把用户体验拖垮。这里有一个经验目标服务器如果是气象、电力这类行业系统很多部署在不对外开放的专用网络里网络策略倾向于限制外部回连被动模式更常见。做客户端时最好在界面上留一个“切换到被动模式”的开关本质是让用户去调整服务器端或出口防火墙策略而不是在代码里硬编码死一种模式。很多人把连不上归咎于密码错误实际上跑一下WireShark看握手包就知道是模式问题。// 设置会话超时参数必须在 GetFtpConnection 之前调用 CInternetSession session(_T(MfcFtpClient/1.0)); DWORD dwTimeout 5000; // 毫秒5 秒超时默认值往往太长 session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, dwTimeout, sizeof(dwTimeout)); session.SetOption(INTERNET_OPTION_DATA_TIMEOUT, dwTimeout, sizeof(dwTimeout));注意SetOption的第三个参数是缓冲区大小有些人漏传导致设置失败。超时时间我一般给 5000 到 8000 毫秒太短会在内网慢速链路里误杀正常连接太长则会让用户在服务器宕机时干瞪眼。这是做 FTP 监控类工具时必须调好的第一组参数。3. 搭一个能跑的最小 MFC FTP 客户端连接、目录、上传下载章节进入可抄作业的环节。这一章按顺序给出最小工程里最核心的代码块包含界面状态栏显示进度的做法新手可以直接照着搭。3.1 建立会话与登录异常分支要写在业务之前之前那段代码是示意放到真实对话框程序里我推荐把会话和连接对象做成对话框类的成员变量而不是在按钮事件里临时创建。原因是文件下载、目录刷新都需要复用同一个连接多次重建连接会引入额外的登录开销和超时风险。// CMfcFtpClientDlg.h 中的成员 CInternetSession* m_pSession; CFtpConnection* m_pConn; BOOL m_bConnected; // CMfcFtpClientDlg.cpp 中实现连接 BOOL CMfcFtpClientDlg::ConnectFtp(const CString strServer, const CString strUser, const CString strPass) { // 先清理旧连接防止重复点击造成句柄泄漏 if (m_pConn) { m_pConn-Close(); delete m_pConn; m_pConn nullptr; } if (m_pSession) { delete m_pSession; m_pSession nullptr; } // 设置 5 秒超时后再发起连接 m_pSession new CInternetSession(_T(MfcFtpClient/1.0)); DWORD dwTimeout 5000; m_pSession-SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, dwTimeout, sizeof(dwTimeout)); m_pSession-SetOption(INTERNET_OPTION_DATA_TIMEOUT, dwTimeout, sizeof(dwTimeout)); try { m_pConn m_pSession-GetFtpConnection(strServer, strUser, strPass); m_bConnected TRUE; return TRUE; } catch (CInternetException* pEx) { m_bConnected FALSE; DWORD dwErr pEx-m_dwError; // 错误码例如 ERROR_INTERNET_CANNOT_CONNECT TCHAR szMsg[256] {}; pEx-GetErrorMessage(szMsg, 256); AfxMessageBox(szMsg); pEx-Delete(); return FALSE; } }这段代码的两个要点一是把重复点击按钮的幂等问题提前解决最怕连一次成功、连第二次时旧对象还挂着二是异常对象必须手动DeleteMFC 的CInternetException不从堆上自动回收漏掉就是内存泄漏。错误码从m_dwError拿后面排查时对照系统头文件里的ERROR_INTERNET_*定义就能定位方向。3.2 目录遍历与文件传输CFtpFileFind 和 GetFile 最容易踩的三个坑目录列举用CFtpFileFind它和 CFileFind 的用法结构一致遍历服务器当前目录下的条目。void CMfcFtpClientDlg::RefreshFileList() { if (!m_pConn) return; m_listFile.DeleteAllItems(); // 清空 ListCtrl 旧数据 BOOL bFound TRUE; CString strFileName; CFtpFileFind finder(m_pConn); if (finder.FindFile(_T(*.*))) // 通配符 *.* 在 FTP 下等价于全部 { while (bFound) { bFound finder.FindNextFile(); strFileName finder.GetFileName(); BOOL bDir finder.IsDirectory(); int nIdx m_listFile.InsertItem(0, strFileName); m_listFile.SetItemText(nIdx, 1, bDir ? _T(目录) : _T(文件)); } } finder.Close(); }这个代码有一个隐藏坑FindNextFile()返回TRUE时GetFileName()拿到的是当前项循环退出后GetFileName()的内容已经无效所以要先取字符串再处理。另外如果服务器目录完全为空FindFile返回FALSE此时不能调用Close()否则会触发断言写法上要先判断再关。文件下载和上传用GetFile、PutFile最直接// 下载服务器文件到本地 if (!m_pConn-GetFile( _T(/data/report_2024.csv), // 服务器绝对路径建议带 / _T(D:\\tmp\\report_2024.csv), // 本地绝对路径目录必须存在 FALSE, // 不覆盖本地同名文件 INTERNET_FLAG_TRANSFER_BINARY)) // 二进制模式避免文本模式换行转换 { DWORD dwErr GetLastError(); // 失败时先看系统错误 }注意两个参数第一个是服务器端路径我建议写绝对路径因为CFtpConnection内部维护的当前目录容易在多次切换后漂移第二个是本地路径如果本地目录不存在会直接失败而且 MFC 封装不会自动创建目录。第三个参数FALSE表示不覆盖正式产品里要不要覆盖得结合业务气象服务器每天推文件就喜欢覆盖。3.3 把进度放到状态栏MFC 状态栏显示 FTP 状态的两招热词里“mfc状态栏怎么显示”问的人很多。状态栏本身是CStatusBar显示 FTP 状态主要做两件事连接中显示“正在连接服务器”下载时显示下载字节数。第一招是简单的状态文字更新// 在 MainFrame 或对话框里状态栏控件通常叫 m_wndStatusBar // SetPaneText 的第 2 个参数表示是否立即重绘 m_wndStatusBar.SetPaneText(0, _T(正在连接 FTP 服务器...)); m_wndStatusBar.SetPaneText(0, _T(已连接开始下载文件...));但这个方案的问题是GetFile是同步调用执行期间界面线程卡死状态栏文字根本不会刷新。所以必须用第二招把下载扔到工作线程线程里定时向主窗口发送自定义消息再由窗口消息处理函数更新状态栏。// 主窗口重载 OnMessage 处理线程回传的进度 afx_msg LRESULT CMfcFtpClientDlg::OnFtpProgress(WPARAM wp, LPARAM lp) { ULONGLONG nDownloaded (ULONGLONG)wp; CString strInfo; strInfo.Format(_T(已下载 %llu KB), nDownloaded / 1024); m_wndStatusBar.SetPaneText(0, strInfo); return 0; }UI 线程只负责显示数据源的获取来自后台线程。这一招是 MFC FTP 工具真正好用的地基后面的工作线程章节会展开写。4. 从“能跑”到“能干活”重连、断点续传与 FTP 监控把基础功能跑通只是第一步。落地到生产环境你会发现更多的精力要花在怎么应对断网、服务器重启、大文件传输中断这些日常事故上。4.1 重连与心跳用 SetTimer 实现 FTP 监控FTP 监控的需求很常见气象服务器每天定时落数据客户端要盯着远端文件是否更新一旦有新文件就拉取。MFC 里最简洁的方案是SetTimer驱动一个空闲时检查逻辑。// 在对话框 OnInitDialog 中启动定时器每 30 秒检查一次 SetTimer(1, 30000, nullptr); void CMfcFtpClientDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent 1) { if (!m_bConnected) // 连接断开尝试重连 { ConnectFtp(m_strServer, m_strUser, m_strPass); return; } // 连接还在但服务器可能已假死最好主动探活 BOOL bAlive FALSE; CString strCurDir; if (m_pConn) { try { bAlive m_pConn-GetCurrentDirectory(strCurDir); } catch (CInternetException* pEx) { bAlive FALSE; pEx-Delete(); } } if (!bAlive) // 探活失败重建连接 { m_bConnected FALSE; ConnectFtp(m_strServer, m_strUser, m_strPass); } else { // 正常状态下再检查有没有新文件 CheckNewFiles(); } } CDialogEx::OnTimer(nIDEvent); }探活我选择GetCurrentDirectory而不是CheckConnection之类的方法因为它会真正走一次 FTP 链路能发现服务器假死。定时器间隔不能低于 30 秒太频繁会给服务器造成压力做气象数据同步这种低频场景也可以把间隔调成 60 秒。注意重连逻辑里要先置m_bConnected FALSE否则重连失败后定时器还会反复触发造成异常死循环。4.2 断点续传的边界原生 FTP 命令快要亲手写最让新人迷糊的一点是MFC 的CFtpConnection官方封装不提供断点续传接口。GetFile失败后重来是从头开始下载。要续传得靠 FTP 协议里的REST重启下载位置和APPE追加写两条原始命令。常见做法是自己封装一个底层 Socket 连接发送REST offset后执行RETR 文件名再把后续数据流追加到本地文件。这个过程不经过CFtpConnection因为CFtpConnection没有暴露发送原始命令的接口。// 伪代码续传核心逻辑 // 1. socket 连接服务器 21 端口 // 2. 登录成功后发送 TYPE I 切换二进制 // 3. 获取本地已下载大小形如 REST 10240 // 4. 发送 RETR /data/bigfile.zip // 5. 循环 recv 数据追加到本地文件句柄这里有三条边界条件必须注意。第一服务器必须支持REST命令Windows 自带的 FTP 服务支持但某些老旧嵌入式服务器不支持。第二文件服务器端的最后修改时间要重新验证如果源文件改动过续传数据是脏的。第三通信期间被动模式的数据连接端口要用227响应里的信息去连接不能复用控制连接。如果你想稳妥优先选 WinINet 的FtpOpenFile配合自定义读取偏移但同一样受REST支持限制。我通常给项目的断点续传设置一个开关默认关闭只有确认服务器支持后才打开。否则表面是续传实际拼出来的文件是错位的这种 bug 最难排查。5. MFC FTP 客户端避坑5 个高频问题的排查记录这一节把做 FTP 客户端过程中反复踩到的问题按“现象 → 原因 → 解决”列出来每一条都是一线调试留下的记录。5.1 一拖动窗口就崩禁止拖动窗口大小引发的连环反应现象在 FTP 下载过程中拖动对话框窗口大小程序崩溃或界面卡死有人业务上要求固定窗口于是设置了禁止拖动窗口大小。原因下载任务在主线程同步执行时GetFile内部的消息泵与窗口拖动产生的消息竞争CInternetSession的内部状态被重入。解决要么把下载放到工作线程要么用OnGetMinMaxInfo固定窗口尺寸不要让用户缩放到异常大小。固定窗口的做法是在对话框属性里去掉WS_THICKFRAME同时重写OnGetMinMaxInfo限制最小尺寸避免负值窗口。核心修复还是把阻塞操作移出 UI 线程这不是窗口的锅是同步调用的锅。5.2 中文文件名乱码ANSI 与 UTF-8 的错位现象服务器上文件名为“2024年_报表.csv”客户端列目录显示“2024骞?_鎶ヨ〃.csv”。原因FTP 协议码本没有强制编码服务器端中文以 UTF-8 存放而 MFC 默认使用本地 ANSI中文系统为 GBK解析错位就成了乱码。反过来的情况也存在服务器 GBK、客户端按 UTF-8 解也会乱。解决首先确认服务器端编码。许多 Windows FTP 服务器默认使用本地代码页Linux 的 vsftpd 使用 UTF-8。如果是 UTF-8连接后把拿到的名字做一次转换// UTF-8 字符串转成宽字符再转成当前代码页 CStringA utf8Name (LPCSTR)finder.GetFileName(); int nLen MultiByteToWideChar(CP_UTF8, 0, utf8Name, -1, nullptr, 0); CStringW wideName; MultiByteToWideChar(CP_UTF8, 0, utf8Name, -1, wideName.GetBuffer(nLen), nLen); wideName.ReleaseBuffer(); // 此时 wideName 用于显示或拼接本地路径写代码之前先摸清服务器侧编码这个坑才能一劳永逸。如果做气象数据落地这类项目服务器端的“e 文件格式”往往带中文文件名转换代码建议做成公共函数全局复用。5.3 下载大文件卡死界面同步调用是罪魁祸首现象点击“下载”后窗口变成白框鼠标转圈几分钟后恢复期间用户不能操作。原因GetFile是阻塞函数在 UI 线程直接调用会让消息循环停摆。文件越大、网速越慢表现越严重。解决把文件传输放进工作线程通过PostMessage把结果通知回主线程。这个看起来是常识但实际项目里为了图省事在主线程调的人真不少。如果你维护的是赶工期的老代码快速止血方案是把 (GetFile) 的服务器路径先拷贝到一个临时文件拷完再移到最终位置但卡顿依旧存在根治方法还是线程化。5.4 超时弹窗遮住主窗口ONNECT_TIMEOUT 没有设置现象服务器业务高峰响应慢客户端弹出一个“Internet 连接已超时”的系统对话框主窗口被挡住程序像假死。原因WinINet 底层有时会弹出系统级提示这时的等待时间远超预期用户以为程序崩溃。解决在创建CInternetSession之后、发起连接之前设置超时参数这在第 2 章代码里已经体现。另一个补充是把INTERNET_OPTION_KEEP_CONNECTION打开防止在连续文件传输中连接被默认策略空闲关闭。超时对话框问题在SetOption(INTERNET_OPTION_CONNECT_TIMEOUT)生效后基本不会再出现。5.5 会话与连接对象释放顺序颠倒程序退出时偶发崩溃现象退出程序时崩溃概率约 20%Debug 版本在输出窗口提示堆栈错误。原因CFtpConnection内部引用会话析构时需要会话还活着我见过不少人先 delete 会话再 delete 连接指针悬挂立马触发崩溃。解决释放顺序必须严格为先连接后会话if (m_pConn) { m_pConn-Close(); delete m_pConn; m_pConn nullptr; } if (m_pSession) { delete m_pSession; m_pSession nullptr; }这段代码放到对话框OnDestroy里是有必要的但也要在OnOK和OnCancel两个出口统一调用避免双击关闭窗口走了另一个出口导致漏释放。6. 进阶一个固定套路把 FTP 调用放进工作线程所有界面卡顿的根因都是同步阻塞最后分享一个我常用的固定写法。操作 FTP 的线程用AfxBeginThread创建线程函数只做传输状态通过自定义消息回头。UINT CMfcFtpClientDlg::FtpDownloadThread(LPVOID pParam) { CMfcFtpClientDlg* pDlg (CMfcFtpClientDlg*)pParam; // 在线程里复制会话与连接指针注意连接对象不能跨线程共享 CFtpConnection* pConn pDlg-m_pConn; // 下载逻辑完成后回传结果 BOOL bOk pConn-GetFile(...); pDlg-PostMessage(WM_FTP_DONE, bOk ? 1 : 0, 0); return 0; } // 启动线程的写法 AfxBeginThread(FtpDownloadThread, this, THREAD_PRIORITY_NORMAL, 0, 0, nullptr);这里的边界是CFtpConnection本身不能同时被 UI 线程和下载线程调用否则内部套接字状态错乱。所以窗口关闭前必须等待线程自然退出我习惯用一个CEvent事件对象OnDestroy里WaitForSingleObject超时 10 秒超时后不强制杀线程只置标志让线程尽快收尾。这是我自己血泪经验的总结强制TerminateThread会泄漏 FTP 连接句柄下一次重连大概率失败。这个套路同时解决了 FTP 监控与界面响应不能兼得的问题。检查文件列表用 30 秒定时器下载文件用独立线程状态栏再配合第 3 章的自定义消息刷新整个工具就有了生产可用的完整度。照这个顺序做新手能在两天内跑出一版能交付的 MFC FTP 客户端熟手也能在接手老项目时快速定位到最常见的五个坑。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑