MFC 自 VC6 起提供 WinInet 封裝類別,位於 afxinet.h,三層架構:
| 層級 | 類別 | 職責 |
|---|---|---|
| 會話管理 | CInternetSession | 初始化 WinInet、設定代理/逾時/回呼、建立連線 |
| 協議連線 | CFtpConnection、CHttpConnection、CGopherConnection | 特定協議命令 (FTP: DIR, GET, PUT; HTTP: GET, POST) |
| 檔案/資料流 | CInternetFile (繼承 CStdioFile → CFile) | 讀寫遠端檔案、支援 Seek、Text/Binary 模式 |
#include // 需在 stdafx.h 或專案包含
#pragma comment(lib, "wininet.lib") // 連結 WinInet
void CFtpSampleDlg::OnDownload()
{
// 1. 建立網際網路會話
CInternetSession session(_T("MyFtpClient/1.0"), 1, PRE_CONFIG_INTERNET_ACCESS);
session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 10000); // 10 秒連線逾時
session.SetOption(INTERNET_OPTION_CONTROL_SEND_TIMEOUT, 5000);
session.SetOption(INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT, 5000);
// 2. 開啟 FTP 連線
CFtpConnection* pFtp = session.GetFtpConnection(
_T("ftp.microsoft.com"), // 伺服器
_T("anonymous"), // 使用者
_T("user@domain.com"), // 密碼 (匿名用 email)
21, // 埠號
FALSE // TRUE = 被動模式 (預設 TRUE,穿透防火牆較好)
);
if (!pFtp) { AfxMessageBox(_T("連線失敗")); return; }
// 3. 開啟遠端檔案 (唯讀)
CInternetFile* pFile = pFtp->OpenFile(_T("/pub/file.txt"), GENERIC_READ, FTP_TRANSFER_TYPE_ASCII);
if (!pFile) { AfxMessageBox(_T("開啟檔案失敗")); pFtp->Close(); delete pFtp; return; }
// 4. 讀取內容 (繼承 CStdioFile,可用 ReadString)
CString strLine, strAll;
while (pFile->ReadString(strLine)) {
strAll += strLine + _T("\r\n");
}
m_editLog.SetWindowText(strAll); // 顯示到 UI
// 清理 (逆序)
pFile->Close(); delete pFile;
pFtp->Close(); delete pFtp;
session.Close();
}
// 下載整個檔案 (斷點續傳需自行實作)
BOOL success = pFtp->GetFile(
_T("/remote/path/file.zip"), // 遠端路徑
_T("C:\\local\\file.zip"), // 本地路徑
FALSE, // TRUE = 失敗時刪除本地檔
FILE_ATTRIBUTE_NORMAL, // 本地屬性
FTP_TRANSFER_TYPE_BINARY, // 二進位模式 (關鍵!)
0 // 內容位移 (0 = 從頭)
);
// 上傳整個檔案
success = pFtp->PutFile(
_T("C:\\local\\report.pdf"),
_T("/upload/report.pdf"),
FTP_TRANSFER_TYPE_BINARY,
0
);
注意:FTP_TRANSFER_TYPE_ASCII 會轉換換行符 (CRLF↔LF),二進位檔案必須用 BINARY。
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();
FILETIME ft; finder.GetLastWriteTime(&ft);
// 加入 ListCtrl 或 TreeCtrl
}
finder.Close();
CInternetFile* pFile = pFtp->OpenFile(_T("/big.iso"), GENERIC_READ, FTP_TRANSFER_TYPE_BINARY);
if (pFile) {
try {
// 已下載 100MB,從此處續傳
pFile->Seek(100 * 1024 * 1024, CFile::begin);
// 繼續讀取...
} catch (CInternetException* e) {
// 伺服器不支援 REST 指令 → 例外
e->Delete();
// 落回重新下載
}
}
pFtp->CreateDirectory(_T("new_folder"));
pFtp->RemoveDirectory(_T("empty_folder"));
pFtp->SetCurrentDirectory(_T("subdir"));
pFtp->Rename(_T("old.txt"), _T("new.txt"));
pFtp->DeleteFile(_T("obsolete.log"));
try {
// 所有 FTP 操作
}
catch (CInternetException* pEx) {
TCHAR szErr[256];
pEx->GetErrorMessage(szErr, 255);
AfxMessageBox(szErr);
pEx->Delete(); // 必須 Delete!MFC 例外物件在堆疊上
}
catch (CMemoryException* pEx) {
AfxMessageBox(_T("記憶體不足"));
pEx->Delete();
}
常見 WinInet 錯誤碼:ERROR_INTERNET_TIMEOUT、ERROR_INTERNET_NAME_NOT_RESOLVED、ERROR_FTP_TRANSFER_IN_PROGRESS。
CHttpConnection* pHttp = session.GetHttpConnection(_T("api.example.com"), 80);
CHttpFile* pFile = pHttp->OpenRequest(CHttpConnection::HTTP_VERB_GET, _T("/data.json"));
pFile->AddRequestHeaders(_T("Accept: application/json"));
pFile->SendRequest();
DWORD dwStatus; pFile->QueryInfoStatusCode(dwStatus); // 200, 404...
CString json; pFile->ReadString(json); // 讀取回應
wininet.lib (已在預設庫清單)。mfcxxx.dll。MFC CInternetSession/CFtpConnection/CInternetFile 三層封裝,將複雜的 WinInet API 簡化為直觀的 C++ 介面。掌握會話配置、連線模式 (主動/被動)、傳輸類型 (ASCII/BINARY)、例外處理、斷點續傳原理,即可快速開發健壯的 FTP/HTTP 客戶端功能。