curl_ws_send(3)

πŸ’Œ curl_ws_send β€” send WebSocket data πŸ’Œ

okay so real talk, bestie, you're holding the man page for curl_ws_send(3), fresh out of the Library Functions Manual, and it's literally the function that yeets your WebSocket data out into the world. let's get into it ✨


🌸 SYNOPSIS 🌸

#include <curl/curl.h>

CURLcode curl_ws_send(CURL *curl, const void *buffer, size_t buflen,
                      size_t *sent, curl_off_t fragsize,
                      unsigned int flags);

that's the whole signature, no cap. five params and a dream 🫢


πŸ’– DESCRIPTION πŸ’–

so what we're doing here is sending the specific message chunk over an already-established WebSocket connection. buffer has to point to a valid memory location that contains (at least) buflen bytes of payload memory. like, bestie, at least buflen, i'm begging you πŸ™ˆ

sent gets set to the number of payload bytes actually sent. and here's the tea: if the return value is CURLE_OK but sent is less than the given buflen, that means libcurl was unable to consume the complete payload in a single call. main character energy but with limits, i fear. in that case the application must call this function again until all payload is processed. and you've gotta update buffer and buflen on every following invocation so they only point to the remaining piece of the payload β€” lowkey important, don't skip it πŸ’…

fragsize should always be set to zero unless a (huge) frame shall be sent using multiple calls with partial content per call explicitly. in that case you must set the CURLWS_OFFSET bit and set the fragsize as documented in the section on CURLWS_OFFSET below. no vibes-based guessing, ok? πŸ‘‘

flags must contain at least one flag indicating the type of the message. to send a fragmented message consisting of multiple frames, additionally set the CURLWS_CONT bit in all frames except the final one. the appropriate message type bit should be set in every frame of a fragmented message without exemption. omitting the message type for continuation frames of a fragmented message is only supported for backwards compatibility and highly discouraged β€” it's giving legacy code, and not in a cute vintage way πŸ’€

for more deets on the supported flags, see below and in curl_ws_meta(3).

if CURLWS_RAW_MODE is enabled in CURLOPT_WS_OPTIONS(3), the flags argument should be set to 0. just zero. that's it. periodt.

⚠️ warning bestie: while it is possible to invoke this function from a callback, such a call is blocking in this situation, e.g. only returns after all data has been sent or an error is encountered. so like... it's not async in there. be aware. 😭

✨ FLAGS ✨

supports all flags documented in curl_ws_meta(3) and additionally the following flags:

  • CURLWS_OFFSET β€” the provided data is only a partial frame and there is more coming in a following call to curl_ws_send(). when sending only a piece of the frame like this, the fragsize must be provided with the total expected frame size in the first call and must be zero in all subsequent calls. first call: full size. every call after: zero. do not mix this up, i'm serious πŸŽ€

🌈 PROTOCOLS 🌈

this functionality affects ws only. websocket girlies only, everyone else can sit this one out πŸ’œ


πŸ¦‹ EXAMPLE πŸ¦‹

here's a whole little program, slay:

#include <string.h> /* for strlen */

int main(void)
{
  const char *buffer = "PAYLOAD";
  size_t offset = 0;
  CURLcode result = CURLE_OK;
  CURL *curl = curl_easy_init();

  curl_easy_setopt(curl, CURLOPT_URL, "wss://example.com/");
  curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
  /* start HTTPS connection and upgrade to WSS, then return control */
  curl_easy_perform(curl);

  while(!result) {
    size_t sent;
    result = curl_ws_send(curl, buffer + offset,
                          strlen(buffer) - offset, &sent,
                          0, CURLWS_TEXT);
    offset += sent;

    if(result == CURLE_OK) {
      if(offset == strlen(buffer))
        break; /* finished sending */
    }

    if(result == CURLE_AGAIN)
      /* in real application: wait for socket here, e.g. using select() */
      result = CURLE_OK;
  }

  curl_easy_cleanup(curl);
  return (int)result;
}

look at that loop go. iconic. CURLWS_TEXT, CURLE_AGAIN, the whole little dance β€” it's all there πŸ”₯


πŸŽ€ AVAILABILITY πŸŽ€

added in curl 7.86.0. so if you're on something older... upgrade bestie, i'm so serious 😭


πŸ‘‘ RETURN VALUE πŸ‘‘

this function returns a CURLcode indicating success or error.

CURLE_OK (0) means everything was OK. non-zero means an error occurred, see libcurl-errors(3). and if CURLOPT_ERRORBUFFER(3) was set with curl_easy_setopt(3) there can be an error message stored in the error buffer when non-zero is returned. love that for us πŸ’–

instead of blocking, the function returns CURLE_AGAIN. the correct behavior is then to wait for the socket to signal readability before calling this function again. go wait by the socket like it's the group chat, and come back when it's readable 🧚

any other non-zero return value indicates an error. see the libcurl-errors(3) man page for the full list with descriptions.


🌸 SEE ALSO 🌸

curl_easy_getinfo(3), curl_easy_perform(3), curl_easy_setopt(3), curl_ws_recv(3), curl_ws_start_frame(3), libcurl-ws(3)


and that's the whole page, bestie πŸ’…βœ¨

courtesy of libcurl, dated 2026-10-12 β€” this page was curl_ws_send(3) and it ate, no crumbs. go send some frames πŸ’–πŸ¦‹πŸŽ€

πŸ“¦ where this page comes from