ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VC6 MFC FTP客户端工程解析:上传下载与避坑指南

VC6 MFC FTP客户端工程解析:上传下载与避坑指南 简介面向 VC 开发者的 FTP 客户端工程包完整实现了基于 FTP 协议的文件上传与下载功能适合学习 MFC 网络编程或需要快速搭建 FTP 客户端原型的开发者。压缩包共 26 个文件以 9 个 h 头文件、8 个 cpp 源文件为主并包含 dsp/dsw 工程文件、rc 资源脚本、ico 图标与工具栏位图整体仅 29KB代码规模精简便于逐文件分析。通过 FtpTransfer、FTPDoc、FTPSite 等模块可了解 FTP 命令交互、套接字连接、会话管理、目录列表显示以及上传下载流程的完整实现MainFrm 与各 View 类则展示了 MFC 文档视图结构下的界面组织方式。工程包已经过实际调试附带 dsp 项目配置文件可直接用 Visual Studio 打开编译便于在调试中观察 FTP 会话状态与文件传输流程代码量紧凑适合作为课程设计、毕业设计或内部工具类项目改造的基础。目前已有 185 人学习下载。1. FTP.zip一份能把“上传、下载”两件事跑通的 VC6 工程做工业软件和运维工具的同行大概都有同感老项目里最不缺的就是“能跑就行”的代码而 FTP 传输恰恰是最容易出现这种遗留物的场景。这份 FTP.zip 就是典型的 VC6.0 MFC 单文档工程压缩包里除了 FtpTransfer.cpp、FTPSite.cpp 这类核心实现还带了 FTP.rc、Toolbar.bmp 等资源文件整个工程按 Visual Studio 6 的 dsw/dsp 组织编译后就是一个带界面、能连 FTP 服务器做上传和下载的 Windows 客户端。它适合两类人一类是接手了十年前工控上位机、触摸屏项目需要补一个文件传输模块的维护工程师另一类是想用最小成本看懂“FTP 客户端到底怎么用套接字和命令交互”的 C 初学者。它不是最新的异步框架但足够直接解开就能看到 USER、PASS、CWD、RETR、STOR 这些命令在代码里的真实落点。2. 先拆包这套 VC 工程里每个文件到底在干什么2.1 从 dsw/dsp 到 rc老工程的入口顺序拿到压缩包先别急着双击里面既有 FTP.dsw 也有 FTP.dsp。dsw 是 Visual Studio 6 的工作区文件dsp 是工程文件如果只把 dsp 拖进 VC6也能编但依赖关系和多配置会丢一部分。我自己习惯直接从 dsw 打开让 VC6 自己把整个工作区恢复出来。至于 FTP.rc 和 FTP.rc2rc 是资源脚本的入口rc2 是 VC 自动维护的部分一般放版本信息、图标这类不宜手工改的内容。真正跟界面打交道的是资源里的对话框和工具栏定义而 Toolbar.bmp 是工具栏位图主窗口上那几个“连接”“上传”“下载”的按钮图像全指着它。编译时 rc 会去读 bmp如果 bmp 缺失常报“Cannot open file: res\Toolbar.bmp”之类的资源编译错误。StdAfx.h / StdAfx.cpp 是预编译头MFC 工程的标配。它把 afxwin.h、afxext.h 这些常用头文件提前编译成 .pch能明显缩短后续编译时间。老项目里如果头文件包含顺序乱了经常会出现“fatal error C1010: unexpected end of file while looking for precompiled header directive”这多半就是 StdAfx.h 没被正确包含。2.2 FTP.cpp、FTPSite.cpp、FtpTransfer.cpp三层的分工边界这三个文件名字很像但职责是完全分开的。FTP.cpp 和 FTPDoc.cpp 是文档类负责把数据从网络层“接收”到内存数据结构里FTPSite.cpp 管站点配置连接哪台服务器、用什么端口、用户名密码是什么、要不要被动模式这些参数在界面里编辑完最后都会落到这个类的成员变量里FtpTransfer.cpp 则是真正动手传输的一层上传走 STOR下载走 RETR进度条和文件流的开关都在这。这三个文件的边界一旦清楚改起来就有方向想加“记住密码”就改 FTPSite想在传输前检查磁盘空间就改 FtpTransfer想再开一个多线程同时传多个文件就得先碰 FTPDoc。别上来就改 FtpTransfer.cpp 里的全局变量那是给自己挖坑。2.3 MainFrm、LeftView、FTPView窗口拆分的经典 MFC 布局MainFrm.cpp 是主框架窗口负责创建菜单、工具栏、状态栏以及把工作区分成左右两块。LeftView 在左侧一般放服务器上的目录树或文件列表FTPView 在右侧显示当前目录下的文件明细也可以做成传输队列。这类“左树右表”的布局在 MFC 里通常用一个 CSplitterWnd 切分两个视图共享同一个 Document。视图之间通过 Document 交换数据是 MFC 文档/视图结构的核心套路。左侧双击目录LeftView 通知 Document“切换目录”Document 更新数据后调用 UpdateAllViews右侧 FTPView 收到更新消息再从 Document 里取新列表刷新。这套机制初看绕但老派、稳定尤其在 UI 线程单线程的环境下不用考虑锁。2.4 FTPView.cpp 里的进度交互和 Refresh 逻辑FTPView 的重头戏不只是显示列表。传输一个几百兆的文件时如果界面卡死多半是传输动作被直接放在 UI 线程里同步执行了。正常做法是在 FTPView 里响应菜单命令启动一个工作线程调 FtpTransfer 的上传/下载函数线程里周期性地发自定义消息回来更新进度条。拆包后你可以在 FTPView.cpp 里搜 ON_MESSAGE 或 PostMessage能看到这类跨线程刷新的写法。另外 Refresh刷新列表是个容易忽略的点从服务器列目录拿到的是内存里的临时列表刷新前必须清空视图缓存否则第二次 LIST 后旧文件还挂在界面上。这类 bug 往往不会崩溃但会让用户觉得“这客户端有毛病文件明明删了还在”。3. 编译复现把老 VC6 工程在 Windows 10/11 上跑起来3.1 前置准备VC6 与系统兼容性处理这是整篇笔记里最“玄学”也最关键的一步。VC6 在 Windows XP 时代是神器到了 Windows 10/11 上最常见的问题是安装 IDE 后编译时莫名其妙崩溃、调试器连不上、资源编辑器打不开。工程本身能不能编译很大程度上取决于你有没有装 VC6 的 Service Pack 6。没装 SP6很多 MFC 头文件在 NT6 以后的内核上行为会异常比如 GetFileVersionInfoSize 这类 API 调用失败。我的做法是先装 VC6 原版紧接打上 SP6再把整个 VC98 目录的兼容模式设为 Windows XP SP3。右键 VC6 安装目录下的 MSDEV.EXE属性 → 兼容性 → 勾选“以兼容模式运行这个程序”下拉选 Windows XP (Service Pack 3)同时勾选“以管理员身份运行”。这一步能避开大量“打开工程后 IDE 直接闪退”的坑。如果系统是 Windows 11 且是 ARM 版那别硬来直接在虚拟机里跑 Windows 10 x86 或 XP比在 ARM 上折腾 x86 模拟器省时间。VC6 没有官方的 ARM 支持。3.2 打开工程到首次编译的完整步骤以下是我每次拿到这类工程都会走一遍的流程用命令或 UI 操作都行1) 将 FTP.zip 解压到纯英文路径推荐 C:\ftp_src避免中文路径触发 ML 汇编器或 RC 编译器解析问题 2) 双击 FTP.dsw用 VC6 打开工作区 3) 菜单 Build - Set Active Configuration选 Win32 Release 4) 菜单 Project - Settings在 C/C 标签页的 Preprocessor 定义里确认 _WIN32、WINVER 等宏存在 5) 菜单 Build - Rebuild All这里有三处容易翻车第一路径里不要出现中文和空格。老版 RC.EXE 对路径里的空格处理有缺陷报错信息经常是“cannot open file”跟系统文件权限无关。第二Release 和 Debug 的区别不只是优化MFC 的运行时库也不同Debug 版默认用 Debug 版 MFC DLL发布到目标机器时如果没带这些 DLL运行就报“mfc42d.dll 找不到”。第三如果你在项目设置里看到“Use MFC in a Static Library”那生成的是静态链接版单文件就能跑体积大一点但部署省心。编译通过后工程会在 Release 目录下生成 FTP.exe。先别急着连服务器先在本地搭一个 FTP 服务端验证连通性哪怕用 Windows 自带的 IIS FTP 也好。3.3 本地起一个 FTP 服务器做冒烟测试这一步我建议用系统自带功能零第三方依赖1) 控制面板 - 启用或关闭 Windows 功能 - 勾选 Internet Information Services - FTP 服务器 2) 打开 IIS 管理器新建 FTP 站点绑定本机 IP 的 21 端口 3) 授权方式选“基本”指定一个本地 Windows 账号 4) 在 FTP 站点根目录放一个 test.txt 和一个小文件夹 5) 用 FTP.exe 连 ftp://127.0.0.1账号填刚才授权的用户注意 IIS FTP 默认可能关闭了被动模式所需的端口范围这时候如果客户端传输中途卡在“425 Cant open data connection”就去 IIS 的“FTP 防火墙支持”里配置数据通道端口范围并在 Windows 防火墙放行这些端口。这一步做好后面所有的上传、下载、列目录才谈得上验证。4. 上传与下载核心逻辑FtpTransfer.cpp 里的参数与改法4.1 MFC 的 FTP 封装CInternetSession 与 CFtpConnectionMFC 提供了 CFtpConnection 类它内部封装了 FTP 协议的交互细节直接调 PutFile/GetFile 就能完成传输。但也正因如此很多人忽略了底层的命令交互逻辑等碰到“中文文件名上传变乱码”就蒙了。这个工程里的 FtpTransfer.cpp 大概率是两种实现之一要么直接用 WinINet 的 CFtpConnection要么用最原始的 CSocket 自己拼 FTP 命令。前者改动方便后者可控性强。判断方法很简单在 FtpTransfer.cpp 里搜有没有 CFtpConnection 关键字没有的话再搜 CSocket。下面是一段基于 CFtpConnection 的上传函数示意结构上跟工程里的实现思路是一致的BOOL FtpUploadFile(CFtpConnection* pConnection, LPCTSTR lpszLocalFile, LPCTSTR lpszRemoteFile) { // 上传前先确认连接有效无效则直接返回避免在传输中途才暴露断连 if (pConnection NULL) return FALSE; // TRUE 表示以二进制方式上传FALSE 表示 ASCII 方式文本文件用 ASCII其他一律用二进制 BOOL bResult pConnection-PutFile(lpszLocalFile, lpszRemoteFile, FTP_TRANSFER_TYPE_BINARY, FALSE); if (!bResult) { DWORD dwErr GetLastError(); // 常见错误: 12007 服务器无法连接, 12014 登录未完成, 12019 连接被重置 TRACE(_T(FTP upload failed, error code: %d\n), dwErr); } return bResult; }这里最值得解释的是第三个参数FTP_TRANSFER_TYPE_BINARY。FTP 协议里有两种传输模式ASCII 模式会做换行符转换适合文本二进制模式原样传输适合 exe、zip、图片。很多人上传配置文件时用的是默认文本模式结果服务端拉下来的文件最后一行没有换行CRC 对不上就是这个原因。GetLastError()之后建议把错误码打在 log 里再返回。12007 和 12019 是最常见的两个错前者是 TCP 层连不上后者是服务器端主动断开了数据连接。12007 优先检查服务器地址和端口12019 优先检查被动模式端口和防火墙。4.2 目录遍历把 LIST 结果解析成视图能识别的结构上传下载之前客户端得先知道服务器上有哪些文件。FTP 的 LIST 命令返回的是一个文本流不同服务器Windows IIS、Linux vsftpd、RouterOS 都有差异返回格式不同。解析这种格式就得写一段比较脏的代码。工程里 LeftView 的目录树数据来源通常就在 FtpTransfer.cpp 的 ListDirectory 函数里void CftpTransfer::ListDirectory(CFtpConnection* pConn, LPCTSTR pszDir, CStringList fileList) { // 用 FindFile 获取服务器端目录下的第一个文件句柄 CFtpFileFind ftpFind(pConn); BOOL bContinue ftpFind.FindFile(pszDir); while (bContinue) { bContinue ftpFind.FindNextFile(); // IsDots 过滤掉 . 和 .. 两个特殊目录项 if (!ftpFind.IsDots() !ftpFind.IsDirectory()) { fileList.AddTail(ftpFind.GetFileName()); } } // 注意: CFtpFileFind 使用完毕必须 Close, 否则句柄泄漏会导致后续 LIST 卡死 ftpFind.Close(); }这块有三个老生常谈的注意点。第一IsDots()不检查的话界面上会出现两个名字为“.”和“..”的鬼条目用户点进去就是原地转圈。第二FindNextFile()的返回值要放在循环的尾部而不是头部否则会莫名其妙丢掉第一个文件。第三整个遍历过程如果超过几十秒要在界面加个等待光标因为服务器文件多的时候LIST 解析是纯阻塞的用户会以为程序死了。4.3 下载目录与断点续传的取舍工程里默认的 GetFile 下载是不带断点续传的。FTP 的断点续传的原理是先发送REST命令告诉服务器“从哪个偏移量继续”再发送RETR文件名服务器会从指定位置开始发送数据。在 MFC 的 CFtpConnection 里需要调用GetFile前通过Command方法直接发送 REST 命令CString strCmd; strCmd.Format(_T(REST %d), nOffset); pConnection-Command(strCmd, FTPLOSTRIP); BOOL bRes pConnection-GetFile(pszRemote, pszLocal, FALSE, FTP_TRANSFER_TYPE_BINARY);FTPLOSTRIP是告诉 MFC 在发送命令后不要等待服务器响应因为 REST 命令的 350 响应会在随后的 RETR 流程里被消费。如果漏了这个参数命令就会卡住等待一个永远不会被处理的响应。这是 FtpTransfer 这类代码里最容易写错的一处。没有断点续传时大文件下载失败就只能整个重来。工程既然“已调试并现场使用”多半是内部网络环境相对稳定这个缺陷能被忍受。如果你想自己加上断点续传记得同时记录本地已下载的字节数并在恢复时用SetFilePointer把本地文件指针移到位。5. 避坑与排查VC6 FTP 组合最容易翻车的几个点5.1 编译报错“cannot open file res\Toolbar.bmp”现象用 VC6 打开工程一编译就报 RC 资源编译错误指向 Toolbar.bmp 不能打开。原因压缩包里的 Toolbar.bmp 文件存在但工程文件 FTP.rc 里的引用路径是相对的如果你把项目解压到了别的目录层级RC 编译器就找不到位图了。解决先把整个工程放到跟原来解压目录结构相同的位置或者直接修改 FTP.rc 里的位图路径为相对路径。最常见的操作是把 Toolbar.bmp 从 src 目录复制到工程根目录的 res 子目录保证磁盘路径和 rc 里写的一致。5.2 FTP 登录成功但列目录为空现象连上服务器后文件列表区域一片空白也不报错。原因服务器工作在被动模式但客户端没有正确处理 PASV 应答或者服务器返回的目录列表格式与客户端解析逻辑不匹配。解决先在 FTPSite.cpp 里确认被动模式的开关有没有生效。如果开关没问题抓一下服务器返回的原始 LIST 文本比对 LeftView 的解析代码看是需要 split 空格还是 split tab。Linux vsftpd 默认格式是-rw-r--r-- 1 ftp ftp 1024 Mar 10 10:00 test.txtWindows IIS 的格式完全不同解析代码必须分开处理。5.3 上传大文件到一半断开没有重试机制现象传 200 MB 的 zip到 60% 左右连接被重置整个传输失败没有断点续传也没有自动重试。原因FtpTransfer.cpp 里没有对数据连接做超时管理和重试封装而路由或服务端的空闲超时时间短于传输时间。解决先给 CFtpConnection 设置数据超时时间pConnection-SetDataTimeout(600)单位是秒再在传输失败时做一个简单的重试循环最多尝试三次。如果一次传输时间确实会超过服务端的连接超时则要么改服务端配置要么按 4.3 的方式做断点续传。5.4 中文文件名上传后变乱码现象本地文件名项目文档.txt传到服务器后显示为乱码下载回来打不开。原因FTP 命令通道默认用 ASCII 传输文件名MFC 老版本把中文按 ANSIGBK编码发送而现代 Linux 服务端按 UTF-8 解释两者字节流不兼容。解决如果是 Windows 对 Windows 的 FTP通常不会出问题跨平台传输时把本地文件名转码为 UTF-8再传入 PUT/RETR 命令。转换可以用 WideCharToMultiByte 配合 CP_UTF8做完后上传到 vsftpd 上文件名就正常了。5.5 连接被服务器以“425 Cant open data connection”拒绝现象登录、列目录都正常一旦执行 GET/PUT 就报 425或卡在传输开始时。原因服务器开启了被动模式但客户端没有进入被动模式而是发起了主动连接或者客户端在被动模式但防火墙没放行服务器的数据端口段。解决先固定客户端模式为被动PASV再在服务器防火墙里放行数据端口段。IIS FTP 的默认数据端口范围在“FTP 防火墙支持”里配置vsftpd 通过/etc/vsftpd.conf里的pasv_min_port和pasv_max_port设置只放开这段范围内的随机端口。6. 进阶做法给这份工程加一个被动模式开关和传输日志虽然这份 FTP.zip 已能完成基本的上传下载但真到了工控现场你还需要一个被动模式开关和一套完整的传输日志。被动模式开关可以做成 FTPSite 里的一个 bool 成员在创建 CFtpConnection 前根据这个值决定用哪种数据连接方式。日志也别随便打建议在每次传输的开始、结束、失败三处各写一条格式固定为[时间] [操作] [文件名] [字节数] [错误码]。我在几个项目的运维里吃了不少亏后养成一个习惯任何基于这份源码改出来的工具都先连 Windows 本机 IIS 跑一遍完整流程再把服务端换成 vsftpd 跑一遍最后才到现场。这两套服务器的目录格式和被动模式行为差异能把绝大多数解析问题提前暴露出来。实际动手改时优先在 FtpTransfer.cpp 里加一个公共函数统一处理“连接、模式判断、上传、下载、重试、日志”这样不管界面和站点管理怎么变传输核心是稳定的。改完再编译一次按下 F5连上服务器传一个文件名带空格的文件、一个中文名文件、一个超过 100 MB 的文件三关过了这份资源才真正变成你自己的工具。从那以后我每次替别人收这类老工程都会先看被动模式有没有做开关、传输出错时有没有日志这两点没满足就先补上再谈业务功能。补完这两个坑这套代码在多数 Windows 场景下都还能再战几年。希望帮到你。本文还有配套的精品资源点击获取
返回列表