WebSocket Client
Simple, flexible WebSocket client. Add the WebSocketConnection MonoBehaviour anywhere in your scene.
com.mikeschweitzer.websocket 
Install via UPM
Add to Unity Package Manager using this URL
https://www.pkglnk.dev/mikerochip-websocket.git README Markdown
Copy this to your project's README.md
## Installation
Add **WebSocket Client** to your Unity project via Package Manager:
1. Open **Window > Package Manager**
2. Click **+** > **Add package from git URL**
3. Enter:
```
https://www.pkglnk.dev/mikerochip-websocket.git
```
[](https://www.pkglnk.dev/pkg/mikerochip-websocket)README
WebSocketConnection
WebSocketConnection is an ergonomic, idiomatic websocket client for Unity. Write-once for all platforms, including Web.
- Ergonomics
- Does not force you to use
async - Does not force you to use
#ifconditional compilation - Listen for messages however you want: events, coroutines, polling, or
async/await
- Does not force you to use
- Reliability
- Reusable: connect, disconnect, change URL, connect, ...
stringis sent as text type,byte[]as binary type- Proper handling of disconnects and bad messages
- Tested with 100+
WebSocketConnectioncomponents with minimal drop in framerate
- Extended Features
- Custom ping-pongs which can be configured to calculate ping times. âšī¸ You must control the server for this.
- Self-signed certificate support. â ī¸ Non-Web only, must use
.NET FrameworkApi level.
Why Use This?
WebSocketConnection has the most ergonomic, write-once support across platforms:
| Supports | WebSocketConnection |
NativeWebSocket |
System.Net.WebSockets.ClientWebSocket |
|---|---|---|---|
| WebGL | â | â | âī¸ |
WebAssembly.Table |
â | â | âī¸ |
Requires using async |
â Optional | â ī¸ Required | â ī¸ Required |
| Code changes for WebGL | â None | â None | âī¸ Need custom jslib |
| Self-signed certs | â non-Web | âī¸ | â non-Web |
| Write-once ping-pong | â | âī¸ | âī¸ |
Installation
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
- Self-Signed Certs
- Only works in editor and non-web builds
- Required Api Compatibility Level
.NET Framework - Requires lots of fiddly setup, see sample below
- Web Limitations
- The underlying Web implementation uses the default browser JavaScript WebSocket API to minimize depedencies, which has limitations
- No custom header support. See this
- No self-signed certs support
- No spec-compliant ping-pong support. âšī¸ Use the custom ping-pong feature instead, which also adds ping timing support.
Test Projects
For reference, if you don't want to roll your own:
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 url here, if you want
Connect();
}
Event Style
private void OnStateChanged(WebSocketConnection connection, WebSocketState oldState, WebSocketState newState)
{
if (newState == WebSocketState.Disconnected)
{
// you may change the url here, 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)
{
// you can also use _Connection.ErrorMessage
Debug.LogError(errorMessage);
}
Send Messages
[!WARNING] You must be
Connectedto 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();
}
}
Custom Ping-Pong Support
This package has a custom ping-pong feature that you can write once for Web and non-Web builds.
[!WARNING]
- Your server must be configured to echo messages of the same message type (text or binary) and content.
- This package has custom ping-pong support because the default browser JavaScript WebSocket client does not implement the WebSocket Ping Pong spec even though .NET's
WebSocketClientdoes implement the spec.
Enable Text Ping-Pongs
private void ConfigureStringPings()
{
_Connection.DesiredConfig = new WebSocketConfig
{
Url = _Url,
PingInterval = TimeSpan.FromSeconds(30),
PingMessage = new WebSocketMessage("hi"),
};
}
Enable Binary Ping-Pongs
private byte[] _pingBytes = Encoding.UTF8.GetBytes("hi");
private void ConfigureBinaryPings()
{
_Connection.DesiredConfig = new WebSocketConfig
{
Url = _Url,
PingInterval = TimeSpan.FromSeconds(30),
PingMessage = new WebSocketMessage(_pingBytes),
};
}
Ping Timing (Round-Trip-Time Tracking)
private void Awake()
{
_Connection.DesiredConfig = new WebSocketConfig
{
Url = _Url,
PingInterval = TimeSpan.FromSeconds(3),
PingMessage = new WebSocketMessage("hi"),
ShouldPingWaitForPong = true,
};
_Connection.PingSent += OnPingSent;
_Connection.PongReceived += OnPongReceived;
}
private void OnDestroy()
{
_Connection.PingSent -= OnPingSent;
_Connection.PongReceived -= OnPongReceived;
}
private void OnPingSent(WebSocketConnection connection, DateTime timestamp)
{
Debug.Log($"OnPingSent timestamp={timestamp:HH:mm:ss.ffff}");
}
private void OnPongReceived(WebSocketConnection connection, DateTime timestamp)
{
Debug.Log($"OnPongReceived timestamp={timestamp:HH:mm:ss.ffff}");
Debug.Log($"OnPongReceived RTT={connection.LastPingPongInterval:ss\\.ffff}");
}
Self-Signed Certificates
If you must use self-signed certificates, then there is a way to make that work with this package by following roughly these steps.
[!WARNING] I highly recommend against self-signed certs. These steps are easy to mess up and overly complicated.
I highly recommend instead:
- Trusted CA certs
- CA certs pre-installed on your servers and devices
- Just using insecure
ws:
- Create a certificate with e.g.
openssl- Example:
openssl req -x509 -newkey rsa:2048 -nodes -out cert.pem -keyout key.pem -days 365 -subj "/CN=example.local" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1,DNS:my-example-domain.com" - In the above example, replace
my-example-domain.comwith your domain name (if you have one, otherwise leave out theDNS:SAN)
- Example:
- Export a pfx file from your cert
- Example:
openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.pfx -password pass:mypass -macalg SHA1 -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES - In the above example, replace
mypasswith a password of your choosing - â ī¸NOTE: You MUST use these algorithm options or Unity will fail to load your cert
- Example:
- Set your Unity project's
Api Compatibility Levelto.NET Framework - Create a class like this somewhere in your project
using System.Net; using System.Security.Cryptography.X509Certificates; private class SelfSignedCertTrustPolicy : ICertificatePolicy { public bool CheckValidationResult(ServicePoint servicePoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } - In some
Awake()method somewhere, add this line:ServicePointManager.CertificatePolicy = new SelfSignedCertTrustPolicy(); - Configure your server to load the certs
- This totally depends on your server
- You can see an example from my test server here
- Configure this Unity package to load the certs
- Put your
cert.pfxin your Unity project ascert.pfx.bytes - Do similar with the password you made earlier: put your password in a text file like
cert.pfx.pass.bytes - Load both of those in your code as
TextAsset - Then do
MyWebSocketConfig.DotNetSelfSignedCert = MyCert.bytes - And
MyWebSocketConfig.DotNetSelfSignedCertPassword = MyCertPassword.text.ToCharArray()
- Put your
WebSocketConnection should now be able to use wss: to connect to your server.
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.
No comments yet. Be the first!