view seobeo/docs/web_socket_server.md @ 183:a8976a008a9d

[BenchMark] Added bun bench mark to test seoboe vs other popular benchmarks.
author MrJuneJune <me@mrjunejune.com>
date Fri, 23 Jan 2026 21:19:08 -0800
parents 7b1719fa918c
children
line wrap: on
line source

# Seobeo WebSocket Server - Usage Guide

A clean, easy-to-use WebSocket server library that integrates seamlessly with the existing Seobeo HTTP server.

## Features

1. **Automatic Upgrade Handling**: Automatically detects and upgrades HTTP connections to WebSocket
2. **Route-Based Handlers**: Register WebSocket handlers for specific paths (like HTTP routing)
3. **Text and Binary Messages**: Send/receive both text and binary data
4. **Broadcast Support**: Send messages to all connected clients
5. **Connection Management**: Automatic connection tracking and cleanup
6. **Integrated with HTTP Server**: Works alongside existing HTTP routes and static file serving
7. **RFC 6455 Compliant**: Full WebSocket protocol support

## API Overview

### Initialization and Registration

```c
// Initialize WebSocket server system
void Seobeo_WebSocket_Server_Init();

// Register a WebSocket handler for a specific path
void Seobeo_WebSocket_Server_Register(const char *path,
    Seobeo_WebSocket_Server_Handler handler, void *p_user_data);
```

### Handler Function Type

```c
typedef void (*Seobeo_WebSocket_Server_Handler)(
  Seobeo_WebSocket_Server_Connection *p_conn,
  Seobeo_WebSocket_Message *p_msg,
  void *p_user_data
);
```

### Sending Messages

```c
// Send to specific client
int32 Seobeo_WebSocket_Server_Send_Text(Seobeo_WebSocket_Server_Connection *p_conn,
    const char *text);
int32 Seobeo_WebSocket_Server_Send_Binary(Seobeo_WebSocket_Server_Connection *p_conn,
    const uint8 *data, size_t length);

// Broadcast to all clients
void Seobeo_WebSocket_Server_Broadcast_Text(const char *text);
void Seobeo_WebSocket_Server_Broadcast_Binary(const uint8 *data, size_t length);
```

### Connection Management

```c
// Close specific connection
void Seobeo_WebSocket_Server_Connection_Close(Seobeo_WebSocket_Server_Connection *p_conn,
    uint16 code, const char *reason);
```

## Examples

### 1. Simple Echo Server

```c
#include "seobeo/seobeo.h"

void Echo_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    printf("Received: %.*s\n", (int)p_msg->length, (char*)p_msg->data);
    Seobeo_WebSocket_Server_Send_Text(p_conn, (char*)p_msg->data);
  }
}

int main()
{
  // Initialize WebSocket routing
  Seobeo_WebSocket_Server_Init();

  // Register echo handler
  Seobeo_WebSocket_Server_Register("/echo", Echo_Handler, NULL);

  // Start HTTP server (automatically handles WebSocket upgrades)
  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);

  return 0;
}
```

### 2. Chat Room Server

```c
void Chat_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    char message[2048];
    snprintf(message, sizeof(message), "[%s]: %.*s",
        p_conn->client_id, (int)p_msg->length, (char*)p_msg->data);

    printf("Broadcasting: %s\n", message);
    Seobeo_WebSocket_Server_Broadcast_Text(message);
  }
}

int main()
{
  Seobeo_WebSocket_Server_Init();
  Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL);

  printf("Chat server started on ws://localhost:8080/chat\n");
  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);

  return 0;
}
```

### 3. Binary Data Broadcasting

```c
void Binary_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_BINARY)
  {
    printf("Received %zu bytes, broadcasting...\n", p_msg->length);
    Seobeo_WebSocket_Server_Broadcast_Binary(p_msg->data, p_msg->length);
  }
}

int main()
{
  Seobeo_WebSocket_Server_Init();
  Seobeo_WebSocket_Server_Register("/binary", Binary_Handler, NULL);

  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

### 4. Multiple Endpoints

```c
void Echo_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
    Seobeo_WebSocket_Server_Send_Text(p_conn, (char*)p_msg->data);
}

void Chat_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    char msg[2048];
    snprintf(msg, sizeof(msg), "[%s]: %.*s",
        p_conn->client_id, (int)p_msg->length, (char*)p_msg->data);
    Seobeo_WebSocket_Server_Broadcast_Text(msg);
  }
}

int main()
{
  Seobeo_WebSocket_Server_Init();

  // Register multiple WebSocket endpoints
  Seobeo_WebSocket_Server_Register("/echo", Echo_Handler, NULL);
  Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL);

  printf("Server started with:\n");
  printf("  ws://localhost:8080/echo - Echo server\n");
  printf("  ws://localhost:8080/chat - Chat room\n");

  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

### 5. Custom User Data

