Introduction to Emscripten Sockets

~ 5 Minute Read.

Since we’re work­ing on our mul­ti­play­er We­bXR game at Vhite Rab­bit, I need­ed to some­how con­nect to some serv­er to ex­change some da­ta via sock­ets from web as­sem­bly.

Our serv­er us­es the Poco li­brary (be­cause I had al­ready used it with py­ro­ma­nia), hence I will go in­to de­tail with that a bit al­so.

Be­ware that to fit this in­to the scope of a dai­ly blog post, I copied quite a bit of code that I had al­ready writ­ten. That code makes use of some Cor­rade li­brary fea­tures and you will have to adapt that if you don’t use that. I can high­ly rec­om­mend it, though.

Sock­ets are not Web­Sock­ets

The first thing you need to re­al­ize is that while web­sock­ets are sock­ets, the re­verse does not hold. They are _not_ equiv­a­lent. Web­Sock­ets ex­change a hand­shake via http re­quests, which the serv­er needs to han­dle cor­rect­ly.

You can there­fore not con­nect to any ar­bi­trary serv­er us­ing web­sock­ets. The serv­er must sup­port it. You have two ways of do­ing so: ei­ther have the serv­er sup­port web­sock­ets or use web­sock­i­fy which I un­der­stand as a proxy to an­oth­er serv­er, han­dling the web­sock­et lay­er on top of it. 1

To set up a serv­er that sup­ports Web­Sock­ets with Poco, check out their ex­am­ple.

“bi­na­ry” sub­pro­to­col

Em­scripten us­es the bi­na­ry sub­pro­to­col of Web­Sock­ets. You will need to han­dle that at ap­pli­ca­tion lev­el by set­ting the ap­pro­pri­ate head­er for the Web­Sock­et re­sponse like this:

// Emscripten uses the binary subprotocol
response.set("Sec-WebSocket-Protocol", "binary");

Poco::Net::WebSocket ws(request, response);

Sock­ets in Em­scripten

First thing you’ll want to do is en­ably the nifty SOCKET_DEBUG flag for de­bug builds. In CMake you would do this by adding the fol­low­ing lines:

# Some handy debug flags
set_property(TARGET your-target APPEND_STRING PROPERTY
    LINK_FLAGS_DEBUG " -s SOCKET_DEBUG=1")

Since em­scripten sock­ets are a wrap­per around Web­Sock­ets to mim­ic the Lin­ux sys­tem sock­ets API, you can pret­ty much use them like that. Be aware, though, that the con­nec­tion will not im­me­di­ate­ly suc­ceed, but will re­turn EINPROGRESS which means you should wait for the con­nec­tion to be com­plet­ed.

Con­nect­ing

You can now start by con­nect­ing a sock­et like in the fol­low­ing code snip­pet: 2

#include <errno.h> /* EINPROGRESS, errno */
#include <sys/types.h> /* timeval */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>

/* ... */

struct Socket::SocketData {
    int socket;
};

/*
class Socket {
    ...
private:
    SocketData _socket;
    bool _connected;
};
*/

void Socket::connect(const std::string& host, int port) {
    _data->socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(_data->socket == -1) {
        Error() << "Socket::connect(): failed to create socket";
        return;
    }
    fcntl(_data->socket, F_SETFL, O_NONBLOCK);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if(inet_pton(AF_INET, host.c_str(), &addr.sin_addr) != 1) {
        Error() << "Socket::connect(): inet_pton failed";
        return;
    }

    const int res = ::connect(_data->socket, (struct sockaddr *)&addr, sizeof(addr));
    if(res == -1) {
        if(errno == EINPROGRESS) {
            Debug() << "Socket::connect(): Connection in progress for fd:" << _data->socket;

            /* Wait for connection to complete */
            fd_set sockets;
            FD_ZERO(&sockets);
            FD_SET(_data->socket, &sockets);

            /* You should probably do other work instead of busy waiting on this...
               or set a timeout or something */
            while(select(_data->socket + 1, nullptr, &sockets, nullptr, nullptr) <= 0) {}

            connected = true;

        } else {
            Error() << "Socket::connect(): connection failed";
            return;
        }
    } else {
        connected = true;
    }
}

I’m us­ing Cor­rade::Util­i­ty::De­bug out­put here, but you may use what­ev­er you pre­fer of course.

Send­ing

Send­ing al­so match­es how you would use sock­ets on desk­top. As I’m not a net pro­gram­ming ex­pert, I ad­vise you to seek out oth­er tu­to­ri­als and re­sources if you need to build some­thing more sta­ble with prop­er er­ror han­dling.

void Socket::send(Containers::ArrayView<char> data) {
    CORRADE_ASSERT(_connected, "Socket::send(): socket not connected", );

    const int ret = ::send(_data->socket, data, data.size(), 0);
    if(ret == -1) {
        Error() << "Socket::send(): send failed";
        close();
        return;
    }
}

Re­ceiv­ing

Sim­i­lar to send­ing, this is not too spe­cial. I’m us­ing Cor­rade::Con­tain­ers::Ar­ray and Ar­rayView here to han­dle the buf­fers.

Containers::ArrayView<char> Socket::receive(Containers::Array<char>& dest, int timeout) {
    CORRADE_ASSERT(_connected, "Socket::receive(): socket not connected", {});

    /* Wait timeout milliseconds to receive data */
    fd_set sockets;
    FD_ZERO(&sockets);
    FD_SET(_data->socket, &sockets);
    timeval t{0, timeout*1000};
    int ret = select(_data->socket + 1, &sockets, nullptr, nullptr, (timeout == -1) ? nullptr : &t);
    if(ret == 0) {
        /* Timeout */
        return nullptr;
    } else if(ret < 0) {
        Utility::Error() << "Socket::receive(): select failed";
        return nullptr;
    }

    ret = recv(_data->socket, dest.data(), dest.size(), 0);
    if(ret < 0) {
        Utility::Error() << "Socket::receive(): recv failed";
        return nullptr;
    }

    return dest.prefix(ret);
}

Clos­ing

Clos­ing the sock­et when you’re done is a sim­ple mat­ter of:

void Socket::close() {
    if(_data->socket != -1) ::close(_data->socket);
}

Con­clu­sion

Web­Sock­et con­nec­tions need to be han­dled dif­fer­ent­ly on the serv­er side. Once that’s done, em­scripten has a nice wrap­per around web­sock­ets to that you can use them as you would use nor­mal sock­ets.

As a re­sult the on­ly doc­u­men­ta­tion I found on em­scripten sock­ets was the tests they had. In­stead check out the Lin­ux Pro­gram­mer’s Man­u­al if you need any help.

I hope this helped out some­one, it did take us at Vhite Rab­bit a while to fig­ure out. Spe­cial thanks go to An­drea Capo­bian­co who spent quite some time on de­bug­ging the above code.

1
Dis­claimer: I have no ex­pe­ri­ence with web­sock­i­fy.
2
Which will not triv­ial­ly com­pile, but you’ll fig­ure it out from here eas­i­ly.

Writ­ten in 60 min­utes, not ed­it­ed yet and the read time is a bold guess.