Skip to content

Easy-to-use WebSocket MonoBehaviour for Unity. NativeWebSocket alternative.

License

Notifications You must be signed in to change notification settings

mikerochip/unity-websocket

Repository files navigation

WebSocket Client for Unity

Unity Version

WebSocketConnection is an easy-to-use WebSocket client for Unity that Just Works

Features

  • Easy to use
    • async/await is optional, not required: use event listeners, coroutines, or polling
    • Doesn't force #if for WebGL: no conditional-compilation required
    • WebSocketConnection is just a MonoBehaviour
    • Public API prevents you from corrupting an active connection
    • WebSocketConnection is reusable: connect, disconnect, change URL, connect again, etc
  • Flexible config
    • URL is the only required config
    • Sane defaults
    • Optionally set subprotocols, max send, and max receive bytes
  • Wide platform support
    • No external install requirements or dependencies
    • string is treated as text and byte[] as binary (some servers care)
    • Customizable ping-pong support for servers that enforce idle timeouts
    • Includes support for WebAssembly.Table (Unity 2023.2+)
    • Web uses a .jslib JavaScript library, other platforms use the built-in System.Net.WebSockets

Install

See official instructions for how to Install a Package from a Git URL. The URL is

https://github.com/mikerochip/unity-websocket.git

⚠️ Known Limitations ⚠️

  • Headers aren't supported in WebGL because the JavaScript WebSocket API doesn't support them
  • You can't bypass server certificate validation when connecting to a secure websocket endpoint (wss). That means the endpoint must have a CA-verifiable SSL certificate, it can't have no certs installed or only self-signed certs.
    • For WebGL, this is due to a limitation in the JavaScript WebSocket API
    • For .NET, this is due to a bug in Unity's mono runtime
    • There is an active issue to address this, but no timeframe for resolution, currently.

Samples

Assume we have a class like this for the following samples:

using MikeSchweitzer.WebSocket;

public class Tester : MonoBehaviour
{
    public WebSocketConnection _Connection;
    public string _Url = "wss://ws.postman-echo.com/raw";
}

Connect

// inline style
public void Connect()
{
    _Connection.Connect(_Url);
}

// property style
public void Connect()
{
    _Connection.DesiredConfig = new WebSocketConfig
    {
        Url = _Url,
    };
    _Connection.Connect();
}

Disconnect

public void Disconnect()
{
    _Connection.Disconnect();
}

State Querying

Update Style

private WebSocketState _oldState;

private void Update()
{
    var newState = WebSocketConnection.State;
    if (_oldState != newState)
    {
        Debug.Log($"OnStateChanged oldState={_oldState}|newState={newState}");
        _oldState = newState;
    }
}

Event Style

private void Awake()
{
    _Connection.StateChanged += OnStateChanged
}

private void OnDestroy()
{
    _Connection.StateChanged -= OnStateChanged;
}

private void OnStateChanged(WebSocketConnection connection, WebSocketState oldState, WebSocketState newState)
{
    Debug.Log($"OnStateChanged oldState={oldState}|newState={newState}");
}

Reconnect

Coroutine Style

public IEnumerator Reconnect()
{
   Disconnect();
   yield return new WaitUntil(_Connection.State == WebSocketState.Disconnected);

   // you may change the desired url now, if you want
   Connect();
}

Event Style

private void OnStateChanged(WebSocketConnection connection, WebSocketState oldState, WebSocketState newState)
{
    switch (newState == WebSocketState.Disconnected)
    {
        // you may change the desired url now, if you want
        _Connection.Connect();
    }
}

Error Messages

NOTE: These are just error messages, not states. See the State Querying section.

Error messages are generally derived from platform-specific WebSocket errors.

private void Awake()
{
    _Connection.ErrorMessageReceived += OnErrorMessageReceived;
}

private void OnDestroy()
{
    _Connection.ErrorMessageReceived -= OnErrorMessageReceived;
}

private void OnErrorMessageReceived(WebSocketConnection connection, string errorMessage)
{
    Debug.LogError(errorMessage);
    // you can also use _Connection.ErrorMessage
}

Send Messages

⚠️ You must be Connected to send messages, otherwise you will get an error

public void SendString()
{
    _Connection.AddOutgoingMessage("hello");
}

public void SendBinary()
{
    var bytes = Encoding.UTF8.GetBytes("hello");
    _Connection.AddOutgoingMessage(bytes);
}

Receive Messages

Update Style

private void Update()
{
    while (_Connection.TryRemoveIncomingMessage(out string message))
        Debug.Log(message);
}

Event Style

private void Awake()
{
    _Connection.MessageReceived += OnMessageReceived;
}

private void OnDestroy()
{
    _Connection.MessageReceived -= OnMessageReceived;
}

private void OnMessageReceived(WebSocketConnection connection, WebSocketMessage message)
{
    Debug.Log(message.String);
}

Coroutine Style

private void Awake()
{
    StartCoroutine(ReceiveMessages());
}

private IEnumerator ReceiveMessages()
{
    while (true)
    {
        if (_Connection.TryRemoveIncomingMessage(out string message))
            Debug.Log(message);
        yield return null;
    }
}

Async/Await Style

private CancellationTokenSource _cts;

private async void Awake()
{
    _cts = new CancellationTokenSource();
    await ReceiveMessagesAsync();
}

private void OnDestroy()
{
    _cts.Cancel();
}

private async Task ReceiveMessagesAsync()
{
    while (!_cts.IsCancellationRequested)
    {
        if (_Connection.TryRemoveIncomingMessage(out string message))
            Debug.Log(message);

        await Task.Yield();
    }
}

Customizable Ping-Pong Support

.NET has built-in ping-pong support, implementing the WebSocket spec. WebGL (specifically, JavaScript) does not.

Use this for ping-pong support that you can write once for web or non-web client builds.

⚠️ Your server must be configured to echo messages of the same message type (text or binary) and content.

private void ConfigureStringPings()
{
    _Connection.DesiredConfig = new WebSocketConfig
    {
        PingInterval = TimeSpan.FromSeconds(30),
        PingMessage = new WebSocketMessage("ping"),
    };
}

private byte[] _pingBytes = Encoding.UTF8.GetBytes("ping");
private void ConfigureBinaryPings()
{
    _Connection.DesiredConfig = new WebSocketConfig
    {
        PingInterval = TimeSpan.FromSeconds(30),
        PingMessage = new WebSocketMessage(_pingBytes),
    };
}

Attribution

Based on this repo by Endel Dreyer, which was
Based on this repo by Jiri Hybek

See license and third party notices for full attribution.