NetBurner 3.5.6
PDF Version
Web Sockets

WebSocket Protocol Implementation for Real-Time Bidirectional Communication

Overview

The NetBurner WebSocket implementation provides full-duplex communication channels over a single TCP connection, enabling real-time data exchange between clients and servers. WebSockets upgrade from HTTP connections and maintain persistent connections for efficient, low-latency bidirectional messaging.

Key features include:

  • RFC 6455 compliant WebSocket protocol implementation
  • Automatic connection upgrade from HTTP to WebSocket
  • Support for text and binary data frames
  • Built-in ping/pong for connection health monitoring
  • Efficient buffering and flow control
  • Support for both client and server roles
  • Graceful connection closure with status codes
  • Frame fragmentation for large messages

Typical WebSocket Workflow

Server Side:**

  1. Register a WebSocket upgrade handler using SetNewWSHandler()
  2. In the handler, call WSUpgrade() to upgrade the HTTP connection
  3. Use WSRead() and WSWrite() for bidirectional communication
  4. Send periodic WSPing() frames to monitor connection health
  5. Close gracefully with close() when done

    Client Side:**

  1. Call WebSocket::Connect() with server hostname/IP and resource path
  2. Receive a WebSocket file descriptor on success
  3. Use WSRead() and WSWrite() for communication
  4. Monitor connection with WSPing() and WSWaitForPingReply()
  5. Close gracefully with close() when done

WebSocket Frame Types

The WebSocket protocol supports several frame types:

  • Text frames (WS_OP_TEXT): UTF-8 encoded text data
  • Binary frames (WS_OP_BIN): Raw binary data
  • Continuation frames (WS_OP_CONT): Fragments of a larger message
  • Close frames (WS_OP_CLOSE): Graceful connection termination
  • Ping frames (WS_OP_PING): Connection liveness check
  • Pong frames (WS_OP_PONG): Response to ping frame

Most applications only need to work with text and binary frames, as control frames (ping, pong, close) are handled automatically by the implementation.

Buffering and Flow Control

The WebSocket implementation uses three internal buffers:

  • RX Buffer: Incoming data from the TCP connection
  • TX Buffer: Outgoing data waiting to be sent
  • Control Buffer: Control frames (ping, pong, close)

Buffer sizes are configurable via compile-time constants:

  • WS_BUFFER_SEGMENTS: Number of buffer segments (default: 4)
  • WS_BUFFER_MIN: Minimum buffer threshold (default: 10)

Use GetWriteSpace() to check available TX buffer space before writing large amounts of data.

Connection Health Monitoring

WebSockets provide built-in mechanisms for connection health monitoring:

Ping/Pong Mechanism:**

Recommended practice:

  • Send periodic pings (e.g., every 30-60 seconds)
  • Measure round-trip time to detect network degradation
  • Close connection if multiple pings timeout
  • Use adaptive ping intervals based on application requirements

Graceful Connection Closure

WebSocket connections should be closed gracefully:

  1. Call close() on the WebSocket file descriptor
  2. The implementation sends a close frame with an optional status code
  3. The peer responds with a close frame
  4. The TCP connection is terminated

Common status codes (WS_StatusCode enum):

  • WS_STAT_NORM_CLOSE (1000): Normal closure
  • WS_STAT_GOING_AWAY (1001): Endpoint going away (server shutdown, browser navigation)
  • WS_STAT_PROT_ERROR (1002): Protocol error
  • WS_STAT_MSG_TOO_BIG (1009): Message too large to process
  • WS_STAT_UNEXPECTED_COND (1011): Unexpected condition prevented request fulfillment

Thread Safety

The WebSocket implementation uses critical sections (OS_CRIT) to protect shared data structures. Multiple tasks can safely operate on different WebSocket connections. However, operations on the same WebSocket from multiple tasks require external synchronization.

Best practices:

  • Use one task per WebSocket connection for simplicity
  • If multiple tasks access the same WebSocket, use application-level locking
  • The implementation handles internal buffer synchronization automatically

Resource Limits

The implementation has compile-time limits:

  • Maximum WebSocket connections: WS_MAX_SOCKS (default: 4)
  • Check available slots with GetFreeWebSocketCount()
  • Each WebSocket consumes buffer memory and a file descriptor
  • Consider system resources when determining WS_MAX_SOCKS value

Error Handling

WebSocket functions return negative values on errors:

  • Check return values from all WebSocket functions
  • WSRead() returns 0 for connection closure, negative for errors
  • WSWrite() returns negative if connection is closed or buffer full
  • Use errno or implementation-specific error codes for details

Common error scenarios:

  • Connection closed by peer (WSRead returns 0 or negative)
  • Write buffer full (WSWrite returns negative, check with GetWriteSpace())
  • Invalid frame received (protocol error, connection closed)
  • Timeout on blocking operations

Security Considerations

WebSocket security best practices:

  • Use WSS (WebSocket Secure) over TLS for production deployments
  • Validate Origin headers to prevent CSRF attacks
  • Implement authentication before WSUpgrade()
  • Rate-limit connections and messages to prevent DoS
  • Validate and sanitize all received data
  • Set maximum message size limits
  • Use secure protocols for sensitive data
  • Implement connection timeouts and limits

