首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

MFC TCP FIN 掃描器:主執行緒阻塞導致 UI 無法更新結果

     編輯:程式設計綜合問答

核心問題定位

在 OnBnClickedButton1 中:

void CscanDlg::OnBnClickedButton1() {
    send_revc(...); // 內部呼叫 WaitForMultipleObjects(..., INFINITE);
}

WaitForMultipleObjects(..., INFINITE) 直接阻塞了 UI 主執行緒 (Message Pump)。

  • 主執行緒卡死 → 無法處理 WM_PAINT (介面凍結)、WM_TIMER、用戶點擊。
  • 接收執行緒 (revcfunc) 嘗試呼叫 m_list->AddString() 更新 UI,**但 MFC 控制項非執行緒安全**,跨執行緒直接操作 UI 會導致未定義行為 (崩潰/無反應/資源鎖死)。
  • 即使不崩潰,主執行緒被阻塞,訊息佇列不處理,AddString 發送的 LB_ADDSTRING 訊息永遠不會被分發處理,列表框自然無內容。

正確架構:背景執行緒 + 執行緒安全 UI 更新

方案一:AfxBeginThread + PostMessage (MFC 經典模式)

// 1. 定義自訂訊息 (stdafx.h 或資源.h)
#define WM_SCAN_RESULT (WM_USER + 100)
#define WM_SCAN_FINISHED (WM_USER + 101)

// 2. 對話框類成員變數
CWinThread* m_pScanThread;
bool m_bScanning;

// 3. 啟動掃描 (按鈕事件)
void CscanDlg::OnBnClickedButton1() {
    if (m_bScanning) return;
    m_bScanning = true;
    GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE); // 禁用按鈕
    m_list.ResetContent(); // 清空舊結果
    
    // 傳遞參數結構體指針 (需 new,執行緒結束 delete)
    ScanParams* pParams = new ScanParams;
    pParams->ip = m_IP; pParams->startPort = _ttoi(m_star_port); 
    pParams->endPort = _ttoi(m_end_port); pParams->hWnd = m_hWnd;
    
    m_pScanThread = AfxBeginThread(ScanThreadProc, pParams, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
    m_pScanThread->m_bAutoDelete = TRUE; // 結束自動刪除 CWinThread 物件
    m_pScanThread->ResumeThread();
}

// 4. 背景執行緒函數 (靜態/全域)
UINT ScanThreadProc(LPVOID pParam) {
    ScanParams* p = (ScanParams*)pParam;
    SOCKET sock = CreateRawSocket(); // 你的原始套接字建立邏輯
    // ... 發送 FIN 包邏輯 (sendfunc) ...
    // ... 接收迴圈 (revcfunc) ...
    
    // 接收到開放端口時,發送訊息給主執行緒更新 UI
    while (receiving) {
        int port = RecvAndParse(sock);
        if (port > 0) {
            ::PostMessage(p->hWnd, WM_SCAN_RESULT, (WPARAM)port, 0); // 跨執行緒安全!
        }
    }
    ::PostMessage(p->hWnd, WM_SCAN_FINISHED, 0, 0);
    delete p;
    return 0;
}

// 5. 訊息映射與處理 (對話框類)
BEGIN_MESSAGE_MAP(CscanDlg, CDialogEx)
    ON_MESSAGE(WM_SCAN_RESULT, OnScanResult)
    ON_MESSAGE(WM_SCAN_FINISHED, OnScanFinished)
END_MESSAGE_MAP()

LRESULT CscanDlg::OnScanResult(WPARAM wParam, LPARAM lParam) {
    int port = (int)wParam;
    CString str; str.Format(_T("%d"), port);
    m_list.AddString(str); // 安全!在主執行緒執行
    return 0;
}

LRESULT CscanDlg::OnScanFinished(WPARAM wParam, LPARAM lParam) {
    m_bScanning = false;
    GetDlgItem(IDC_BUTTON1)->EnableWindow(TRUE);
    AfxMessageBox(_T("掃描完成"));
    return 0;
}

方案二:現代 C++ (C++11/17) + std::thread + PostMessage (VS2015+)

邏輯同上,將 AfxBeginThread 換為 std::thread,參數用 std::shared_ptr 管理生命週期,核心 **UI 更新仍必須透過 PostMessage / SendMessage 回主執行緒**。

關鍵修正清單

錯誤做法正確做法
WaitForMultipleObjects(..., INFINITE) 在 UI 執行緒背景執行緒跑任務,主執行緒自由跑 Message Pump
背景執行緒直接 m_list.AddString()背景執行緒 PostMessage(hWnd, WM_CUSTOM, port, 0),主執行緒處理訊息再 AddString
CreateThread 裸 API,無 MFC 環境AfxBeginThread (MFC) 或 std::thread (現代),確保 MFC 模組狀態正確
原始 Socket 需管理員權限程式清單加 requireAdministrator 或右鍵「以管理員身分執行」
Checksum 計算可能錯誤 (偽首部協議號應為 IPPROTO_TCP=6,非 UDP=17)psdHeader.ptcl = IPPROTO_TCP;

總結

**MFC 鐵律:耗時操作 (網路、檔案、計算) 必須移出 UI 執行緒;跨執行緒更新 UI 必須用 PostMessage/SendMessage。**
你的代碼邏輯 (原始 Socket、Checksum、FIN 掃描) 大方向正確,只因 WaitForMultipleObjects 卡死主執行緒 + 跨執行緒直接操控 CListBox 導致「跑完沒結果、介面假死」。套用上述 AfxBeginThread + PostMessage 模式即可完美解決。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved