Skip to content

Commit 6527283

Browse files
author
David Douglas
committed
IWebSocket interface for Unity including MessageWebSocket for Windows UWP
1 parent 283fcb3 commit 6527283

File tree

9 files changed

+556
-101
lines changed

9 files changed

+556
-101
lines changed

README.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,37 @@ For Unity developers looking to use Web Sockets in their Unity game / app.
66

77
**First download the required dependencies and extract the contents into your Unity project "Assets" folder.**
88

9-
* [WebSocket-Sharp (fork with Custom Headers support)](https://github.com/deadlyfingers/websocket-sharp)
9+
* [WebSocket-Sharp* forked for supporting custom headers](https://github.com/deadlyfingers/websocket-sharp)
1010

11-
## Developer notes
11+
## Features
1212

13-
When using Unity 2017.2.1p2 and the .NET 4.6 API (Experimental) player settings I tried the system [ClientWebSocket](https://msdn.microsoft.com/en-us/library/system.net.websockets.clientwebsocket%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396). While this initially had some success the problem was that after 3 mins or so the following error would occur on the async/await connect function:
13+
- **IWebSocket** interface for targeting the various platforms Unity supports.
14+
- **WebSocketMono** utilizes [WebSocket-Sharp*](https://github.com/deadlyfingers/websocket-sharp) and should work on all mono platforms including the Unity Editor on Mac and PC.
15+
- **WebSocketUWP** utilizes [MessageWebSocket](https://docs.microsoft.com/en-us/uwp/api/windows.networking.sockets.messagewebsocket) for Windows 10 (UWP) apps.
16+
17+
## Interface methods
18+
API | Description
19+
--- | -----------
20+
ConfigureWebSocket(url) | Configures web socket with url and optional headers
21+
ConnectAsync() | Connect to web socket
22+
CloseAsync() | Close web socket connection
23+
SendAsync(data) | Send binary `byte[]` or UTF-8 text `string` with optional callback
24+
IsOpen() | Check if web socket status is open
25+
Url() | Return the URL being used by the web socket
26+
27+
## Interface event delegates
28+
OnError(object sender, WebSocketErrorEventArgs e);
29+
OnOpen(object sender, EventArgs e);
30+
OnMessage(object sender, WebSocketMessageEventArgs e);
31+
OnClose(object sender, WebSocketCloseEventArgs e);
32+
33+
## Usage
34+
35+
[UnityWebSocketDemo project repo](https://github.com/Unity3dAzure/UnityWebSocketDemo) contains sample scenes showing how to hook all this up in the Unity Editor.
36+
37+
## Other developer notes
38+
39+
When using Unity 2017.2.1p2 and the .NET 4.6 API (Experimental) player settings I tried the system [ClientWebSocket](https://msdn.microsoft.com/en-us/library/system.net.websockets.clientwebsocket%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396). While this initially had some success the problem was that after 3 mins or so the following error would occur on the async/await connect function in the Unity Editor:
1440

1541
```
1642
ObjectDisposedException: Cannot access a disposed object.

UnityWebSocket.cs

Lines changed: 52 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.IO;
55
using System.Text;
66
using System.Text.RegularExpressions;
77
using UnityEngine;
8+
using UnityEngine.Networking;
89
using UnityEngine.UI;
9-
using WebSocketSharp;
10-
11-
#if !NETFX_CORE || UNITY_ANDROID
12-
using System.Net;
13-
using System.Net.Security;
14-
using System.Security.Cryptography.X509Certificates;
15-
#endif
1610

1711
namespace Unity3dAzure.WebSockets {
1812
public abstract class UnityWebSocket : MonoBehaviour {
@@ -21,14 +15,13 @@ public abstract class UnityWebSocket : MonoBehaviour {
2115
public static Data OnData;
2216

2317
protected string WebSocketUri;
24-
protected string Origin;
2518
protected List<UnityKeyValue> Headers;
26-
protected uint WaitTime = 0; // seconds
2719

28-
private WebSocket _ws;
29-
protected bool isActivated = false;
20+
private IWebSocket _ws;
21+
22+
protected bool isAttached = false;
3023

31-
#region Web Socket methods
24+
#region Web Socket methods
3225

3326
public virtual void Connect () {
3427
ConnectWebSocket ();
@@ -38,39 +31,40 @@ public virtual void Close () {
3831
DisconnectWebSocket ();
3932
}
4033

41-
#endregion
34+
#endregion
4235

43-
#region Web Socket handlers
36+
#region Web Socket handlers
4437

4538
protected virtual void OnWebSocketOpen (object sender, EventArgs e) {
4639
Debug.Log ("Web socket is open");
4740
}
4841

49-
protected virtual void OnWebSocketClose (object sender, CloseEventArgs e) {
42+
protected virtual void OnWebSocketClose (object sender, WebSocketCloseEventArgs e) {
5043
Debug.Log ("Web socket closed with reason: " + e.Reason);
5144
if (!e.WasClean) {
5245
DisconnectWebSocket ();
5346
}
47+
DettachHandlers();
5448
}
5549

56-
protected virtual void OnWebSocketMessage (object sender, MessageEventArgs e) {
50+
protected virtual void OnWebSocketMessage (object sender, WebSocketMessageEventArgs e) {
5751
Debug.LogFormat ("Web socket {1} message:\n{0}", e.Data, e.IsBinary ? "binary" : "string");
5852
// Raise web socket data handler event
5953
if (OnData != null) {
6054
OnData (e.RawData, e.Data, e.IsBinary);
6155
}
6256
}
63-
64-
protected virtual void OnWebSocketError (object sender, WebSocketSharp.ErrorEventArgs e) {
57+
58+
protected virtual void OnWebSocketError (object sender, WebSocketErrorEventArgs e) {
6559
Debug.LogError ("Web socket error: " + e.Message);
6660
DisconnectWebSocket ();
6761
}
6862

69-
#endregion
63+
#endregion
7064

7165
public void SendText (string text, Action<bool> callback = null) {
72-
if (_ws == null || _ws.ReadyState != WebSocketSharp.WebSocketState.Open) {
73-
Debug.LogWarning ("Web socket is not available to send message. Try connecting?");
66+
if (_ws == null || !_ws.IsOpen()) {
67+
Debug.LogWarning("Web socket is not available to send text message. Try connecting?");
7468
return;
7569
}
7670
_ws.SendAsync (text, callback);
@@ -86,100 +80,71 @@ public void SendInputText (InputField inputField) {
8680
}
8781

8882
public void SendBytes (byte[] data, Action<bool> callback = null) {
89-
if (_ws == null || _ws.ReadyState != WebSocketSharp.WebSocketState.Open) {
90-
Debug.LogWarning ("Web socket is not available to send message. Try connecting?");
83+
if (_ws == null || !_ws.IsOpen()) {
84+
Debug.LogWarning("Web socket is not available to send bytes. Try connecting?");
9185
return;
9286
}
9387
_ws.SendAsync (data, callback);
9488
}
9589

96-
public void SendFile (FileInfo fileInfo, Action<bool> callback = null) {
97-
if (_ws == null || _ws.ReadyState != WebSocketSharp.WebSocketState.Open) {
98-
Debug.LogWarning ("Web socket is not available to send message. Try connecting?");
99-
return;
100-
}
101-
_ws.SendAsync (fileInfo, callback);
102-
}
103-
10490
protected void ConnectWebSocket () {
10591
if (string.IsNullOrEmpty (WebSocketUri)) {
10692
Debug.LogError ("WebSocketUri must be set");
10793
return;
10894
}
10995

11096
if (_ws == null) {
111-
Debug.Log ("Create web socket uri: " + WebSocketUri);
112-
_ws = new WebSocket (WebSocketUri);
113-
// add origin
114-
if (!string.IsNullOrEmpty (Origin)) {
115-
_ws.Origin = Origin;
116-
}
117-
// set properties
118-
if (WaitTime > 0) {
119-
_ws.WaitTime = TimeSpan.FromSeconds (WaitTime);
120-
}
121-
// add headers
122-
if (Headers != null || Headers.Count > 0) {
123-
var customHeaders = new List<KeyValuePair<string, string>> ();
124-
foreach (UnityKeyValue header in Headers) {
125-
customHeaders.Add (new KeyValuePair<string, string> (header.key, header.value));
126-
}
127-
_ws.CustomHeaders = customHeaders;
97+
var customHeaders = new List<KeyValuePair<string, string>>();
98+
foreach (UnityKeyValue header in Headers) {
99+
customHeaders.Add(new KeyValuePair<string, string>(header.key, header.value));
128100
}
101+
102+
Debug.Log ("Create Web Socket: " + WebSocketUri);
103+
#if ENABLE_WINMD_SUPPORT
104+
Debug.Log ("Using UWP Web Socket");
105+
_ws = new WebSocketUWP();
106+
#else
107+
Debug.Log("Using Mono Web Socket");
108+
_ws = new WebSocketMono();
109+
#endif
110+
_ws.ConfigureWebSocket(WebSocketUri, customHeaders);
129111
}
130112

131-
if (!isActivated) {
132-
Debug.Log ("Connect web socket");
133-
isActivated = true;
134-
_ws.OnError += OnWebSocketError;
135-
_ws.OnOpen += OnWebSocketOpen;
136-
_ws.OnMessage += OnWebSocketMessage;
137-
_ws.OnClose += OnWebSocketClose;
113+
if (!isAttached) {
114+
Debug.Log ("Connect Web Socket: " + _ws.Url());
115+
AttachHandlers();
138116
_ws.ConnectAsync ();
139117
}
140118
}
141119

142120
protected void DisconnectWebSocket () {
143-
if (_ws != null && isActivated) {
144-
Debug.Log ("Disconnect web socket");
121+
if (_ws != null && isAttached) {
122+
Debug.Log ("Disconnect Web Socket");
145123
_ws.CloseAsync ();
146-
_ws.OnError -= OnWebSocketError;
147-
_ws.OnOpen -= OnWebSocketOpen;
148-
_ws.OnMessage -= OnWebSocketMessage;
149-
_ws.OnClose -= OnWebSocketClose;
150-
isActivated = false;
151124
}
152125
}
153126

154-
#region Server Certificate Validation
155-
156-
protected void ValidateServerCertificate () {
157-
// required for running in Windows and Android
158-
#if !NETFX_CORE || UNITY_ANDROID
159-
ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCallback;
160-
#endif
161-
}
162-
163-
private static string HostName (string url) {
164-
var match = Regex.Match (url, @"^(https:\/\/|http:\/\/|wss:\/\/|ws:\/\/)(www\.)?([a-z0-9-_]+\.[a-z]+)", RegexOptions.IgnoreCase);
165-
if (match.Groups.Count == 4 && match.Groups[3].Value.Length > 0) {
166-
return match.Groups[3].Value;
127+
protected void AttachHandlers() {
128+
if (isAttached) {
129+
return;
167130
}
168-
return url;
131+
isAttached = true;
132+
_ws.OnError += OnWebSocketError;
133+
_ws.OnOpen += OnWebSocketOpen;
134+
_ws.OnMessage += OnWebSocketMessage;
135+
_ws.OnClose += OnWebSocketClose;
169136
}
170137

171-
#if !NETFX_CORE || UNITY_ANDROID
172-
private bool RemoteCertificateValidationCallback (System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
173-
// Check the certificate to see if it was issued from host
174-
if (certificate.Subject.Contains (HostName (WebSocketUri))) {
175-
return true;
176-
} else {
177-
return false;
138+
protected void DettachHandlers() {
139+
if (!isAttached) {
140+
return;
178141
}
142+
isAttached = false;
143+
_ws.OnError -= OnWebSocketError;
144+
_ws.OnOpen -= OnWebSocketOpen;
145+
_ws.OnMessage -= OnWebSocketMessage;
146+
_ws.OnClose -= OnWebSocketClose;
179147
}
180-
#endif
181-
182-
#endregion
183148

184149
}
185150

UnityWebSocketScript.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using UnityEngine;
44
using WebSocketSharp;
@@ -12,9 +12,7 @@ public sealed class UnityWebSocketScript : UnityWebSocket {
1212
[SerializeField]
1313
private bool AutoConnect = false;
1414

15-
[Header ("Optional settings")]
16-
[SerializeField]
17-
private string origin;
15+
[Header ("Optional config")]
1816
[SerializeField]
1917
private List<UnityKeyValue> headers;
2018

@@ -23,12 +21,8 @@ public sealed class UnityWebSocketScript : UnityWebSocket {
2321
void Start () {
2422
// Config Websocket
2523
WebSocketUri = webSocketUri;
26-
Origin = origin;
2724
Headers = headers;
2825

29-
// Validate Server Certificate
30-
ValidateServerCertificate ();
31-
3226
if (AutoConnect) {
3327
Connect ();
3428
}
@@ -64,22 +58,23 @@ protected override void OnWebSocketOpen (object sender, EventArgs e) {
6458
Debug.Log ("Web socket is open");
6559
}
6660
67-
protected override void OnWebSocketClose (object sender, CloseEventArgs e) {
61+
protected override void OnWebSocketClose (object sender, WebSocketCloseEventArgs e) {
6862
Debug.Log ("Web socket closed with reason: " + e.Reason);
6963
if (!e.WasClean) {
7064
DisconnectWebSocket ();
7165
}
66+
DettachHandlers();
7267
}
7368
74-
protected override void OnWebSocketMessage (object sender, MessageEventArgs e) {
69+
protected override void OnWebSocketMessage (object sender, WebSocketMessageEventArgs e) {
7570
Debug.LogFormat ("Web socket {1} message:\n{0}", e.Data, e.IsBinary ? "binary" : "string");
7671
// Raise web socket data handler event
7772
if (OnData != null) {
7873
OnData (e.RawData, e.Data, e.IsBinary);
7974
}
8075
}
8176
82-
protected override void OnWebSocketError (object sender, WebSocketSharp.ErrorEventArgs e) {
77+
protected override void OnWebSocketError (object sender, WebSocketErrorEventArgs e) {
8378
Debug.LogError ("Web socket error: " + e.Message);
8479
DisconnectWebSocket ();
8580
}

WebSocket/IWebSocket.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
7+
namespace Unity3dAzure.WebSockets {
8+
public interface IWebSocket {
9+
void ConfigureWebSocket(string url);
10+
void ConfigureWebSocket(string url, List<KeyValuePair<string, string>> headers);
11+
12+
void ConnectAsync();
13+
void CloseAsync();
14+
15+
void SendAsync(byte[] data, Action<bool> completed = null);
16+
void SendAsync(string text, Action<bool> completed = null);
17+
18+
bool IsOpen();
19+
string Url();
20+
21+
event OnError OnError;
22+
event OnOpen OnOpen;
23+
event OnMessage OnMessage;
24+
event OnClose OnClose;
25+
}
26+
27+
public delegate void OnError(object sender, WebSocketErrorEventArgs e);
28+
public delegate void OnOpen(object sender, EventArgs e);
29+
public delegate void OnMessage(object sender, WebSocketMessageEventArgs e);
30+
public delegate void OnClose(object sender, WebSocketCloseEventArgs e);
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace Unity3dAzure.WebSockets {
4+
public class WebSocketCloseEventArgs : EventArgs {
5+
public ushort Code;
6+
public string Reason;
7+
public bool WasClean;
8+
9+
public WebSocketCloseEventArgs(string reason="Unknown", ushort code=0) {
10+
this.Code = code;
11+
this.Reason = reason;
12+
this.WasClean = (code == 1000) ? true : false;
13+
}
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace Unity3dAzure.WebSockets {
4+
public class WebSocketErrorEventArgs : EventArgs {
5+
public string Message;
6+
7+
public WebSocketErrorEventArgs(string error) {
8+
this.Message = error;
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)