-
Notifications
You must be signed in to change notification settings - Fork 7.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Less tcp packet fragmentation (IDFGH-11082) #12256
base: master
Are you sure you want to change the base?
Conversation
@@ -123,7 +123,7 @@ static ssize_t tcp_read(esp_tls_t *tls, char *data, size_t datalen) | |||
|
|||
static ssize_t tcp_write(esp_tls_t *tls, const char *data, size_t datalen) | |||
{ | |||
return send(tls->sockfd, data, datalen, 0); | |||
return send(tls->sockfd, data, datalen, MSG_MORE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we could hard-code this flag here. It makes sense for this specific use-case, but it's not universally the case for other usages. In fact, users usually prefer that the data are sent immediately, even for TCP, so we typically suggest that people set TCP_NODELAY
to disable naggling.
I'd suggest adding this to esp_tls
config options, something like:
@@ -200,6 +200,7 @@ typedef struct esp_tls_cfg {
esp_tls_addr_family_t addr_family; /*!< The address family to use when connecting to a host. */
const int *ciphersuites_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites.
Please check the list validity by esp_tls_get_ciphersuites_list() API */
+ int socket_flags;
} esp_tls_cfg_t;
@@ -56,7 +56,7 @@ int httpd_send(httpd_req_t *r, const char *buf, size_t buf_len) | |||
} | |||
|
|||
struct httpd_req_aux *ra = r->aux; | |||
int ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, 0); | |||
int ret = ra->sd->send_fn(ra->sd->handle, ra->sd->fd, buf, buf_len, MSG_MORE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, suggest making this configurable
* @brief Sends websocket raw message with custom opcode and payload in a optimized way | ||
* | ||
* This method is similar to esp_transport_ws_send_raw(), but | ||
* it assumes, that the first MAX_WEBSOCKET_HEADER_SIZE bytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add this note as a @pre
(prerequisite for using this API) that it expects the buffer to hold some space for headers.
* @param[in] t Websocket transport handle | ||
* @param[in] opcode ws operation code | ||
* @param[in] buffer The buffer | ||
* @param[in] len The length |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be made clear that the len
parameter refers to the entire buffer with headers, so the actual (payload) length is len-MAX_WEBSOCKET_HEADER_SIZE
* - Number of bytes was written | ||
* - (-1) if there are any errors, should check errno | ||
*/ | ||
int esp_transport_ws_send_raw_optimized(esp_transport_handle_t t, ws_transport_opcodes_t opcode, const char *b, int len, int timeout_ms); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just a matter of taste, but I much don't like API names suffixed with ..._optimized()
. Could we think of a better name that would reflect the actual purpose? Maybe ..send_raw_once()
, ...send_raw_ext()
.
int poll_write; | ||
if ((poll_write = esp_transport_poll_write(ws->parent, timeout_ms)) <= 0) { | ||
ESP_LOGE(TAG, "Error transport_poll_write"); | ||
return poll_write; | ||
} | ||
|
||
int len2 = len - MAX_WEBSOCKET_HEADER_SIZE; | ||
if (len2 <= 125) { | ||
ws_header = buffer+MAX_WEBSOCKET_HEADER_SIZE-2-4; | ||
ws_header[header_len++] = opcode; | ||
ws_header[header_len++] = (uint8_t)(len2 | mask_flag); | ||
} else if (len2 < 65536) { | ||
ws_header = buffer+MAX_WEBSOCKET_HEADER_SIZE-4-4; | ||
ws_header[header_len++] = opcode; | ||
ws_header[header_len++] = WS_SIZE16 | mask_flag; | ||
ws_header[header_len++] = (uint8_t)(len2 >> 8); | ||
ws_header[header_len++] = (uint8_t)(len2 & 0xFF); | ||
} else { | ||
ws_header = buffer+MAX_WEBSOCKET_HEADER_SIZE-10-4; | ||
ws_header[header_len++] = opcode; | ||
ws_header[header_len++] = WS_SIZE64 | mask_flag; | ||
/* Support maximum 4 bytes length */ | ||
ws_header[header_len++] = 0; //(uint8_t)((len >> 56) & 0xFF); | ||
ws_header[header_len++] = 0; //(uint8_t)((len >> 48) & 0xFF); | ||
ws_header[header_len++] = 0; //(uint8_t)((len >> 40) & 0xFF); | ||
ws_header[header_len++] = 0; //(uint8_t)((len >> 32) & 0xFF); | ||
ws_header[header_len++] = (uint8_t)((len2 >> 24) & 0xFF); | ||
ws_header[header_len++] = (uint8_t)((len2 >> 16) & 0xFF); | ||
ws_header[header_len++] = (uint8_t)((len2 >> 8) & 0xFF); | ||
ws_header[header_len++] = (uint8_t)((len2 >> 0) & 0xFF); | ||
} | ||
|
||
if (mask_flag) { | ||
ws_header[header_len++] = 0; | ||
ws_header[header_len++] = 0; | ||
ws_header[header_len++] = 0; | ||
ws_header[header_len++] = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part could be made a static
function that would be called from both variants of send_raw()
functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0xFEEDC0DE64 Nice work! The changes make a lot of sense to me and this PR is a good start, but still needs some polishing.
Please note that IDF uses this concept of "code owners" defined in Line 143 in 8fc8f3f
and your PR touches two different areas with two owners. I'd suggest that you split your changeset into a smaller per-component PRs, with very few changes. This way it could be very easily reviewed by responsible code-owners from Espressif side. You can use this PR for changes in |
@0xFEEDC0DE64 Any news from your side? The ideas you shared in this PR look very good to me and I think would be useful to others, we just need to formally proceed with the changes. Could you please check if you can split the change in two PRs? |
sorry didnt have enough time, and we didnt do a rebase for a long time, maybe i find time to do this finally in the next few days, we are just doing a rebase of everything right now |
@0xFEEDC0DE64 Will you have time to check the status of this PR and update your work per my comments? |
Sorry, I cant do it at the moment, our esp-idf fork already has sooo many fixes and improvements ontop that I dont even know which commits I have to cherry pick to here to upstream them to you. And I dont have enough time at the moment to do this. Lets close this pull request |
I noticed that our products waste about 40% of their tcp traffic, because we send every second a very short status update through a websocket client to our cloud and esp_websocket_client keeps sending two tcp packets, one for the websocket header, and one for the websocket payload.
I tried merging those two buffers into a single one by providing additional websocket methods that expect a unused 16 byte prefix buffer to put the websocket header into, meaning the user code has to preallocate these bytes already.
websocket_client from esp-protocols also has a patch available, which uses those new optimized websocket sending mechanisms and my wireshark recording shows great success! all websocket headers are now merged together with the websocket payload.
I expect lots of review comments and change requests, I just want to start the discussion how to integrate this properly in the esp-idf