```c
typedef struct {
  int message_count;
  char name[64];
} ChatRoomData;

void Chat_Handler_With_Data(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  ChatRoomData *p_data = (ChatRoomData*)p_user_data;

  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    p_data->message_count++;

    char message[2048];
    snprintf(message, sizeof(message), "[%s #%d]: %.*s",
        p_data->name, p_data->message_count,
        (int)p_msg->length, (char*)p_msg->data);

    Seobeo_WebSocket_Server_Broadcast_Text(message);
  }
}

int main()
{
  ChatRoomData room_data = {0};
  strcpy(room_data.name, "Main Room");

  Seobeo_WebSocket_Server_Init();
  Seobeo_WebSocket_Server_Register("/chat", Chat_Handler_With_Data, &room_data);

  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

### 6. Mixed HTTP and WebSocket Server

```c
// HTTP route handler
Seobeo_Request_Entry* Get_Status(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;
  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
  Dowa_HashMap_Push_Arena(resp, "body", "{\"status\": \"online\"}", arena);
  return resp;
}

// WebSocket handler
void WS_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
    Seobeo_WebSocket_Server_Send_Text(p_conn, (char*)p_msg->data);
}

int main()
{
  // Initialize both HTTP and WebSocket routing
  Seobeo_Router_Init();
  Seobeo_WebSocket_Server_Init();

  // Register HTTP routes
  Seobeo_Router_Register("GET", "/api/status", Get_Status);

  // Register WebSocket routes
  Seobeo_WebSocket_Server_Register("/ws", WS_Handler, NULL);

  printf("Server started:\n");
  printf("  HTTP: http://localhost:8080/api/status\n");
  printf("  WebSocket: ws://localhost:8080/ws\n");

  Seobeo_Web_Server_Start("./public", "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

## Building

### Build the server example:
```bash
bazel build //seobeo:websocket_server_example
```

### Run the server:
```bash
bazel-bin/seobeo/websocket_server_example
```

### Test with a client:
You can test the server using the WebSocket client API or any WebSocket client tool:
```bash
# Using websocat (install: cargo install websocat)
websocat ws://localhost:8080/echo

# Using wscat (install: npm install -g wscat)
wscat -c ws://localhost:8080/chat
```

## Connection Structure

```c
struct Seobeo_WebSocket_Server_Connection_Struct {
  Seobeo_Handle *p_handle;      // Underlying socket handle
  char          *client_id;     // Unique client identifier
  boolean        is_active;     // Connection status

  // Fragment handling (internal)
  uint8  *fragment_buffer;
  size_t  fragment_length;
  size_t  fragment_capacity;
  Seobeo_WebSocket_Opcode fragment_opcode;

  Seobeo_WebSocket_Server_Connection *next;  // Linked list
};
```

## Protocol Details

The server implementation follows RFC 6455:

1. **Handshake**: Responds to HTTP Upgrade requests with proper Sec-WebSocket-Accept key
2. **Frame Format**: Server sends unmasked frames (RFC requirement)
3. **Frame Receiving**: Server receives and unmasks client frames
4. **Fragmentation**: Handles fragmented messages automatically
5. **Control Frames**: Proper handling of ping, pong, and close frames
6. **Broadcast**: Efficiently sends to all active connections

## Key Differences from Client

| Feature | Client | Server |
|---------|--------|--------|
| Frame Masking | Masks outgoing frames | Does NOT mask outgoing frames |
| Connection | Initiates connection | Accepts connections |
| Upgrade | Sends upgrade request | Responds to upgrade request |
| Multiple Connections | Single connection | Manages multiple connections |
| Broadcast | N/A | Can broadcast to all clients |

## Coding Standards

The implementation follows your specified coding standards:
- Naming: `Seobeo_WebSocket_Server_Init`, `Seobeo_WebSocket_Server_Send_Text`
- Two spaces for indentation
- New line before `{` unless it's a struct
- Single statement: no need for `{}`

## Integration with Existing Server

The WebSocket server integrates seamlessly:

1. **Automatic Detection**: The HTTP server automatically checks for WebSocket upgrade requests
2. **No Conflicts**: WebSocket and HTTP routes coexist peacefully
3. **Same Port**: WebSocket and HTTP share the same server port
4. **Unified Server**: Use `Seobeo_Web_Server_Start()` for everything

## Testing the Server

### JavaScript Client (Browser):
```javascript
const ws = new WebSocket('ws://localhost:8080/echo');

ws.onopen = () => {
  console.log('Connected!');
  ws.send('Hello, Server!');
};

ws.onmessage = (event) => {
  console.log('Received:', event.data);
};
```

### Python Client:
```python
import websocket

ws = websocket.create_connection("ws://localhost:8080/chat")
ws.send("Hello from Python!")
print(ws.recv())
ws.close()
```

## Performance Considerations

1. **Fork Mode**: Each connection runs in a separate process (SEOBEO_MODE_FORK)
2. **Edge Mode**: Multi-threaded connections (SEOBEO_MODE_EDGE)
3. **Broadcast**: Iterates through all connections (O(n) complexity)
4. **Fragmentation**: Large messages are automatically fragmented (1MB chunks)

## Complete Feature Comparison

| Feature | Implemented |
|---------|-------------|
| ✅ WebSocket upgrade handshake | Yes |
| ✅ Text messages | Yes |
| ✅ Binary messages | Yes |
| ✅ Message fragmentation | Yes |
| ✅ Ping/Pong | Yes |
| ✅ Close handshake | Yes |
| ✅ Multiple endpoints | Yes |
| ✅ Broadcast | Yes |
| ✅ Connection tracking | Yes |
| ✅ Integration with HTTP server | Yes |
| ✅ Custom user data per handler | Yes |

The Seobeo WebSocket Server provides a complete, production-ready WebSocket implementation that works seamlessly with the existing HTTP server!