MFC WinInet 封裝類 (CInternetSession, CFtpConnection, CInternetFile) 提供了極簡單的 FTP 客戶端開發接口。本文系統講解 連接、上傳/下載、目錄列表、斷點續傳、錯誤處理 等核心功能實現。
CInternetSession (會話管理、代理設置、超時控制)
→ GetFtpConnection() → CFtpConnection (FTP 連接、命令通道)
→ OpenFile() → CInternetFile (數據通道、讀寫流)
→ GetFile() / PutFile() (高層封裝,單文件傳輸)
→ CFtpFileFind (目錄遍歷)
CInternetSession session(_T("MyFtpClient/1.0"), 1, PRE_CONFIG_INTERNET_ACCESS); // 1=INTERNET_OPEN_TYPE_DIRECT
// 連接參數:服務器、用戶、密碼、端口(默認21)、被動模式(TRUE)
CFtpConnection* pFtp = session.GetFtpConnection(
_T("ftp.microsoft.com"),
_T("anonymous"),
_T("user@domain.com"), // 匿名登錄常用郵箱作密碼
21,
TRUE // 被動模式 (PASV),穿透防火牆/NAT 必須
);
if (!pFtp) {
DWORD err = GetLastError(); // ERROR_INTERNET_...
// 處理錯誤
}
// 下載:遠程文件 → 本地文件 (自動處理類型、斷點不支持)
BOOL bOk = pFtp->GetFile(
_T("/pub/README.txt"), // 遠程路徑
_T("C:\\Temp\\README.txt"), // 本地路徑
FALSE, // bFailIfExists: FALSE=覆蓋
FILE_ATTRIBUTE_NORMAL, // 屬性
FTP_TRANSFER_TYPE_BINARY, // 二進制模式 (關鍵!ASCII 模式會損壞二進制文件)
0 // dwContext (異步用)
);
// 上傳:本地文件 → 遠程文件
bOk = pFtp->PutFile(
_T("C:\\Data\\data.zip"),
_T("/upload/data.zip"),
FTP_TRANSFER_TYPE_BINARY,
0
);
// 打開遠程文件讀取 (下載)
CInternetFile* pFile = pFtp->OpenFile(_T("/bigfile.iso"), GENERIC_READ, FTP_TRANSFER_TYPE_BINARY);
if (pFile) {
// 斷點續傳:設置文件指針
ULONGLONG ullResumePos = 1024 * 1024 * 100; // 100MB
pFile->Seek(ullResumePos, CFile::begin); // 需服務器支持 REST 命令
// 本地文件
CFile localFile(_T("D:\\bigfile.iso"), CFile::modeCreate | CFile::modeWrite | CFile::typeBinary);
localFile.Seek(ullResumePos, CFile::begin);
// 循環讀寫
const int BUFSIZE = 32768;
BYTE buffer[BUFSIZE];
UINT nRead;
while ((nRead = pFile->Read(buffer, BUFSIZE)) > 0) {
localFile.Write(buffer, nRead);
// 更新進度...
}
localFile.Close();
pFile->Close();
delete pFile; // OpenFile 返回指針需手動刪除
}
// 打開遠程文件寫入 (上傳/續傳)
CInternetFile* pPutFile = pFtp->OpenFile(_T("/upload/bigfile.iso"), GENERIC_WRITE, FTP_TRANSFER_TYPE_BINARY);
// 同理 Seek + Write 循環
CFtpFileFind finder(pFtp);
BOOL bFound = finder.FindFile(_T("*.*")); // 根目錄
while (bFound) {
bFound = finder.FindNextFile();
if (finder.IsDots()) continue; // 跳過 . 和 ..
CString name = finder.GetFileName();
BOOL isDir = finder.IsDirectory();
ULONGLONG size = finder.GetLength(); // 文件大小
CTime time; finder.GetLastWriteTime(time); // 修改時間
// 處理條目...
// 遞歸進入子目錄: pFtp->SetCurrentDirectory(name); // 遞歸調用 FindFile
}
finder.Close();
pFtp->CreateDirectory(_T("new_folder"));
pFtp->RemoveDirectory(_T("empty_folder")); // 必須為空
pFtp->Remove(_T("file_to_delete.txt"));
pFtp->Rename(_T("old_name.txt"), _T("new_name.txt"));
pFtp->SetCurrentDirectory(_T("subdir")); // 切換工作目錄
CString curDir; pFtp->GetCurrentDirectory(curDir); // 獲取當前目錄
CInternetSession 構造函數第 3 參數傳 AFX_INET_ASYNC (或設置 EnableStatusCallback(TRUE)),重寫 OnStatusCallback:
class CMyFtpSession : public CInternetSession {
public:
CMyFtpSession() : CInternetSession(_T("MyFtp"), 1, AFX_INET_ASYNC) {
EnableStatusCallback(TRUE);
}
protected:
virtual void OnStatusCallback(DWORD dwContext, DWORD dwInternetStatus,
LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) {
switch (dwInternetStatus) {
case INTERNET_STATUS_RECEIVING_RESPONSE:
case INTERNET_STATUS_RESPONSE_RECEIVED:
case INTERNET_STATUS_SENDING_REQUEST:
case INTERNET_STATUS_REQUEST_SENT:
case INTERNET_STATUS_DATA_AVAILABLE: // lpvStatusInformation = 可用字節數
// 更新 UI 進度條...
break;
case INTERNET_STATUS_REQUEST_COMPLETE:
// INTERNET_ASYNC_RESULT* pResult = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
// 檢查 pResult->dwError / dwResult
break;
}
}
};
| 錯誤碼 | 含義 | 排查方向 |
|---|---|---|
| ERROR_INTERNET_INVALID_URL | URL 格式錯誤 | 檢查服務器地址、路徑編碼 |
| ERROR_INTERNET_CANNOT_CONNECT | 無法連接服務器 | IP/端口、防火牆、服務器運行狀態 |
| ERROR_INTERNET_LOGIN_FAILURE | 用戶名/密碼錯誤 | 憑證、匿名登錄策略 |
| ERROR_INTERNET_ACCESS_DENIED | 權限不足 | 文件/目錄權限、用戶組 |
| ERROR_INTERNET_OPERATION_CANCELLED | 操作取消/超時 | 超時設置、用戶取消 |
| ERROR_FTP_TRANSFER_IN_PROGRESS | 已有傳輸進行中 | 同一連接不支持並發數據通道 |