Websocket Server in C

When it comes to embedded systems, you don't really want to depend on big libraries, or have extra clutter. It is good to keep things simple, and C is - in a way - simple. But on the big picture, you will need to interact with more complex systems in a reliable and efficient way. Websockets is one of those means, since it is - at the bottom - plain TCP, and it has of course quite wide support.

There is plenty of libraries for websockets around, but I'm specially fond of this one:

wsServer

With this one, you can make interact in real time between some native code with the wider network. It does not support natively HTTPS, but it does not mean that you cannot, nor must not secure it. You are rather encouraged to terminate HTTPS behind either one of: apache, haproxy,nginx or stunnel.

We can setup the environment by cloning and installing the mentioned library:

mkdir ext
cd ext
git clone git@github.com:Theldus/wsServer.git
cd wsServer
make 
sudo make install
cd ../../

Then the code can be as simple as that:

#include <stdio.h>
#include <stdlib.h>
#include <ws.h>

void onopen(int fd)
{
    char *cli;
    cli = ws_getaddress(fd);
    printf("Connection opened, client: %d | addr: %s\n", fd, cli);
    free(cli);
}

void onmessage(int fd, const unsigned char *msg, size_t size, int type)
{
    char *cli;
    cli = ws_getaddress(fd);
    printf("Received message: [%s] (size: %zu, type: %d), from: %s/%d\n", msg, size,
           type, cli, fd);
    free(cli);

    ws_sendframe(fd, (char *)msg, size, true, type);
}

void onclose(int fd)
{
    char *cli;
    cli = ws_getaddress(fd);
    printf("Connection closed, client: %d | addr: %s\n", fd, cli);
    free(cli);
}

int main(int argc, char *argv[])
{
    struct ws_events evs;
    evs.onopen    = &onopen;
    evs.onclose   = &onclose;
    evs.onmessage = &onmessage;
    ws_socket(&evs, 8080); 

    return (0);
}

Here basically we just implement the 3 pointer to function or 'events':

  • onopen
  • onclose
  • onmessage

A Makefile to compile the code above could be like the following, if we call our source zero-ws.c:

CC=${CROSS_COMPILE}gcc                                                             
CXX=${CROSS_COMPILE}g++                                                            
RANLIB=${CROSS_COMPILE}ranlib                                                      
LD=${CROSS_COMPILE}ld                                                              
LDXX=${CROSS_COMPILE}g++                                                           
AR=${CROSS_COMPILE}ar 

WSDIR    = $(CURDIR)/ext/wsServer
INCLUDE  = -I $(WSDIR)/include
CFLAGS   =  -Wall -Wextra -O2
CFLAGS  +=  $(INCLUDE) -pthread -pedantic
LIB      =  $(WSDIR)/libws.a
LDFLAGS  =  -lpthread

all: zero-ws

zero-ws: 
	$(CC) $(CFLAGS) $(LDFLAGS) zero-ws.c -o zero-ws $(LIB)


clean: zero-ws
	rm -f zero-ws 

to compile the code on the local machine (no crosscompiling) just give the command:

make

In order to start the server, just start the executable:

./zero-ws 
Waiting for incoming connections...

If you are going to work with websockets, of course you will want to have some test client to interact with the server, but here you might want to have your own client. Instead I believe it is good to have at least a CLI to connect to the websocket for testing and development.

I'd recommend getting one of the following tools:

Connection with wscat:

wscat --connect ws://127.0.0.1:8080

Connection with websocat:

websocat ws://127.0.0.1:8080/

Example session

client-side:

+ wscat --connect ws://127.0.0.1:8080
Connected (press CTRL+C to quit)
> command1
< command1
> command2
< command2
> 

server-side:

./zero-ws 
Waiting for incoming connections...
Connection opened, client: 4 | addr: 127.0.0.1
Received message: [command1] (size: 8, type: 1), from: 127.0.0.1/4
Received message: [command2] (size: 8, type: 1), from: 127.0.0.1/4

Paolo Lulli 2021



[git]