/** * @file WSServer.h * @brief Multi-client WebSocket server using MARTe2 BasicTCPSocket. * * Accepts WebSocket connections over HTTP/1.1 (RFC 6455 upgrade). * - One accept thread (started by Start()). * - One read thread per connected client (started when client upgrades). * - BroadcastText / BroadcastBinary called from any thread (push loop or * command handlers); per-client write is protected by a mutex. * * Maximum concurrent clients: WS_MAX_CLIENTS (16). */ #ifndef STREAMHUB_WSSERVER_H_ #define STREAMHUB_WSSERVER_H_ #include "BasicTCPSocket.h" #include "FastPollingMutexSem.h" namespace StreamHub { using MARTe::uint8; using MARTe::uint16; using MARTe::uint32; using MARTe::BasicTCPSocket; using MARTe::FastPollingMutexSem; /** Maximum simultaneously connected WebSocket clients. */ static const uint32 WS_MAX_CLIENTS = 16u; /** Maximum WebSocket frame payload we will receive (commands are JSON, small). */ static const uint32 WS_MAX_RECV_PAYLOAD = 65536u; /** Maximum WebSocket frame payload we will send (data frames can be large). */ static const uint32 WS_MAX_SEND_PAYLOAD = 4u * 1024u * 1024u; /* 4 MiB */ /** * @brief Callback interface — implemented by StreamHub. */ class WSCommandCallback { public: virtual ~WSCommandCallback() {} /** Called from client read thread with a fully-received text frame. * @param slotIdx Client slot index — usable with WSServer::SendText for * unicast replies. */ virtual void OnWSCommand(const char *json, uint32 len, uint32 slotIdx) = 0; /** Called from accept thread when a new client upgrades. */ virtual void OnWSClientConnected() = 0; /** Called from client read thread when the connection closes. */ virtual void OnWSClientDisconnected() = 0; }; /** * @brief One connected WebSocket client slot. */ struct WSClientSlot { BasicTCPSocket *sock; ///< Owned pointer; NULL when slot is free bool active; MARTe::ThreadIdentifier readTid; FastPollingMutexSem writeMutex; ///< Serialises writes from push + pong threads }; /** * @brief Minimal HTTP/1.1 + WebSocket server. */ class WSServer { public: WSServer(); ~WSServer(); /** * @brief Open listen socket and start the accept thread. * @param port TCP port to listen on. * @param cb Command callback (StreamHub); must outlive WSServer. * @return true on success. */ bool Start(uint16 port, WSCommandCallback *cb); /** * @brief Stop accept thread; close all client connections; close listener. */ bool Stop(); /** * @brief Broadcast a text (JSON) frame to all connected clients. * Safe to call from any thread. */ void BroadcastText(const char *json, uint32 len); /** * @brief Broadcast a binary frame to all connected clients. * Safe to call from any thread. */ void BroadcastBinary(const uint8 *buf, uint32 len); /** * @brief Send a text frame to a single client slot. * Used internally to send pong / direct responses. */ void SendText(uint32 slotIdx, const char *json, uint32 len); /** @return Number of currently active client connections. */ uint32 ClientCount() const; /* ---- Internal thread entry points (public for Threads trampoline) ---- */ void AcceptLoop(); void ClientReadLoop(uint32 slotIdx); private: bool UpgradeHTTP(BasicTCPSocket *sock); bool SendFrame(WSClientSlot &slot, uint8 opcode, const uint8 *payload, uint32 payloadLen); uint32 AllocSlot(BasicTCPSocket *sock); void FreeSlot(uint32 idx); BasicTCPSocket tcpListener; WSClientSlot clients[WS_MAX_CLIENTS]; uint32 numClients; mutable FastPollingMutexSem clientsMutex; ///< Protects numClients and clients[] array WSCommandCallback *callback; volatile bool running; MARTe::ThreadIdentifier acceptTid; }; } /* namespace StreamHub */ #endif /* STREAMHUB_WSSERVER_H_ */