Performance Optimization

Tips for optimal WebSocket performance:

  • Batch small messages when possible to reduce overhead
  • Use binary frames for non-text data (more efficient than base64 text)
  • Call Flush() explicitly when immediate transmission is needed
  • Adjust WS_BUFFER_SEGMENTS for your message patterns
  • Use GetWriteSpace() to avoid blocking on full buffers
  • Consider message compression for large text payloads (if supported)
  • Reuse connections rather than frequent connect/disconnect cycles

Debugging WebSocket Issues

Debug utilities:

  • DumpSock(): Display detailed state of a single WebSocket
  • DumpSockets(): Display state of all WebSocket connections
  • Monitor buffer levels with GetWriteSpace()
  • Track connection state with GetState()
  • Use ping/pong to measure latency and detect connection issues
  • Enable diagnostic output to track frame exchanges

Expand for Example Usage

Examples

Example 1: Basic WebSocket Echo Server
#include <init.h>
#include <nbrtos.h>
#include <http.h>
#include <websockets.h>
const char *AppName = "WebSocket Echo Server";
// WebSocket task for each connection
void WebSocketEchoTask(void *pd) {
int wsFd = (int)(intptr_t)pd;
char buffer[512];
iprintf("WebSocket echo task started on fd %d\r\n", wsFd);
while (1) {
// Read incoming message with 30 second timeout
int len = WSRead(wsFd, buffer, sizeof(buffer) - 1, TICKS_PER_SECOND * 30);
if (len > 0) {
buffer[len] = '\0';
iprintf("Received: %s\r\n", buffer);
// Echo the message back
if (WSWrite(wsFd, buffer, len) < 0) {
iprintf("Failed to send echo\r\n");
break;
}
} else if (len == 0) {
iprintf("WebSocket connection closed by peer\r\n");
break;
} else {
iprintf("WebSocket read error or timeout\r\n");
break;
}
}
close(wsFd);
iprintf("WebSocket echo task ended\r\n");
}
// HTTP handler for WebSocket upgrade
int HandleWebSocketUpgrade(HTTP_Request *req, int sock) {
// Upgrade the HTTP connection to WebSocket
if (WSUpgrade(req, sock) >= 0) {
iprintf("WebSocket connection established\r\n");
// Create a task to handle this WebSocket
OSSimpleTaskCreatewName(WebSocketEchoTask,
MAIN_PRIO - 1,
(void *)(intptr_t)sock,
"WSEcho");
return 0; // Success
} else {
iprintf("WebSocket upgrade failed\r\n");
return -1;
}
}
void UserMain(void *pd) {
init();
// Register WebSocket endpoint at /ws
SetNewWSHandler("/ws", HandleWebSocketUpgrade);
iprintf("%s\r\n", AppName);
iprintf("WebSocket endpoint: ws://<device-ip>/ws\r\n");
while (1) {
OSTimeDly(TICKS_PER_SECOND);
}
}
#define TICKS_PER_SECOND
System clock ticks per second.
Definition constants.h:49
#define MAIN_PRIO
Recommend UserMain priority.
Definition constants.h:130
int close(int fd)
Close the specified file descriptor and free the associated resources.
int WSUpgrade(HTTP_Request *req, int sock)
Upgrade an HTTP connection to a WebSocket connection.
void StartHttp(uint16_t port, bool RunConfigMirror)
Start the HTTP web server. Further documentation in the Initialization section Initialization - Syste...
void init()
System initialization. Ideally called at the beginning of all applications, since the easiest Recover...
HTTP Request Structure.
Definition http.h:87
Example 2: WebSocket Client Connection
#include <websockets.h>
void WebSocketClientTask(void *pd) {
// Connect to WebSocket server
int wsFd = NB::WebSocket::Connect("ws.example.com",
"/api/v1/stream",
"chat", // protocol
80, // port
false); // useSSL
if (wsFd < 0) {
iprintf("Failed to connect to WebSocket server\r\n");
return;
}
iprintf("Connected to WebSocket server (fd %d)\r\n", wsFd);
// Send a message
const char *msg = "Hello from NetBurner!";
if (WSWrite(wsFd, msg, strlen(msg)) < 0) {
iprintf("Failed to send message\r\n");
close(wsFd);
return;
}
// Wait for response
char buffer[256];
int len = WSRead(wsFd, buffer, sizeof(buffer) - 1, TICKS_PER_SECOND * 10);
if (len > 0) {
buffer[len] = '\0';
iprintf("Server response: %s\r\n", buffer);
}
// Clean up
close(wsFd);
iprintf("WebSocket client disconnected\r\n");
}
static int Connect(const char *host, const char *resource, const char *protocol, int portnum=80, bool useSSL=false)
Connect to a WebSocket server using hostname with optional protocol negotiation.
Example 3: WebSocket with Connection Health Monitoring
void MonitoredWebSocketTask(void *pd) {
int wsFd = (int)(intptr_t)pd;
uint32_t lastPingTime = Secs;
uint32_t consecutiveFailures = 0;
const uint32_t PING_INTERVAL_SEC = 30;
const uint32_t MAX_FAILURES = 3;
char buffer[512];
while (1) {
// Send periodic pings
if ((Secs - lastPingTime) >= PING_INTERVAL_SEC) {
uint32_t pingSent;
if (WSPing(wsFd, 0, &pingSent) >= 0) {
// Wait for pong with timeout
if (WSWaitForPingReply(wsFd, TICKS_PER_SECOND * 5) >= 0) {
uint32_t pongReceived;
if (WSGetPingReplyTick(wsFd, &pongReceived) >= 0) {
uint32_t rtt = (pongReceived - pingSent) / (TICKS_PER_SECOND / 1000);
iprintf("Ping successful, RTT: %lu ms\r\n", rtt);
}
consecutiveFailures = 0;
} else {
iprintf("Ping timeout\r\n");
consecutiveFailures++;
}
} else {
iprintf("Failed to send ping\r\n");
consecutiveFailures++;
}
lastPingTime = Secs;
// Check if connection is dead
if (consecutiveFailures >= MAX_FAILURES) {
iprintf("Connection appears dead, closing\r\n");
break;
}
}
// Read messages with short timeout to allow ping checks
int len = WSRead(wsFd, buffer, sizeof(buffer) - 1, TICKS_PER_SECOND);
if (len > 0) {
buffer[len] = '\0';
iprintf("Received: %s\r\n", buffer);
} else if (len < 0) {
iprintf("Read error or connection closed\r\n");
break;
}
}
close(wsFd);
}
int WSPing(int fd, uint32_t len, uint32_t *sentTick)
Send a WebSocket ping frame to test connection liveness and measure round-trip time.
int WSWaitForPingReply(int fd, uint32_t timeout)
Wait for a pong reply to a previously sent ping with a specified timeout.
int WSGetPingReplyTick(int fd, uint32_t *replyTick)
Retrieve the timestamp of the most recent pong reply received on a WebSocket connection.
Example 4: WebSocket Broadcast Server
#define MAX_WS_CLIENTS 10
struct WSClient {
int fd;
bool active;
};
WSClient wsClients[MAX_WS_CLIENTS];
OS_CRIT wsClientsCrit;
// Add client to broadcast list
bool AddWSClient(int fd) {
OSCritEnter(&wsClientsCrit, TICKS_PER_SECOND);
for (int i = 0; i < MAX_WS_CLIENTS; i++) {
if (!wsClients[i].active) {
wsClients[i].fd = fd;
wsClients[i].active = true;
OSCritLeave(&wsClientsCrit);
return true;
}
}
OSCritLeave(&wsClientsCrit);
return false;
}
// Remove client from broadcast list
void RemoveWSClient(int fd) {
OSCritEnter(&wsClientsCrit, TICKS_PER_SECOND);
for (int i = 0; i < MAX_WS_CLIENTS; i++) {
if (wsClients[i].active && wsClients[i].fd == fd) {
wsClients[i].active = false;
break;
}
}
OSCritLeave(&wsClientsCrit);
}
// Broadcast message to all connected clients
void BroadcastMessage(const char *msg, int len) {
OSCritEnter(&wsClientsCrit, TICKS_PER_SECOND);
for (int i = 0; i < MAX_WS_CLIENTS; i++) {
if (wsClients[i].active) {
if (WSWrite(wsClients[i].fd, msg, len) < 0) {
iprintf("Failed to send to client %d\r\n", wsClients[i].fd);
}
}
}
OSCritLeave(&wsClientsCrit);
}
// Task for each WebSocket client
void WSClientTask(void *pd) {
int wsFd = (int)(intptr_t)pd;
char buffer[512];
if (!AddWSClient(wsFd)) {
iprintf("Too many clients, rejecting connection\r\n");
close(wsFd);
return;
}
iprintf("Client connected: fd %d\r\n", wsFd);
while (1) {
int len = WSRead(wsFd, buffer, sizeof(buffer) - 1, TICKS_PER_SECOND * 30);
if (len > 0) {
buffer[len] = '\0';
iprintf("Broadcasting message from fd %d: %s\r\n", wsFd, buffer);
// Broadcast to all clients
BroadcastMessage(buffer, len);
} else {
iprintf("Client disconnected: fd %d\r\n", wsFd);
break;
}
}
RemoveWSClient(wsFd);
close(wsFd);
}
int HandleBroadcastWS(HTTP_Request *req, int sock) {
if (WSUpgrade(req, sock) >= 0) {
OSSimpleTaskCreatewName(WSClientTask, MAIN_PRIO - 1,
(void *)(intptr_t)sock, "WSClient");
return 0;
}
return -1;
}
Example 5: WebSocket with Authentication and Options
#include <http.h>
#include <websockets.h>
// Validate authentication token
bool ValidateAuth(HTTP_Request *req) {
// Check for Authorization header or query parameter
const char *token = GetRequestParam(req, "token");
if (token == NULL) {
return false;
}
// Validate token (simplified)
return (strcmp(token, "secret_key_12345") == 0);
}
void AuthenticatedWSTask(void *pd) {
int wsFd = (int)(intptr_t)pd;
char buffer[1024];
// Set WebSocket options for text mode
ws_setoption(wsFd, WS_SO_TEXT);
iprintf("Authenticated WebSocket connection established\r\n");
// Send welcome message
const char *welcome = "{\"type\":\"welcome\",\"message\":\"Connected to secure endpoint\"}";
WSWrite(wsFd, welcome, strlen(welcome));
while (1) {
int len = WSRead(wsFd, buffer, sizeof(buffer) - 1, TICKS_PER_SECOND * 60);
if (len > 0) {
buffer[len] = '\0';
// Process JSON messages or other application protocol
iprintf("Secure message: %s\r\n", buffer);
// Echo with status
char response[1024];
snprintf(response, sizeof(response),
"{\"type\":\"ack\",\"received\":%d}", len);
WSWrite(wsFd, response, strlen(response));
} else if (len == 0) {
iprintf("Client disconnected gracefully\r\n");
break;
} else {
iprintf("Error or timeout\r\n");
break;
}
}
close(wsFd);
}
int HandleSecureWS(HTTP_Request *req, int sock) {
// Authenticate before upgrade
if (!ValidateAuth(req)) {
iprintf("Authentication failed\r\n");
writestring(sock, "HTTP/1.1 401 Unauthorized\r\n");
writestring(sock, "Content-Type: text/plain\r\n\r\n");
writestring(sock, "Authentication required");
return -1;
}
// Upgrade to WebSocket
if (WSUpgrade(req, sock) >= 0) {
OSSimpleTaskCreatewName(AuthenticatedWSTask, MAIN_PRIO - 1,
(void *)(intptr_t)sock, "WSAuth");
return 0;
}
return -1;
}
int writestring(int fd, const char *str)
Write a null terminated ascii string to the stream associated with a file descriptor (fd)....
#define WS_SO_TEXT
Socket option flag to enable text frame mode (vs binary mode)
Definition websockets.h:72


