Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Orderless websocket messages (Don't wait for retransmission of lost packets)

When a server is sending a websocket message and the packet gets lost, the client won't see any messages until the server realizes that the packet was lost, retransmits it etc and it actually arrives at the client... As you can imagine, this can cause unnecessarily large lags in real-time applications.

I am aware that this happens by design because TCP ensures that packets get delivered in the correct order.

But I wonder if there are any socket.io-like libraries which work around that mechanism. It would seem like a lot of work to write something like that from scratch.

By work around I mean for instance using UDP instead of TCP using the new WebRTC functionalities or even simpler, just create multiple websocket connections and ensure that consecutive messages are sent over different connections.

I know that the client would potentially receive outdated information this way, but it could easily compensate by ignoring them. You'd just have to give each message an incrementing id.

For instance a wrapper for socket.io would be nice. Something that has the same interface, but internally creates multiple connections. I tried to start writing a wrapper class for that, but I'm really unsure on how to properly pass the events between the wrapper and the socket.io instances.

I mean if I simply pass all events fired on the sockets to the wrapper class and all event fired on the wrapper class to one of the socket.io instances, then every event would circle around forever.

const EventEmitter = require('events');
const Server = require('socket.io');

class ServerWrapper extends EventEmitter {
    constructor() {
        /* instanciation manual:
            new ServerWrapper(httpServer[, options][, connectionCount])
            new ServerWrapper(port[, options][, connectionCount])
            new ServerWrapper(options[, connectionCount])
            (connectionCount is the number of socket.io instances that will be used)
        */

        let port, srv, opts; // not really necessary
        let connCount = 5; //default
        let args = arguments;
        // The following if statements are used to maintain full compatibility with the original socket.io constructor (https://socket.io/docs/server-api/)
        if (arguments.length === 0)
            return;
        else if (arguments.length === 1)
            opts  = arguments[0];
        else if (arguments.length === 2) {
            if (typeof arguments[0] === 'object' && arguments[1] === 'object') {
                srv = arguments[0];
                opts = arguments[1];
            } else if (typeof arguments[0] === 'number' && arguments[1] === 'object') {
               port = arguments[0];
               opts = arguments[1];
            } else if (typeof arguments[0] === 'object' && arguments[1] === 'number') {
                opts = arguments[0];
                connCount = arguments[1];
                args = arguments.pop();
            }
        } else if (arguments.length === 3) {
            opts = arguments[1];
            connCount = arguments[2];
            if (typeof arguments[0] === 'number')
                port = arguments[0];
            else
                srv = arguments[0];
            args = arguments.pop();
        }

        // Create X socket.io instances and store them in this array
        this._io = [];
        for (let i=0; i<connCount; i++)
            this._io.push(new Server(args));

        // Pass all socket.io events to this wrapper class
        this._io.forEach(io=>{
            io.on("*",this.emit);
        });

        // Pass all events fired on this wrapper class to one of the socket.io instances:
        this.nextConn = 0;
        this.on("*", (event,data) => {
            this._io[this.nextConn].emit(...arguments);
            this.nextConn++;
            if (this.nextConn >= this.connCount)
                this.nextConn = 0;
        });

        let sioProps = ['sockets'];
        sioProps.forEach(prop=>{ // map all socket.io properties from the first instance to 'this[prop]'
            this[prop] = this._io[0][prop];
        });

        let sioMethods = ['seveClient','path','adapter','origins','attach','listen','bind','onconnection','of','close'];
        sioMethods.forEach(fName=>{ // redirect all socket.io function calls to all the socket.io instances
            this[fName] = () => {
                this._io.forEach(io=>{
                    this[fName] = io[fName](...arguments);
                });
            };
        });
    }
}
module.exports = ServerWrapper;
like image 286
Forivin Avatar asked Jan 08 '18 14:01

Forivin


1 Answers

The socket.io-p2p project provides a socket.io interface around the excellent simple-peer WebRTC library. If your application is real-time and can tolerate messages arriving out of order, then you should be able to do something like this to disable the ordering guarantee (to prevent lost/late messages from delaying later messages):

let peerOpts = {channelConfig: {ordered: false}}
let p2psocket = new P2P(socket, peerOpts)

To help with finding documentation, note that the peerOpts value becomes the opts param for the SimplePeer object from simple-peer.

like image 96
kanaka Avatar answered Oct 26 '22 08:10

kanaka