From 68800e2e7a152a1b359a3f192635c6805926fe60 Mon Sep 17 00:00:00 2001 From: Jozef Sovcik Date: Tue, 23 Oct 2018 18:01:33 +0200 Subject: [PATCH 1/5] implementing heartbeat --- src/WebSockets.cpp | 46 ++++++++++++++++++++++++++++++++++++++++ src/WebSockets.h | 10 +++++++++ src/WebSocketsClient.cpp | 46 +++++++++++++++++++++++++++++++++++++++- src/WebSocketsClient.h | 5 +++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index 5e32a93..f0e39b9 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -433,10 +433,12 @@ void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t break; case WSop_ping: // send pong back + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ping received (%s)\n", client->num, payload ? (const char*)payload : ""); sendFrame(client, WSop_pong, payload, header->payloadLen); break; case WSop_pong: DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char*)payload : ""); + client->pongReceived = true; break; case WSop_close: { #ifndef NODEBUG_WEBSOCKETS @@ -652,3 +654,47 @@ size_t WebSockets::write(WSclient_t * client, const char *out) { if(out == NULL) return 0; return write(client, (uint8_t*)out, strlen(out)); } + +/** + * enable ping/pong heartbeat process + * @param client WSclient_t * + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount){ + if(client == NULL) return; + client->pingInterval = pingInterval; + client->pongTimeout = pongTimeout; + client->disconnectTimeoutCount = disconnectTimeoutCount; + +} + +/** + * handle ping/pong heartbeat timeout process + * @param client WSclient_t * + */ +void WebSockets::handleHBTimeout(WSclient_t * client){ + if (client->pingInterval) { // if heartbeat is enabled + uint32_t pi = millis() - client->lastPing; + + if (client->pongReceived) { + client->pongTimeoutCount = 0; + } else { + if (pi > client->pongTimeout){ // pong not received in time + client->pongTimeoutCount++; + client->lastPing = millis() - client->pingInterval - 500; // force ping on the next run + + DEBUG_WEBSOCKETS("[HBtimeout] pong TIMEOUT! lp=%d millis=%d pi=%d count=%d\n", client->lastPing, millis(), pi, client->pongTimeoutCount); + + if (client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount){ + DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); + client->status = WSC_NOT_CONNECTED; + //clientDisconnect(client); + } + } + } + + } + +} \ No newline at end of file diff --git a/src/WebSockets.h b/src/WebSockets.h index da88f08..39a1e0b 100644 --- a/src/WebSockets.h +++ b/src/WebSockets.h @@ -264,6 +264,13 @@ typedef struct { bool cHttpHeadersValid; ///< non-websocket http header validity indicator size_t cMandatoryHeadersCount; ///< non-websocket mandatory http headers present count + bool pongReceived; + uint32_t pingInterval; // how often ping will be sent, 0 means "heartbeat is not active" + uint32_t lastPing; // millis when last pong has been received + uint32_t pongTimeout; // interval in millis after which pong is considered to timeout + uint8_t disconnectTimeoutCount; // after how many subsequent pong timeouts discconnect will happen, 0 means "do not disconnect" + uint8_t pongTimeoutCount; // current pong timeout count + #if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) String cHttpLine; ///< HTTP header lines #endif @@ -303,6 +310,9 @@ class WebSockets { virtual size_t write(WSclient_t * client, uint8_t *out, size_t n); size_t write(WSclient_t * client, const char *out); + void enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); + void handleHBTimeout(WSclient_t * client); + }; diff --git a/src/WebSocketsClient.cpp b/src/WebSocketsClient.cpp index 94ca434..9bfb274 100644 --- a/src/WebSocketsClient.cpp +++ b/src/WebSocketsClient.cpp @@ -66,6 +66,10 @@ void WebSocketsClient::begin(const char *host, uint16_t port, const char * url, _client.plainAuthorization = ""; _client.isSocketIO = false; + _client.lastPing = 0; + _client.pongReceived = false; + _client.pongTimeoutCount = 0; + #ifdef ESP8266 randomSeed(RANDOM_REG32); #else @@ -169,6 +173,10 @@ void WebSocketsClient::loop(void) { } } else { + if (_client.status == WSC_CONNECTED){ + handleHBPing(); + handleHBTimeout(&_client); + } handleClientData(); } } @@ -243,7 +251,10 @@ bool WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) { */ bool WebSocketsClient::sendPing(uint8_t * payload, size_t length) { if(clientIsConnected(&_client)) { - return sendFrame(&_client, WSop_ping, payload, length); + bool sent = sendFrame(&_client, WSop_ping, payload, length); + if (sent) + _client.lastPing = millis(); + return sent; } return false; } @@ -761,3 +772,36 @@ void WebSocketsClient::asyncConnect() { } #endif + +/** + * send heartbeat ping to server in set intervals + */ +void WebSocketsClient::handleHBPing(){ + if (_client.pingInterval == 0) return; + uint32_t pi = millis() - _client.lastPing; + if (pi > _client.pingInterval){ + DEBUG_WEBSOCKETS("[WS-Client] sending HB ping\n"); + if (sendPing()) { + _client.lastPing = millis(); + _client.pongReceived = false; + } + } + +} + +/** + * enable ping/pong heartbeat process + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSocketsClient::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount){ + WebSockets::enableHeartbeat(&_client, pingInterval, pongTimeout, disconnectTimeoutCount); +} + +/** + * disable ping/pong heartbeat process + */ +void WebSocketsClient::disableHeartbeat(){ + _client.pingInterval = 0; +} \ No newline at end of file diff --git a/src/WebSocketsClient.h b/src/WebSocketsClient.h index 8e6305e..47fe8c8 100644 --- a/src/WebSocketsClient.h +++ b/src/WebSocketsClient.h @@ -86,6 +86,9 @@ class WebSocketsClient: private WebSockets { void setReconnectInterval(unsigned long time); + void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); + void disableHeartbeat(); + protected: String _host; uint16_t _port; @@ -115,6 +118,8 @@ class WebSocketsClient: private WebSockets { void connectedCb(); void connectFailedCb(); + void handleHBPing(); // send ping in specified intervals + #if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) void asyncConnect(); #endif From 9b6ce7563a2d8876f71ce113f402b61a24e8cdc6 Mon Sep 17 00:00:00 2001 From: Jozef Sovcik Date: Wed, 24 Oct 2018 08:17:37 +0200 Subject: [PATCH 2/5] added example for heartbeat --- examples/esp8266/WebSocketClient/WebSocketClient.ino | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/esp8266/WebSocketClient/WebSocketClient.ino b/examples/esp8266/WebSocketClient/WebSocketClient.ino index 3ce8498..86fc241 100644 --- a/examples/esp8266/WebSocketClient/WebSocketClient.ino +++ b/examples/esp8266/WebSocketClient/WebSocketClient.ino @@ -84,6 +84,12 @@ void setup() { // try ever 5000 again if connection has failed webSocket.setReconnectInterval(5000); + + // start heartbeat (optional) + // ping server every 15000 ms + // expect pong from server within 3000 ms + // consider connection disconnected if pong is not received 2 times + webSocket.enableHeartbeat(15000, 3000, 2); } From d6934379087bbce9dee177bf635e69736599ea90 Mon Sep 17 00:00:00 2001 From: Jozef Sovcik Date: Wed, 24 Oct 2018 08:19:02 +0200 Subject: [PATCH 3/5] version bump --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index f1a5892..978e584 100644 --- a/library.json +++ b/library.json @@ -13,7 +13,7 @@ "type": "git", "url": "https://github.com/Links2004/arduinoWebSockets.git" }, - "version": "2.1.2", + "version": "2.1.3", "license": "LGPL-2.1", "export": { "exclude": [ diff --git a/library.properties b/library.properties index 00d0945..a30fdbf 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=WebSockets -version=2.1.2 +version=2.1.3 author=Markus Sattler maintainer=Markus Sattler sentence=WebSockets for Arduino (Server + Client) From d6dd7be9879bca9bfe36d7f6eead77a98bce7555 Mon Sep 17 00:00:00 2001 From: Jozef Sovcik Date: Sat, 3 Nov 2018 13:15:14 +0100 Subject: [PATCH 4/5] disconnecting after pong not received --- src/WebSockets.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index f0e39b9..fc4a6ba 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -689,8 +689,7 @@ void WebSockets::handleHBTimeout(WSclient_t * client){ if (client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount){ DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); - client->status = WSC_NOT_CONNECTED; - //clientDisconnect(client); + clientDisconnect(client); } } } From 1b6b42b877c201be6a01788713386e2bcd26bcc6 Mon Sep 17 00:00:00 2001 From: Jozef Sovcik Date: Sat, 3 Nov 2018 13:16:12 +0100 Subject: [PATCH 5/5] processing client data before potential disconnect --- src/WebSocketsClient.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/WebSocketsClient.cpp b/src/WebSocketsClient.cpp index 9bfb274..2d96358 100644 --- a/src/WebSocketsClient.cpp +++ b/src/WebSocketsClient.cpp @@ -173,11 +173,13 @@ void WebSocketsClient::loop(void) { } } else { + handleClientData(); + if (_client.status == WSC_CONNECTED){ handleHBPing(); handleHBTimeout(&_client); } - handleClientData(); + } } #endif @@ -726,7 +728,7 @@ void WebSocketsClient::connectedCb() { } void WebSocketsClient::connectFailedCb() { - DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Faild\n", _host.c_str(), _port); + DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Failed\n", _host.c_str(), _port); } #if (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)