6. The Server API

6.1. Core Server

The core server API is the foundation on which all the other servers work. Once you understand how it works, everything else is intuitive. The API is simple and designed to make it very easy for you to focus on processing messages. There are only a few steps required to create a server. You create a server instance, define a processing function to handle incoming data, and send data back to the remote peer. We will take each of these in turn.

You create a server instance using the vrtql_svr_new() function. This takes three arguments: the number of worker threads, the connection backlog, and the maximum message queue size. If you set the latter two arguments to zero, it will use the default values. Next, you create a processing function. The signature of this function varies according to the server. For the core server, which deals with unstructured data, this signature is given by the vrtql_svr_process_data callback. It takes a single argument: a vrtql_svr_data instance created on the heap. This structure simply holds a blob of data. It is up to your processing function to make sense of that data and respond accordingly. If you need to send data back to the peer, you do so using vrtql_svr_send(). With all these things in place, you call vrtql_svr_run() to start the server.

The following illustrates writing a basic echo server:

#include <vws/server.h>

cstr server_host = "127.0.0.1";
int  server_port = 8181;

void process(vws_svr_data* req, void* ctx)
{
    vws.trace(VL_INFO, "process (%p)", req);

    vws_tcp_svr* server = req->server;

    //> Prepare the response: echo the data back

    // Allocate memory for the data to be sent in response
    char* data = (char*)vws.malloc(req->size);

    // Copy the request's data to the response data
    strncpy(data, req->data, req->size);

    // Create response
    vws_svr_data* reply;

    reply = vws_svr_data_own(req->server, req->cid, (ucstr)data, req->size);

    // Free request
    vws_svr_data_free(req);

    if (vws.tracelevel >= VT_APPLICATION)
    {
        vws.trace(VL_INFO, "process(%lu): %i bytes", reply->cid, reply->size);
    }

    // Send reply. This will wakeup network thread.
    vws_tcp_svr_send(reply);
}

int main(int argc, const char* argv[])
{
    // Setup
    vrtql_svr* server  = vrtql_svr_new(10, 0, 0);
    vws.tracelevel     = VT_THREAD;
    server->on_data_in = process;

    // Run
    vrtql_svr_run(server, server_host, server_port);

    // Shutdown
    vrtql_svr_stop(server);
    uv_thread_join(&server_tid);
    vrtql_svr_free(server);
}

6.2. WebSocket Server

Writing a WebSocket server is even simpler. It follows the same pattern but uses vws_svr_new() to create the server. The processing function signature is given by the vws_svr_process_msg callback. Rather than using unstructured data, it operates on WebSocket messages.

The following illustrates writing a WebSocket server:

#include <vws/server.h>

cstr server_host = "127.0.0.1";
int  server_port = 8181;

// Server function to process messages. Runs in context of worker thread.
void process(vws_svr* s, vws_cid_t cid, vws_msg* m, void* ctx)
{
    vws.trace(VL_INFO, "process_message (%ul) %p", cid, m);

    // Echo back. Note: You should always set reply messages format to the
    // format of the connection.

    // Create reply message
    vws_msg* reply = vws_msg_new();

    // Use same format
    reply->opcode  = m->opcode;

    // Copy content
    vws_buffer_append(reply->data, m->data->data, m->data->size);

    // Send. We don't free message as send() does it for us.
    s->send(s, cid, reply, NULL);

    // Clean up request
    vws_msg_free(m);
}

int main(int argc, const char* argv[])
{
    // Setup
    vws_svr* server = vws_svr_new(10, 0, 0);
    server->process = process;

    // Run
    vrtql_tcp_svr_run((vrtql_svr*)server, server_host, server_port);

    // Shutdown
    vrtql_svr_stop((vrtql_svr*)server);
    uv_thread_join(&server_tid);
    vws_svr_free(server);
    vws_cleanup();
}

6.3. Message Server

The Message API server works in exactly the same way. The only difference is that it operates on vrtql_msg messages.

The following illustrates creating a Message server:

#include <vws/server.h>

cstr server_host = "127.0.0.1";
int  server_port = 8181;

// Server function to process messages. Runs in context of worker thread.
void process(vws_svr* s, vws_cid_t cid, vrtql_msg* m, void* ctx)
{
    vrtql_msg_svr* server = (vrtql_msg_svr*)s;

    vws.trace(VL_INFO, "process (%lu) %p", cid, m);

    // Echo back. Note: You should always set reply messages format to the
    // format of the connection.

    // Create reply message
    vrtql_msg* reply = vrtql_msg_new();
    reply->format    = m->format;

    // Copy content
    ucstr data  = m->content->data;
    size_t size = m->content->size;
    vws_buffer_append(reply->content, data, size);

    // Send. We don't free message as send() does it for us.
    server->send(s, cid, reply, NULL);

    // Clean up request
    vrtql_msg_free(m);
}

int main(int argc, const char* argv[])
{
    // Setup
    vrtql_msg_svr* server = vrtql_msg_svr_new(10, 0, 0);
    server->process       = process;

    // Run
    vrtql_svr_run((vrtql_svr*)server, server_host, server_port);

    // Shutdown
    vrtql_svr_stop((vrtql_svr*)server);
    uv_thread_join(&server_tid);
    vrtql_msg_svr_free(server);
    vws_cleanup();
}