diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index c70acd70..2be186e7 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -44,14 +44,14 @@ namespace ix close(); } - PollResultType Socket::poll(int timeoutSecs) + PollResultType Socket::poll(int timeoutMs) { if (_sockfd == -1) { return PollResultType::Error; } - return isReadyToRead(1000 * timeoutSecs); + return isReadyToRead(timeoutMs); } PollResultType Socket::select(bool readyToRead, int timeoutMs) diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index d42b7111..640e885a 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -56,7 +56,7 @@ namespace ix bool init(std::string& errorMsg); // Functions to check whether there is activity on the socket - PollResultType poll(int timeoutSecs = kDefaultPollTimeout); + PollResultType poll(int timeoutMs = kDefaultPollTimeout); bool wakeUpFromPoll(uint8_t wakeUpCode); PollResultType isReadyToWrite(int timeoutMs); diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index f848c7cd..e87c403f 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -215,6 +215,11 @@ namespace ix return getReadyState() == WebSocket_ReadyState_Closing; } + bool WebSocket::isConnectedOrClosing() const + { + return isConnected() || isClosing(); + } + void WebSocket::close() { _ws.close(); @@ -229,7 +234,7 @@ namespace ix millis duration; // Try to connect only once when we don't have automaticReconnection setup - if (!isConnected() && !isClosing() && !_stop && !_automaticReconnection) + if (!isConnectedOrClosing() && !_stop && !_automaticReconnection) { status = connect(_handshakeTimeoutSecs); @@ -251,7 +256,7 @@ namespace ix // Otherwise try to reconnect perpertually while (true) { - if (isConnected() || isClosing() || _stop || !_automaticReconnection) + if (isConnectedOrClosing() || _stop || !_automaticReconnection) { break; } @@ -286,20 +291,17 @@ namespace ix while (true) { - if (_stop) return; + if (_stop && !isClosing()) return; // 1. Make sure we are always connected reconnectPerpetuallyIfDisconnected(); - if (_stop) return; - // 2. Poll to see if there's any new data available - _ws.poll(); - - if (_stop) return; + WebSocketTransport::PollPostTreatment pollPostTreatment = _ws.poll(); // 3. Dispatch the incoming messages _ws.dispatch( + pollPostTreatment, [this](const std::string& msg, size_t wireSize, bool decompressionError, @@ -340,7 +342,7 @@ namespace ix }); // If we aren't trying to reconnect automatically, exit if we aren't connected - if (!isConnected() && !_automaticReconnection) return; + if (!isConnectedOrClosing() && !_automaticReconnection) return; } } diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 8c6bf702..07c00a4a 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -136,6 +136,7 @@ namespace ix bool isConnected() const; bool isClosing() const; + bool isConnectedOrClosing() const; void reconnectPerpetuallyIfDisconnected(); std::string readyStateToString(ReadyState readyState); static void invokeTrafficTrackerCallback(size_t size, bool incoming); diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 40c9787f..21530e77 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -71,14 +71,18 @@ namespace ix const int WebSocketTransport::kDefaultPingIntervalSecs(-1); const int WebSocketTransport::kDefaultPingTimeoutSecs(-1); const bool WebSocketTransport::kDefaultEnablePong(true); + const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200); constexpr size_t WebSocketTransport::kChunkSize; + const uint16_t WebSocketTransport::kInternalErrorCode(1011); const uint16_t WebSocketTransport::kAbnormalCloseCode(1006); const uint16_t WebSocketTransport::kProtocolErrorCode(1002); + const uint16_t WebSocketTransport::kNoStatusCodeErrorCode(1005); const std::string WebSocketTransport::kInternalErrorMessage("Internal error"); const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure"); const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout"); const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error"); + const std::string WebSocketTransport::kNoStatusCodeErrorMessage("No status code"); WebSocketTransport::WebSocketTransport() : _useMask(true), @@ -89,10 +93,12 @@ namespace ix _closeRemote(false), _enablePerMessageDeflate(false), _requestInitCancellation(false), + _closingTimePoint(std::chrono::steady_clock::now()), _enablePong(kDefaultEnablePong), _pingIntervalSecs(kDefaultPingIntervalSecs), _pingTimeoutSecs(kDefaultPingTimeoutSecs), _pingIntervalOrTimeoutGCDSecs(-1), + _nextGCDTimePoint(std::chrono::steady_clock::now()), _lastSendPingTimePoint(std::chrono::steady_clock::now()), _lastReceivePongTimePoint(std::chrono::steady_clock::now()) { @@ -128,6 +134,11 @@ namespace ix { _pingIntervalOrTimeoutGCDSecs = pingIntervalSecs; } + + if (_pingIntervalOrTimeoutGCDSecs > 0) + { + _nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs); + } } // Client @@ -244,10 +255,15 @@ namespace ix return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs); } - void WebSocketTransport::poll() + bool WebSocketTransport::closingDelayExceeded() { - PollResultType pollResult = _socket->poll(_pingIntervalOrTimeoutGCDSecs); + std::lock_guard lock(_closingTimePointMutex); + auto now = std::chrono::steady_clock::now(); + return now - _closingTimePoint > std::chrono::milliseconds(kClosingMaximumWaitingDelayInMs); + } + WebSocketTransport::PollPostTreatment WebSocketTransport::poll() + { if (_readyState == OPEN) { // if (1) ping timeout is enabled and (2) duration since last received @@ -265,6 +281,30 @@ namespace ix sendPing(ss.str()); } } + + // No timeout if state is not OPEN, otherwise computed + // pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set) + int lastingTimeoutDelayInMs = (_readyState != OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs; + + if (_pingIntervalOrTimeoutGCDSecs > 0) + { + // compute lasting delay to wait for next ping / timeout, if at least one set + auto now = std::chrono::steady_clock::now(); + + if (now >= _nextGCDTimePoint) + { + _nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs); + + lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000; + } + else + { + lastingTimeoutDelayInMs = (int)std::chrono::duration_cast(_nextGCDTimePoint - now).count(); + } + } + + // poll the socket + PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs); // Make sure we send all the buffered data // there can be a lot of it for large messages. @@ -300,17 +340,12 @@ namespace ix } else if (ret <= 0) { - _rxbuf.clear(); + // if there are received data pending to be processed, then delay the abnormal closure + // to after dispatch (other close code/reason could be read from the buffer) + _socket->close(); - { - std::lock_guard lock(_closeDataMutex); - _closeCode = kAbnormalCloseCode; - _closeReason = kAbnormalCloseMessage; - _closeWireSize = 0; - _closeRemote = true; - } - setReadyState(CLOSED); - break; + + return CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH; } else { @@ -329,12 +364,15 @@ namespace ix _socket->close(); } - // Avoid a race condition where we get stuck in select - // while closing. - if (_readyState == CLOSING) + if (_readyState == CLOSING && closingDelayExceeded()) { + _rxbuf.clear(); + // close code and reason were set when calling close() _socket->close(); + setReadyState(CLOSED); } + + return NONE; } bool WebSocketTransport::isSendBufferEmpty() const @@ -396,12 +434,13 @@ namespace ix // | Payload Data continued ... | // +---------------------------------------------------------------+ // - void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback) + void WebSocketTransport::dispatch(WebSocketTransport::PollPostTreatment pollPostTreatment, + const OnMessageCallback& onMessageCallback) { while (true) { wsheader_type ws; - if (_rxbuf.size() < 2) return; /* Need at least 2 */ + if (_rxbuf.size() < 2) break; /* Need at least 2 */ const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume ws.fin = (data[0] & 0x80) == 0x80; ws.rsv1 = (data[0] & 0x40) == 0x40; @@ -409,7 +448,7 @@ namespace ix ws.mask = (data[1] & 0x80) == 0x80; ws.N0 = (data[1] & 0x7f); ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0); - if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */ + if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */ // // Calculate payload length: @@ -540,20 +579,58 @@ namespace ix } else if (ws.opcode == wsheader_type::CLOSE) { + std::string reason; + uint16_t code = 0; + unmaskReceiveBuffer(ws); - // Extract the close code first, available as the first 2 bytes - uint16_t code = 0; - code |= ((uint64_t) _rxbuf[ws.header_size]) << 8; - code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0; + if (ws.N >= 2) + { + // Extract the close code first, available as the first 2 bytes + code |= ((uint64_t) _rxbuf[ws.header_size]) << 8; + code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0; - // Get the reason. - std::string reason(_rxbuf.begin()+ws.header_size + 2, - _rxbuf.begin()+ws.header_size + (size_t) ws.N); + // Get the reason. + if (ws.N > 2) + { + reason.assign(_rxbuf.begin()+ws.header_size + 2, + _rxbuf.begin()+ws.header_size + (size_t) ws.N); + } + } + else + { + // no close code received + code = kNoStatusCodeErrorCode; + reason = kNoStatusCodeErrorMessage; + } - bool remote = true; + // We receive a CLOSE frame from remote and are NOT the ones who triggered the close + if (_readyState != CLOSING) + { + // send back the CLOSE frame + sendCloseFrame(code, reason); - close(code, reason, _rxbuf.size(), remote); + _socket->wakeUpFromPoll(Socket::kCloseRequest); + + bool remote = true; + closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote); + } + else + { + // we got the CLOSE frame answer from our close, so we can close the connection if + // the code/reason are the same + bool identicalReason; + { + std::lock_guard lock(_closeDataMutex); + identicalReason = _closeCode == code && _closeReason == reason; + } + + if (identicalReason) + { + bool remote = false; + closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote); + } + } } else { @@ -566,6 +643,25 @@ namespace ix _rxbuf.erase(_rxbuf.begin(), _rxbuf.begin() + ws.header_size + (size_t) ws.N); } + + // if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in + // the received and processed data then close the connection + if (pollPostTreatment == CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH) + { + _rxbuf.clear(); + + // if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before) + if (_readyState == CLOSING) + { + _socket->close(); + setReadyState(CLOSED); + } + // if we weren't closing, then close using abnormal close code and message + else if (_readyState != CLOSED) + { + closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false); + } + } } std::string WebSocketTransport::getMergedChunks() const @@ -859,29 +955,32 @@ namespace ix } } - void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) + void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason) { - _requestInitCancellation = true; - - if (_readyState == CLOSING || _readyState == CLOSED) return; - - // See list of close events here: - // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent - - int codeLength = 2; - std::string closure{(char)(code >> 8), (char)(code & 0xff)}; - closure.resize(codeLength + reason.size()); - - // copy reason after code - closure.replace(codeLength, reason.size(), reason); - bool compress = false; - sendData(wsheader_type::CLOSE, closure, compress); - setReadyState(CLOSING); - _socket->wakeUpFromPoll(Socket::kCloseRequest); + // if a status is set/was read + if (code != kNoStatusCodeErrorCode) + { + // See list of close events here: + // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent + std::string closure{(char)(code >> 8), (char)(code & 0xff)}; + + // copy reason after code + closure.append(reason); + + sendData(wsheader_type::CLOSE, closure, compress); + } + else + { + // no close code/reason set + sendData(wsheader_type::CLOSE, "", compress); + } + } + + void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) + { _socket->close(); - { std::lock_guard lock(_closeDataMutex); _closeCode = code; @@ -889,10 +988,33 @@ namespace ix _closeWireSize = closeWireSize; _closeRemote = remote; } - setReadyState(CLOSED); } + void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) + { + _requestInitCancellation = true; + + if (_readyState == CLOSING || _readyState == CLOSED) return; + + sendCloseFrame(code, reason); + { + std::lock_guard lock(_closeDataMutex); + _closeCode = code; + _closeReason = reason; + _closeWireSize = closeWireSize; + _closeRemote = remote; + } + { + std::lock_guard lock(_closingTimePointMutex); + _closingTimePoint = std::chrono::steady_clock::now(); + } + setReadyState(CLOSING); + + // wake up the poll, but do not close yet + _socket->wakeUpFromPoll(Socket::kSendRequest); + } + size_t WebSocketTransport::bufferedAmount() const { std::lock_guard lock(_txbufMutex); diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index bc54e41f..7767a8e5 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -56,6 +56,12 @@ namespace ix FRAGMENT }; + enum PollPostTreatment + { + NONE, + CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH + }; + using OnMessageCallback = std::function _requestInitCancellation; + + mutable std::mutex _closingTimePointMutex; + std::chrono::time_point_closingTimePoint; + static const int kClosingMaximumWaitingDelayInMs; // Constants for dealing with closing conneections static const uint16_t kInternalErrorCode; static const uint16_t kAbnormalCloseCode; static const uint16_t kProtocolErrorCode; + static const uint16_t kNoStatusCodeErrorCode; static const std::string kInternalErrorMessage; static const std::string kAbnormalCloseMessage; static const std::string kPingTimeoutMessage; static const std::string kProtocolErrorMessage; + static const std::string kNoStatusCodeErrorMessage; // enable auto response to ping bool _enablePong; @@ -187,6 +200,9 @@ namespace ix static const int kDefaultPingTimeoutSecs; static const std::string kPingMessage; + // Record time step for ping/ ping timeout to ensure we wait for the right left duration + std::chrono::time_point _nextGCDTimePoint; + // We record when ping are being sent so that we can know when to send the next one // We also record when pong are being sent as a reply to pings, to close the connections // if no pong were received sufficiently fast. @@ -201,6 +217,16 @@ namespace ix // No PONG data was received through the socket for longer than ping timeout delay bool pingTimeoutExceeded(); + // after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion + bool closingDelayExceeded(); + + void sendCloseFrame(uint16_t code, const std::string& reason); + + void closeSocketAndSwitchToClosedState(uint16_t code, + const std::string& reason, + size_t closeWireSize, + bool remote); + void sendOnSocket(); WebSocketSendInfo sendData(wsheader_type::opcode_type type, const std::string& message, diff --git a/test/IXWebSocketPingTest.cpp b/test/IXWebSocketPingTest.cpp index 3e67b293..0dc55f66 100644 --- a/test/IXWebSocketPingTest.cpp +++ b/test/IXWebSocketPingTest.cpp @@ -68,9 +68,13 @@ namespace // The important bit for this test. // Set a 1 second heartbeat with the setter method to test if (_useHeartBeatMethod) + { _webSocket.setHeartBeatPeriod(1); + } else + { _webSocket.setPingInterval(1); + } std::stringstream ss; log(std::string("Connecting to url: ") + url); @@ -109,8 +113,7 @@ namespace } else if (messageType == ix::WebSocket_MessageType_Message) { - ss << "Received message " << str; - log(ss.str()); + // too many messages to log } else { @@ -162,6 +165,14 @@ namespace log("Server received a ping"); receivedPingMessages++; } + else if (messageType == ix::WebSocket_MessageType_Message) + { + // to many messages to log + for(auto client: server.getClients()) + { + client->sendText("reply"); + } + } } ); } @@ -179,96 +190,6 @@ namespace } } -TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") -{ - SECTION("Make sure that ping messages are sent when no other data are sent.") - { - ix::setupWebSocketTrafficTrackerCallback(); - - int port = getFreePort(); - ix::WebSocketServer server(port); - std::atomic serverReceivedPingMessages(0); - REQUIRE(startServer(server, serverReceivedPingMessages)); - - std::string session = ix::generateSessionId(); - bool useSetHeartBeatPeriodMethod = true; - WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod); - - webSocketClient.start(); - - // Wait for all chat instance to be ready - while (true) - { - if (webSocketClient.isReady()) break; - ix::msleep(10); - } - - REQUIRE(server.getClients().size() == 1); - - ix::msleep(1900); - - webSocketClient.stop(); - - - // Here we test ping interval - // -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second - REQUIRE(serverReceivedPingMessages == 1); - - // Give us 500ms for the server to notice that clients went away - ix::msleep(500); - REQUIRE(server.getClients().size() == 0); - - ix::reportWebSocketTraffic(); - } -} - -TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") -{ - SECTION("Make sure that ping messages are sent, even if other messages are sent") - { - ix::setupWebSocketTrafficTrackerCallback(); - - int port = getFreePort(); - ix::WebSocketServer server(port); - std::atomic serverReceivedPingMessages(0); - REQUIRE(startServer(server, serverReceivedPingMessages)); - - std::string session = ix::generateSessionId(); - bool useSetHeartBeatPeriodMethod = true; - WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod); - - webSocketClient.start(); - - // Wait for all chat instance to be ready - while (true) - { - if (webSocketClient.isReady()) break; - ix::msleep(10); - } - - REQUIRE(server.getClients().size() == 1); - - ix::msleep(900); - webSocketClient.sendMessage("hello world"); - ix::msleep(900); - webSocketClient.sendMessage("hello world"); - ix::msleep(1100); - - webSocketClient.stop(); - - // Here we test ping interval - // client has sent data, but ping should have been sent no matter what - // -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second - REQUIRE(serverReceivedPingMessages == 2); - - // Give us 500ms for the server to notice that clients went away - ix::msleep(500); - REQUIRE(server.getClients().size() == 0); - - ix::reportWebSocketTraffic(); - } -} - TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]") { SECTION("Make sure that ping messages are sent when no other data are sent.") @@ -346,9 +267,6 @@ TEST_CASE("Websocket_ping_data_sent_setPingInterval", "[setPingInterval]") webSocketClient.stop(); - // without this sleep test fails on Windows - ix::msleep(100); - // Here we test ping interval // client has sent data, but ping should have been sent no matter what // -> expected ping messages == 3 as 900+900+1300 = 3100 seconds, 1 ping sent every second @@ -361,3 +279,203 @@ TEST_CASE("Websocket_ping_data_sent_setPingInterval", "[setPingInterval]") ix::reportWebSocketTraffic(); } } + +TEST_CASE("Websocket_ping_data_sent_setPingInterval_half_full", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent, even if other messages are sent continuously during a given time") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval + WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + // send continuously for 1100ms + auto now = std::chrono::steady_clock::now(); + + while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(900)) + { + webSocketClient.sendMessage("message"); + ix::msleep(1); + } + ix::msleep(150); + + // Here we test ping interval + // client has sent data, but ping should have been sent no matter what + // -> expected ping messages == 1, as 900+150 = 1050ms, 1 ping sent every second + REQUIRE(serverReceivedPingMessages == 1); + + ix::msleep(100); + + webSocketClient.stop(); + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_data_sent_setPingInterval_full", "[setPingInterval]") +{ + SECTION("Make sure that ping messages are sent, even if other messages are sent continuously for longer than ping interval") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval + WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(1); + } + + REQUIRE(server.getClients().size() == 1); + + // send continuously for 1100ms + auto now = std::chrono::steady_clock::now(); + + while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(1100)) + { + webSocketClient.sendMessage("message"); + ix::msleep(1); + } + + // Here we test ping interval + // client has sent data, but ping should have been sent no matter what + // -> expected ping messages == 1, 1 ping sent every second + REQUIRE(serverReceivedPingMessages == 1); + + ix::msleep(100); + + webSocketClient.stop(); + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +// Using setHeartBeatPeriod + +TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") +{ + SECTION("Make sure that ping messages are sent when no other data are sent.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + bool useSetHeartBeatPeriodMethod = true; + WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(1); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(1850); + + webSocketClient.stop(); + + + // Here we test ping interval + // -> expected ping messages == 1 as 1850 seconds, 1 ping sent every second + REQUIRE(serverReceivedPingMessages == 1); + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") +{ + SECTION("Make sure that ping messages are sent, even if other messages are sent") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + std::atomic serverReceivedPingMessages(0); + REQUIRE(startServer(server, serverReceivedPingMessages)); + + std::string session = ix::generateSessionId(); + bool useSetHeartBeatPeriodMethod = true; + WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(1); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(900); + webSocketClient.sendMessage("hello world"); + ix::msleep(900); + webSocketClient.sendMessage("hello world"); + ix::msleep(900); + + webSocketClient.stop(); + + // without this sleep test fails on Windows + ix::msleep(100); + + // Here we test ping interval + // client has sent data, but ping should have been sent no matter what + // -> expected ping messages == 2 as 900+900+900 = 2700 seconds, 1 ping sent every second + REQUIRE(serverReceivedPingMessages == 2); + + // Give us 500ms for the server to notice that clients went away + ix::msleep(500); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} diff --git a/test/IXWebSocketPingTimeoutTest.cpp b/test/IXWebSocketPingTimeoutTest.cpp index 45e839bb..5e72a104 100644 --- a/test/IXWebSocketPingTimeoutTest.cpp +++ b/test/IXWebSocketPingTimeoutTest.cpp @@ -78,6 +78,7 @@ namespace } _webSocket.setUrl(url); + _webSocket.disableAutomaticReconnection(); // The important bit for this test. // Set a ping interval, and a ping timeout @@ -100,7 +101,6 @@ namespace { log("client connected"); - _webSocket.disableAutomaticReconnection(); } else if (messageType == ix::WebSocket_MessageType_Close) { @@ -111,15 +111,11 @@ namespace _closedDueToPingTimeout = true; } - _webSocket.disableAutomaticReconnection(); - } else if (messageType == ix::WebSocket_MessageType_Error) { ss << "Error ! " << error.reason; log(ss.str()); - - _webSocket.disableAutomaticReconnection(); } else if (messageType == ix::WebSocket_MessageType_Pong) { diff --git a/test/IXWebSocketTestConnectionDisconnection.cpp b/test/IXWebSocketTestConnectionDisconnection.cpp index d9bf9bc8..64359974 100644 --- a/test/IXWebSocketTestConnectionDisconnection.cpp +++ b/test/IXWebSocketTestConnectionDisconnection.cpp @@ -70,7 +70,9 @@ namespace } else if (messageType == ix::WebSocket_MessageType_Error) { - log("cmd_websocket_satori_chat: Error!"); + ss << "cmd_websocket_satori_chat: Error! "; + ss << error.reason; + log(ss.str()); } else if (messageType == ix::WebSocket_MessageType_Message) { @@ -84,6 +86,10 @@ namespace { log("cmd_websocket_satori_chat: received pong message.!"); } + else if (messageType == ix::WebSocket_MessageType_Fragment) + { + log("cmd_websocket_satori_chat: received fragment.!"); + } else { log("Invalid ix::WebSocketMessageType");