Minimalistic header only WebSocket server implementation in C++11
PUBLIC DOMAIN - no warranty implied or offered, use this at your own risk
-
"I don't care! Just gimme something to start with already!" (click)
-
this is just an experiment, currently full of bugs and probably full of hidden deadlocks and other nasty race conditions
-
only a tiny part of RFC 6455 is implemented
-
no TLS, secured connections are not supported and probably never will be supported
-
the API might (and probably will) change in the future
-
you can use this as a simple TCP network library as well
-
there is a primitive HTTP server class you might want to use as well
-
WebSocket frame continuation is supported (and automatically resolved for you)
-
performance is not great (but should be enough in most cases)
-
UTF-8? Unicode? What?!
-
bug-fixes and suggestions are welcome!
I needed an easy way to quickly create remote connection interfaces. By embedding small HTTP/WebSocket server into my code, I can communicate with it from almost any browser, any platform and any place over network. Writing simple debugging tools, remote controllers or profilers gets much easier, because all I need is just a bit of HTML and JavaScript!
If you are looking for something production ready, safe and fast, you should probably use C++ Web Toolkit or WebSocket++. This library is meant to be used just for experimental and simple debugging purposes.
In this example server accepts and creates client connections asynchronously and every client gets asynchronous callback async_received_data
when complete data block is received (continuation WebSocket frames are resolved automatically):
#include <iostream>
#define HEADSOCKET_IMPLEMENTATION
#include "HeadSocket.h"
using namespace headsocket;
class client : public web_socket_client
{
HEADSOCKET_CLIENT(client, web_socket_client) { }
public:
bool async_received_data(const data_block &db, uint8_t *ptr, size_t length) override
{
if (db.op == opcode::text)
{
// Handle text message (null-terminated string is in 'ptr')
// ...
// Send text response back to client
push("Thank you for this beautiful message!");
}
else
{
// Handle 'length' bytes of binary data in 'ptr'
// ...
// Send binary response back to client
push(&length, sizeof(size_t));
}
// Consume this data block
return true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
int main()
{
int port = 12345;
typedef web_socket_server<client> server_t;
auto server = server_t::create(port);
if (server->is_running())
std::getChar();
return 0;
}
This example has server accepting clients asynchronously, but reading client's data is done by polling (clients still send and receive all the data asynchronously in the background):
#include <iostream>
#define HEADSOCKET_IMPLEMENTATION
#include "HeadSocket.h"
using namespace headsocket;
int main()
{
int port = 12345;
typedef web_socket_server<web_socket_client> server_t;
auto server = server_t::create(port);
// Buffer for transfering data blocks out of connected clients
std::vector<uint8_t> buffer;
// Your main loop
while (true)
{
for (auto client : server->clients())
{
// Loop until there is no new data block available (returned size is 0)
while (size_t size = client->peek())
{
// Resize buffer accordingly
buffer.resize(size);
// Copy next available client's data block into our buffer
client->pop(buffer.data(), size);
// ... process the buffer!
}
}
// ... do some other work
}
return 0;
}
Abstract class for handling incomming connections. You should never create an instance of it directly, since it acts only as a factory base for derived classes, such as tcp_server<T>
or web_socket_server<T>
. Public interface provides only those methods:
void
stop()
: Stops the server, disconnects all clients.bool
is_running()
const
: Returnstrue
if server is still running.void
disconnect(ptr<basic_tcp_client> client)
: Forcibly disconnects a client.
If you want to derive your own basic_tcp_server
, you are required to implement these methods:
bool
handshake(connection &conn)
: Right after server accepts new socket connection, you can optionally do some handshake logic there. If the handshake succeeds or you don't need to do any handshaking at all, returntrue
.ptr<basic_tcp_client>
accept(connection &conn)
: Called by the server after handshake is successfully done. This is a factory method for creating your own instances ofbasic_tcp_client
classes. If you are not able to create client instance, returnnullptr
.void
client_connected(ptr<basic_tcp_client> client)
: Called when new client is successfully created by previousaccept
call.void
client_disconnected(ptr<basic_tcp_client> client)
: Called before client is disconnected by server.
When constructed, basic_tcp_server
automatically spawns two helper threads; one for accepting incoming connections and one for closing disconnected clients. You can take a look at basic_tcp_server::accept_thread
implementation to see how the new incoming connections are handled with handshake
, accept
and client_connected
calls.
Concrete implementation of basic_tcp_server
that makes sure you are not working against basic_tcp_client
instances, but rather with clients of specified type <T>
, where <T>
must be derived from basic_tcp_client
. This is done by making sure that:
basic_tcp_server::accept
is overridden and returns new instances of<T>
basic_tcp_server::client_connected
is overridden and redirects all calls to a separateclient_connected(ptr<T> client)
methodbasic_tcp_server::client_disconnected
is overridden and redirects as well- all three methods are made private
For convenience, basic_tcp_server::handshake
returns just true
.
Public interface provides this extra method:
detail::enumerator<T>
clients()
const
: Returns enumerator for iterating through all clients. Look at example 2 to see how it can be used.
Abstract class for connected clients with very basic interface. You should not try to create instance of this class directly, since there are no methods for sending or receiving data.
Public interface provides:
void
disconnect()
: Disconnects this client from the server.bool
is_connected()
const
: Returnstrue
if client is still connected.ptr<basic_tcp_server>
server()
const
: Returns server instance which originally created this client. Could benullptr
if client was created manually.id_t
id()
const
: Returns ID assigned by server.
Concrete implementation of basic_tcp_client
, allows sending and receiving data synchronously.
Public interface provides these extra methods:
size_t
write(const void *ptr, size_t length)
: Writes (sends) length bytes from memory location ptr. Returns number of bytes written, orbasic_tcp_client::invalid_operation
otherwise.size_t
read(void *ptr, size_t length)
: Reads up to length bytes into memory location ptr. Returns number of bytes received orbasic_tcp_client::invalid_operation
otherwise.bool
force_write(const void *ptr, size_t length)
: Forcibly writes length bytes from ptr - callswrite
method repeatedly to make sure alllength
bytes are sent by this one call. Returnstrue
on success,false
if there was an error.bool
force_read(void *ptr, size_t length)
: Similar toforceWrite
, forcibly reads length bytes into ptr - callsread
method repeatedly until alllength
bytes are received by this one call. Returnstrue
on success,false
on error.bool
read_line(std::string &output)
: Reads line into output. Returnstrue
on success.
Another concrete implementation of basic_tcp_client
, allows sending and receiving data asynchronously.
Public interface provides these extra methods:
void
push(const void *ptr, size_t length)
: Writes (sends) length bytes from memory location ptr.void
push(const std::string &text)
: Writes (sends) string text.size_t
peek()
const
: Returns number of bytes available for reading throughpop
.size_t
pop(void *ptr, size_t length)
: Copies up to length received bytes into memory location ptr. Returns number of bytes copied.
If you are not interested in polling the data through peek
and pop
, you can implement your own asynchronous receiving handler:
bool
async_received_data(const data_block &db, uint8_t *ptr, size_t length)
: This will be called by the reading thread whenever there is a new complete block of data ready. Returningtrue
signals that you've processed all the data and the data block can be removed. By returningfalse
, the data block is kept in the reading queue and can be popped later throughpop
call. If you decide to keep the data in the reading queue, make sure you actually pop the data later viapop
, otherwise it will be kept in memory forever. See example 1.
When constructed, async_tcp_client
spawns two threads for sending and receiving data. You can alter this behavior by overriding init_threads
. Actual sending and receiving is then handled by async_write_handler
and async_read_handler
methods.
Extended implementation of async_tcp_client
that handles WebSocket connections and hides away most communication details (parsing frame headers, frame continuation, etc.).
Public interface provides this extra method:
size_t
peek(opcode *op)
const
: Same as baseasync_tcp_client::peek
, but can also report the type of the next available data block. Set op tonullptr
if you are not interested, or use just baseasync_tcp_client::peek()
without parameters.
Extended implementation of tcp_server<T>
, where <T>
must be derived from web_socket_client
. The only difference between this and tcp_server<T>
is additional handling of WebSocket handshake. Rest of the behavior is handled by tcp_server<T>
itself.
Extended implementation of tcp_server<tcp_client>
with slightly altered behavior, providing VERY NAIVE HTTP server functionality. You can no longer use regular basic_tcp_server
callbacks (handshake
, accept
, etc.), because http_server
does not require any clients. The connections are not persistent, so every request is followed by a response and then the socket is closed. You are only required to implement your own request
handler:
bool
request(const std::string &path, const parameters_t ¶ms, response &resp)
: TODO
- XmPlayer test uses awesome libxm by Artefact2 (Romain Dalmaso)
- song.xm (Hybrid Song 2:20) in XmPlayer test downloaded from modarchive.org