Function Description
WSUpgrade() Upgrade HTTP connection to WebSocket
WSRead() Read data from WebSocket (ws_read wrapper)
WSWrite() Write data to WebSocket (ws_write wrapper)
WSPing() Send ping frame for connection health check
WSWaitForPingReply() Wait for pong response with timeout
WSGetPingReplyTick() Get timestamp of last pong for latency measurement
SetNewWSHandler() Register WebSocket upgrade handler
NB::WebSocket::Connect() Create client WebSocket connection
close() Close WebSocket connection

Configuration and Return Code Definitions

#define WS_BUFFER_SEGMENTS 4
#define WS_MAX_SOCKS 4
#define WS_BUFFER_MIN 10
#define WS_ACCUM_DLY ((TICKS_PER_SECOND / 4) + 1)
#define WS_FLUSH_TIMEOUT 0
#define WS_FIN_BIT 0x80
#define WS_OP_CONT 0x0
#define WS_OP_TEXT 0x1
#define WS_OP_BIN 0x2
#define WS_OP_CLOSE 0x8
#define WS_OP_PING 0x9
#define WS_OP_PONG 0xA
#define WS_SO_TEXT 0x8

Number of buffer segments allocated for each WebSocket connection.

Each WebSocket connection uses segmented buffers for efficient memory management and flow control. This value determines how many segments are allocated for the receive (RX), transmit (TX), and control buffers.

