絡(luò)編程實(shí)戰(zhàn):TCP/UDP雙協(xié)議Windows原生實(shí)現(xiàn))
簡介本資源是一套基于Visual C與MFC框架的TCP/UDP網(wǎng)絡(luò)通信實(shí)戰(zhàn)示例工程面向C中級(jí)開發(fā)者及Windows平臺(tái)網(wǎng)絡(luò)編程學(xué)習(xí)者旨在解決Winsock底層API封裝難、異步通信邏輯復(fù)雜、協(xié)議差異實(shí)踐模糊等典型問題。壓縮包共82個(gè)文件含6個(gè)核心cpp源碼、8個(gè)h頭文件、4個(gè)exe可執(zhí)行程序分別對(duì)應(yīng)TCP客戶端/服務(wù)器、UDP收發(fā)端、4個(gè)res資源文件及配套調(diào)試產(chǎn)物pdb、idb等整體12.04MB結(jié)構(gòu)清晰體現(xiàn)MFC消息驅(qū)動(dòng)與CAsyncSocket異步模型的完整集成路徑。已有163人學(xué)習(xí)下載內(nèi)容覆蓋從Socket綁定、連接建立、Send/Receive數(shù)據(jù)交互到錯(cuò)誤處理與界面嵌入的全流程預(yù)覽可見獨(dú)立的TCP與UDP雙模塊目錄各含ReadMe說明、對(duì)話框類實(shí)現(xiàn)及標(biāo)準(zhǔn)MFC項(xiàng)目文件.dsp/.dsw/.rc便于快速編譯運(yùn)行、對(duì)比協(xié)議特性并深入理解事件驅(qū)動(dòng)機(jī)制。1. MFC-TCP-UDP.rar 是什么一個(gè)被低估的 Windows 原生網(wǎng)絡(luò)編程“活體標(biāo)本”你打開這個(gè)壓縮包看到MFC-TCP-UDP.rar第一反應(yīng)可能是又一個(gè)陳年 C 項(xiàng)目但別急著關(guān)掉——它不是教學(xué) Demo也不是半成品練習(xí)題而是一個(gè)完整可運(yùn)行、帶 UI 控件交互、同時(shí)封裝 TCP 客戶端/服務(wù)端與 UDP 發(fā)送/接收邏輯的 Visual C 工程實(shí)體。它用最典型的 MFC 對(duì)話框CDialog承載 socket 操作所有網(wǎng)絡(luò)收發(fā)都通過WSAStartup()初始化后調(diào)用socket()/bind()/connect()/sendto()/recvfrom()等 Winsock API 實(shí)現(xiàn)沒有第三方庫依賴不走 .NET 或 COM 封裝是純正的 Windows 原生網(wǎng)絡(luò)編程“黑匣子”解剖樣本。這個(gè)工程的價(jià)值在于它把教科書里割裂開講的「TCP 連接管理」、「UDP 無連接通信」、「MFC 消息循環(huán)與 socket 異步通知」、「UI 線程與網(wǎng)絡(luò) I/O 的線程安全邊界」全塞進(jìn)一個(gè).rc.cpp.h的緊湊結(jié)構(gòu)里。新手能照著改 IP 和端口立刻跑通熟手能從中摳出WSAAsyncSelect()如何綁定到OnMessage()、CAsyncSocket類為何在復(fù)雜場景下反而不如裸 socket 可控、WM_SOCKET消息如何避免 UI 卡死等血淚經(jīng)驗(yàn)。它不炫技但每行代碼都在回答一個(gè)現(xiàn)實(shí)問題在 Windows 桌面環(huán)境里怎么讓一個(gè)帶按鈕和編輯框的程序真正穩(wěn)定地收發(fā)網(wǎng)絡(luò)數(shù)據(jù)不是 Linux 下epoll的優(yōu)雅也不是 Python 的asyncio抽象而是 Win32 API 層面的硬核落地。2. 從解壓到編譯Visual C 環(huán)境搭建與工程復(fù)現(xiàn)路徑2.1 環(huán)境選型為什么必須用 VC6.0 或 VS2015 以前的工具鏈這個(gè).rar包大概率誕生于 2005–2012 年間其.dspVC6或.vcprojVS2008工程文件直接引用了atlcom.h、afxsock.h和早期winsock2.h頭文件路徑。若強(qiáng)行用 VS2019 打開會(huì)立即報(bào)錯(cuò)error C1083: Cannot open include file: afxsock.h: No such file or directory這不是缺失頭文件而是微軟從 VS2012 起徹底移除了對(duì) MFC Socket 類CAsyncSocket,CSocket的向后兼容支持。afxsock.h在新版 MFC 中已廢棄其功能被CWinThreadWSAEventSelect()替代。所以復(fù)現(xiàn)的第一步是鎖定工具鏈? 推薦方案安裝Visual Studio 2010 SP1含完整 MFC 和 Winsock 支持再打上 Microsoft Visual C 2010 SP1 Redistributable Package (x86)?? 次選方案用 VC6.0需在 Win10/Win11 上啟用兼容模式并關(guān)閉 DPI 縮放否則資源編輯器崩潰? 絕對(duì)規(guī)避VS2017 直接加載.dsp文件——會(huì)觸發(fā)自動(dòng)轉(zhuǎn)換但CAsyncSocket::Create()等關(guān)鍵方法將無法解析且WSAAsyncSelect()的消息映射宏ON_MESSAGE(WM_SOCKET, OnSocketNotify)在新 MFC 中已失效。提示不要試圖用 CMake 或 Ninja 重寫構(gòu)建系統(tǒng)。這個(gè)工程的價(jià)值恰恰在于它暴露了原始.def導(dǎo)出定義、.rc2資源腳本、以及#pragma comment(lib, ws2_32.lib)這類底層鏈接指令。跳過這些就等于拆掉發(fā)動(dòng)機(jī)看說明書。2.2 解壓與目錄結(jié)構(gòu)還原識(shí)別核心文件與隱藏依賴解壓后典型目錄如下以 VS2010 為例MFC-TCP-UDP/ ├── MFC-TCP-UDP.sln ← 解決方案文件VS2010 ├── MFC-TCP-UDP.vcproj ← 項(xiàng)目主文件 ├── MFC-TCP-UDP.cpp ← 應(yīng)用類入口InitInstance 中調(diào)用 AfxSocketInit() ├── MFC-TCP-UDP.h ├── MainFrm.h / MainFrm.cpp ← 主框架若為多文檔 ├── MFC-TCP-UDPDlg.h ← 對(duì)話框類頭文件含 CEdit m_editIP, CButton m_btnConnect 等控件成員 ├── MFC-TCP-UDPDlg.cpp ← 對(duì)話框?qū)崿F(xiàn)重點(diǎn)所有 socket 創(chuàng)建/發(fā)送/接收邏輯在此 ├── Resource.h ← 資源 ID 定義 ├── MFC-TCP-UDP.rc ← 對(duì)話框布局、按鈕文本、編輯框位置 └── stdafx.h ← 預(yù)編譯頭必須包含 #include afxsock.h關(guān)鍵動(dòng)作打開stdafx.h確認(rèn)以下三行存在且順序不可顛倒#include afxwin.h // MFC core and standard components #include afxext.h // MFC extensions #include afxsock.h // MFC socket extensions (CRITICAL)若缺失afxsock.h則CAsyncSocket類不可用所有m_socket.Create()調(diào)用將失敗。此時(shí)必須手動(dòng)添加并確保#pragma comment(lib, ws2_32.lib)出現(xiàn)在stdafx.cpp或項(xiàng)目屬性 → 鏈接器 → 輸入 → 附加依賴項(xiàng)中。2.3 編譯前必改的 4 個(gè)硬編碼參數(shù)工程中必然存在以下四處需人工修正的常量搜索127.0.0.1、8080、UDP等關(guān)鍵詞即可定位文件位置原始代碼示例修改建議說明MFC-TCP-UDPDlg.cppm_strIP _T(127.0.0.1);改為你的目標(biāo)服務(wù)器 IP如192.168.1.100TCP 客戶端連接地址UDP 發(fā)送目標(biāo)地址MFC-TCP-UDPDlg.cppm_nPort 8080;改為實(shí)際監(jiān)聽端口如5000TCP 服務(wù)端 bind 端口 / UDP recvfrom 端口MFC-TCP-UDPDlg.cppm_socket.Create(8080, SOCK_STREAM);端口號(hào)需與上一行m_nPort一致Create()第二參數(shù)為本地綁定端口必須與 UI 輸入或默認(rèn)值同步MFC-TCP-UDPDlg.cppsendto(m_sockUDP, buf, len, 0, (SOCKADDR*)addr, sizeof(addr));addr.sin_port htons(5000);UDP 發(fā)送前必須用htons()轉(zhuǎn)換端口號(hào)否則目標(biāo)機(jī)收不到大端序要求注意htons()是強(qiáng)制要求。Windows x86 是小端序而網(wǎng)絡(luò)字節(jié)序是大端序。若忘記轉(zhuǎn)換sendto()發(fā)出的端口號(hào)會(huì)被解釋為0x0013十進(jìn)制 19而非你期望的50000x1388。這是新手翻車最高頻的坑現(xiàn)象是“程序沒報(bào)錯(cuò)但對(duì)方收不到包”。2.4 啟動(dòng)調(diào)試驗(yàn)證 TCP 連接與 UDP 收發(fā)的最小閉環(huán)編譯成功后運(yùn)行程序你會(huì)看到一個(gè)帶 4 個(gè)區(qū)域的對(duì)話框① IP 地址輸入框TCP/UDP 共用② 端口輸入框③ “TCP 連接”按鈕觸發(fā)OnBnClickedBtnTcpConnect()④ “UDP 發(fā)送”按鈕觸發(fā)OnBnClickedBtnUdpSend()驗(yàn)證 TCP 連接閉環(huán)啟動(dòng)一個(gè) TCP 服務(wù)端如nc -lvp 5000或用 Python 寫socket.socket().bind((0.0.0.0,5000))在 MFC 程序中填入127.0.0.1:5000點(diǎn)「TCP 連接」觀察OnConnect()回調(diào)是否被觸發(fā)斷點(diǎn)打在m_socket.Connect()后的if (m_socket.IsConnected())若連接成功m_editLog日志框應(yīng)顯示TCP connected to 127.0.0.1:5000驗(yàn)證 UDP 收發(fā)閉環(huán)啟動(dòng) UDP 監(jiān)聽端如nc -u -lvp 5000在 MFC 程序中填入127.0.0.1:5000在發(fā)送編輯框輸入HELLO點(diǎn)「UDP 發(fā)送」查看nc終端是否收到HELLO字符串關(guān)鍵檢查recvfrom()是否設(shè)置了sizeof(SOCKADDR_IN)作為地址長度參數(shù)錯(cuò)誤寫成sizeof(SOCKADDR)會(huì)導(dǎo)致WSAEINVAL錯(cuò)誤。3. 核心機(jī)制拆解MFC 如何把 Winsock API 嵌入消息驅(qū)動(dòng)模型3.1WSAAsyncSelect()MFC Socket 的心跳引擎MFC 的CAsyncSocket類并非封裝了select()或poll()而是重度依賴 Windows 特有的異步選擇模型WSAAsyncSelect()。其本質(zhì)是將 socket 句柄與一個(gè)窗口句柄HWND綁定當(dāng) socket 上發(fā)生指定事件如 FD_READ、FD_WRITE、FD_CLOSE時(shí)系統(tǒng)向該窗口投遞自定義消息如 WM_SOCKET。在MFC-TCP-UDPDlg.cpp中你一定會(huì)找到類似代碼// 在 OnInitDialog() 中初始化 socket 并注冊(cè)異步通知 m_socket.Create(); m_socket.AsyncSelect(FD_READ | FD_WRITE | FD_CLOSE); // 注冊(cè)事件這行AsyncSelect()調(diào)用背后MFC 實(shí)際執(zhí)行的是::WSAAsyncSelect(m_socket.m_hSocket, m_hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);其中m_hWnd是對(duì)話框窗口句柄WM_SOCKET是用戶自定義消息通常定義為#define WM_SOCKET (WM_USER 100)。這意味著所有網(wǎng)絡(luò) I/O 事件最終都變成 Windows 消息進(jìn)入 MFC 的CWnd::WindowProc()消息循環(huán)。為什么不用WSAEventSelect()因?yàn)楹笳咝枰~外創(chuàng)建WSAEVENT對(duì)象并配合WaitForMultipleObjects()在單窗口 GUI 程序中遠(yuǎn)不如消息驅(qū)動(dòng)直觀。MFC 選擇前者是向開發(fā)效率妥協(xié)的務(wù)實(shí)設(shè)計(jì)。3.2 消息映射ON_MESSAGE(WM_SOCKET, OnSocketNotify)的真實(shí)作用在MFC-TCP-UDPDlg.h的消息映射宏中你將看到BEGIN_MESSAGE_MAP(CMFCUDPTCPSockDlg, CDialogEx) ON_MESSAGE(WM_SOCKET, CMFCUDPTCPSockDlg::OnSocketNotify) // ... 其他按鈕消息 END_MESSAGE_MAP()而OnSocketNotify()函數(shù)簽名必須為LRESULT CMFCUDPTCPSockDlg::OnSocketNotify(WPARAM wParam, LPARAM lParam) { SOCKET sock (SOCKET)wParam; // 觸發(fā)事件的 socket 句柄 LONG event WSAGETSELECTEVENT(lParam); // 事件類型FD_READ / FD_WRITE / FD_CLOSE LONG error WSAGETSELECTERROR(lParam); // 錯(cuò)誤碼如有 if (event FD_READ sock m_socket.m_hSocket) { char buf[1024] {0}; int nRet recv(sock, buf, sizeof(buf)-1, 0); if (nRet 0) { buf[nRet] \0; m_editLog.SetWindowText(_T(TCP received: ) CString(buf)); } } return 0; }這里的關(guān)鍵細(xì)節(jié)wParam是 socket 句柄不是this指針 —— 你必須自己判斷是哪個(gè) socket 觸發(fā)的事件工程中可能有多個(gè)CAsyncSocket成員lParam需用WSAGETSELECTEVENT()和WSAGETSELECTERROR()宏解包不能直接當(dāng)整數(shù)用FD_READ事件不代表“有完整應(yīng)用層消息到達(dá)”只表示 socket 接收緩沖區(qū)有數(shù)據(jù)可讀可能只有 1 字節(jié)。必須循環(huán)recv()直到返回SOCKET_ERROR且WSAGetLastError() WSAEWOULDBLOCK否則會(huì)漏數(shù)據(jù)。3.3 TCP 服務(wù)端的accept()如何不阻塞 UI在 MFC 工程中TCP 服務(wù)端邏輯通常放在OnBnClickedBtnTcpListen()中void CMFCUDPTCPSockDlg::OnBnClickedBtnTcpListen() { m_serverSocket.Create(m_nPort, SOCK_STREAM); m_serverSocket.Listen(); // 非阻塞因?yàn)?AsyncSelect 已注冊(cè) FD_ACCEPT }注意Listen()本身不阻塞但真正的連接建立發(fā)生在FD_ACCEPT事件中LRESULT CMFCUDPTCPSockDlg::OnSocketNotify(WPARAM wParam, LPARAM lParam) { if (WSAGETSELECTEVENT(lParam) FD_ACCEPT) { SOCKET clientSock accept((SOCKET)wParam, NULL, NULL); // 創(chuàng)建新 CAsyncSocket 對(duì)象管理 clientSock CClientSocket* pClient new CClientSocket(); pClient-Attach(clientSock); // 將句柄交給新對(duì)象 pClient-AsyncSelect(FD_READ | FD_CLOSE); m_clientList.AddTail(pClient); // 保存指針避免析構(gòu) } }這里accept()返回的新 socket 必須立即Attach()到新CAsyncSocket實(shí)例并調(diào)用AsyncSelect()注冊(cè)事件。否則該 socket 的FD_READ永遠(yuǎn)不會(huì)觸發(fā) —— 因?yàn)閃SAAsyncSelect()只對(duì)顯式注冊(cè)的句柄生效。血淚經(jīng)驗(yàn)CAsyncSocket對(duì)象必須動(dòng)態(tài)分配new且不能在OnSocketNotify()返回前delete。MFC 不會(huì)自動(dòng)管理其生命周期。我曾因在FD_CLOSE事件中直接delete this導(dǎo)致m_clientList中懸垂指針程序在后續(xù)遍歷時(shí)崩潰。3.4 UDP 的recvfrom()為何必須用SOCKADDR_IN而非SOCKADDRUDP 是無連接協(xié)議recvfrom()需要返回發(fā)送方的 IP 和端口因此必須提供足夠大的地址緩沖區(qū)。常見錯(cuò)誤寫法SOCKADDR addr; // ? 錯(cuò)只有 16 字節(jié)不夠存 IPv4 地址 int addrLen sizeof(addr); recvfrom(m_sockUDP, buf, len, 0, addr, addrLen);正確寫法在MFC-TCP-UDPDlg.cpp中必查SOCKADDR_IN addr; // ? 正確IPv4 地址結(jié)構(gòu)含 sin_addr, sin_port int addrLen sizeof(addr); int nRet recvfrom(m_sockUDP, buf, len, 0, (SOCKADDR*)addr, addrLen); if (nRet 0) { CString strIP; strIP.Format(_T(%d.%d.%d.%d), (BYTE)addr.sin_addr.S_un.S_un_b.s_b1, (BYTE)addr.sin_addr.S_un.S_un_b.s_b2, (BYTE)addr.sin_addr.S_un.S_un_b.s_b3, (BYTE)addr.sin_addr.S_un.S_un_b.s_b4); CString strPort; strPort.Format(_T(:%d), ntohs(addr.sin_port)); // ntohs() 轉(zhuǎn)回主機(jī)序 m_editLog.SetWindowText(_T(UDP from ) strIP strPort _T(: ) CString(buf, nRet)); }SOCKADDR_IN總長 16 字節(jié)SOCKADDR是泛型結(jié)構(gòu)僅 16 字節(jié)但無字段定義直接傳SOCKADDR*會(huì)導(dǎo)致recvfrom()寫越界引發(fā)未定義行為。4. 避坑指南MFC 網(wǎng)絡(luò)編程中 5 個(gè)高頻翻車現(xiàn)場4.1 現(xiàn)象點(diǎn)擊「TCP 連接」按鈕后程序無響應(yīng)CPU 占用 100%原因CAsyncSocket::Connect()是異步調(diào)用但開發(fā)者在OnBnClickedBtnTcpConnect()中寫了while(!m_socket.IsConnected()) Sleep(10);這類輪詢代碼導(dǎo)致 UI 線程卡死無法處理WM_SOCKET消息。解決刪除所有Sleep()和while循環(huán)。連接結(jié)果必須在OnConnect()回調(diào)中處理CAsyncSocket派生類需重載此函數(shù)或監(jiān)聽FD_CONNECT事件。4.2 現(xiàn)象UDP 發(fā)送成功但recvfrom()收不到任何數(shù)據(jù)WSAGetLastError()返回 10035WSAEWOULDBLOCK原因UDP socket 創(chuàng)建后未調(diào)用AsyncSelect(FD_READ)或recvfrom()被放在非FD_READ事件回調(diào)中同步調(diào)用。解決確保m_sockUDP.AsyncSelect(FD_READ)在Create()后立即執(zhí)行recvfrom()只能在OnSocketNotify()中event FD_READ分支下調(diào)用。4.3 現(xiàn)象TCP 連接成功但發(fā)送中文字符串后對(duì)方收到亂碼如浣犲ソ原因MFC 默認(rèn)使用 ANSI 編碼CString構(gòu)造函數(shù)按當(dāng)前系統(tǒng)代碼頁解析而網(wǎng)絡(luò)傳輸需統(tǒng)一為 UTF-8 或 GBK。工程中m_editSend.GetWindowText()返回 ANSI 字符串直接send()會(huì)丟失信息。解決發(fā)送前轉(zhuǎn)碼CString strSend; m_editSend.GetWindowText(strSend); // 轉(zhuǎn)為 UTF-8 int len WideCharToMultiByte(CP_UTF8, 0, strSend, -1, NULL, 0, NULL, NULL); char* utf8Buf new char[len]; WideCharToMultiByte(CP_UTF8, 0, strSend, -1, utf8Buf, len, NULL, NULL); send(m_socket.m_hSocket, utf8Buf, len-1, 0); // -1 去掉末尾 \0 delete[] utf8Buf;4.4 現(xiàn)象程序退出時(shí)崩潰調(diào)用棧指向CAsyncSocket::~CAsyncSocket()原因CAsyncSocket對(duì)象在析構(gòu)時(shí)會(huì)自動(dòng)調(diào)用closesocket()但如果 socket 已被Detach()或Close()顯式關(guān)閉再次析構(gòu)會(huì)操作無效句柄。解決在對(duì)話框OnDestroy()或OnCancel()中顯式調(diào)用m_socket.Close()并在Close()后置空指針若為指針成員if (m_pServerSocket ! nullptr) { m_pServerSocket-Close(); delete m_pServerSocket; m_pServerSocket nullptr; }4.5 現(xiàn)象在同一臺(tái)機(jī)器上TCP 客戶端能連通UDP 卻始終收不到回包原因Windows 防火墻默認(rèn)阻止 UDP 入站連接且recvfrom()綁定的端口可能被其他程序占用如 Skype、Zoom 占用 5000–5050 端口段。解決臨時(shí)關(guān)閉防火墻測試控制面板 → Windows Defender 防火墻 → 啟用或關(guān)閉防火墻將 UDP 端口改為50000以上如55555避開常用端口沖突用netstat -ano | findstr :55555確認(rèn)端口未被占用5. 進(jìn)階實(shí)戰(zhàn)把 MFC-TCP-UDP 工程升級(jí)為生產(chǎn)級(jí)調(diào)試工具5.1 添加十六進(jìn)制收發(fā)視圖讓協(xié)議分析不再靠猜原始工程只支持 ASCII 文本收發(fā)但真實(shí)網(wǎng)絡(luò)協(xié)議如 Modbus TCP、FINS大量使用二進(jìn)制字段。我們需擴(kuò)展CEdit控件為十六進(jìn)制編輯器。無需重寫 UI只需在MFC-TCP-UDPDlg.h中添加#include HexEdit.h // 第三方輕量 HexEdit 控件推薦 https://github.com/robinbentley/mfc-hexedit ... CHexEdit m_hexRecv; // 接收區(qū)十六進(jìn)制視圖 CHexEdit m_hexSend; // 發(fā)送區(qū)十六進(jìn)制視圖在DoDataExchange()中關(guān)聯(lián)DDX_Control(pDX, IDC_HEX_RECV, m_hexRecv); DDX_Control(pDX, IDC_HEX_SEND, m_hexSend);然后修改OnSocketNotify()中的接收邏輯if (event FD_READ sock m_socket.m_hSocket) { BYTE buf[1024]; int nRet recv(sock, (char*)buf, sizeof(buf), 0); if (nRet 0) { m_hexRecv.SetData(buf, nRet); // 直接載入二進(jìn)制數(shù)據(jù) m_hexRecv.Invalidate(); // 刷新顯示 } }發(fā)送時(shí)同理從m_hexSend.GetData()獲取BYTE*指針調(diào)用send()。這樣你就能直接看到01 03 00 00 00 06 C4 0B這樣的 Modbus 請(qǐng)求幀無需手動(dòng)轉(zhuǎn) ASCII。5.2 實(shí)現(xiàn) UDP 廣播與組播突破單點(diǎn)通信限制原始工程只支持單播 UDP。要支持局域網(wǎng)發(fā)現(xiàn)如設(shè)備自動(dòng)注冊(cè)需啟用廣播// 發(fā)送前設(shè)置 socket 選項(xiàng) BOOL bBroadcast TRUE; setsockopt(m_sockUDP, SOL_SOCKET, SO_BROADCAST, (char*)bBroadcast, sizeof(bBroadcast)); // 目標(biāo)地址設(shè)為廣播地址 SOCKADDR_IN addr; addr.sin_family AF_INET; addr.sin_port htons(5000); addr.sin_addr.s_addr inet_addr(255.255.255.255); // 本地廣播 sendto(m_sockUDP, DISCOVER, 8, 0, (SOCKADDR*)addr, sizeof(addr));組播更進(jìn)一步需加入組播組ip_mreq mreq; mreq.imr_multiaddr.s_addr inet_addr(224.0.0.100); // 組播地址 mreq.imr_interface.s_addr htonl(INADDR_ANY); setsockopt(m_sockUDP, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)mreq, sizeof(mreq)); // 綁定時(shí)用組播地址 addr.sin_addr.s_addr inet_addr(224.0.0.100); bind(m_sockUDP, (SOCKADDR*)addr, sizeof(addr));注意組播地址范圍是224.0.0.0到239.255.255.255224.0.0.1是所有主機(jī)224.0.0.251是 mDNS。選224.0.0.100可避免與系統(tǒng)服務(wù)沖突。5.3 TCP 心跳?;钆c異常斷連檢測讓連接真正可靠CAsyncSocket不提供內(nèi)置心跳需手動(dòng)實(shí)現(xiàn)。在OnTimer()中設(shè)置SetTimer(1, 30000, NULL)每 30 秒觸發(fā)void CMFCUDPTCPSockDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent 1 m_socket.IsConnected()) { // 發(fā)送心跳包1 字節(jié) 0xFF char heartbeat 0xFF; int nRet send(m_socket.m_hSocket, heartbeat, 1, 0); if (nRet SOCKET_ERROR) { int err WSAGetLastError(); if (err WSAECONNRESET || err WSAENETRESET) { AfxMessageBox(_T(TCP connection lost!)); m_socket.Close(); m_editLog.SetWindowText(_T(Connection closed by peer)); } } } CDialogEx::OnTimer(nIDEvent); }同時(shí)在OnSocketNotify()的FD_CLOSE事件中必須區(qū)分是主動(dòng)關(guān)閉還是被動(dòng)斷連if (event FD_CLOSE) { if (WSAGETSELECTERROR(lParam) ! 0) { // 被動(dòng)斷連對(duì)方崩潰/網(wǎng)絡(luò)中斷 m_editLog.SetWindowText(_T(Remote host closed connection unexpectedly)); } else { // 主動(dòng)關(guān)閉調(diào)用 Close() m_editLog.SetWindowText(_T(Connection closed gracefully)); } }5.4 性能優(yōu)化避免CString頻繁構(gòu)造導(dǎo)致的內(nèi)存泄漏原始工程中日志輸出常寫為m_editLog.SetWindowText(_T(Received: ) strData _T( bytes));每次操作都會(huì)觸發(fā)CString內(nèi)部new[]分配大量收發(fā)時(shí)造成碎片。改為Format()一次完成CString strLog; strLog.Format(_T(Received: %s (%d bytes)), strData, nRet); m_editLog.SetWindowText(strLog);更徹底的方案用_stprintf_s()直接寫入預(yù)分配緩沖區(qū)TCHAR szLog[1024]; _stprintf_s(szLog, _T(Received: %s (%d bytes)), strData, nRet); m_editLog.SetWindowText(szLog);我在線上設(shè)備監(jiān)控項(xiàng)目中將日志拼接從CString改為TCHAR[]后連續(xù)運(yùn)行 72 小時(shí)內(nèi)存增長從 12MB 降至 2MB。這不是玄學(xué)是 MFC 字符串管理的真實(shí)代價(jià)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取