Korzystanie z kompresji websocket w plikach uWebSockets.js i Websocket-Sharp

11

Mamy grę mobilną używającą websocket do połączeń. Serwer to aplikacja Node.js korzystająca z biblioteki uWebSockets.js, a klient to aplikacja Unity korzystająca z biblioteki Websocket-Sharp . Obaj grają dobrze razem i nie mieliśmy z nimi problemu.

Ostatnio chcieliśmy włączyć kompresję websocket . Obie biblioteki stwierdziły, że obsługują rozszerzenie kompresji per-message, ale wygląda na to, że jest z nimi coś niezgodnego. Ponieważ gdy skonfigurujemy użycie kompresji, połączenie websocket zamyka się natychmiast po uzgadnianiu.

Przetestowaliśmy również klienta z biblioteką ws i jest to przykład kompresji z takim samym wynikiem. Próbowaliśmy majstrować przy opcjach kompresji ws i stwierdziliśmy, że kiedy komentujemy opcję serverMaxWindowBits (domyślnie jest to wynegocjowana wartość), można nawiązać połączenie, a wysyłanie i odbieranie wiadomości działa bez problemu. Zapytaliśmy również o kontrolowanie serverMaxWindowBits w uWebsockets.

Ostatnią rzeczą, jakiej próbowaliśmy, było połączenie minimalnego serwera uWS i klienta z ostrymi gniazdami internetowymi. Oto kod serwera:

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);
        }
    });

Oto kod klienta:

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);
      }
    }
  }
}

Gdy uruchomiliśmy serwer i klienta, klient emituje następujący błąd:

Błąd | WebSocket.checkHandshakeResponse | Serwer nie odesłał odpowiedzi „server_no_context_takeover”. Fatal | WebSocket.doHandshake | Zawiera nieprawidłowy nagłówek Sec-WebSocket-Extensions.

Wyglądało na to, że klient oczekiwał nagłówka server_no_context_takeover i go nie otrzymał. Przeanalizowaliśmy uWebsockets źródło (C ++ część modułu uWebsockets.js) i znalazł skomentował stan wysyłania nagłówek z powrotem server_no_context_takeover. Odkomentowaliśmy więc ten warunek i zbudowaliśmy plik uWebsockets.js i ponownie przetestowaliśmy pod kątem następującego błędu w kliencie:

WebSocketSharp.WebSocketException: Nagłówka ramki nie można odczytać ze strumienia.

Jakieś sugestie, aby te dwie biblioteki ze sobą współpracowały?

Wysypka
źródło
Nie jestem pewien, czy to w ogóle pomaga, ale socket.io domyślnie kompresuje. Wiem, że Best HTTP 2 ma całkiem dobre wsparcie dla socket.io - websockets.
Samuel G
@SamuelG Dzięki, ale korzystanie z socket.io nie jest opcją, ponieważ obecnie obsługujemy równoczesne połączenia 5k + przy minimalnych zasobach.
Koorosh Pasokhi,
@KooroshPasokhi, co zakończyło twoje rozumowanie, że socket.io nie byłby w stanie obsłużyć twojego obciążenia lub wymaga dużych zasobów? Chciałbym usłyszeć więcej o twoich testach.
Samuel G,
@SamuelG Komentarze nie są przeznaczone do dyskusji, ale powiedzmy, że głównymi powodami są skupienie się na socket.io nie na wydajności i nie potrzebujemy wyższych abstrakcji warstw w socket.io.
Koorosh Pasokhi,

Odpowiedzi:

3

Aktualizacja: W oparciu o mój odczyt kodu uWebSockets.jsnależy wprowadzić zmiany, aby włączyć wszystkie parametry websocket-sharpwymagane do włączenia kompresji. W Vertx, wysokowydajnym serwerze Java, następujące ustawienia działają z websocket-sharpkompresją zgodną z Unity :

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

Poprzednio:

Błąd jest prawdziwy, websocket-sharpobsługuje tylko permessage-deflate, zamiast tego użyj DEDICATED_COMPRESSOR( compression: 2).

DoctorPangloss
źródło
Niestety ustawienie metody kompresji na 2 nie zmieniło komunikatu o błędzie :(
Koorosh Pasokhi
Następnie istnieje pewien podstawowy problem z uWebSockets. Używam websockets Java Vertx z kompresją z WebSocketSharp w kliencie, który ma więcej pól konfiguracji i po prostu działa.
DoctorPangloss,
Może spróbuj utworzyć test bezpośrednio w pliku uWebSockets.js, który odtwarza zachowanie WebSocket.csodrzucania nagłówka? DEDICATED_COMPRESSORnaprawdę powinno działać!
DoctorPangloss,
Co masz na myśli? Architektura pliku uWebSockets.js jest nieco skomplikowana. Ma trzy warstwy napisane w JS, C ++ i C, co utrudnia debugowanie.
Koorosh Pasokhi,
Mam na myśli napisanie testu w katalogu projektu uWebSockets.js, który odtwarza uścisk dłoni websocket-sharp, który możesz odkryć, podłączając go do zgodnego serwera i widząc, co się stanie?
DoctorPangloss,