#define WS_BUFFER_SEGMENTS 4

Default Value:** 4 segments per buffer

Memory Impact:**

  • Each connection uses WS_BUFFER_SEGMENTS segments for three buffers (RX, TX, Control)
  • Total segments per connection: WS_BUFFER_SEGMENTS x 3
  • Adjust based on available memory and expected message sizes

    Tuning Guidelines:**

  • Increase for applications with large messages or high throughput
  • Decrease to conserve memory when handling many small messages
  • Each segment is typically sized based on the buffer implementation
  • Consider buffer exhaustion when multiple connections are active
Note
This is a compile-time constant and cannot be changed at runtime
Changing this value requires recompiling the WebSocket library
See also
WS_MAX_SOCKS
WS_BUFFER_MIN

Maximum number of simultaneous WebSocket connections supported.

This defines the compile-time limit for concurrent WebSocket connections that can be active on the device. Each connection consumes memory for buffers and internal state management.

define WS_MAX_SOCKS 4

Default Value:** 4 connections

Resource Considerations:**

  • Each connection uses approximately (WS_BUFFER_SEGMENTS x 3) buffer segments
  • Each connection requires a file descriptor and internal state structure
  • Total memory usage scales linearly with WS_MAX_SOCKS

    Tuning Guidelines:**

  • Increase for servers handling multiple simultaneous clients
  • Decrease to conserve memory on resource-constrained devices
  • Use GetFreeWebSocketCount() to check available connection slots at runtime
  • Consider total system file descriptors and memory when adjusting

    Example Memory Calculation:**

    // Approximate per-connection overhead:
    // - Buffers: WS_BUFFER_SEGMENTS x 3 x segment_size
    // - State: sizeof(WebSocket) ~= 200+ bytes
    // - File descriptor: 1 fd
    //
    // For WS_MAX_SOCKS = 4:
    // - Total: 4 connections x overhead_per_connection
