本文展示一個整合 TCP 聊天、UDP 快速訊息、檔案傳輸(支援斷點續傳)、遠端螢幕捕獲的局域網即時通訊程式。核心技術包含:CSocket 封裝、工作者執行緒監聽、自訂協定封包(旗標+長度+資料)、動態埠號管理與執行緒安全關閉機制。
// 封包結構
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' // 控制指令
};
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 | FLAG_TEXT_MSG, 即時顯示於 ListBox |
| 檔案傳輸 | TCP | 分片封包、CRC32 校驗、斷點續傳 |
| 螢幕監控 | TCP | BitBlt + GetDIBits、FLAG_SCREEN 串流 |
| 本機/對方 IP 顯示 | — | gethostname + gethostbyname / Accept 回填 sockaddr_in |
| 滾動 ListBox | — | SetTopIndex / InsertString + 自動捲動 |