對(duì)端檢測(cè))
后端網(wǎng)絡(luò)【免費(fèi)下載鏈接】cpp-httplibA C header-only HTTP/HTTPS server and client library項(xiàng)目地址https://gitcode.com/GitHub_Trending/cp/cpp-httplib點(diǎn)擊查看免費(fèi)下載本篇指南聚焦 cpp-httplib 頭文件庫中 WebSocket 連接的心跳Heartbeat機(jī)制如何通過自動(dòng)發(fā)送 Ping 幀保持長連接不被代理或負(fù)載均衡器因空閑而斷開如何利用 Pong 超時(shí)max missed pongs檢測(cè)靜默失聯(lián)的對(duì)端以及如何在服務(wù)端與客戶端兩側(cè)正確設(shè)置心跳間隔。讀完本文你將掌握set_websocket_ping_interval()、set_websocket_max_missed_pongs()兩個(gè)核心 API 的用法、編譯期默認(rèn)宏的調(diào)優(yōu)方式以及心跳線程的底層實(shí)現(xiàn)原理并能結(jié)合倉庫內(nèi)的測(cè)試用例驗(yàn)證行為。為什么 WebSocket 需要心跳WebSocket 連接通常會(huì)保持很長時(shí)間。鏈路中間的代理Proxy或負(fù)載均衡器Load Balancer往往會(huì)在連接長時(shí)間空閑idle時(shí)將其斷開以回收資源。即便 TCP 連接兩端都還健在中間設(shè)備一斷兩端都會(huì)失去這條通道。解決思路是在空閑期間周期性地發(fā)送 Ping 控制幀讓連接持續(xù)有流量活躍從而騙過空閑超時(shí)判定。RFC 6455 協(xié)議規(guī)定 Ping 幀必須由對(duì)端以 Pong 幀應(yīng)答這正是 WebSocket 協(xié)議自帶的?;顧C(jī)制。cpp-httplib 把這件事做成了內(nèi)置能力你只需要設(shè)置一個(gè)間隔庫就會(huì)自動(dòng)定時(shí)發(fā)送 Ping 幀無需在應(yīng)用代碼里手工處理。服務(wù)端設(shè)置心跳間隔服務(wù)端通過Server::set_websocket_ping_interval()設(shè)置心跳間隔單位是秒。設(shè)置在WebSocket()注冊(cè)路由之前或之后均可它作用于該服務(wù)端接受的所有 WebSocket 連接httplib::Server svr; svr.set_websocket_ping_interval(30); // 每 30 秒發(fā)送一次 Ping svr.WebSocket(/chat, [](const auto req, auto ws) { // ... });只需傳入以秒為單位的間隔。此后該服務(wù)端接受的每條 WebSocket 連接都會(huì)按這個(gè)間隔被自動(dòng) Ping 一次。該接口還提供std::chrono重載語義完全等價(jià)using namespace std::chrono_literals; svr.set_websocket_ping_interval(30s);從源碼看chrono 重載會(huì)通過detail::duration_to_sec_and_usec()把時(shí)長換算成秒后落到同一個(gè)time_t版本最終寫入成員websocket_ping_interval_sec_見 httplib.h。服務(wù)端在升級(jí)握手完成后構(gòu)造ws::WebSocket時(shí)會(huì)把該成員連同websocket_max_missed_pongs_一起傳給每個(gè)連接見 httplib.h所以這個(gè)設(shè)置是服務(wù)端全局生效的??蛻舳嗽O(shè)置心跳間隔客戶端的 API 與服務(wù)端完全一致WebSocketClient同樣提供set_websocket_ping_interval()httplib::ws::WebSocketClient cli(ws://localhost:8080/chat); cli.set_websocket_ping_interval(30); cli.connect();注意客戶端必須在調(diào)用connect()之前設(shè)置。源碼中WebSocketClient::set_websocket_ping_interval()只寫入成員websocket_ping_interval_sec_見 httplib.h實(shí)際的心跳線程是在連接建立、ws::WebSocket對(duì)象構(gòu)造時(shí)啟動(dòng)的因此 connect 之后再改間隔不會(huì)生效。同理WebSocketClient::set_websocket_max_missed_pongs()也必須在connect()之前調(diào)用見 httplib.h。默認(rèn)值編譯期宏 CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND默認(rèn)心跳間隔由編譯期宏CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND決定在 httplib.h 中定義默認(rèn)值為30 秒#ifndef CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND #define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND 30 #endif通常不需要改動(dòng)這個(gè)宏——運(yùn)行時(shí)調(diào)用set_websocket_ping_interval()即可覆蓋默認(rèn)值。但如果你的部署環(huán)境面臨非常激進(jìn)的代理空閑超時(shí)很短可以把它調(diào)低例如在編譯時(shí)通過-DCPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND15全局生效。與心跳相關(guān)的還有一組編譯期宏同樣位于 httplib.h在調(diào)參時(shí)值得一并了解宏默認(rèn)值說明CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND30自動(dòng) Ping 心跳間隔秒CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS0禁用連續(xù) N 次 Ping 無 Pong 應(yīng)答后關(guān)閉連接CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND300服務(wù)端讀取超時(shí)兜底秒CPPHTTPLIB_WEBSOCKET_CLIENT_READ_TIMEOUT_SECOND0客戶端讀取超時(shí)秒0表示永遠(yuǎn)等待CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND5等待對(duì)端 Close 應(yīng)答的超時(shí)秒Pong 自動(dòng)應(yīng)答應(yīng)用代碼無需關(guān)心WebSocket 協(xié)議要求 Ping 幀必須以 Pong 幀應(yīng)答。cpp-httplib 對(duì)此是全自動(dòng)處理的——無論服務(wù)端還是客戶端收到 Ping 幀都會(huì)在內(nèi)部直接回送 Pong應(yīng)用代碼完全不需要參與。從 httplib.h 的WebSocket::read()實(shí)現(xiàn)可以看到當(dāng)解析出的幀是Opcode::Ping時(shí)庫會(huì)在寫鎖保護(hù)下立即把相同的載荷以O(shè)pcode::Pong寫回當(dāng)收到Opcode::Pong時(shí)則會(huì)把未應(yīng)答計(jì)數(shù)unacked_pings_清零這一點(diǎn)對(duì)后面的失聯(lián)檢測(cè)至關(guān)重要。這些處理發(fā)生在read()內(nèi)部所以只要你的應(yīng)用代碼在正常的read()循環(huán)中對(duì)端的 Ping 就會(huì)被自動(dòng)應(yīng)答。如何選擇心跳間隔間隔的選擇需要在?;钚Чc帶寬/CPU 開銷之間權(quán)衡。原文檔給出了基于環(huán)境的建議值環(huán)境建議間隔普通互聯(lián)網(wǎng)30–60 秒嚴(yán)格代理如 AWS ALB15–30 秒移動(dòng)網(wǎng)絡(luò)60 秒以上過短會(huì)額外耗電間隔太短會(huì)浪費(fèi)帶寬并增加 CPU 開銷太長則可能在中間設(shè)備判定空閑之前來不及續(xù)命導(dǎo)致連接被斷開。經(jīng)驗(yàn)法則是把間隔設(shè)為你與客戶端之間鏈路空閑超時(shí)的一半左右。需要特別注意的是過短的心跳間隔會(huì)為每一條連接都產(chǎn)生周期性的后臺(tái)工作從而推高 CPU 占用。對(duì)于連接數(shù)很多的服務(wù)器請(qǐng)保持一個(gè)適中的間隔見原文檔的 Warning。檢測(cè)無響應(yīng)的對(duì)端max missed pongs僅發(fā)送 Ping 并不能告訴你對(duì)端是否還活著——如果對(duì)端進(jìn)程已經(jīng)崩潰但 TCP 連接尚未被系統(tǒng)回收socket 看起來仍是打開的Ping 發(fā)出去也無人應(yīng)答。要抓住這種情況需要啟用最大未應(yīng)答 Ping 數(shù)檢查當(dāng)連續(xù) N 次 Ping 都沒有收到 Pong 時(shí)連接將被關(guān)閉。cli.set_websocket_max_missed_pongs(2); // 連續(xù) 2 次 Ping 無應(yīng)答即關(guān)閉服務(wù)端也有完全相同的set_websocket_max_missed_pongs()見 httplib.h。心跳線程的實(shí)現(xiàn)邏輯在WebSocket::start_heartbeat()見 httplib.hinline void WebSocket::start_heartbeat() { if (ping_interval_sec_ 0) { return; } // 間隔為 0 則完全禁用心跳 ping_thread_ std::thread([this]() { std::unique_lockstd::mutex lock(ping_mutex_); while (!closed_) { ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_)); if (closed_) { break; } // 連續(xù)未應(yīng)答數(shù)達(dá)到上限時(shí)主動(dòng)關(guān)閉連接 if (max_missed_pongs_ 0 unacked_pings_ max_missed_pongs_) { lock.unlock(); close(CloseStatus::GoingAway, pong timeout); return; } lock.unlock(); if (!send_frame(Opcode::Ping, nullptr, 0)) { // 發(fā)送失敗也關(guān)閉 lock.lock(); closed_ true; break; } lock.lock(); unacked_pings_; // 每次發(fā)出 Ping 后計(jì)數(shù)加 1 } }); }關(guān)鍵點(diǎn)在于每經(jīng)過一個(gè)ping_interval_sec_周期心跳線程發(fā)出一個(gè) Ping 幀并把unacked_pings_加 1當(dāng)read()消費(fèi)到對(duì)端的 Pong 幀時(shí)unacked_pings_被清零一旦unacked_pings_達(dá)到max_missed_pongs_心跳線程以CloseStatus::GoingAway和原因pong timeout主動(dòng)關(guān)閉連接。以 30 秒心跳間隔、max_missed_pongs 2為例對(duì)端死亡后約 60 秒內(nèi)即可被檢測(cè)到連接以CloseStatus::GoingAway、原因?yàn)閜ong timeout關(guān)閉。前提條件計(jì)數(shù)器只有在read()真正消費(fèi)到 Pong 幀時(shí)才會(huì)重置所以該機(jī)制只有在你持續(xù)調(diào)用read()循環(huán)時(shí)才生效——這正是正常 WebSocket 客戶端/服務(wù)端代碼的常見寫法。為什么默認(rèn)值是 0max_missed_pongs默認(rèn)值為0含義是永遠(yuǎn)不因缺少 Pong 而關(guān)閉連接。此時(shí) Ping 幀仍會(huì)按心跳間隔照常發(fā)送保持連接活躍但它們的應(yīng)答不會(huì)被檢查。即使為0服務(wù)端的死連接也不會(huì)永遠(yuǎn)滯留當(dāng) handler 阻塞在read()內(nèi)時(shí)編譯期宏CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND默認(rèn)300 秒 5 分鐘充當(dāng)兜底——服務(wù)端在升級(jí)握手后會(huì)把流讀取超時(shí)設(shè)置為該值見 httplib.h超時(shí)后read()返回Fail并關(guān)閉連接??蛻舳藙t沒有任何內(nèi)置兜底——除非你自己調(diào)用set_read_timeout()否則它會(huì)無限期等待。因此對(duì)客戶端而言max_missed_pongs是唯一能感知無響應(yīng)對(duì)端的機(jī)制對(duì)服務(wù)端而言max_missed_pongs是比 5 分鐘兜底更快地發(fā)現(xiàn)失聯(lián)對(duì)端的手段。如果你需要失聯(lián)檢測(cè)請(qǐng)顯式把它設(shè)置為1或更大的值。心跳線程模型與開銷cpp-httplib 的 WebSocket 實(shí)現(xiàn)是阻塞 I/O、每連接一個(gè)線程外加每連接一個(gè)心跳線程的模型見 README-websocket.md。start_heartbeat()會(huì)為每條連接創(chuàng)建獨(dú)立的心跳線程它通過條件變量ping_cv_定時(shí)喚醒與處理read()的應(yīng)用線程并發(fā)工作。這也是原文檔警告過短的心跳間隔會(huì)為每連接引入后臺(tái)工作、增加 CPU 占用的根源連接數(shù)越多、心跳越頻繁心跳線程喚醒與寫幀的開銷越大。對(duì)于大規(guī)模并發(fā)連接場(chǎng)景應(yīng)把間隔保持在合理范圍內(nèi)需要多線程并發(fā)讀寫的場(chǎng)景如一個(gè)線程read()、另一個(gè)線程send()/close()在庫內(nèi)是受支持的心跳線程的 Ping 與你的read()循環(huán)共享寫路徑庫內(nèi)部已做串行化處理。測(cè)試用例驗(yàn)證倉庫在 test/test_websocket_heartbeat.cc 中提供了專門針對(duì)心跳行為的獨(dú)立測(cè)試。該文件在編譯時(shí)通過宏把間隔壓縮為 1 秒#define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND 1見 L5-L7從而可以在數(shù)秒內(nèi)驗(yàn)證本需要 30 秒才可見的行為測(cè)試還設(shè)置了 3 秒的讀寫超時(shí)來對(duì)照。其覆蓋的場(chǎng)景與本文講解一一對(duì)應(yīng)IdleConnectionStaysAlive空閑連接靠 1 秒心跳活過 3 秒讀超時(shí)5 秒后仍能正常收發(fā)消息L43-L61RuntimePingIntervalOverride客戶端set_websocket_ping_interval(2)覆蓋編譯期默認(rèn)L64-L81ZeroDisablesHeartbeatset_websocket_ping_interval(0)禁用心跳但不破壞基本收發(fā)L84-L96ServerRuntimeInterval服務(wù)端運(yùn)行時(shí)設(shè)置 2 秒間隔同樣生效L125-L140ClientDetectsNonResponsivePeer服務(wù)端禁用心跳且 handler 不調(diào)用read()不產(chǎn)生自動(dòng) Pong客戶端設(shè)max_missed_pongs 2后在約 3 秒內(nèi)自行關(guān)閉L179-L195ResponsivePeerNeverTimesOut正常交互的對(duì)端即使max_missed_pongs 2也不會(huì)被誤殺——read()持續(xù)消費(fèi) Pong 重置計(jì)數(shù)L271-L290BackstopEndsHandlerLoop服務(wù)端讀超時(shí)兜底觸發(fā)后while (ws.read(msg))循環(huán)按預(yù)期結(jié)束L246-L265。這些測(cè)試通過test/Makefile中的test_websocket_heartbeat目標(biāo)編譯運(yùn)行見 test/Makefile可直接作為心跳行為是否如本文所述的驗(yàn)證依據(jù)。相關(guān)文檔連接關(guān)閉包括如何區(qū)分主動(dòng)關(guān)閉與被踢下線、處理CloseStatus與關(guān)閉原因是心跳機(jī)制的自然延伸請(qǐng)參見 W03. Handle connection closeWebSocket 的整體 API 與線程模型可參閱 README-websocket.md完整的可運(yùn)行示例見 example/wsecho.cc。贊分享后端網(wǎng)絡(luò)【免費(fèi)下載鏈接】cpp-httplibA C header-only HTTP/HTTPS server and client library項(xiàng)目地址https://gitcode.com/GitHub_Trending/cp/cpp-httplib點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Camunda DMN 模型 API 詳解基于 DMN 1.1 Schema 的 Java 決策模型讀寫引擎Camunda DMN 模型 API 詳解基于 DMN 1.1 Schema 的 Java 決策模型讀寫引擎 camunda dmn model 是 Camu后端網(wǎng)絡(luò)WebSocket庫連接管理Ping-Pong機(jī)制與心跳檢測(cè)終極指南WebSocket庫連接管理Ping Pong機(jī)制與心跳檢測(cè)終極指南 在現(xiàn)代Web應(yīng)用中實(shí)時(shí)通信已成為不可或缺的功能。WebSocket作為一種全雙工通信協(xié)后端SocketRocket ping/pong機(jī)制保持WebSocket連接活性SocketRocket ping/pong機(jī)制保持WebSocket連接活性 在移動(dòng)應(yīng)用開發(fā)中WebSocket套接字連接經(jīng)常因網(wǎng)絡(luò)不穩(wěn)定或服務(wù)器超時(shí)網(wǎng)絡(luò)通信上一篇Hyperopt與其他優(yōu)化庫對(duì)比5大超參優(yōu)化工具深度評(píng)測(cè)下一篇5分鐘快速上手Sionna構(gòu)建你的第一個(gè)5G通信系統(tǒng)仿真創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考