Note
This is a compile-time constant and cannot be changed at runtime
Exceeding this limit will cause new connection attempts to fail
Use GetFreeWebSocketCount() to monitor available slots
See also
WS_BUFFER_SEGMENTS
GetFreeWebSocketCount()

Expand for Example Usage

Examples

Example: Checking Available Connection Slots
int HandleWebSocketUpgrade(HTTP_Request *req, int sock) {
// Check if we can accept more connections
if (freeSlots <= 0) {
iprintf("No WebSocket slots available (%d/%d used)\r\n",
WS_MAX_SOCKS - freeSlots, WS_MAX_SOCKS);
writestring(sock, "HTTP/1.1 503 Service Unavailable\r\n");
writestring(sock, "Content-Type: text/plain\r\n\r\n");
writestring(sock, "Maximum WebSocket connections reached");
return -1;
}
iprintf("Accepting WebSocket connection (%d slots remaining)\r\n", freeSlots);
if (WSUpgrade(req, sock) >= 0) {
OSSimpleTaskCreatewName(WebSocketTask, MAIN_PRIO - 1,
(void *)(intptr_t)sock, "WSTask");
return 0;
}
return -1;
}
static int GetFreeWebSocketCount()
Get the number of available WebSocket connection slots.
#define WS_MAX_SOCKS
Maximum number of simultaneous WebSocket connections supported.
Definition websockets.h:59


Minimum buffer space threshold before triggering buffer management actions.

This defines the minimum number of bytes that should remain available in the transmit buffer before the WebSocket implementation takes buffer management actions, such as flushing pending data or blocking writes.

define WS_BUFFER_MIN 10

Default Value:** 10 bytes

Purpose:**

  • Prevents buffer exhaustion by maintaining a minimum safety margin
  • Triggers automatic flush operations when buffer space is low
  • Helps prevent write operations from blocking unnecessarily

    Buffer Management:**

  • When available space falls below this threshold, writes may block or flush
  • Use GetWriteSpace() to check available buffer space before large writes
  • Automatic flushing helps maintain data flow even with small buffer margins
Note
This is a compile-time constant affecting buffer management behavior
Setting too high may reduce effective buffer capacity
Setting too low may increase risk of buffer overrun
See also
GetWriteSpace()
Flush()

Accumulation delay for batching small writes before transmission.

Time delay (in system ticks) that the WebSocket implementation waits to accumulate small writes before sending them as a single frame. This reduces protocol overhead by batching multiple small writes together.

define WS_ACCUM_DLY ((TICKS_PER_SECOND / 4) + 1)

Default Value:** (TICKS_PER_SECOND / 4) + 1 (approximately 250ms + 1 tick)

Purpose:**

  • Reduces frame overhead by combining multiple small writes
  • Improves network efficiency for applications with frequent small messages
  • Balances latency against protocol overhead

    Behavior:**

  • Small writes are buffered for up to WS_ACCUM_DLY ticks
  • If buffer fills or Flush() is called, data is sent immediately
  • Larger writes may bypass accumulation delay

    Tuning Considerations:**

  • Increase for applications prioritizing throughput over latency
  • Decrease for real-time applications requiring minimal latency
  • Set to 0 to disable accumulation (send every write immediately)
  • Call Flush() explicitly to bypass accumulation when needed
Note
Value is in system ticks (convert using TICKS_PER_SECOND)
Explicit Flush() calls override this delay
See also
Flush()
TICKS_PER_SECOND
WS_FLUSH_TIMEOUT

Timeout value for flush operations (in system ticks).

Specifies the maximum time to wait when flushing buffered WebSocket data. A value of 0 indicates non-blocking flush operations.

define WS_FLUSH_TIMEOUT 0

Default Value:** 0 (non-blocking)

Behavior:**

  • 0: Flush returns immediately, even if not all data is sent
  • Positive value: Flush waits up to specified ticks for data transmission
  • Used internally by the Flush() method

    Non-blocking Flush (0):**

  • Queues data for transmission but doesn't wait
  • Suitable for most applications where blocking is undesirable
  • Transmission occurs in background as TCP allows
Note
Value is in system ticks
Most applications should use the default non-blocking behavior
See also
Flush()
WS_ACCUM_DLY

WebSocket frame FIN bit mask indicating final fragment.

The FIN bit is set in the first byte of a WebSocket frame to indicate whether this is the final fragment of a message. According to RFC 6455, the FIN bit is the most significant bit (bit 7) of the first frame byte.

define WS_FIN_BIT 0x80

Bit Position:** 0x80 (bit 7 set)

Frame Fragmentation:**

  • FIN = 1: This is the final (or only) fragment of the message
  • FIN = 0: More fragments follow this one

    Usage:**

  • Set on single-frame messages (most common case)
  • Set on the last frame of a fragmented message
  • Clear on continuation frames (except the last one)

    Message Fragmentation Example:**

    // Large message sent in 3 fragments:
    // Frame 1: opcode=TEXT, FIN=0 (first fragment)
    // Frame 2: opcode=CONT, FIN=0 (middle fragment)
    // Frame 3: opcode=CONT, FIN=1 (final fragment)
