Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using websocket compression with uWebSockets.js and Websocket-Sharp

We have a mobile game using websocket for connections. The server is a Node.js app using uWebSockets.js library and the client is a Unity app using Websocket-Sharp library. They both play well together and we didn't have encountered an issue with them.

Recently we wanted to enable websocket compression. Both libraries stated that they support Per-message Compression extension but it seems there's something incompatible with them. Because when we configure to use compression the websocket connection closes immediately on handshake.

We also tested client with ws library and it's provided example for compression with the same result. We tried tinkering with ws compression options and found that when we comment serverMaxWindowBits option (defaults to negotiated value) the connection could be established and sending and receiving messages works without a problem. We also asked about controlling the serverMaxWindowBits in uWebsockets.

The last thing we tried was connecting a minimal uWS server and websocket-sharp client. Here is the code for the server:

const uWS = require('uWebSockets.js');
const port = 5001;

const app = uWS.App({
    }).ws('/*', {
        /* Options */
        compression: 1, // Setting shared compression method
        maxPayloadLength: 4 * 1024,
        idleTimeout: 1000,
        /* Handlers */
        open: (ws, req) => {
            console.log('A WebSocket connected via URL: ' + req.getUrl() + '!');
        },
        message: (ws, message, isBinary) => {
            /* echo every message received */
            let ok = ws.send(message, isBinary);
        },
        drain: (ws) => {
            console.log('WebSocket backpressure: ' + ws.getBufferedAmount());
        },
        close: (ws, code, message) => {
            console.log('WebSocket closed');
        }
    }).any('/*', (res, req) => {
        res.end('Nothing to see here!');
    }).listen(port, (token) => {
        if (token) {
            console.log('Listening to port ' + port);
        } else {
            console.log('Failed to listen to port ' + port);
        }
    });

Here is the client code:

using System;
using WebSocketSharp;

namespace Example
{
  public class Program
  {
    public static void Main (string[] args)
    {
      using (var ws = new WebSocket ("ws://localhost:5001")) {
        ws.OnMessage += (sender, e) =>
            Console.WriteLine ("server says: " + e.Data);

        ws.Compression = CompressionMethod.Deflate; // Turning on compression
        ws.Connect ();

        ws.Send ("{\"comm\":\"example\"}");
        Console.ReadKey (true);
      }
    }
  }
}

When we ran the server and the client, the client emits the following error:

Error|WebSocket.checkHandshakeResponse|The server hasn't sent back 'server_no_context_takeover'. Fatal|WebSocket.doHandshake|Includes an invalid Sec-WebSocket-Extensions header.

It seemed the client expected server_no_context_takeover header and didn't received one. We reviewed uWebsockets source (C++ part of uWebsockets.js module) and found a commented condition for sending back server_no_context_takeover header. So we uncommented the condition and built uWebsockets.js and tested again to encounter the following error in the client:

WebSocketSharp.WebSocketException: The header of a frame cannot be read from the stream.

Any suggestions for making these two libraries work together?

like image 971
Arash Avatar asked Dec 04 '19 16:12

Arash


1 Answers

Update: Based on my reading of the code in uWebSockets.js, changes would need to be made to enable all the parameters websocket-sharp needs set to enable compression. In Vertx, a high-performance Java server, the following settings work with Unity-compatible websocket-sharp for compression:

vertx.createHttpServer(new HttpServerOptions()
                .setMaxWebsocketFrameSize(65536)
                .setWebsocketAllowServerNoContext(true)
                .setWebsocketPreferredClientNoContext(true)
                .setMaxWebsocketMessageSize(100 * 65536)
                .setPerFrameWebsocketCompressionSupported(true)
                .setPerMessageWebsocketCompressionSupported(true)
                .setCompressionSupported(true));

Previously:

The error is real, websocket-sharp only supports permessage-deflate, use DEDICATED_COMPRESSOR (compression: 2) instead.

like image 103
DoctorPangloss Avatar answered Nov 14 '22 01:11

DoctorPangloss