Mercurial
diff seobeo/docs/web_socket_server.md @ 121:7b1719fa918c
[Seobeo] Added web socket server.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Thu, 08 Jan 2026 06:45:10 -0800 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/docs/web_socket_server.md Thu Jan 08 06:45:10 2026 -0800 @@ -0,0 +1,391 @@ +# 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!