Note
This is typically handled automatically by the WebSocket implementation
Fragmentation allows sending large messages without buffering entirely
See also
WS_OP_CONT
WS_OP_TEXT
WS_OP_BIN

WebSocket continuation frame opcode.

Opcode value for continuation frames, which carry subsequent fragments of a message that was started with a TEXT or BINARY frame. Continuation frames allow large messages to be sent in multiple fragments.

define WS_OP_CONT 0x0

Opcode Value:** 0x0

Frame Sequence:**

  1. First frame: TEXT or BINARY opcode with FIN=0
  2. Middle frames: CONT opcode with FIN=0
  3. Final frame: CONT opcode with FIN=1

    Rules:**

  • Must follow a TEXT or BINARY frame with FIN=0
  • Cannot start a new message (must be part of fragmented message)
  • All fragments must be same type (all text or all binary)
  • Control frames (PING, PONG, CLOSE) cannot be fragmented
Note
The WebSocket implementation typically handles fragmentation automatically
Interleaving control frames between fragments is allowed by RFC 6455
See also
WS_OP_TEXT
WS_OP_BIN
WS_FIN_BIT

WebSocket text frame opcode for UTF-8 encoded text data.

Opcode value for frames containing UTF-8 encoded text data. Text frames are used for sending human-readable text messages and JSON data.

#define WS_OP_TEXT 0x1

Opcode Value:** 0x1

Characteristics:**

  • Payload must be valid UTF-8 encoded text
  • Most common frame type for web applications
  • Used for JSON, XML, plain text, and other text protocols
  • Invalid UTF-8 results in protocol error and connection closure

    vs Binary Frames:**

  • TEXT: Human-readable, UTF-8 validated, JSON-friendly
  • BINARY: Raw bytes, no encoding constraints, more efficient for non-text
Note
Payload validation ensures UTF-8 compliance (per RFC 6455)
For non-text data, use WS_OP_BIN for better performance
See also
WS_OP_BIN
WS_OP_CONT
WS_SO_TEXT

Expand for Example Usage

Examples

Example: Sending Text vs Binary Data
int wsFd = ...; // WebSocket connection
// Send text data (automatically uses WS_OP_TEXT)
const char *jsonMsg = "{\"temp\":25.5,\"humidity\":60}";
WSWrite(wsFd, jsonMsg, strlen(jsonMsg));
// For binary data, use WS_OP_BIN (implicit)
// Set socket option if needed:
ws_clroption(wsFd, WS_SO_TEXT); // Ensure binary mode
uint8_t binaryData[100];
// ... fill with binary data ...
WSWrite(wsFd, (char*)binaryData, sizeof(binaryData));


WebSocket binary frame opcode for raw binary data.

Opcode value for frames containing arbitrary binary data. Binary frames are more efficient than text frames for non-textual data as they bypass UTF-8 validation and encoding overhead.

#define WS_OP_BIN 0x2

Opcode Value:** 0x2

Characteristics:**

  • Payload can contain any byte sequence
  • No UTF-8 validation performed
  • More efficient for images, files, compressed data, etc.
  • Commonly used for protocol buffers, MessagePack, CBOR

    Use Cases:**

  • Binary protocols (Protocol Buffers, MessagePack, BSON)
  • Image and media data
  • Compressed data
  • Encrypted payloads
  • Raw sensor data
  • Any non-text binary format

    vs Text Frames:**

  • BINARY: No encoding overhead, any byte values, faster
  • TEXT: UTF-8 validated, human-readable, JSON-compatible
Note
More efficient than Base64-encoding binary data in text frames
No content validation means any byte sequence is valid
See also
WS_OP_TEXT
WS_OP_CONT

Expand for Example Usage

Examples

Example 1: Sending Binary Sensor Data
struct SensorReading {
uint32_t timestamp;
float temperature;
float humidity;
uint16_t pressure;
} __attribute__((packed));
void SendSensorData(int wsFd) {
SensorReading reading;
reading.timestamp = Secs;
reading.temperature = 25.5f;
reading.humidity = 60.0f;
reading.pressure = 1013;
// Send as binary frame (more efficient than JSON)
ws_clroption(wsFd, WS_SO_TEXT); // Ensure binary mode
WSWrite(wsFd, (char*)&reading, sizeof(reading));
}
Example 2: Binary vs Text Efficiency
// JSON text approach (less efficient):
// {"timestamp":12345,"temp":25.5,"humidity":60,"pressure":1013}
// = ~65 bytes as text
// Binary approach (more efficient):
// struct: 4 + 4 + 4 + 2 = 14 bytes
// Savings: ~78% size reduction
void CompareTextVsBinary(int wsFd) {
// Text frame approach
ws_setoption(wsFd, WS_SO_TEXT);
char jsonBuf[100];
int jsonLen = snprintf(jsonBuf, sizeof(jsonBuf),
"{\"ts\":%lu,\"t\":%.1f,\"h\":%.1f,\"p\":%d}",
Secs, 25.5f, 60.0f, 1013);
WSWrite(wsFd, jsonBuf, jsonLen);
iprintf("Text frame: %d bytes\r\n", jsonLen);
// Binary frame approach
ws_clroption(wsFd, WS_SO_TEXT);
SensorReading reading = {Secs, 25.5f, 60.0f, 1013};
WSWrite(wsFd, (char*)&reading, sizeof(reading));
iprintf("Binary frame: %d bytes\r\n", sizeof(reading));
}
Example 3: Sending Image Data
void SendImageFrame(int wsFd, uint8_t *imageData, uint32_t imageSize) {
// Ensure binary mode for image data
ws_clroption(wsFd, WS_SO_TEXT);
// Check buffer space
if (GetWriteSpace() < imageSize) {
iprintf("Insufficient buffer space\r\n");
return;
}
// Send image as binary frame
int sent = WSWrite(wsFd, (char*)imageData, imageSize);
if (sent == imageSize) {
iprintf("Sent %u byte image\r\n", imageSize);
} else {
iprintf("Image send failed\r\n");
}
}


