WebSocket

WebSocket is a high level protocol compared to plain TCP connections, while it keeps similiarities with its simple API. While they have similar naming too, it must be highlighted that a WebSocket connection must go through a HTTP upgrade process and it can't connect to a plain TCP socket that implements a custom protcol to exchange messages!

Constructors

Constructor to specify only the server's endpoint:

var webSocket = new WebSocket(new Uri("wss://echo.websocket.org"));

Constructor to add origin, protocol and used extension information:

var webSocket = new WebSocket(uri: new Uri("wss://echo.websocket.org"), origin: "echo.websocket.org", protocol: "Echo", extensions: null);

Warning

The extension parameter isn't available under WebGL!

Events

webSocket.OnOpen += OnWebSocketOpen;
private void OnWebSocketOpen(WebSocket webSocket)
{
    Debug.Log("WebSocket is now Open!");
}
webSocket.OnMessage += OnMessageReceived;
private void OnMessageReceived(WebSocket webSocket, string message)
{
    Debug.Log("Text Message received from server: " + message);
}
webSocket.OnBinary += OnBinaryMessageReceived;
private void OnBinaryMessageReceived(WebSocket webSocket, byte[] message)
{
    Debug.Log("Binary Message received from server. Length: " + message.Length);
}
webSocket.OnClosed += OnWebSocketClosed;
private void OnWebSocketClosed(WebSocket webSocket, UInt16 code, string message)
{
    Debug.Log("WebSocket is now Closed!");
}
webSocket.OnError += OnError;

void OnError(WebSocket ws, string error)
{
    Debug.LogError("Error: " + error);
}

Warning

OnClose and OnError are mutually exclusive! When OnError is called no OnClosed going to be triggered. But, the connections is closed in both cases. When OnClose is called, the plugin could receive and send a close frame to the server and even if there were some kind of error (protocol error, too big message, etc), the tcp connection is healthy and the server could inform the client that it's about to close the connection. On the other hand, when OnError is called, that's because something really bad happened (tcp channel disconnected for example). In case when the editor is exiting from play mode, the plugin has no time sending a close frame to the server and waiting for an answer, so it just shuts down everything immediately.

Methods

All methods are non-blocking, Open and Close just starts the opening and closing logic, Send places the data to a buffer that will be picked up by the sender thread.

webSocket.Open();

Notice

Just as other calls, Open is not a blocking call. Messages can be sent to the server after an OnOpen event.

Sending out text messages:

webSocket.Send("Message to the Server");

Sending out binary messages:

// Allocate and fill up the buffer with data
byte[] buffer = new byte[length];

webSocket.Send(buffer);

Large messages (larger than 32767 bytes by default) are sent fragmented to the server.

Websocket frames produced by the Send methods are placed into an internal queue and a sender thread going to send them one by one. The BufferedAmount property keeps track the amount of bytes sitting in this queue.

webSocket.Close();

Notice

You can’t reuse a closed WebSocket instance, you have to create and setup a new one.

Properties

string token = "...";

var ws = new WebSocket(new Uri("wss://server.com/"));
ws.OnInternalRequestCreated += (ws, req) => req.AddHeader("Authentication", $"Bearer {token}");

Per-Message Compression Extension

The plugin enables and uses the Per-Message Compression Extension by default. It can be disabled by passing null as the last (extensions) parameter of the websocket constructor. To change defaults we can use the same constructor, but with a new PerMessageCompression object:

using BestHTTP.WebSocket;
using BestHTTP.WebSocket.Extensions;

var perMessageCompressionExtension = new PerMessageCompression(/*compression level: */           BestHTTP.Decompression.Zlib.CompressionLevel.Default,
                                                               /*clientNoContextTakeover: */     false,
                                                               /*serverNoContextTakeover: */     false,
                                                               /*clientMaxWindowBits: */         BestHTTP.Decompression.Zlib.ZlibConstants.WindowBitsMax,
                                                               /*desiredServerMaxWindowBits: */  BestHTTP.Decompression.Zlib.ZlibConstants.WindowBitsMax,
                                                               /*minDatalengthToCompress: */     PerMessageCompression.MinDataLengthToCompressDefault);
var webSocket = new WebSocket(new Uri("wss://echo.websocket.org/"), null, null, perMessageCompressionExtension);

Extension usage depends on the server too, but if the server agrees to use the extension, the plugin can receive and send compressed messages automatically.

Implementations

The plugin now have three implementations:

WebGL

Under WebGL the plugin must use the underlying browser's WebSocket implementation. Browsers are exposing a limited API, hence not all features, methods and properties are available under this platform.

HTTP/1 Upgrade

This implementation uses HTTP/1 upgrade mechanism. This was the default for every non-webgl platform.

If the server agrees on the upgrade the plugin creates a WebSocketResponse object (instead of the regular HTTPResponse) to handle message sending and receiving. This WebSocketResponse object's lifetime is bound to its websocket object and it's possible to access it after the OnOpen event. Accessing it has little usage, but in a few cases it can be beneficial:

void OnOpened(WebSocket webSocket)
{
    (webSocket.InternalRequest.Response as WebSocketResponse).MaxFragmentSize = 16 * 1024;
}

WebSocket Over HTTP/2

This new implementation is based on RFC 8441 and uses an already open HTTP/2 connection that advertised itself as one that supports the Extended Connect method. If there's no open HTTP/2 connection the plugin uses the 'old' HTTP/1 based one. Because connecting over the already open HTTP/2 connection still can fail, the plugin can fallback to the HTTP/1 based one. When a fallback happens a new HTTPRequest object will be created by the new implementation and the OnInternalRequestCreated callback will be called again for this request too. If fallback is disabled WebSocket's OnError will be called.

This implementation uses the underlying HTTP/2 connection's framing mechanism, the maximum fragment size is the one that the HTTP/2 connection negotiated.

Both WebSocket Over HTTP/2 and its fallback mechanism can be disabled:

// Disable WebSocket Over HTTP/2
HTTPManager.HTTP2Settings.WebSocketOverHTTP2Settings.EnableWebSocketOverHTTP2 = false;

// Disable fallback mechanism
HTTPManager.HTTP2Settings.WebSocketOverHTTP2Settings.EnableImplementationFallback = false;

Pros of WebSocket Over HTTP/2: