首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

TCP/UDP 雙模式局域網聊天與檔案傳輸實作:多執行緒監聽、協定封包與螢幕捕獲

     編輯:關於C語言

本文展示一個整合 TCP 聊天、UDP 快速訊息、檔案傳輸(支援斷點續傳)、遠端螢幕捕獲的局域網即時通訊程式。核心技術包含:CSocket 封裝、工作者執行緒監聽、自訂協定封包(旗標+長度+資料)、動態埠號管理與執行緒安全關閉機制。

整體架構

  • TCP 模式:可靠傳輸,用於聊天訊息、檔案傳輸、螢幕資料流。
  • UDP 模式:低延遲,用於心跳、狀態廣播、快速文字訊息。
  • 執行緒模型:主 UI 執行緒 + TCP 監聽執行緒 + UDP 接收執行緒 + 檔案傳輸工作者執行緒。

核心協定封包設計

// 封包結構
struct PacketHeader {
    BYTE flag;      // 訊息類型
    DWORD length;   // 資料長度 (不含頭)
    DWORD seq;      // 序號 (檔案分片用)
    DWORD crc32;    // 校驗和
};

enum PacketFlag {
    FLAG_TEXT_MSG   = 'T', // 文字訊息
    FLAG_FILE_BEGIN = 'F', // 檔案開始 (附檔名/大小)
    FLAG_FILE_DATA  = 'D', // 檔案資料分片
    FLAG_FILE_END   = 'E', // 檔案結束
    FLAG_SCREEN     = 'S', // 螢幕畫面資料
    FLAG_HEARTBEAT  = 'H', // 心跳
    FLAG_CMD        = 'C'  // 控制指令
};

TCP 監聽執行緒完整實作

UINT _ListenTcpThread(LPVOID pParam)
{
    CMyQQDlg* pDlg = (CMyQQDlg*)pParam;
    CSocket sockSrvr;
    pDlg->m_nListenPort = BASE_PORT + pDlg->m_nServerIndex; // 動態埠

    if (!sockSrvr.Create(pDlg->m_nListenPort) || !sockSrvr.Listen()) {
        pDlg->PostMessage(WM_LOG_MSG, 0, (LPARAM)_T("監聽啟動失敗"));
        return -1;
    }

    while (pDlg->m_bListening) {
        CSocket clientSock;
        SOCKADDR_IN clientAddr;
        int addrLen = sizeof(clientAddr);

        // Accept 會阻塞,需定期檢查 m_bListening 旗標
        if (sockSrvr.Accept(clientSock, &clientAddr, &addrLen)) {
            // 讀取封包頭判斷類型
            PacketHeader hdr;
            if (clientSock.Receive(&hdr, sizeof(hdr)) == sizeof(hdr)) {
                pDlg->m_nCurrentFlag = hdr.flag;

                // 啟動新執行緒處理此連線,主執行緒繼續 Accept
                AfxBeginThread(_HandleClientThread, new ClientContext(pDlg, clientSock.Detach(), clientAddr));
            }
        } else {
            // Accept 失敗,檢查是否為關閉監聽導致
            if (!pDlg->m_bListening) break;
            Sleep(100);
        }
    }
    sockSrvr.Close();
    return 0;
}

安全關閉監聽執行緒機制

void CMyQQDlg::StopListening()
{
    m_bListening = FALSE; // 設定旗標

    // 關閉監聽 socket 以喚醒阻塞中的 Accept
    CSocket tempSock;
    tempSock.Create(m_nListenPort); // 連接自己觸發 Accept 返回
    tempSock.Connect("127.0.0.1", m_nListenPort);
    tempSock.Close();

    // 等待執行緒結束
    if (m_pListenThread) {
        WaitForSingleObject(m_pListenThread->m_hThread, 5000);
        m_pListenThread = NULL;
    }
}

檔案傳輸流程(支援斷點續傳)

發送端

