Wslay Documentation

repository·master·Indexed 20 days ago

https://github.com/tatsuhiro-t/wslay

A lightweight C library for implementing the WebSocket protocol (RFC 6455). Wslay is I/O agnostic and provides two API levels: an event-based API for non-blocking reactor patterns and a low-level frame-based API for direct frame transmission. It handles data transfer and control frames, including automatic ping replies, but does not perform the initial HTTP opening handshake.

Tokens
9.9K
Snippets
36
Records
45
Agent score
69%

What's inside Wslay

  1. Overview of Wslay WebSocket library

    master

    Wslay is a C library that implements the WebSocket protocol version 13 (RFC 6455). It focuses exclusively on the data transfer part of the protocol and does not handle the initial HTTP opening handshake.

    Key features include:

    • Support for Text and Binary messages.
    • Automatic ping replies.
    • A callback-based interface.
    • Support for external event loops.

    Crucially, Wslay performs no I/O operations itself. Instead, it uses callbacks for I/O, making it independent of specific I/O frameworks, SSL libraries, or socket implementations. This allows it to be integrated into any existing I/O architecture.

  2. Core features of Wslay

    master

    Wslay supports the following core WebSocket capabilities:

    • Message Types: Support for both Text and Binary messages.
    • Control Frames: Automatic ping reply.
    • Extensibility: Callback interface for event handling.
    • Integration: Support for external event loops.
  3. Overview of Wslay's API levels

    master

    Wslay provides two distinct levels of API depending on your integration needs:

    1. Event-based API: Designed for non-blocking reactor patterns. You register callbacks for various WebSocket events, making it ideal for integration with event loops.
    2. Frame-based API: A low-level API that allows you to send WebSocket frames directly.

    Note: Wslay only handles the data transfer portion of the WebSocket protocol. It does not perform the initial HTTP opening handshake.

  4. Understand the Wslay API levels

    master

    Wslay provides two distinct API levels depending on your application's architecture:

    1. Event-based API: Designed for non-blocking reactor patterns. You register callbacks for various WebSocket events.
    2. Frame-based API: A low-level API that allows you to send WebSocket frames directly.
  5. How Wslay handles I/O and portability

    master
    Wslay does not perform any I/O operations itself. Instead, it uses a callback interface to delegate I/O tasks to the application. This design makes Wslay independent of specific I/O frameworks, SSL libraries, or socket implementations, allowing it to be portable across various platforms and easily integrated into any existing I/O framework.
  6. Use Wslay event-based callbacks

    master

    Wslay's event-based API relies on three primary callbacks defined in struct wslay_event_callbacks to bridge the library with your socket I/O:

    1. recv_callback: Invoked by wslay_event_recv when the library needs to read data from the client.

      • If the underlying recv returns EAGAIN or EWOULDBLOCK, call wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK) to tell the library to stop reading for now.
      • For other errors or unexpected EOF, call wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE).
    2. send_callback: Invoked by wslay_event_send when the library needs to transmit data to the client.

      • Handle EAGAIN or EWOULDBLOCK by calling wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK).
      • Handle other errors by calling wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE).
    3. on_msg_recv_callback: Invoked by wslay_event_recv when a complete WebSocket message has been assembled.

      • Use wslay_is_ctrl_frame(arg->opcode) to distinguish between control frames and data frames.
      • Use wslay_event_queue_msg(ctx, &msgarg) to queue a message (e.g., for echoing).
    struct wslay_event_callbacks callbacks = {
      recv_callback,
      send_callback,
      NULL,
      NULL,
      NULL,
      NULL,
      on_msg_recv_callback
    };
    
    /* Example on_msg_recv_callback for an echo server */
    void on_msg_recv_callback(wslay_event_context_ptr ctx, 
                              const struct wslay_event_on_msg_recv_arg *arg, 
                              void *user_data) {
      if(!wslay_is_ctrl_frame(arg->opcode)) {
        struct wslay_event_msg msgarg = {
          arg->opcode, arg->msg, arg->msg_length
        };
        wslay_event_queue_msg(ctx, &msgarg);
      }
    }
  7. Implement the Wslay event loop with poll

    master

    The event loop manages the lifecycle of the WebSocket connection by checking which I/O operations the library requires. The loop should continue as long as wslay_event_want_read(ctx) or wslay_event_want_write(ctx) returns non-zero.

    Loop Logic

    1. Check Requirements: Use wslay_event_want_read(ctx) and wslay_event_want_write(ctx) to set the POLLIN and POLLOUT flags in your pollfd structure.
    2. Wait for Events: Call poll() to wait for socket activity.
    3. Process Events:
      • If POLLIN is set: Call wslay_event_recv(ctx).
      • If POLLOUT is set: Call wslay_event_send(ctx).
      • If POLLERR, POLLHUP, or POLLNVAL is set: Terminate the connection.
    4. Error Handling: If wslay_event_recv or wslay_event_send returns a non-zero value, a serious error has occurred; exit the loop and close the connection.
    while(wslay_event_want_read(ctx) || wslay_event_want_write(ctx)) {
      int r;
      while((r = poll(&event, 1, -1)) == -1 && errno == EINTR);
      if(r == -1) {
        perror("poll");
        res = -1;
        break;
      }
      if(((event.revents & POLLIN) && wslay_event_recv(ctx) != 0) ||
         ((event.revents & POLLOUT) && wslay_event_send(ctx) != 0) ||
         (event.revents & (POLLERR | POLLHUP | POLLNVAL))) {
        res = -1;
        break;
      }
      event.events = 0;
      if(wslay_event_want_read(ctx)) {
        event.events |= POLLIN;
      }
      if(wslay_event_want_write(ctx)) {
        event.events |= POLLOUT;
      }
    }
  8. Implement a WebSocket Echo Server with Wslay

    master

    To create a WebSocket echo server using Wslay, you follow a pattern of performing an HTTP handshake, setting up event-based callbacks, and running an event loop using a mechanism like poll.

    High-level Workflow

    1. Handshake: Use http_handshake to complete the initial WebSocket HTTP handshake.
    2. Socket Configuration: Set the file descriptor (fd) to non-blocking mode (e.g., using O_NONBLOCK) and optionally set TCP_NODELAY.
    3. Callback Setup: Initialize a struct wslay_event_callbacks with functions to handle data reception, data transmission, and message arrival.
    4. Initialization: Initialize the Wslay event-based API context using wslay_event_context_server_init.
    5. Event Loop: Run a loop that checks wslay_event_want_read and wslay_event_want_write to determine which socket events to wait for via poll.
    6. Data Transfer: Call wslay_event_recv when POLLIN is triggered and wslay_event_send when POLLOUT is triggered.
    7. Cleanup: Close the connection using shutdown(fd, SHUT_WR) and close(fd) once the loop terminates.
    /* High-level logic flow */
    // 1. http_handshake(fd)
    // 2. fcntl(fd, F_SETFL, O_NONBLOCK)
    // 3. wslay_event_context_server_init(&ctx, &callbacks, &session)
    // 4. while(wslay_event_want_read(ctx) || wslay_event_want_write(ctx)) { ... poll ... }
    // 5. close(fd)
  9. Receive messages with wslay_event_recv()

    master

    Use wslay_event_recv to receive messages from a peer. A single call to this function can receive multiple messages. The function continues to process incoming data until the provided wslay_event_recv_callback returns the error code WSLAY_ERR_WOULDBLOCK, indicating no more data is currently available.

    Automatic Control Frame Handling

    • Close Frames: When a close control frame is received, wslay_event_recv automatically queues a close control frame and calls wslay_event_set_read_enabled with 0 to disable further reading.
    • Ping Frames: When a ping control frame is received, wslay_event_recv automatically queues a pong control frame.

    Callback Lifecycle

    During the reception process, the following callbacks are triggered:

    1. wslay_event_on_frame_recv_start_callback: Called when a new frame starts being received.
    2. wslay_event_on_frame_recv_chunk_callback: Called when a chunk of the frame payload is received.
    3. wslay_event_on_frame_recv_end_callback: Called when a frame is completely received.
    4. wslay_event_on_msg_recv_callback: Called when a full message is completely received.
    #include <wslay/wslay.h>
    
    int wslay_event_recv(wslay_event_context_ptr ctx);
  10. Queue a close control frame with wslay_event_queue_close

    master

    Use wslay_event_queue_close to queue a WebSocket close control frame in the event-based API. This function is a convenience wrapper around wslay_event_queue_msg.

    Parameters:

    • ctx: The wslay_event_context_ptr.
    • status_code: The status code for the close frame.
    • reason: A UTF-8 encoded string containing the close reason.
    • reason_length: The length of the reason in bytes. This must be less than 123 bytes.

    Behavior:

    • If status_code is 0, the reason and reason_length are ignored, and a close control frame with a zero-length payload is queued.
    • Important: This function only queues the message. To actually transmit the close frame, you must call wslay_event_send.

    Return Values:

    • 0: Success.
    • -WSLAY_ERR_NO_MORE_MSG: Could not queue the message (e.g., a close frame has already been queued/sent, and no further messages are allowed).
    • -WSLAY_ERR_INVALID_ARGUMENT: The provided arguments are invalid.
    • -WSLAY_ERR_NOMEM: Out of memory.
    #include <wslay/wslay.h>
    
    // Example: Queueing a close frame with a reason
    int ret = wslay_event_queue_close(ctx, 1000, (const uint8_t *)"Normal Closure", 14);
    
    // Remember to call send to actually transmit the queued frame
    if (ret == 0) {
        wslay_event_send(ctx);
    }