From f6f5a3d6e82beb384c690e0ee189ff6b669aff5c Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Thu, 11 May 2023 11:18:58 +0200 Subject: [PATCH] Add large non-ssl, non-compressed WebSocket message sending optimization --- src/WebSocket.h | 87 ++++++++++++++++++++++++++++++------------------- uSockets | 2 +- 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/WebSocket.h b/src/WebSocket.h index c6d163b..0a5f029 100644 --- a/src/WebSocket.h +++ b/src/WebSocket.h @@ -105,48 +105,67 @@ public: /* If we are subscribers and have messages to drain we need to drain them here to stay synced */ WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData(); - if (webSocketData->subscriber) { - /* This will call back into us, send. */ - webSocketContextData->topicTree->drain(webSocketData->subscriber); - } - /* Transform the message to compressed domain if requested */ - if (compress) { - WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData(); - - /* Check and correct the compress hint. It is never valid to compress 0 bytes */ - if (message.length() && opCode < 3 && webSocketData->compressionStatus == WebSocketData::ENABLED) { - LoopData *loopData = Super::getLoopData(); - /* Compress using either shared or dedicated deflationStream */ - if (webSocketData->deflationStream) { - message = webSocketData->deflationStream->deflate(loopData->zlibContext, message, false); + /* Special path for long sends of non-compressed, non-SSL messages */ + if (message.length() >= 16 * 1024 && !compress && !SSL && !webSocketData->subscriber && getBufferedAmount() == 0 && Super::getLoopData()->corkOffset == 0) { + char header[14]; + int header_length = (int) protocol::formatMessage(header, nullptr, 0, opCode, message.length(), compress, fin); + int written = us_socket_write2(0, (struct us_socket_t *)this, header, header_length, message.data(), (int) message.length()); + + if (written != header_length + (int) message.length()) { + /* Buffer up backpressure */ + if (written > header_length) { + webSocketData->buffer.append(message.data() + written, message.length() - (size_t) written); } else { - message = loopData->deflationStream->deflate(loopData->zlibContext, message, true); + webSocketData->buffer.append(header + written, (size_t) header_length - (size_t) written); } - } else { - compress = false; } - } + } else { - /* Get size, allocate size, write if needed */ - size_t messageFrameSize = protocol::messageFrameSize(message.length()); - auto [sendBuffer, sendBufferAttribute] = Super::getSendBuffer(messageFrameSize); - protocol::formatMessage(sendBuffer, message.data(), message.length(), opCode, message.length(), compress, fin); + if (webSocketData->subscriber) { + /* This will call back into us, send. */ + webSocketContextData->topicTree->drain(webSocketData->subscriber); + } - /* Depending on size of message we have different paths */ - if (sendBufferAttribute == SendBufferAttribute::NEEDS_DRAIN) { - /* This is a drain */ - auto[written, failed] = Super::write(nullptr, 0); - if (failed) { - /* Return false for failure, skipping to reset the timeout below */ - return BACKPRESSURE; + /* Transform the message to compressed domain if requested */ + if (compress) { + WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData(); + + /* Check and correct the compress hint. It is never valid to compress 0 bytes */ + if (message.length() && opCode < 3 && webSocketData->compressionStatus == WebSocketData::ENABLED) { + LoopData *loopData = Super::getLoopData(); + /* Compress using either shared or dedicated deflationStream */ + if (webSocketData->deflationStream) { + message = webSocketData->deflationStream->deflate(loopData->zlibContext, message, false); + } else { + message = loopData->deflationStream->deflate(loopData->zlibContext, message, true); + } + } else { + compress = false; + } } - } else if (sendBufferAttribute == SendBufferAttribute::NEEDS_UNCORK) { - /* Uncork if we came here uncorked */ - auto [written, failed] = Super::uncork(); - if (failed) { - return BACKPRESSURE; + + /* Get size, allocate size, write if needed */ + size_t messageFrameSize = protocol::messageFrameSize(message.length()); + auto [sendBuffer, sendBufferAttribute] = Super::getSendBuffer(messageFrameSize); + protocol::formatMessage(sendBuffer, message.data(), message.length(), opCode, message.length(), compress, fin); + + /* Depending on size of message we have different paths */ + if (sendBufferAttribute == SendBufferAttribute::NEEDS_DRAIN) { + /* This is a drain */ + auto[written, failed] = Super::write(nullptr, 0); + if (failed) { + /* Return false for failure, skipping to reset the timeout below */ + return BACKPRESSURE; + } + } else if (sendBufferAttribute == SendBufferAttribute::NEEDS_UNCORK) { + /* Uncork if we came here uncorked */ + auto [written, failed] = Super::uncork(); + if (failed) { + return BACKPRESSURE; + } } + } /* Every successful send resets the timeout */ diff --git a/uSockets b/uSockets index b950efd..0866daf 160000 --- a/uSockets +++ b/uSockets @@ -1 +1 @@ -Subproject commit b950efd6b10f06dd3ecb5b692e5d415f48474647 +Subproject commit 0866daff30cf9692f1b9c22598dd10b4535aca66