WebSocket close frame opcode for connection termination.

Opcode value for close frames, which initiate graceful connection closure. Close frames may include a status code and optional reason string explaining why the connection is being closed.

define WS_OP_CLOSE 0x8

Opcode Value:** 0x8

Closure Handshake:**

  1. One endpoint sends CLOSE frame with optional status code
  2. Peer responds with CLOSE frame
  3. Both endpoints close the underlying TCP connection

    Close Frame Format:**

  • Bytes 0-1: Status code (optional, see WS_StatusCode enum)
  • Bytes 2+: Reason string in UTF-8 (optional)

    Common Status Codes:**

  • 1000: Normal closure (WS_STAT_NORM_CLOSE)
  • 1001: Going away (WS_STAT_GOING_AWAY)
  • 1002: Protocol error (WS_STAT_PROT_ERROR)
  • 1009: Message too big (WS_STAT_MSG_TOO_BIG)
  • 1011: Unexpected condition (WS_STAT_UNEXPECTED_COND)
Note
Close frames are automatically handled by the WebSocket implementation
After sending/receiving CLOSE, no more data frames should be sent
Always close gracefully rather than abruptly terminating TCP connection
See also
WS_STAT_NORM_CLOSE
WS_STAT_GOING_AWAY

WebSocket ping frame opcode for connection liveness check.

Opcode value for ping frames, which test connection liveness and measure round-trip time. The recipient must respond with a pong frame containing the same payload.

define WS_OP_PING 0x9

Opcode Value:** 0x9

Ping/Pong Protocol:**

  • Sender transmits PING frame with optional payload (up to 125 bytes)
  • Recipient must respond with PONG frame echoing the same payload
  • Used to verify connection is alive and measure latency

    Use Cases:**

  • Keep-alive mechanism to prevent idle timeouts
  • Connection health monitoring
  • Round-trip time measurement
  • Network quality assessment
  • Dead connection detection

    Implementation Notes:**

  • Ping frames are control frames and cannot be fragmented
  • Payload should be kept small (RFC 6455 recommends <= 125 bytes)
  • Multiple pings can be outstanding simultaneously
  • Pongs should be sent in response order
Note
Use WSPing() function rather than manually constructing frames
Automatic pong responses are handled by the implementation
See also
WS_OP_PONG
WSPing()
WSWaitForPingReply()
WSGetPingReplyTick()

Expand for Example Usage

Examples

Example: Connection Health Monitoring
void MonitorWebSocketHealth(int wsFd) {
uint32_t pingSent, pongReceived;
// Send ping frame (handled by WSPing, not raw WS_OP_PING)
if (WSPing(wsFd, 0, &pingSent) >= 0) {
// Wait for pong response
if (WSWaitForPingReply(wsFd, TICKS_PER_SECOND * 5) >= 0) {
if (WSGetPingReplyTick(wsFd, &pongReceived) >= 0) {
uint32_t rtt = (pongReceived - pingSent) / (TICKS_PER_SECOND / 1000);
iprintf("Connection alive, RTT: %lu ms\r\n", rtt);
}
} else {
iprintf("Ping timeout - connection may be dead\r\n");
}
}
}


WebSocket pong frame opcode for ping response.

Opcode value for pong frames, which are sent in response to ping frames. Pong frames must echo back the exact payload received in the corresponding ping frame.

#define WS_OP_PONG 0xA

Opcode Value:** 0xA

Ping/Pong Protocol:**

  • Automatically sent by implementation when PING is received
  • Must contain the same payload as the received PING
  • Can also be sent unsolicited as a unidirectional heartbeat

    Response Rules:**

  • Pong payload must exactly match the ping payload
  • Should be sent as soon as practical after receiving ping
  • Multiple pongs may be needed if multiple pings are received
  • Pongs should be sent in the order pings were received

    Unsolicited Pongs:**

  • Can be sent without a corresponding ping (RFC 6455 allows this)
  • Useful as unidirectional heartbeat mechanism
  • Receiving endpoint may ignore unsolicited pongs