void SendFile(CSocket& sock, LPCTSTR filePath)
{
    CFile file(filePath, CFile::modeRead | CFile::shareDenyWrite);
    ULONGLONG totalSize = file.GetLength();
    ULONGLONG sent = 0;

    // 1. 發送 FILE_BEGIN (含檔名、總大小)
    PacketHeader hdr = {FLAG_FILE_BEGIN, 0, 0, 0};
    CStringA fileName = CStringA(filePath); // 取檔名
    sock.Send(&hdr, sizeof(hdr));
    sock.Send(fileName, fileName.GetLength()+1);
    sock.Send(&totalSize, sizeof(totalSize));

    // 2. 分片發送 FILE_DATA
    const int CHUNK = 64*1024;
    BYTE* buf = new BYTE[CHUNK];
    UINT nRead;
    while ((nRead = file.Read(buf, CHUNK)) > 0) {
        hdr.flag = FLAG_FILE_DATA;
        hdr.length = nRead;
        hdr.seq = (DWORD)(sent / CHUNK);
        sock.Send(&hdr, sizeof(hdr));
        sock.Send(buf, nRead);
        sent += nRead;
        // 更新進度條...
    }

    // 3. 發送 FILE_END
    hdr.flag = FLAG_FILE_END; hdr.length = 0;
    sock.Send(&hdr, sizeof(hdr));

    delete[] buf; file.Close();
}

接收端(支援斷點續傳)

void ReceiveFile(CSocket& sock, PacketHeader& firstHdr)
{
    CString fileName; sock.Receive(fileName.GetBuffer(256), 256); fileName.ReleaseBuffer();
    ULONGLONG totalSize; sock.Receive(&totalSize, sizeof(totalSize));

    // 檢查本地是否有部分檔案
    CFile file(fileName, CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive);
    ULONGLONG existing = file.GetLength();
    if (existing > 0 && existing < totalSize) {
        file.Seek(0, CFile::end); // 斷點續傳:接續寫入
        // 可選:通知發送端從 existing 位置開始
    } else {
        file.SetLength(0); // 重新開始
    }

    PacketHeader hdr;
    while (true) {
        sock.Receive(&hdr, sizeof(hdr));
        if (hdr.flag == FLAG_FILE_END) break;
        if (hdr.flag == FLAG_FILE_DATA) {
            BYTE* buf = new BYTE[hdr.length];
            int received = 0;
            while (received < hdr.length) {
                int n = sock.Receive(buf + received, hdr.length - received);
                if (n <= 0) break;
                received += n;
            }
            file.Write(buf, received);
            delete[] buf;
        }
    }
    file.Close();
}

螢幕捕獲與傳輸

void CaptureAndSendScreen(CSocket& sock)
{
    // 1. 建立螢幕 DC 與相容位圖
    HDC hScreen = GetDC(NULL);
    HDC hMem = CreateCompatibleDC(hScreen);
    int w = GetSystemMetrics(SM_CXSCREEN);
    int h = GetSystemMetrics(SM_CYSCREEN);
    HBITMAP hBmp = CreateCompatibleBitmap(hScreen, w, h);
    SelectObject(hMem, hBmp);
    BitBlt(hMem, 0, 0, w, h, hScreen, 0, 0, SRCCOPY);

    // 2. 取得 DIB bits
    BITMAPINFO bmi = {{sizeof(BITMAPINFOHEADER), w, -h, 1, 24, BI_RGB}};
    int bufSize = ((w * 3 + 3) & ~3) * h;
    BYTE* pBits = new BYTE[bufSize];
    GetDIBits(hMem, hBmp, 0, h, pBits, &bmi, DIB_RGB_COLORS);

    // 3. 發送 SCREEN 封包
    PacketHeader hdr = {FLAG_SCREEN, (DWORD)bufSize, 0, 0};
    sock.Send(&hdr, sizeof(hdr));
    sock.Send(pBits, bufSize);

    // 清理
    delete[] pBits;
    DeleteObject(hBmp);
    DeleteDC(hMem);
    ReleaseDC(NULL, hScreen);
}

介面功能對照

執行畫面

聊天檔案傳輸程式介面

圖:TCP/UDP 雙模式聊天、檔案傳輸、螢幕捕獲整合介面

總結

此範例展示了局域網即時通訊的核心技術棧:CSocket 封裝簡化 API、多執行緒分離關注點、自訂封包協定實現多業務複用、動態埠號避免衝突、安全關閉機制避免資源洩漏。可作為 IM、遠端桌面、協同工具的基礎框架擴充。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
功能協定關鍵技術
文字聊天TCPFLAG_TEXT_MSG, 即時顯示於 ListBox
檔案傳輸TCP分片封包、CRC32 校驗、斷點續傳
螢幕監控TCPBitBlt + GetDIBits、FLAG_SCREEN 串流
本機/對方 IP 顯示—gethostname + gethostbyname / Accept 回填 sockaddr_in
滾動 ListBox—SetTopIndex / InsertString + 自動捲動