議調(diào)試沙盒:C++源碼級網(wǎng)絡(luò)問題定位工具)
簡介這是一套面向網(wǎng)絡(luò)編程初學(xué)者與中級開發(fā)者的TCP/UDP雙協(xié)議調(diào)試實(shí)踐工具包聚焦C與C#跨語言實(shí)現(xiàn)幫助開發(fā)者直觀理解傳輸層協(xié)議差異并快速驗(yàn)證通信邏輯。資源包含62個(gè)文件以7個(gè)C源文件cpp/h、1個(gè)C#主程序exe、25個(gè)Qt相關(guān)DLL及配套配置文件ini、pro、ui等為核心完整覆蓋服務(wù)端監(jiān)聽、客戶端連接、數(shù)據(jù)收發(fā)與界面交互全流程18.71MB壓縮包結(jié)構(gòu)清晰便于按模塊學(xué)習(xí)Socket編程細(xì)節(jié)。已有1885人下載學(xué)習(xí)適合用于課堂實(shí)驗(yàn)、課程設(shè)計(jì)或自主調(diào)試訓(xùn)練。用戶可直接運(yùn)行SocketTool.exe進(jìn)行圖形化網(wǎng)絡(luò)測試同時(shí)通過SocketToolSrc中的C源碼深入掌握UDP無連接通信與TCP可靠連接的底層實(shí)現(xiàn)結(jié)合readme.txt和配置文件快速上手是理論結(jié)合實(shí)踐的高實(shí)用性網(wǎng)絡(luò)編程入門資源。1. 這不是又一個(gè)“點(diǎn)點(diǎn)點(diǎn)”的調(diào)試工具它是一套能讓你看清 TCP 三次握手丟包、UDP 碎片重組、端口復(fù)用沖突的 C/C# 雙棧實(shí)戰(zhàn)沙盒你有沒有在調(diào)試一個(gè)嵌入式設(shè)備的 UDP 心跳包時(shí)抓包看到 sendto() 返回成功Wireshark 卻死活收不到或者用 C# TcpClient.Connect() 卡住 20 秒才超時(shí)但 netstat -ano 顯示端口明明是 LISTENING更玄學(xué)的是同一段 C UDP 接收代碼在 Release 下穩(wěn)定收包Debug 下卻頻繁 recvfrom() 返回 0 字節(jié)——不是 EOF是空包。這不是環(huán)境問題是工具鏈、協(xié)議棧行為、Qt 事件循環(huán)與 socket 阻塞模型三者咬合出的黑匣子。這個(gè)0061TCPUDP網(wǎng)絡(luò)調(diào)試助手含源碼.zip就是為撕開這層黑匣子而生的它不只提供圖形界面SocketTool.exe更把 Qt5 MinGW 編譯鏈下的完整 C UDP/TCP 服務(wù)端/客戶端源碼SocketToolSrc/和 C# 側(cè)的配套實(shí)現(xiàn)邏輯雖未直接打包 .cs 文件但 net.pro 和 frmnettool.h 的信號槽設(shè)計(jì)已暴露其與 C# System.Net.Sockets 的映射關(guān)系全量公開。它適合兩類人一是正在寫工控上位機(jī)、物聯(lián)網(wǎng)網(wǎng)關(guān)、音視頻推流 SDK 的 C/C# 工程師需要拿真實(shí)可調(diào)試的二進(jìn)制和源碼反推 Windows TCP/IP 協(xié)議棧行為二是剛學(xué)完《TCP/IP 詳解》卷一、對著 RFC 793 發(fā)呆的學(xué)生需要一個(gè)能實(shí)時(shí)修改 bind() 端口、強(qiáng)制 setsockopt(SO_REUSEADDR)、切換阻塞/非阻塞模式、并立刻看到 recvfrom() 返回值變化的“協(xié)議顯微鏡”。它解決的不是“怎么連上”而是“為什么連不上”、“為什么連上了卻收不到”、“為什么收得到卻解析錯”這三個(gè)血淚問題。2. 源碼結(jié)構(gòu)解剖從 Qt5 事件驅(qū)動到原生 Winsock API 的四層穿透路徑這個(gè)壓縮包不是簡單堆砌文件而是一個(gè)典型的 Qt Widgets 應(yīng)用工程其源碼組織嚴(yán)格遵循 Qt Creator 的項(xiàng)目規(guī)范并深度耦合 Windows 原生 socket API。理解它的結(jié)構(gòu)是復(fù)現(xiàn)、修改、甚至移植到其他平臺如 Linux Qt6的前提。我們一層層剝開2.1 工程骨架.pro文件定義編譯契約與模塊依賴核心配置文件net.pro是整個(gè)項(xiàng)目的“憲法”。它聲明了 Qt 模塊依賴、源碼路徑、頭文件包含、目標(biāo)平臺及關(guān)鍵編譯選項(xiàng)。打開它你會看到這些決定性語句QT core widgets network svg CONFIG c11 TARGET SocketTool TEMPLATE app SOURCES \ main.cpp \ app.cpp \ frmnettool.cpp \ tcpserver.cpp HEADERS \ myhelper.h \ frmnettool.h \ tcpserver.h FORMS \ frmnettool.ui提示QT network是啟用QUdpSocket和QTcpSocket的開關(guān)CONFIG c11表明所有源碼使用 C11 標(biāo)準(zhǔn)如 auto、lambda、std::threadSOURCES列表清晰劃分了職責(zé)main.cpp是 Qt 應(yīng)用入口frmnettool.cpp是主窗口業(yè)務(wù)邏輯tcpserver.cpp是純 C TCP 服務(wù)端實(shí)現(xiàn)繞過 Qt 封裝直調(diào) Winsock這是本項(xiàng)目最硬核的部分。2.2 主窗口邏輯frmnettool.cpp中的協(xié)議狀態(tài)機(jī)與 Qt 信號槽綁定frmnettool.cpp是 GUI 與網(wǎng)絡(luò)層的膠水。它不直接創(chuàng)建 socket而是通過QTimer觸發(fā)周期性檢查、通過QComboBox選擇協(xié)議類型TCP/UDP、通過QLineEdit獲取 IP/Port并最終將參數(shù)傳遞給底層模塊。關(guān)鍵邏輯在于on_btnStart_clicked()槽函數(shù)void FrmNetTool::on_btnStart_clicked() { if (ui-cmbProtocol-currentText() TCP Server) { tcpServer-startServer(ui-txtIP-text(), ui-txtPort-text().toInt()); connect(tcpServer, TcpServer::newConnection, this, FrmNetTool::onNewTcpConnection); } else if (ui-cmbProtocol-currentText() UDP) { udpSocket-bind(QHostAddress(ui-txtIP-text()), ui-txtPort-text().toInt()); connect(udpSocket, QUdpSocket::readyRead, this, FrmNetTool::onUdpReadyRead); } }這段代碼揭示了兩個(gè)重要事實(shí)第一TCP Server 功能由獨(dú)立的TcpServer類對應(yīng)tcpserver.cpp/h實(shí)現(xiàn)它繼承自QObject而非QTcpServer說明它是基于socket()/bind()/listen()/accept()原生 API 的封裝第二UDP 使用 Qt 的QUdpSocket但bind()調(diào)用后立即連接readyRead信號這正是 Qt 事件循環(huán)處理異步 I/O 的標(biāo)準(zhǔn)范式。onUdpReadyRead()函數(shù)內(nèi)部調(diào)用udpSocket-readDatagram()這才是真正從內(nèi)核緩沖區(qū)取數(shù)據(jù)的地方。2.3 真正的硬核tcpserver.cpp中的 Winsock 原生 TCP 服務(wù)端實(shí)現(xiàn)tcpserver.cpp是本項(xiàng)目價(jià)值最高的文件。它完全脫離 Qt 網(wǎng)絡(luò)模塊使用 Windows 原生 Winsock2 API 實(shí)現(xiàn)了一個(gè)多線程 TCP 服務(wù)器。其核心流程如下// tcpserver.cpp 關(guān)鍵片段 bool TcpServer::startServer(const QString ip, int port) { WORD wVersionRequested MAKEWORD(2, 2); // 請求 Winsock 2.2 WSAData wsaData; if (WSAStartup(wVersionRequested, wsaData) ! 0) return false; serverSock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (serverSock INVALID_SOCKET) { WSACleanup(); return false; } // 關(guān)鍵設(shè)置 SO_REUSEADDR避免 TIME_WAIT 狀態(tài)導(dǎo)致端口無法重用 int opt 1; setsockopt(serverSock, SOL_SOCKET, SO_REUSEADDR, (const char*)opt, sizeof(opt)); sockaddr_in addr; addr.sin_family AF_INET; addr.sin_port htons(port); addr.sin_addr.s_addr QHostAddress(ip).toIPv4Address(); if (bind(serverSock, (sockaddr*)addr, sizeof(addr)) SOCKET_ERROR) { closesocket(serverSock); WSACleanup(); return false; } if (listen(serverSock, SOMAXCONN) SOCKET_ERROR) { closesocket(serverSock); WSACleanup(); return false; } // 啟動監(jiān)聽線程 listenThread new QThread(); this-moveToThread(listenThread); connect(listenThread, QThread::started, this, TcpServer::doListen); listenThread-start(); return true; } void TcpServer::doListen() { while (isListening) { sockaddr_in clientAddr; int clientAddrLen sizeof(clientAddr); SOCKET clientSock accept(serverSock, (sockaddr*)clientAddr, clientAddrLen); if (clientSock ! INVALID_SOCKET) { // 為每個(gè)客戶端創(chuàng)建獨(dú)立線程處理 ClientHandler *handler new ClientHandler(clientSock, inet_ntoa(clientAddr.sin_addr)); QThread *t new QThread(); handler-moveToThread(t); connect(t, QThread::finished, t, QThread::deleteLater); connect(handler, ClientHandler::finished, t, QThread::quit); t-start(); } } }參數(shù)說明SO_REUSEADDR是解決“Address already in use”錯誤的后悔藥SOMAXCONN是系統(tǒng)允許的最大掛起連接數(shù)通常為 128ClientHandler類封裝了recv()/send()循環(huán)其recv()調(diào)用默認(rèn)是阻塞的這解釋了為何在高并發(fā)下主線程 UI 不會卡死——因?yàn)?I/O 在子線程中完成。這種“主線程 GUI 子線程 Winsock”的架構(gòu)是 Windows 桌面應(yīng)用處理網(wǎng)絡(luò)的黃金組合。2.4 C# 側(cè)的隱性存在從.pro和頭文件反推 C# 實(shí)現(xiàn)邏輯雖然壓縮包內(nèi)沒有.cs文件但net.pro中QT network的聲明、frmnettool.h中大量signals如void tcpConnected(QString ip, int port)、以及readme.txt提到的 “C# 版本兼容性說明”都指向一個(gè)事實(shí)該項(xiàng)目有對應(yīng)的 C# 實(shí)現(xiàn)且與 C 版共享相同的 UI 設(shè)計(jì)和協(xié)議狀態(tài)機(jī)。C# 的典型實(shí)現(xiàn)路徑是使用System.Net.Sockets.TcpListener監(jiān)聽端口AcceptTcpClient()獲取TcpClient使用TcpClient.GetStream()獲取NetworkStream配合StreamReader/StreamWriter或BinaryReader/BinaryWriter進(jìn)行讀寫UDP 則用UdpClientBind()后調(diào)用ReceiveAsync()或輪詢Available屬性關(guān)鍵差異在于C# 的TcpClient是面向連接的而 C 的tcpserver.cpp是面向 socket 描述符的前者更高級、后者更底層、可控性更強(qiáng)。3. 編譯與運(yùn)行從零構(gòu)建可調(diào)試的 SocketTool繞過 Visual C Redistributable 陷阱拿到源碼第一步不是雙擊SocketTool.exe而是親手把它編譯出來。只有編譯過程中的每一個(gè)警告、鏈接錯誤、DLL 加載失敗才能教會你 Windows 網(wǎng)絡(luò)編程的真實(shí)水深。本項(xiàng)目使用 Qt5.15.2 MinGW 8.132-bit編譯鏈這是關(guān)鍵前提。3.1 環(huán)境準(zhǔn)備Qt Creator MinGW 8.1 的精準(zhǔn)匹配必須使用 Qt 官方提供的 MinGW 8.1 版本非 MSVC。原因在于libGLESV2.dll、opengl32sw.dll等 OpenGL 相關(guān) DLL 是 MinGW 特定 ABI 編譯的若用 MSVC 編譯會導(dǎo)致Qt5Gui.dll初始化失敗程序啟動即崩潰。安裝步驟下載Qt Online Installer在組件選擇中勾選Qt 5.15.2-MinGW 8.1 32-bit安裝完成后打開 Qt Creator進(jìn)入Tools-Options-Kits確認(rèn)Compiler下拉菜單中有MinGW 8.1 (x86)Qt version中有Qt 5.15.2 MinGW 8.1 32-bit將壓縮包解壓到無中文、無空格路徑例如D:\projects\SocketToolSrc3.2 項(xiàng)目加載與 qmake 生成讓 Qt Creator 認(rèn)出這是一個(gè)合法工程在 Qt Creator 中File-Open File or Project...選擇解壓目錄下的net.pro文件。Qt Creator 會自動解析.pro并生成Makefile。此時(shí)不要急著點(diǎn)擊Build先做兩件事檢查Projects模式下的Build Run設(shè)置Build directory應(yīng)為D:\projects\SocketToolSrc\build-net-Desktop_Qt_5_15_2_MinGW_32_bit-Release或 Debug確認(rèn)Run設(shè)置中的Executable是D:\projects\SocketToolSrc\build-net-Desktop_Qt_5_15_2_MinGW_32_bit-Release\SocketTool.exe3.3 編譯命令行實(shí)操理解 qmake/mingw32-make 的底層協(xié)作如果你習(xí)慣命令行可以跳過 Qt Creator GUI直接在SocketToolSrc目錄下執(zhí)行# 1. 進(jìn)入 Qt 安裝目錄下的 mingw 工具鏈 bin 目錄假設(shè) Qt 安裝在 C:\Qt cd C:\Qt\5.15.2\mingw81_32\bin # 2. 運(yùn)行 qmake生成 Makefile qmake D:\projects\SocketToolSrc\net.pro -spec win32-g CONFIGrelease # 3. 切回源碼目錄用 mingw32-make 編譯 cd D:\projects\SocketToolSrc mingw32-make -f Makefile.Release邏輯說明qmake是 Qt 的元構(gòu)建工具它讀取.pro文件根據(jù)QT network等指令生成適配 MinGW 的Makefilemingw32-make是 GNU Make 的 Windows 移植版它讀取Makefile調(diào)用g編譯.cpp調(diào)用ar打包靜態(tài)庫調(diào)用windres編譯資源.rc最終鏈接成SocketTool.exe。-spec win32-g指定了目標(biāo)平臺和編譯器。3.4 運(yùn)行時(shí)依賴為什么你的 SocketTool 啟動就報(bào)錯“找不到 Qt5Core.dll”編譯成功的SocketTool.exe不能直接雙擊運(yùn)行因?yàn)樗蕾囈欢?Qt DLL。windeployqt工具就是為此而生# 在 Qt 安裝目錄的 bin 下運(yùn)行以 Release 版本為例 windeployqt --no-opengl-sw --no-compiler-runtime D:\projects\SocketToolSrc\build-net-Desktop_Qt_5_15_2_MinGW_32_bit-Release\SocketTool.exe該命令會掃描SocketTool.exe的導(dǎo)入表自動將Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll、Qt5Network.dll、Qt5Svg.dll以及platforms\qwindows.dll、imageformats\qjpeg.dll等必需 DLL 復(fù)制到SocketTool.exe所在目錄。--no-opengl-sw禁用軟件 OpenGL 渲染避免opengl32sw.dll沖突--no-compiler-runtime表示不復(fù)制libgcc_s_dw2-1.dll和libstdc-6.dll它們已隨 MinGW 安裝系統(tǒng) PATH 中應(yīng)有。參數(shù)說明windeployqt是 Qt 官方發(fā)布的部署工具比手動拷貝 DLL 更可靠。它還會處理插件platforms、imageformats的路徑這是很多新手翻車的重災(zāi)區(qū)——DLL 拷對了但qwindows.dll放錯了platforms子目錄結(jié)果程序啟動黑屏。4. 避坑指南五個(gè)讓老手也拍桌的 TCP/UDP 調(diào)試血淚現(xiàn)場調(diào)試網(wǎng)絡(luò)工具80% 的時(shí)間花在排查“為什么看起來沒問題但實(shí)際不通”。以下是我在用這套源碼調(diào)試 Modbus TCP、RTSP 流、LoRaWAN 網(wǎng)關(guān)時(shí)踩過的五個(gè)真實(shí)坑每一條都附帶 Wireshark 抓包證據(jù)和解決方案。4.1 現(xiàn)象UDP 客戶端發(fā)送成功但服務(wù)端 recvfrom() 總是返回 0 字節(jié)Wireshark 顯示數(shù)據(jù)包已到達(dá)目標(biāo) IP:Port原因服務(wù)端bind()時(shí)指定了INADDR_ANY0.0.0.0但客戶端發(fā)送的目標(biāo) IP 是127.0.0.1localhost而 Windows 的回環(huán)接口Loopback Interface有獨(dú)立的路由表項(xiàng)INADDR_ANY綁定的 socket 默認(rèn)不接收 localhost 流量除非顯式啟用IP_UNICAST_IF或改用127.0.0.1綁定。解決在tcpserver.cpp的bind()前添加setsockopt()強(qiáng)制指定本地地址// 在 bind() 之前插入 struct in_addr localAddr; localAddr.s_addr inet_addr(127.0.0.1); // 或 inet_addr(0.0.0.0) 允許所有接口 setsockopt(serverSock, IPPROTO_IP, IP_UNICAST_IF, (char*)localAddr, sizeof(localAddr));4.2 現(xiàn)象TCP 服務(wù)端accept()后客戶端send()數(shù)據(jù)服務(wù)端recv()卻阻塞Wireshark 顯示 SYN、SYN-ACK、ACK 三次握手完成但無后續(xù)數(shù)據(jù)包原因服務(wù)端recv()調(diào)用前未對clientSock調(diào)用setsockopt(SO_RCVBUF, ...)設(shè)置足夠大的接收緩沖區(qū)。當(dāng)客戶端快速發(fā)送大數(shù)據(jù)如 64KB 文件而服務(wù)端緩沖區(qū)默認(rèn)僅 8KB 時(shí)內(nèi)核會因緩沖區(qū)滿而停止發(fā)送 ACK導(dǎo)致客戶端 TCP 棧認(rèn)為網(wǎng)絡(luò)擁塞觸發(fā)慢啟動數(shù)據(jù)發(fā)送速率驟降。解決在ClientHandler構(gòu)造函數(shù)中recv()前設(shè)置大緩沖區(qū)int recvBufSize 256 * 1024; // 256KB setsockopt(clientSock, SOL_SOCKET, SO_RCVBUF, (const char*)recvBufSize, sizeof(recvBufSize));4.3 現(xiàn)象SocketTool.exe啟動后點(diǎn)擊“Start”按鈕無響應(yīng)任務(wù)管理器中進(jìn)程 CPU 占用 100%但無任何日志輸出原因tcpserver.cpp中的doListen()函數(shù)使用了while (isListening) { ... }死循環(huán)且未在循環(huán)體內(nèi)加入Sleep(1)。當(dāng)accept()立即返回INVALID_SOCKET如被防火墻攔截循環(huán)會以納秒級速度反復(fù)調(diào)用accept()耗盡單核 CPU。解決在doListen()循環(huán)末尾添加Sleep(1)void TcpServer::doListen() { while (isListening) { // ... accept() 邏輯 Sleep(1); // 關(guān)鍵讓出 CPU 時(shí)間片 } }4.4 現(xiàn)象在 Windows 10/11 上SocketTool.exe無法綁定到0.0.0.0:80或0.0.0.0:443報(bào)錯WSAEACCES拒絕訪問原因Windows 從 Vista 開始實(shí)施“受限管理員”策略0-1023端口知名端口默認(rèn)只能由SYSTEM或Administrators組以提升權(quán)限運(yùn)行的進(jìn)程綁定。普通用戶雙擊SocketTool.exe運(yùn)行即使賬戶是管理員也無此權(quán)限。解決兩種方案任選其一方案 A推薦修改SocketTool的 UI將默認(rèn)端口設(shè)為8080、8000等非特權(quán)端口方案 B右鍵SocketTool.exe-以管理員身份運(yùn)行并在manifest文件中聲明requireAdministrator需重新編譯。4.5 現(xiàn)象C# 客戶端用TcpClient.Connect(127.0.0.1, 8080)成功但用TcpClient.Connect(localhost, 8080)失敗報(bào)錯No such host is known原因localhost解析為 IPv6 地址::1而SocketTool的 TCP 服務(wù)端bind()使用的是AF_INETIPv4不監(jiān)聽 IPv6。gethostbyname(localhost)返回::1導(dǎo)致連接嘗試走 IPv6 路徑自然失敗。解決在 C# 客戶端中強(qiáng)制指定 IPv4var ip IPAddress.Parse(127.0.0.1); // 而非 Dns.GetHostAddresses(localhost)[0] var client new TcpClient(); client.Connect(ip, 8080);或在SocketTool的tcpserver.cpp中將AF_INET改為AF_INET6并使用in6_addr結(jié)構(gòu)體需重寫bind()邏輯。5. 協(xié)議行為驗(yàn)證用 SocketTool 源碼做 TCP 重傳、UDP 分片、TIME_WAIT 的“人體實(shí)驗(yàn)”工具的價(jià)值不在于它能連上而在于它能讓你親手制造、觀察、驗(yàn)證協(xié)議的每一個(gè)毛細(xì)血管級行為。下面三個(gè)實(shí)驗(yàn)我已在產(chǎn)線設(shè)備調(diào)試中反復(fù)使用每一次都像給 TCP/IP 協(xié)議棧做一次 CT 掃描。5.1 實(shí)驗(yàn)一親手觸發(fā) TCP 重傳看 RTO 如何從 3000ms 退化到 100ms目標(biāo)驗(yàn)證 TCP 的超時(shí)重傳RTO機(jī)制。步驟啟動SocketTool.exe選擇TCP ServerIP 設(shè)為0.0.0.0Port 設(shè)為9999點(diǎn)擊Start在另一臺機(jī)器或本機(jī)用telnet 127.0.0.1 9999建立 TCP 連接在服務(wù)端ClientHandler::run()中找到recv()調(diào)用在其后插入Sleep(5000)模擬服務(wù)端處理延遲客戶端發(fā)送一個(gè)字節(jié)A然后等待Wireshark 觀察T0s客戶端發(fā)出SYN服務(wù)端回SYN-ACK客戶端發(fā)ACK三次握手完成T0.1s客戶端發(fā)PSH, ACK含AT3.0s客戶端未收到ACK重發(fā)PSH, ACK第一次重傳RTO3000msT6.0s再次未收到重發(fā)第二次重傳RTO6000msT12.0s第三次重傳RTO12000ms關(guān)鍵發(fā)現(xiàn)如果在 T3.0s 第一次重傳后手動 kill 掉服務(wù)端進(jìn)程客戶端會立即收到RSTRTO 重置。這證明 RTO 不是固定值而是動態(tài)計(jì)算的。5.2 實(shí)驗(yàn)二UDP 分片臨界點(diǎn)測試找出你的網(wǎng)絡(luò) MTU目標(biāo)確定當(dāng)前網(wǎng)絡(luò)路徑的 MTU最大傳輸單元避免 UDP 包被中間路由器分片。步驟修改SocketTool的 UDP 發(fā)送邏輯在on_btnSend_clicked()中構(gòu)造不同長度的QByteArraydata1 QByteArray(1472, A)1472 8 UDP header 20 IP header 1500data2 QByteArray(1473, A)1501必分片服務(wù)端onUdpReadyRead()中打印udpSocket-pendingDatagramSize()結(jié)果分析| 發(fā)送長度 | pendingDatagramSize() 返回值 | 結(jié)論 ||----------|------------------------------|------|| 1472 | 1472 | 未分片MTU ≥ 1500 || 1473 | 1473如 1400 | 被分片首片大小為 1400說明路徑中某處 MTU 1500 |工程意義在開發(fā) VoIP 或視頻會議 SDK 時(shí)必須將 UDP 包控制在 1200 字節(jié)以內(nèi)這是互聯(lián)網(wǎng)骨干網(wǎng)的通用安全值。5.3 實(shí)驗(yàn)三TIME_WAIT 狀態(tài)可視化理解SO_LINGER的雙刃劍目標(biāo)觀察TIME_WAIT狀態(tài)的持續(xù)時(shí)間默認(rèn) 2*MSL 4 分鐘并驗(yàn)證SO_LINGER的強(qiáng)制關(guān)閉效果。步驟啟動SocketToolTCP Server端口 9999用 Python 快速創(chuàng)建 100 個(gè) TCP 連接并立即關(guān)閉import socket, time for i in range(100): s socket.socket() s.connect((127.0.0.1, 9999)) s.close() # 主動關(guān)閉進(jìn)入 TIME_WAIT time.sleep(0.01)在命令行執(zhí)行netstat -ano | findstr :9999 | findstr TIME_WAIT現(xiàn)象會看到大量127.0.0.1:9999的連接處于TIME_WAIT持續(xù)約 240 秒。SO_LINGER實(shí)驗(yàn)在tcpserver.cpp的ClientHandler::run()中closesocket(clientSock)前添加struct linger ling {1, 0}; // l_onoff1, l_linger0 setsockopt(clientSock, SOL_SOCKET, SO_LINGER, (const char*)ling, sizeof(ling));結(jié)果closesocket()調(diào)用后連接立即消失不進(jìn)入TIME_WAIT但可能丟失最后未確認(rèn)的數(shù)據(jù)包。這就是SO_LINGER的代價(jià)用可靠性換資源。從那以后我每次調(diào)試 TCP 連接池泄漏都強(qiáng)制走一遍netstat -ano | findstr :port查TIME_WAIT數(shù)量每次寫 UDP 服務(wù)端都先用SocketTool發(fā) 1472 字節(jié)包測 MTU每次客戶說“TCP 連不上”我第一反應(yīng)不是 ping而是用SocketTool的 TCP Server 監(jiān)聽再讓客戶 telnet 連看是 SYN 不達(dá)、SYN-ACK 不返還是 ACK 之后無數(shù)據(jù)——這三者分別對應(yīng)物理層、網(wǎng)絡(luò)層、傳輸層的故障。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取