Note
Pong responses are typically handled automatically by the WebSocket stack
Applications rarely need to manually send pong frames
Use WSPong() if manual pong transmission is needed
See also
WS_OP_PING
WSPing()
WSWaitForPingReply()

WebSocket socket option to enable text frame mode.

Socket option flag to configure a WebSocket connection to send data as text frames (WS_OP_TEXT) rather than binary frames (WS_OP_BIN). This affects the opcode used for outgoing data frames.

#define WS_SO_TEXT 0x8

Option Value:** 0x8

Behavior:**

  • When set: Outgoing frames use WS_OP_TEXT opcode
  • When clear: Outgoing frames use WS_OP_BIN opcode

    Text Frame Requirements:**

  • Payload must be valid UTF-8 encoded text
  • Implementation may validate UTF-8 encoding
  • Use for JSON, XML, HTML, or plain text data

    Functions:**

  • ws_setoption(fd, WS_SO_TEXT): Enable text mode
  • ws_clroption(fd, WS_SO_TEXT): Enable binary mode
  • ws_getoption(fd): Check current option state

    When to Use Text Mode:**

  • Sending JSON, XML, or other text-based protocols
  • Human-readable messages
  • When client/server expects text frames
  • For maximum compatibility with web browsers

    When to Use Binary Mode:**

  • Raw sensor data or binary protocols
  • Image, audio, video data
  • Compressed or encrypted payloads
  • Maximum efficiency (no UTF-8 overhead)
Note
Default mode may vary; set explicitly for predictable behavior
Mode can be changed dynamically during connection lifetime
See also
WS_OP_TEXT
WS_OP_BIN
ws_setoption()
ws_clroption()
ws_getoption()

Expand for Example Usage

Examples

Example 1: Setting Text Mode for JSON
void SendJSONMessage(int wsFd, const char *jsonStr) {
// Enable text mode for JSON data
ws_setoption(wsFd, WS_SO_TEXT);
// Send JSON string (will use WS_OP_TEXT)
int sent = WSWrite(wsFd, jsonStr, strlen(jsonStr));
if (sent > 0) {
iprintf("Sent JSON: %s\r\n", jsonStr);
}
}
Example 2: Switching Between Text and Binary
void SendMixedData(int wsFd) {
// Send text message
ws_setoption(wsFd, WS_SO_TEXT);
const char *textMsg = "{\"type\":\"status\",\"value\":\"ready\"}";
WSWrite(wsFd, textMsg, strlen(textMsg));
// Switch to binary mode for sensor data
ws_clroption(wsFd, WS_SO_TEXT);
struct {
uint32_t timestamp;
float temperature;
} sensorData = {Secs, 25.5f};
WSWrite(wsFd, (char*)&sensorData, sizeof(sensorData));
// Back to text mode for acknowledgment
ws_setoption(wsFd, WS_SO_TEXT);
const char *ack = "{\"type\":\"ack\"}";
WSWrite(wsFd, ack, strlen(ack));
}
Example 3: Checking Current Mode
void DisplayWebSocketMode(int wsFd) {
int options = ws_getoption(wsFd);
if (options & WS_SO_TEXT) {
iprintf("WebSocket fd %d: TEXT mode (UTF-8)\r\n", wsFd);
} else {
iprintf("WebSocket fd %d: BINARY mode\r\n", wsFd);
}
}
Example 4: Application-Specific Protocol
// Define message types
enum MessageType {
MSG_TEXT_JSON,
MSG_BINARY_PROTOBUF,
MSG_BINARY_IMAGE
};
void SendMessage(int wsFd, MessageType type, const void *data, int len) {
switch (type) {
case MSG_TEXT_JSON:
// Use text frames for JSON
ws_setoption(wsFd, WS_SO_TEXT);
iprintf("Sending as TEXT frame\r\n");
break;
case MSG_BINARY_PROTOBUF:
case MSG_BINARY_IMAGE:
// Use binary frames for protobuf and images
ws_clroption(wsFd, WS_SO_TEXT);
iprintf("Sending as BINARY frame\r\n");
break;
}
int sent = WSWrite(wsFd, (const char*)data, len);
iprintf("Sent %d bytes\r\n", sent);
}
Example 5: WebSocket Mode Configuration Helper
// Helper function to configure WebSocket for JSON API
void ConfigureJSONWebSocket(int wsFd) {
// Set text mode
ws_setoption(wsFd, WS_SO_TEXT);
// Verify setting
int opts = ws_getoption(wsFd);
if (opts & WS_SO_TEXT) {
iprintf("WebSocket configured for JSON (text mode)\r\n");
} else {
iprintf("ERROR: Failed to set text mode\r\n");
}
}
// Helper function to configure WebSocket for binary protocol
void ConfigureBinaryWebSocket(int wsFd) {
// Clear text mode (enable binary)
ws_clroption(wsFd, WS_SO_TEXT);
// Verify setting
int opts = ws_getoption(wsFd);
if (!(opts & WS_SO_TEXT)) {
iprintf("WebSocket configured for binary protocol\r\n");
} else {
iprintf("ERROR: Failed to clear text mode\r\n");
}
}