Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4589fa9979d007040b5a807b0304b1ff
|
||||
folderAsset: yes
|
||||
timeCreated: 1466577973
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1a1a6aea65cc413faf8fb4421138b29
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,339 @@
|
||||
var WebSocketLibrary =
|
||||
{
|
||||
$webSocketManager:
|
||||
{
|
||||
/*
|
||||
* Map of instances
|
||||
*
|
||||
* Instance structure:
|
||||
* {
|
||||
* url: string,
|
||||
* ws: WebSocket,
|
||||
* subProtocols: string[],
|
||||
* }
|
||||
*/
|
||||
instances: {},
|
||||
|
||||
/* Last instance ID */
|
||||
lastId: 0,
|
||||
|
||||
/* Event listeners */
|
||||
onOpen: null,
|
||||
onMessage: null,
|
||||
onError: null,
|
||||
onClose: null
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onOpen callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnOpen: function(callback)
|
||||
{
|
||||
webSocketManager.onOpen = callback;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onMessage callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnMessage: function(callback)
|
||||
{
|
||||
webSocketManager.onMessage = callback;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onMessage callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnMessageStr: function(callback)
|
||||
{
|
||||
webSocketManager.onMessageStr = callback;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onError callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnError: function(callback)
|
||||
{
|
||||
webSocketManager.onError = callback;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onClose callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnClose: function(callback)
|
||||
{
|
||||
webSocketManager.onClose = callback;
|
||||
},
|
||||
|
||||
/**
|
||||
* Allocate new WebSocket instance struct
|
||||
*
|
||||
* @param url Server URL
|
||||
*/
|
||||
WebSocketAllocate: function(url)
|
||||
{
|
||||
var urlStr = UTF8ToString(url);
|
||||
var id = ++webSocketManager.lastId;
|
||||
webSocketManager.instances[id] = {
|
||||
url: urlStr,
|
||||
ws: null
|
||||
};
|
||||
return id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add Sub Protocol
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param protocol Sub Protocol
|
||||
*/
|
||||
WebSocketAddSubProtocol: function(instanceId, protocol)
|
||||
{
|
||||
var instance = webSocketManager.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
var protocolStr = UTF8ToString(protocol);
|
||||
|
||||
if(instance.subProtocols == null)
|
||||
instance.subProtocols = [];
|
||||
|
||||
instance.subProtocols.push(protocolStr);
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove reference to WebSocket instance
|
||||
*
|
||||
* If socket is not closed function will close it but onClose event will not be emitted because
|
||||
* this function should be invoked by C# WebSocket destructor.
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketFree: function(instanceId)
|
||||
{
|
||||
var instance = webSocketManager.instances[instanceId];
|
||||
if (!instance) return 0;
|
||||
|
||||
// Close if not closed
|
||||
if (instance.ws !== null && instance.ws.readyState < 2)
|
||||
instance.ws.close();
|
||||
|
||||
// Remove reference
|
||||
delete webSocketManager.instances[instanceId];
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Connect WebSocket to the server
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketConnect: function(instanceId)
|
||||
{
|
||||
var instance = webSocketManager.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
if (instance.ws !== null) return -2;
|
||||
|
||||
if(instance.subProtocols != null)
|
||||
instance.ws = new WebSocket(instance.url, instance.subProtocols);
|
||||
else
|
||||
instance.ws = new WebSocket(instance.url);
|
||||
|
||||
instance.ws.onopen = function()
|
||||
{
|
||||
if (webSocketManager.onOpen)
|
||||
Module.dynCall_vi(webSocketManager.onOpen, instanceId);
|
||||
};
|
||||
|
||||
instance.ws.onmessage = function(ev)
|
||||
{
|
||||
if (webSocketManager.onMessage === null)
|
||||
return;
|
||||
|
||||
if (ev.data instanceof ArrayBuffer)
|
||||
{
|
||||
var dataBuffer = new Uint8Array(ev.data);
|
||||
var buffer = _malloc(dataBuffer.length);
|
||||
HEAPU8.set(dataBuffer, buffer);
|
||||
try
|
||||
{
|
||||
Module.dynCall_viii(webSocketManager.onMessage, instanceId, buffer, dataBuffer.length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_free(buffer);
|
||||
}
|
||||
}
|
||||
else if (ev.data instanceof Blob)
|
||||
{
|
||||
var reader = new FileReader();
|
||||
reader.addEventListener("loadend", function()
|
||||
{
|
||||
var dataBuffer = new Uint8Array(reader.result);
|
||||
var buffer = _malloc(dataBuffer.length);
|
||||
HEAPU8.set(dataBuffer, buffer);
|
||||
try
|
||||
{
|
||||
Module.dynCall_viii(webSocketManager.onMessage, instanceId, buffer, dataBuffer.length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader = null;
|
||||
_free(buffer);
|
||||
}
|
||||
});
|
||||
reader.readAsArrayBuffer(ev.data);
|
||||
}
|
||||
else if(typeof ev.data == 'string')
|
||||
{
|
||||
var length = lengthBytesUTF8(ev.data) + 1;
|
||||
var buffer = _malloc(length);
|
||||
stringToUTF8(ev.data, buffer, length);
|
||||
try
|
||||
{
|
||||
Module.dynCall_vii(webSocketManager.onMessageStr, instanceId, buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_free(buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log("[JSLIB WebSocket Err] not support message type: ", (typeof ev.data));
|
||||
}
|
||||
};
|
||||
|
||||
instance.ws.onerror = function(ev)
|
||||
{
|
||||
if (webSocketManager.onError)
|
||||
{
|
||||
var msg = "WebSocket error.";
|
||||
var length = lengthBytesUTF8(msg) + 1;
|
||||
var buffer = _malloc(length);
|
||||
stringToUTF8(msg, buffer, length);
|
||||
try
|
||||
{
|
||||
Module.dynCall_vii(webSocketManager.onError, instanceId, buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_free(buffer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
instance.ws.onclose = function(ev)
|
||||
{
|
||||
if (webSocketManager.onClose)
|
||||
{
|
||||
var msg = ev.reason;
|
||||
var length = lengthBytesUTF8(msg) + 1;
|
||||
var buffer = _malloc(length);
|
||||
stringToUTF8(msg, buffer, length);
|
||||
try
|
||||
{
|
||||
Module.dynCall_viii(webSocketManager.onClose, instanceId, ev.code, buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
instance.ws = null;
|
||||
};
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close WebSocket connection
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param code Close status code
|
||||
* @param reasonPtr Pointer to reason string
|
||||
*/
|
||||
WebSocketClose: function(instanceId, code, reasonPtr)
|
||||
{
|
||||
var instance = webSocketManager.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
if (instance.ws === null) return -3;
|
||||
if (instance.ws.readyState === 2) return -4;
|
||||
if (instance.ws.readyState === 3) return -5;
|
||||
|
||||
var reason = ( reasonPtr ? UTF8ToString(reasonPtr) : undefined );
|
||||
try
|
||||
{
|
||||
instance.ws.close(code, reason);
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
return -7;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send message over WebSocket
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param bufferPtr Pointer to the message buffer
|
||||
* @param length Length of the message in the buffer
|
||||
*/
|
||||
WebSocketSend: function(instanceId, bufferPtr, length)
|
||||
{
|
||||
var instance = webSocketManager.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
if (instance.ws === null) return -3;
|
||||
if (instance.ws.readyState !== 1) return -6;
|
||||
|
||||
instance.ws.send(buffer.slice(bufferPtr, bufferPtr + length));
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send message string over WebSocket
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param stringPtr Pointer to the message string
|
||||
*/
|
||||
WebSocketSendStr: function(instanceId, stringPtr)
|
||||
{
|
||||
var instance = webSocketManager.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
if (instance.ws === null) return -3;
|
||||
if (instance.ws.readyState !== 1) return -6;
|
||||
|
||||
instance.ws.send(UTF8ToString(stringPtr));
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return WebSocket readyState
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketGetState: function(instanceId)
|
||||
{
|
||||
var instance = webSocketManager.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
if (instance.ws === null) return 3;
|
||||
|
||||
return instance.ws.readyState;
|
||||
}
|
||||
};
|
||||
|
||||
autoAddDeps(WebSocketLibrary, '$webSocketManager');
|
||||
mergeInto(LibraryManager.library, WebSocketLibrary);
|
||||
@@ -0,0 +1,37 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd88770aa13fc47b08f87d2145e9ac6e
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Facebook: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89cd0cf8603ef4069b2f6a5d79cbdbe1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb71bb4fb62590c4b975ef865b4df25f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,229 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Networking;
|
||||
using System.IO;
|
||||
using System;
|
||||
|
||||
namespace UnityWebSocket.Editor
|
||||
{
|
||||
internal class SettingsWindow : EditorWindow
|
||||
{
|
||||
static SettingsWindow window = null;
|
||||
[MenuItem("Tools/UnityWebSocket", priority = 100)]
|
||||
internal static void Open()
|
||||
{
|
||||
if (window != null)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
|
||||
window = GetWindow<SettingsWindow>(true, "UnityWebSocket");
|
||||
window.minSize = window.maxSize = new Vector2(600, 310);
|
||||
window.Show();
|
||||
window.BeginCheck();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
DrawLogo();
|
||||
DrawVersion();
|
||||
DrawSeparator(80);
|
||||
DrawSeparator(186);
|
||||
DrawHelper();
|
||||
DrawFooter();
|
||||
}
|
||||
|
||||
Texture2D logoTex = null;
|
||||
private void DrawLogo()
|
||||
{
|
||||
if (logoTex == null)
|
||||
{
|
||||
logoTex = new Texture2D(66, 66);
|
||||
logoTex.LoadImage(Convert.FromBase64String(LOGO_BASE64.VALUE));
|
||||
for (int i = 0; i < 66; i++) for (int j = 0; j < 15; j++) logoTex.SetPixel(i, j, Color.clear);
|
||||
logoTex.Apply();
|
||||
}
|
||||
|
||||
var logoPos = new Rect(10, 10, 66, 66);
|
||||
GUI.DrawTexture(logoPos, logoTex);
|
||||
var title = "<color=#3A9AD8><b>UnityWebSocket</b></color>";
|
||||
var titlePos = new Rect(80, 20, 500, 50);
|
||||
GUI.Label(titlePos, title, TextStyle(24));
|
||||
}
|
||||
|
||||
private void DrawSeparator(int y)
|
||||
{
|
||||
EditorGUI.DrawRect(new Rect(10, y, 580, 1), Color.white * 0.5f);
|
||||
}
|
||||
|
||||
private GUIStyle TextStyle(int fontSize = 10, TextAnchor alignment = TextAnchor.UpperLeft, float alpha = 0.85f)
|
||||
{
|
||||
var style = new GUIStyle();
|
||||
style.fontSize = fontSize;
|
||||
style.normal.textColor = (EditorGUIUtility.isProSkin ? Color.white : Color.black) * alpha;
|
||||
style.alignment = alignment;
|
||||
style.richText = true;
|
||||
return style;
|
||||
}
|
||||
|
||||
private void DrawVersion()
|
||||
{
|
||||
GUI.Label(new Rect(440, 10, 150, 10), "Current Version: " + Settings.VERSION, TextStyle(alignment: TextAnchor.MiddleLeft));
|
||||
if (string.IsNullOrEmpty(latestVersion))
|
||||
{
|
||||
GUI.Label(new Rect(440, 30, 150, 10), "Checking for Updates...", TextStyle(alignment: TextAnchor.MiddleLeft));
|
||||
}
|
||||
else if (latestVersion == "unknown")
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Label(new Rect(440, 30, 150, 10), "Latest Version: " + latestVersion, TextStyle(alignment: TextAnchor.MiddleLeft));
|
||||
if (Settings.VERSION == latestVersion)
|
||||
{
|
||||
if (GUI.Button(new Rect(440, 50, 150, 18), "Check Update"))
|
||||
{
|
||||
latestVersion = "";
|
||||
changeLog = "";
|
||||
BeginCheck();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUI.Button(new Rect(440, 50, 150, 18), "Update to | " + latestVersion))
|
||||
{
|
||||
ShowUpdateDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowUpdateDialog()
|
||||
{
|
||||
var isOK = EditorUtility.DisplayDialog("UnityWebSocket",
|
||||
"Update UnityWebSocket now?\n" + changeLog,
|
||||
"Update Now", "Cancel");
|
||||
|
||||
if (isOK)
|
||||
{
|
||||
UpdateVersion();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVersion()
|
||||
{
|
||||
Application.OpenURL(Settings.GITHUB + "/releases");
|
||||
}
|
||||
|
||||
private void DrawHelper()
|
||||
{
|
||||
GUI.Label(new Rect(330, 200, 100, 18), "GitHub:", TextStyle(10, TextAnchor.MiddleRight));
|
||||
if (GUI.Button(new Rect(440, 200, 150, 18), "UnityWebSocket"))
|
||||
{
|
||||
Application.OpenURL(Settings.GITHUB);
|
||||
}
|
||||
|
||||
GUI.Label(new Rect(330, 225, 100, 18), "Report:", TextStyle(10, TextAnchor.MiddleRight));
|
||||
if (GUI.Button(new Rect(440, 225, 150, 18), "Report an Issue"))
|
||||
{
|
||||
Application.OpenURL(Settings.GITHUB + "/issues/new");
|
||||
}
|
||||
|
||||
GUI.Label(new Rect(330, 250, 100, 18), "Email:", TextStyle(10, TextAnchor.MiddleRight));
|
||||
if (GUI.Button(new Rect(440, 250, 150, 18), Settings.EMAIL))
|
||||
{
|
||||
var uri = new Uri(string.Format("mailto:{0}?subject={1}", Settings.EMAIL, "UnityWebSocket Feedback"));
|
||||
Application.OpenURL(uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
GUI.Label(new Rect(330, 275, 100, 18), "QQ群:", TextStyle(10, TextAnchor.MiddleRight));
|
||||
if (GUI.Button(new Rect(440, 275, 150, 18), Settings.QQ_GROUP))
|
||||
{
|
||||
Application.OpenURL(Settings.QQ_GROUP_LINK);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFooter()
|
||||
{
|
||||
EditorGUI.DropShadowLabel(new Rect(10, 230, 400, 20), "Developed by " + Settings.AUHTOR);
|
||||
EditorGUI.DropShadowLabel(new Rect(10, 250, 400, 20), "All rights reserved");
|
||||
}
|
||||
|
||||
UnityWebRequest req;
|
||||
string changeLog = "";
|
||||
string latestVersion = "";
|
||||
void BeginCheck()
|
||||
{
|
||||
EditorApplication.update -= VersionCheckUpdate;
|
||||
EditorApplication.update += VersionCheckUpdate;
|
||||
|
||||
req = UnityWebRequest.Get(Settings.GITHUB + "/releases/latest");
|
||||
req.SendWebRequest();
|
||||
}
|
||||
|
||||
private void VersionCheckUpdate()
|
||||
{
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
if (req == null
|
||||
|| req.result == UnityWebRequest.Result.ConnectionError
|
||||
|| req.result == UnityWebRequest.Result.DataProcessingError
|
||||
|| req.result == UnityWebRequest.Result.ProtocolError)
|
||||
#elif UNITY_2018_1_OR_NEWER
|
||||
if (req == null || req.isNetworkError || req.isHttpError)
|
||||
#else
|
||||
if (req == null || req.isError)
|
||||
#endif
|
||||
{
|
||||
EditorApplication.update -= VersionCheckUpdate;
|
||||
latestVersion = "unknown";
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.isDone)
|
||||
{
|
||||
EditorApplication.update -= VersionCheckUpdate;
|
||||
latestVersion = req.url.Substring(req.url.LastIndexOf("/") + 1).TrimStart('v');
|
||||
|
||||
if (Settings.VERSION != latestVersion)
|
||||
{
|
||||
var text = req.downloadHandler.text;
|
||||
var st = text.IndexOf("content=\"" + latestVersion);
|
||||
st = st > 0 ? text.IndexOf("\n", st) : -1;
|
||||
var end = st > 0 ? text.IndexOf("\" />", st) : -1;
|
||||
if (st > 0 && end > st)
|
||||
{
|
||||
changeLog = text.Substring(st + 1, end - st - 1).Trim();
|
||||
changeLog = changeLog.Replace("\r", "");
|
||||
changeLog = changeLog.Replace("\n", "\n- ");
|
||||
changeLog = "\nCHANGE LOG: \n- " + changeLog + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class LOGO_BASE64
|
||||
{
|
||||
internal const string VALUE = "iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAMAAADUivDaAAAAq1BMVEUAAABKmtcvjtYzl" +
|
||||
"9szmNszl9syl9k0mNs0mNwzmNs0mNszl9szl9s0mNs0mNwzmNw0mNwyltk0mNw0mNwzl9s0mNsymNs0mNszmNwzmNwzm" +
|
||||
"NszmNs0mNwzl9w0mNwzmNw0mNs0mNs0mNwzl9wzmNs0mNwzmNs0mNwzl90zmNszmNszl9szmNsxmNszmNszmNw0mNwzm" +
|
||||
"Nw0mNs2neM4pe41mt43ouo2oOY5qfM+UHlaAAAAMnRSTlMAAwXN3sgI+/069MSCK6M/MA74h9qfFHB8STWMJ9OSdmNcI" +
|
||||
"8qya1IeF+/U0EIa57mqmFTYJe4AAAN3SURBVFjD7ZbpkppAFEa/bgVBREF2kEVGFNeZsM77P1kadURnJkr8k1Qlx1Khu" +
|
||||
"/pw7+2lwH/+YcgfMBBLG7VocwDamzH+wJBB8Qhjve2f0TdrGwjei6o4Ub/nM/APw5Z7vvSB/qrCrqbD6fBEVtigeMxks" +
|
||||
"fX9zWbj+z1jhqgSBplQ50eGo4614WXlRAzgrRhmtSfvxAn7pB0N5ObaKKZZuU5/d37IBcBgUQwqDuf7Z2gUmVAl4NGNr" +
|
||||
"/UeHxV5n39ulbaKLI86h6HilmM5M1aN126lpNhtl59yeTsp8nUMvpNC1J3bh5FtfVRk+bJrJunn5d4U4piJ/Vw9eXgsj" +
|
||||
"4ZpZaCjg9waZkIpnBWLJ44OwoNu60F2UnSaEkKv4XnAlCpm6B4F/aKMDiyGi2L8SEEAVdxNLuzmgV7nFwObEe2xQVuX+" +
|
||||
"RV1lWetga3w+cN1sXgvm4cJH8OEgZC1DPKhfF/BIymmQrMjq/x65FUeEkDup8GxoexZmznHCvANtXU/CAq13yimhQGtm" +
|
||||
"H4VCPnBBL1fTKo3CqEcvq7Lb/OwHxWTYlyw+JmjKoVvDLVOQB4pVsM8K8smgvLCxZDlIijwyOEc+nr/msMwK0+GQWGBd" +
|
||||
"tmhjv8icTds1s2ammaFh04QLLe69NK7guP6mTDMaw3o6nAX/Z7EXUskPSvWEWg4srVlp5NTDXv9Lce9HGN5eeG4nj5Yz" +
|
||||
"ACteU2wQLo4MBtJfd1nw5nG1/s9zwUQ6pykL1TQjqdeuvQW0naz2XKLYL4Cwzr4vj+OQdD96CSp7Lrynp4aeFF0xdm5q" +
|
||||
"6OFtFfPv7URxpWJNjd/N+3+I9+1klMav12Qtgbt9R2JaIopjkzaPtOFq4KxUpqfUMSFnQrySWjLoQzRZS4HMH84ME1ej" +
|
||||
"S1YJpQZ3B+sR1uCQJSBdGdCk1eAEgORR88KK05W8dh2MA+A/SKCYu3mCJ0Ek7HBx4HHeuwYy5G3x8hSMTJcOMFbinCsn" +
|
||||
"hO1V1aszGULvA0g4UFsb4VA0hAFcyo6cgLsAoT7uUtGAH5wQKQle0wuLyxLTaNyJEYwxw4wSljLK1TP8CAaOyhBMMEsj" +
|
||||
"OBoXgo7VGElFkSWL+vef1RF2YNXeRWYzQBTpkhC8KaZHhuIogArkQLKClBZjU26B2IZgGz+cpZkHl8g3fYUaW/YP2kb2" +
|
||||
"M/V97JY/vZN859n+QmO7XtC9Bf2jAAAAABJRU5ErkJggg==";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c42d421cc4c34f3eae1fbd67f0dced0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "UnityWebSocket.Editor",
|
||||
"references": [
|
||||
"UnityWebSocket.Runtime"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee833745c57bd4369ab8f0ff380a96fa
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53e0ed9fdc3af42eba12a5b1b9a5f873
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b3a2a8f55d4a47f599b1fa3ed612389
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the event data for the <see cref="IWebSocket.OnClose"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// That event occurs when the WebSocket connection has been closed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If you would like to get the reason for the close, you should access
|
||||
/// the <see cref="Code"/> or <see cref="Reason"/> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class CloseEventArgs : EventArgs
|
||||
{
|
||||
#region Internal Constructors
|
||||
|
||||
internal CloseEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
internal CloseEventArgs(ushort code)
|
||||
: this(code, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal CloseEventArgs(CloseStatusCode code)
|
||||
: this((ushort)code, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal CloseEventArgs(CloseStatusCode code, string reason)
|
||||
: this((ushort)code, reason)
|
||||
{
|
||||
}
|
||||
|
||||
internal CloseEventArgs(ushort code, string reason)
|
||||
{
|
||||
Code = code;
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status code for the close.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="ushort"/> that represents the status code for the close if any.
|
||||
/// </value>
|
||||
public ushort Code { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason for the close.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that represents the reason for the close if any.
|
||||
/// </value>
|
||||
public string Reason { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the connection has been closed cleanly.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the connection has been closed cleanly; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool WasClean { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enum value same as Code
|
||||
/// </summary>
|
||||
public CloseStatusCode StatusCode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Enum.IsDefined(typeof(CloseStatusCode), Code))
|
||||
return (CloseStatusCode)Code;
|
||||
return CloseStatusCode.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29b987d07ba15434cb1744135a7a5416
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the status code for the WebSocket connection close.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The values of this enumeration are defined in
|
||||
/// <see href="http://tools.ietf.org/html/rfc6455#section-7.4">
|
||||
/// Section 7.4</see> of RFC 6455.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// "Reserved value" cannot be sent as a status code in
|
||||
/// closing handshake by an endpoint.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public enum CloseStatusCode : ushort
|
||||
{
|
||||
Unknown = 65534,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1000. Indicates normal close.
|
||||
/// </summary>
|
||||
Normal = 1000,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1001. Indicates that an endpoint is
|
||||
/// going away.
|
||||
/// </summary>
|
||||
Away = 1001,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1002. Indicates that an endpoint is
|
||||
/// terminating the connection due to a protocol error.
|
||||
/// </summary>
|
||||
ProtocolError = 1002,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1003. Indicates that an endpoint is
|
||||
/// terminating the connection because it has received a type of
|
||||
/// data that it cannot accept.
|
||||
/// </summary>
|
||||
UnsupportedData = 1003,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1004. Still undefined. A Reserved value.
|
||||
/// </summary>
|
||||
Undefined = 1004,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1005. Indicates that no status code was
|
||||
/// actually present. A Reserved value.
|
||||
/// </summary>
|
||||
NoStatus = 1005,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1006. Indicates that the connection was
|
||||
/// closed abnormally. A Reserved value.
|
||||
/// </summary>
|
||||
Abnormal = 1006,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1007. Indicates that an endpoint is
|
||||
/// terminating the connection because it has received a message that
|
||||
/// contains data that is not consistent with the type of the message.
|
||||
/// </summary>
|
||||
InvalidData = 1007,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1008. Indicates that an endpoint is
|
||||
/// terminating the connection because it has received a message that
|
||||
/// violates its policy.
|
||||
/// </summary>
|
||||
PolicyViolation = 1008,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1009. Indicates that an endpoint is
|
||||
/// terminating the connection because it has received a message that
|
||||
/// is too big to process.
|
||||
/// </summary>
|
||||
TooBig = 1009,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1010. Indicates that a client is
|
||||
/// terminating the connection because it has expected the server to
|
||||
/// negotiate one or more extension, but the server did not return
|
||||
/// them in the handshake response.
|
||||
/// </summary>
|
||||
MandatoryExtension = 1010,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1011. Indicates that a server is
|
||||
/// terminating the connection because it has encountered an unexpected
|
||||
/// condition that prevented it from fulfilling the request.
|
||||
/// </summary>
|
||||
ServerError = 1011,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1015. Indicates that the connection was
|
||||
/// closed due to a failure to perform a TLS handshake. A Reserved value.
|
||||
/// </summary>
|
||||
TlsHandshakeFailure = 1015,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e34ee317292e4225a10427cc35f85ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the event data for the <see cref="IWebSocket.OnError"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// That event occurs when the <see cref="IWebSocket"/> gets an error.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If you would like to get the error message, you should access
|
||||
/// the <see cref="Message"/> property.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// And if the error is due to an exception, you can get it by accessing
|
||||
/// the <see cref="Exception"/> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class ErrorEventArgs : EventArgs
|
||||
{
|
||||
#region Internal Constructors
|
||||
|
||||
internal ErrorEventArgs(string message)
|
||||
: this(message, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal ErrorEventArgs(string message, Exception exception)
|
||||
{
|
||||
this.Message = message;
|
||||
this.Exception = exception;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exception that caused the error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An <see cref="System.Exception"/> instance that represents the cause of
|
||||
/// the error if it is due to an exception; otherwise, <see langword="null"/>.
|
||||
/// </value>
|
||||
public Exception Exception { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that represents the error message.
|
||||
/// </value>
|
||||
public string Message { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 884e7db60b6444154b7200e0e436f2de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>IWebSocket indicate a network connection.</para>
|
||||
/// <para>It can be connecting, connected, closing or closed state. </para>
|
||||
/// <para>You can send and receive messages by using it.</para>
|
||||
/// <para>Register callbacks for handling messages.</para>
|
||||
/// <para> ----------------------------------------------------------- </para>
|
||||
/// <para>IWebSocket 表示一个网络连接,</para>
|
||||
/// <para>它可以是 connecting connected closing closed 状态,</para>
|
||||
/// <para>可以发送和接收消息,</para>
|
||||
/// <para>通过注册消息回调,来处理接收到的消息。</para>
|
||||
/// </summary>
|
||||
public interface IWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Establishes a connection asynchronously.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method does not wait for the connect process to be complete.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This method does nothing if the connection has already been
|
||||
/// established.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// <para>
|
||||
/// This instance is not a client.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// -or-
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The close process is in progress.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// -or-
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A series of reconnecting has failed.
|
||||
/// </para>
|
||||
/// </exception>
|
||||
void ConnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection asynchronously.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method does not wait for the close to be complete.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This method does nothing if the current state of the connection is
|
||||
/// Closing or Closed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
void CloseAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Sends the specified data asynchronously using the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method does not wait for the send to be complete.
|
||||
/// </remarks>
|
||||
/// <param name="data">
|
||||
/// An array of <see cref="byte"/> that represents the binary data to send.
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The current state of the connection is not Open.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="data"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
void SendAsync(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the specified data using the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <param name="text">
|
||||
/// A <see cref="string"/> that represents the text data to send.
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The current state of the connection is not Open.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="text"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="text"/> could not be UTF-8 encoded.
|
||||
/// </exception>
|
||||
void SendAsync(string text);
|
||||
|
||||
/// <summary>
|
||||
/// get the address which to connect.
|
||||
/// </summary>
|
||||
string Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// get sub protocols .
|
||||
/// </summary>
|
||||
string[] SubProtocols { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current state of the connection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <para>
|
||||
/// One of the <see cref="WebSocketState"/> enum values.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It indicates the current state of the connection.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The default value is <see cref="WebSocketState.Connecting"/>.
|
||||
/// </para>
|
||||
/// </value>
|
||||
WebSocketState ReadyState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the WebSocket connection has been established.
|
||||
/// </summary>
|
||||
event EventHandler<OpenEventArgs> OnOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the WebSocket connection has been closed.
|
||||
/// </summary>
|
||||
event EventHandler<CloseEventArgs> OnClose;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="IWebSocket"/> gets an error.
|
||||
/// </summary>
|
||||
event EventHandler<ErrorEventArgs> OnError;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="IWebSocket"/> receives a message.
|
||||
/// </summary>
|
||||
event EventHandler<MessageEventArgs> OnMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37ee2146eb8c34ffab8b081a632b05cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
private byte[] _rawData;
|
||||
private string _data;
|
||||
|
||||
internal MessageEventArgs(Opcode opcode, byte[] rawData)
|
||||
{
|
||||
Opcode = opcode;
|
||||
_rawData = rawData;
|
||||
}
|
||||
|
||||
internal MessageEventArgs(Opcode opcode, string data)
|
||||
{
|
||||
Opcode = opcode;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the opcode for the message.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <see cref="Opcode.Text"/>, <see cref="Opcode.Binary"/>.
|
||||
/// </value>
|
||||
internal Opcode Opcode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message data as a <see cref="string"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that represents the message data if its type is
|
||||
/// text and if decoding it to a string has successfully done;
|
||||
/// otherwise, <see langword="null"/>.
|
||||
/// </value>
|
||||
public string Data
|
||||
{
|
||||
get
|
||||
{
|
||||
SetData();
|
||||
return _data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message data as an array of <see cref="byte"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An array of <see cref="byte"/> that represents the message data.
|
||||
/// </value>
|
||||
public byte[] RawData
|
||||
{
|
||||
get
|
||||
{
|
||||
SetRawData();
|
||||
return _rawData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the message type is binary.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the message type is binary; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsBinary
|
||||
{
|
||||
get
|
||||
{
|
||||
return Opcode == Opcode.Binary;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the message type is text.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the message type is text; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsText
|
||||
{
|
||||
get
|
||||
{
|
||||
return Opcode == Opcode.Text;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetData()
|
||||
{
|
||||
if (_data != null) return;
|
||||
|
||||
if (RawData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_data = Encoding.UTF8.GetString(RawData);
|
||||
}
|
||||
|
||||
private void SetRawData()
|
||||
{
|
||||
if (_rawData != null) return;
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rawData = Encoding.UTF8.GetBytes(_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b44eda173b4924081bab76ae9d1b0a9c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the WebSocket frame type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of this enumeration are defined in
|
||||
/// <see href="http://tools.ietf.org/html/rfc6455#section-5.2">
|
||||
/// Section 5.2</see> of RFC 6455.
|
||||
/// </remarks>
|
||||
public enum Opcode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 1. Indicates text frame.
|
||||
/// </summary>
|
||||
Text = 0x1,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 2. Indicates binary frame.
|
||||
/// </summary>
|
||||
Binary = 0x2,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 8. Indicates connection close frame.
|
||||
/// </summary>
|
||||
Close = 0x8,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eeac0ef90273544ebbae046672caf362
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
public class OpenEventArgs : EventArgs
|
||||
{
|
||||
internal OpenEventArgs()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fb6fd704bd4e4b8ba63cd0b28712955
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
public static class Settings
|
||||
{
|
||||
public const string GITHUB = "https://github.com/psygames/UnityWebSocket";
|
||||
public const string QQ_GROUP = "1126457634";
|
||||
public const string QQ_GROUP_LINK = "https://qm.qq.com/cgi-bin/qm/qr?k=KcexYJ9aYwogFXbj2aN0XHH5b2G7ICmd";
|
||||
public const string EMAIL = "799329256@qq.com";
|
||||
public const string AUHTOR = "psygames";
|
||||
public const string VERSION = "2.6.6";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e268303c7a605e343b1b132e5559f01f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Reference html5 WebSocket ReadyState Properties
|
||||
/// Indicates the state of a WebSocket connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of this enumeration are defined in
|
||||
/// <see href="http://www.w3.org/TR/websockets/#dom-websocket-readystate">
|
||||
/// The WebSocket API</see>.
|
||||
/// </remarks>
|
||||
public enum WebSocketState : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 0. Indicates that the connection has not
|
||||
/// yet been established.
|
||||
/// </summary>
|
||||
Connecting = 0,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 1. Indicates that the connection has
|
||||
/// been established, and the communication is possible.
|
||||
/// </summary>
|
||||
Open = 1,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 2. Indicates that the connection is
|
||||
/// going through the closing handshake, or the close method has
|
||||
/// been invoked.
|
||||
/// </summary>
|
||||
Closing = 2,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 3. Indicates that the connection has
|
||||
/// been closed or could not be established.
|
||||
/// </summary>
|
||||
Closed = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f6567ad13cb147a59f8af784f1c5f60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 396c66b333d624d539153070900bb73b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c110a898ae8b0b41bcf4da49c2b0425
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
#if !NET_LEGACY && (UNITY_EDITOR || !UNITY_WEBGL)
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.WebSockets;
|
||||
using System.IO;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
public class WebSocket : IWebSocket
|
||||
{
|
||||
public string Address { get; private set; }
|
||||
public string[] SubProtocols { get; private set; }
|
||||
|
||||
public WebSocketState ReadyState
|
||||
{
|
||||
get
|
||||
{
|
||||
if (socket == null)
|
||||
return WebSocketState.Closed;
|
||||
switch (socket.State)
|
||||
{
|
||||
case System.Net.WebSockets.WebSocketState.Closed:
|
||||
case System.Net.WebSockets.WebSocketState.None:
|
||||
return WebSocketState.Closed;
|
||||
case System.Net.WebSockets.WebSocketState.CloseReceived:
|
||||
case System.Net.WebSockets.WebSocketState.CloseSent:
|
||||
return WebSocketState.Closing;
|
||||
case System.Net.WebSockets.WebSocketState.Connecting:
|
||||
return WebSocketState.Connecting;
|
||||
case System.Net.WebSockets.WebSocketState.Open:
|
||||
return WebSocketState.Open;
|
||||
}
|
||||
return WebSocketState.Closed;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<OpenEventArgs> OnOpen;
|
||||
public event EventHandler<CloseEventArgs> OnClose;
|
||||
public event EventHandler<ErrorEventArgs> OnError;
|
||||
public event EventHandler<MessageEventArgs> OnMessage;
|
||||
|
||||
private ClientWebSocket socket;
|
||||
private bool isOpening => socket != null && socket.State == System.Net.WebSockets.WebSocketState.Open;
|
||||
|
||||
#region APIs
|
||||
public WebSocket(string address)
|
||||
{
|
||||
this.Address = address;
|
||||
}
|
||||
|
||||
public WebSocket(string address, string subProtocol)
|
||||
{
|
||||
this.Address = address;
|
||||
this.SubProtocols = new string[] { subProtocol };
|
||||
}
|
||||
|
||||
public WebSocket(string address, string[] subProtocols)
|
||||
{
|
||||
this.Address = address;
|
||||
this.SubProtocols = subProtocols;
|
||||
}
|
||||
|
||||
public void ConnectAsync()
|
||||
{
|
||||
#if !UNITY_WEB_SOCKET_ENABLE_ASYNC
|
||||
WebSocketManager.Instance.Add(this);
|
||||
#endif
|
||||
if (socket != null)
|
||||
{
|
||||
HandleError(new Exception("Socket is busy."));
|
||||
return;
|
||||
}
|
||||
socket = new ClientWebSocket();
|
||||
if (this.SubProtocols != null)
|
||||
{
|
||||
foreach (var protocol in this.SubProtocols)
|
||||
{
|
||||
if (string.IsNullOrEmpty(protocol)) continue;
|
||||
Log($"Add Sub Protocol {protocol}");
|
||||
socket.Options.AddSubProtocol(protocol);
|
||||
}
|
||||
}
|
||||
Task.Run(ConnectTask);
|
||||
}
|
||||
|
||||
public void CloseAsync()
|
||||
{
|
||||
if (!isOpening) return;
|
||||
SendBufferAsync(new SendBuffer(null, WebSocketMessageType.Close));
|
||||
}
|
||||
|
||||
public void SendAsync(byte[] data)
|
||||
{
|
||||
if (!isOpening) return;
|
||||
var buffer = new SendBuffer(data, WebSocketMessageType.Binary);
|
||||
SendBufferAsync(buffer);
|
||||
}
|
||||
|
||||
public void SendAsync(string text)
|
||||
{
|
||||
if (!isOpening) return;
|
||||
var data = Encoding.UTF8.GetBytes(text);
|
||||
var buffer = new SendBuffer(data, WebSocketMessageType.Text);
|
||||
SendBufferAsync(buffer);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
private async Task ConnectTask()
|
||||
{
|
||||
Log("Connect Task Begin ...");
|
||||
|
||||
try
|
||||
{
|
||||
var uri = new Uri(Address);
|
||||
UnityEngine.Debug.Log($"WebSocket Connect {Address}");
|
||||
await socket.ConnectAsync(uri, CancellationToken.None);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HandleError(e);
|
||||
HandleClose((ushort)CloseStatusCode.Abnormal, e.Message);
|
||||
SocketDispose();
|
||||
return;
|
||||
}
|
||||
UnityEngine.Debug.Log("WebSocket Connect Open ok!!");
|
||||
HandleOpen();
|
||||
|
||||
Log("Connect Task End !");
|
||||
|
||||
await ReceiveTask();
|
||||
}
|
||||
|
||||
class SendBuffer
|
||||
{
|
||||
public byte[] data;
|
||||
public WebSocketMessageType type;
|
||||
public SendBuffer(byte[] data, WebSocketMessageType type)
|
||||
{
|
||||
this.data = data;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
private object sendQueueLock = new object();
|
||||
private Queue<SendBuffer> sendQueue = new Queue<SendBuffer>();
|
||||
private bool isSendTaskRunning;
|
||||
|
||||
private void SendBufferAsync(SendBuffer buffer)
|
||||
{
|
||||
if (isSendTaskRunning)
|
||||
{
|
||||
lock (sendQueueLock)
|
||||
{
|
||||
if (buffer.type == WebSocketMessageType.Close)
|
||||
{
|
||||
sendQueue.Clear();
|
||||
}
|
||||
sendQueue.Enqueue(buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isSendTaskRunning = true;
|
||||
sendQueue.Enqueue(buffer);
|
||||
Task.Run(SendTask);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendTask()
|
||||
{
|
||||
Log("Send Task Begin ...");
|
||||
|
||||
try
|
||||
{
|
||||
SendBuffer buffer = null;
|
||||
while (sendQueue.Count > 0 && isOpening)
|
||||
{
|
||||
lock (sendQueueLock)
|
||||
{
|
||||
buffer = sendQueue.Dequeue();
|
||||
}
|
||||
if (buffer.type == WebSocketMessageType.Close)
|
||||
{
|
||||
Log($"Close Send Begin ...");
|
||||
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Normal Closure", CancellationToken.None);
|
||||
Log($"Close Send End !");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Send, type: {buffer.type}, size: {buffer.data.Length}, queue left: {sendQueue.Count}");
|
||||
await socket.SendAsync(new ArraySegment<byte>(buffer.data), buffer.type, true, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HandleError(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSendTaskRunning = false;
|
||||
}
|
||||
|
||||
Log("Send Task End !");
|
||||
}
|
||||
|
||||
private async Task ReceiveTask()
|
||||
{
|
||||
Log("Receive Task Begin ...");
|
||||
|
||||
string closeReason = "";
|
||||
ushort closeCode = 0;
|
||||
bool isClosed = false;
|
||||
var segment = new ArraySegment<byte>(new byte[8192]);
|
||||
var ms = new MemoryStream();
|
||||
|
||||
try
|
||||
{
|
||||
while (!isClosed)
|
||||
{
|
||||
var result = await socket.ReceiveAsync(segment, CancellationToken.None);
|
||||
//UnityEngine.Debug.Log($"C# WebSocket recv {result.Count}");
|
||||
ms.Write(segment.Array, 0, result.Count);
|
||||
if (!result.EndOfMessage) continue;
|
||||
//UnityEngine.Debug.Log($"C# WebSocket recv 2");
|
||||
var data = ms.ToArray();
|
||||
ms.SetLength(0);
|
||||
switch (result.MessageType)
|
||||
{
|
||||
case WebSocketMessageType.Binary:
|
||||
HandleMessage(Opcode.Binary, data);
|
||||
break;
|
||||
case WebSocketMessageType.Text:
|
||||
HandleMessage(Opcode.Text, data);
|
||||
break;
|
||||
case WebSocketMessageType.Close:
|
||||
isClosed = true;
|
||||
closeCode = (ushort)result.CloseStatus;
|
||||
closeReason = result.CloseStatusDescription;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HandleError(e);
|
||||
closeCode = (ushort)CloseStatusCode.Abnormal;
|
||||
closeReason = e.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ms.Close();
|
||||
}
|
||||
|
||||
HandleClose(closeCode, closeReason);
|
||||
SocketDispose();
|
||||
|
||||
Log("Receive Task End !");
|
||||
}
|
||||
|
||||
private void SocketDispose()
|
||||
{
|
||||
sendQueue.Clear();
|
||||
socket.Dispose();
|
||||
socket = null;
|
||||
}
|
||||
|
||||
private void HandleOpen()
|
||||
{
|
||||
Log("OnOpen");
|
||||
#if !UNITY_WEB_SOCKET_ENABLE_ASYNC
|
||||
HandleEventSync(new OpenEventArgs());
|
||||
#else
|
||||
OnOpen?.Invoke(this, new OpenEventArgs());
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleMessage(Opcode opcode, byte[] rawData)
|
||||
{
|
||||
Log($"OnMessage, type: {opcode}, size: {rawData.Length}");
|
||||
#if !UNITY_WEB_SOCKET_ENABLE_ASYNC
|
||||
HandleEventSync(new MessageEventArgs(opcode, rawData));
|
||||
#else
|
||||
OnMessage?.Invoke(this, new MessageEventArgs(opcode, rawData));
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleClose(ushort code, string reason)
|
||||
{
|
||||
Log($"OnClose, code: {code}, reason: {reason}");
|
||||
#if !UNITY_WEB_SOCKET_ENABLE_ASYNC
|
||||
HandleEventSync(new CloseEventArgs(code, reason));
|
||||
#else
|
||||
OnClose?.Invoke(this, new CloseEventArgs(code, reason));
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleError(Exception exception)
|
||||
{
|
||||
Log("OnError, error: " + exception.Message);
|
||||
#if !UNITY_WEB_SOCKET_ENABLE_ASYNC
|
||||
HandleEventSync(new ErrorEventArgs(exception.Message));
|
||||
#else
|
||||
OnError?.Invoke(this, new ErrorEventArgs(exception.Message));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !UNITY_WEB_SOCKET_ENABLE_ASYNC
|
||||
private readonly Queue<EventArgs> eventQueue = new Queue<EventArgs>();
|
||||
private readonly object eventQueueLock = new object();
|
||||
private void HandleEventSync(EventArgs eventArgs)
|
||||
{
|
||||
lock (eventQueueLock)
|
||||
{
|
||||
eventQueue.Enqueue(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
EventArgs e;
|
||||
while (eventQueue.Count > 0)
|
||||
{
|
||||
lock (eventQueueLock)
|
||||
{
|
||||
e = eventQueue.Dequeue();
|
||||
}
|
||||
|
||||
if (e is CloseEventArgs)
|
||||
{
|
||||
OnClose?.Invoke(this, e as CloseEventArgs);
|
||||
WebSocketManager.Instance.Remove(this);
|
||||
}
|
||||
else if (e is OpenEventArgs)
|
||||
{
|
||||
OnOpen?.Invoke(this, e as OpenEventArgs);
|
||||
}
|
||||
else if (e is MessageEventArgs)
|
||||
{
|
||||
OnMessage?.Invoke(this, e as MessageEventArgs);
|
||||
}
|
||||
else if (e is ErrorEventArgs)
|
||||
{
|
||||
OnError?.Invoke(this, e as ErrorEventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_WEB_SOCKET_LOG")]
|
||||
static void Log(string msg)
|
||||
{
|
||||
UnityEngine.Debug.Log($"<color=yellow>[UnityWebSocket]</color>" +
|
||||
$"<color=green>[T-{Thread.CurrentThread.ManagedThreadId:D3}]</color>" +
|
||||
$"<color=red>[{DateTime.Now.TimeOfDay}]</color>" +
|
||||
$" {msg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d10f88a23641b4beb8df74460fb7f705
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#if !NET_LEGACY && (UNITY_EDITOR || !UNITY_WEBGL) && !UNITY_WEB_SOCKET_ENABLE_ASYNC
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
[DefaultExecutionOrder(-10000)]
|
||||
internal class WebSocketManager : MonoBehaviour
|
||||
{
|
||||
private const string rootName = "[UnityWebSocket]";
|
||||
private static WebSocketManager _instance;
|
||||
public static WebSocketManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_instance) CreateInstance();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
public static void CreateInstance()
|
||||
{
|
||||
GameObject go = GameObject.Find("/" + rootName);
|
||||
if (!go) go = new GameObject(rootName);
|
||||
_instance = go.GetComponent<WebSocketManager>();
|
||||
if (!_instance) _instance = go.AddComponent<WebSocketManager>();
|
||||
}
|
||||
|
||||
private readonly List<WebSocket> sockets = new List<WebSocket>();
|
||||
|
||||
public void Add(WebSocket socket)
|
||||
{
|
||||
if (!sockets.Contains(socket))
|
||||
sockets.Add(socket);
|
||||
}
|
||||
|
||||
public void Remove(WebSocket socket)
|
||||
{
|
||||
if (sockets.Contains(socket))
|
||||
sockets.Remove(socket);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (sockets.Count <= 0) return;
|
||||
for (int i = sockets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
sockets[i].Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99157fb5def394c83a9e5342036c92b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fb37927ec1ce4def9c5e7cff883f9f5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,147 @@
|
||||
#if !UNITY_EDITOR && UNITY_WEBGL
|
||||
using System;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
public class WebSocket : IWebSocket
|
||||
{
|
||||
public string Address { get; private set; }
|
||||
public string[] SubProtocols { get; private set; }
|
||||
public WebSocketState ReadyState { get { return (WebSocketState)WebSocketManager.WebSocketGetState(instanceId); } }
|
||||
|
||||
public event EventHandler<OpenEventArgs> OnOpen;
|
||||
public event EventHandler<CloseEventArgs> OnClose;
|
||||
public event EventHandler<ErrorEventArgs> OnError;
|
||||
public event EventHandler<MessageEventArgs> OnMessage;
|
||||
|
||||
internal int instanceId = 0;
|
||||
|
||||
public WebSocket(string address)
|
||||
{
|
||||
this.Address = address;
|
||||
AllocateInstance();
|
||||
}
|
||||
|
||||
public WebSocket(string address, string subProtocol)
|
||||
{
|
||||
this.Address = address;
|
||||
this.SubProtocols = new string[] { subProtocol };
|
||||
AllocateInstance();
|
||||
}
|
||||
|
||||
public WebSocket(string address, string[] subProtocols)
|
||||
{
|
||||
this.Address = address;
|
||||
this.SubProtocols = subProtocols;
|
||||
AllocateInstance();
|
||||
}
|
||||
|
||||
internal void AllocateInstance()
|
||||
{
|
||||
instanceId = WebSocketManager.AllocateInstance(this.Address);
|
||||
Log($"Allocate socket with instanceId: {instanceId}");
|
||||
if (this.SubProtocols == null) return;
|
||||
foreach (var protocol in this.SubProtocols)
|
||||
{
|
||||
if (string.IsNullOrEmpty(protocol)) continue;
|
||||
Log($"Add Sub Protocol {protocol}, with instanceId: {instanceId}");
|
||||
int code = WebSocketManager.WebSocketAddSubProtocol(instanceId, protocol);
|
||||
if (code < 0)
|
||||
{
|
||||
HandleOnError(GetErrorMessageFromCode(code));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~WebSocket()
|
||||
{
|
||||
Log($"Free socket with instanceId: {instanceId}");
|
||||
WebSocketManager.WebSocketFree(instanceId);
|
||||
}
|
||||
|
||||
public void ConnectAsync()
|
||||
{
|
||||
Log($"Connect with instanceId: {instanceId}");
|
||||
WebSocketManager.Add(this);
|
||||
int code = WebSocketManager.WebSocketConnect(instanceId);
|
||||
if (code < 0) HandleOnError(GetErrorMessageFromCode(code));
|
||||
}
|
||||
|
||||
public void CloseAsync()
|
||||
{
|
||||
Log($"Close with instanceId: {instanceId}");
|
||||
int code = WebSocketManager.WebSocketClose(instanceId, (int)CloseStatusCode.Normal, "Normal Closure");
|
||||
if (code < 0) HandleOnError(GetErrorMessageFromCode(code));
|
||||
}
|
||||
|
||||
public void SendAsync(string text)
|
||||
{
|
||||
Log($"Send, type: {Opcode.Text}, size: {text.Length}");
|
||||
int code = WebSocketManager.WebSocketSendStr(instanceId, text);
|
||||
if (code < 0) HandleOnError(GetErrorMessageFromCode(code));
|
||||
}
|
||||
|
||||
public void SendAsync(byte[] data)
|
||||
{
|
||||
Log($"Send, type: {Opcode.Binary}, size: {data.Length}");
|
||||
int code = WebSocketManager.WebSocketSend(instanceId, data, data.Length);
|
||||
if (code < 0) HandleOnError(GetErrorMessageFromCode(code));
|
||||
}
|
||||
|
||||
internal void HandleOnOpen()
|
||||
{
|
||||
Log("OnOpen");
|
||||
OnOpen?.Invoke(this, new OpenEventArgs());
|
||||
}
|
||||
|
||||
internal void HandleOnMessage(byte[] rawData)
|
||||
{
|
||||
Log($"OnMessage, type: {Opcode.Binary}, size: {rawData.Length}");
|
||||
OnMessage?.Invoke(this, new MessageEventArgs(Opcode.Binary, rawData));
|
||||
}
|
||||
|
||||
internal void HandleOnMessageStr(string data)
|
||||
{
|
||||
Log($"OnMessage, type: {Opcode.Text}, size: {data.Length}");
|
||||
OnMessage?.Invoke(this, new MessageEventArgs(Opcode.Text, data));
|
||||
}
|
||||
|
||||
internal void HandleOnClose(ushort code, string reason)
|
||||
{
|
||||
Log($"OnClose, code: {code}, reason: {reason}");
|
||||
OnClose?.Invoke(this, new CloseEventArgs(code, reason));
|
||||
WebSocketManager.Remove(instanceId);
|
||||
}
|
||||
|
||||
internal void HandleOnError(string msg)
|
||||
{
|
||||
Log("OnError, error: " + msg);
|
||||
OnError?.Invoke(this, new ErrorEventArgs(msg));
|
||||
}
|
||||
|
||||
internal static string GetErrorMessageFromCode(int errorCode)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case -1: return "WebSocket instance not found.";
|
||||
case -2: return "WebSocket is already connected or in connecting state.";
|
||||
case -3: return "WebSocket is not connected.";
|
||||
case -4: return "WebSocket is already closing.";
|
||||
case -5: return "WebSocket is already closed.";
|
||||
case -6: return "WebSocket is not in open state.";
|
||||
case -7: return "Cannot close WebSocket. An invalid code was specified or reason is too long.";
|
||||
default: return $"Unknown error code {errorCode}.";
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_WEB_SOCKET_LOG")]
|
||||
static void Log(string msg)
|
||||
{
|
||||
UnityEngine.Debug.Log($"[UnityWebSocket]" +
|
||||
$"[{DateTime.Now.TimeOfDay}]" +
|
||||
$" {msg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74a5b3c22251243d2a2f33e74741559d
|
||||
timeCreated: 1466578513
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#if !UNITY_EDITOR && UNITY_WEBGL
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using AOT;
|
||||
|
||||
namespace UnityWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Class providing static access methods to work with JSLIB WebSocket
|
||||
/// </summary>
|
||||
internal static class WebSocketManager
|
||||
{
|
||||
/* Map of websocket instances */
|
||||
private static Dictionary<int, WebSocket> sockets = new Dictionary<int, WebSocket>();
|
||||
|
||||
/* Delegates */
|
||||
public delegate void OnOpenCallback(int instanceId);
|
||||
public delegate void OnMessageCallback(int instanceId, IntPtr msgPtr, int msgSize);
|
||||
public delegate void OnMessageStrCallback(int instanceId, IntPtr msgStrPtr);
|
||||
public delegate void OnErrorCallback(int instanceId, IntPtr errorPtr);
|
||||
public delegate void OnCloseCallback(int instanceId, int closeCode, IntPtr reasonPtr);
|
||||
|
||||
/* WebSocket JSLIB functions */
|
||||
[DllImport("__Internal")]
|
||||
public static extern int WebSocketConnect(int instanceId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern int WebSocketClose(int instanceId, int code, string reason);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern int WebSocketSend(int instanceId, byte[] dataPtr, int dataLength);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern int WebSocketSendStr(int instanceId, string dataPtr);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern int WebSocketGetState(int instanceId);
|
||||
|
||||
/* WebSocket JSLIB callback setters and other functions */
|
||||
[DllImport("__Internal")]
|
||||
public static extern int WebSocketAllocate(string url);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern int WebSocketAddSubProtocol(int instanceId, string protocol);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern void WebSocketFree(int instanceId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern void WebSocketSetOnOpen(OnOpenCallback callback);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern void WebSocketSetOnMessage(OnMessageCallback callback);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern void WebSocketSetOnMessageStr(OnMessageStrCallback callback);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern void WebSocketSetOnError(OnErrorCallback callback);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
public static extern void WebSocketSetOnClose(OnCloseCallback callback);
|
||||
|
||||
/* If callbacks was initialized and set */
|
||||
private static bool isInitialized = false;
|
||||
|
||||
/* Initialize WebSocket callbacks to JSLIB */
|
||||
private static void Initialize()
|
||||
{
|
||||
WebSocketSetOnOpen(DelegateOnOpenEvent);
|
||||
WebSocketSetOnMessage(DelegateOnMessageEvent);
|
||||
WebSocketSetOnMessageStr(DelegateOnMessageStrEvent);
|
||||
WebSocketSetOnError(DelegateOnErrorEvent);
|
||||
WebSocketSetOnClose(DelegateOnCloseEvent);
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(OnOpenCallback))]
|
||||
public static void DelegateOnOpenEvent(int instanceId)
|
||||
{
|
||||
if (sockets.TryGetValue(instanceId, out var socket))
|
||||
{
|
||||
socket.HandleOnOpen();
|
||||
}
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(OnMessageCallback))]
|
||||
public static void DelegateOnMessageEvent(int instanceId, IntPtr msgPtr, int msgSize)
|
||||
{
|
||||
if (sockets.TryGetValue(instanceId, out var socket))
|
||||
{
|
||||
var bytes = new byte[msgSize];
|
||||
Marshal.Copy(msgPtr, bytes, 0, msgSize);
|
||||
socket.HandleOnMessage(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(OnMessageCallback))]
|
||||
public static void DelegateOnMessageStrEvent(int instanceId, IntPtr msgStrPtr)
|
||||
{
|
||||
if (sockets.TryGetValue(instanceId, out var socket))
|
||||
{
|
||||
string msgStr = Marshal.PtrToStringAuto(msgStrPtr);
|
||||
socket.HandleOnMessageStr(msgStr);
|
||||
}
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(OnErrorCallback))]
|
||||
public static void DelegateOnErrorEvent(int instanceId, IntPtr errorPtr)
|
||||
{
|
||||
if (sockets.TryGetValue(instanceId, out var socket))
|
||||
{
|
||||
string errorMsg = Marshal.PtrToStringAuto(errorPtr);
|
||||
socket.HandleOnError(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(OnCloseCallback))]
|
||||
public static void DelegateOnCloseEvent(int instanceId, int closeCode, IntPtr reasonPtr)
|
||||
{
|
||||
if (sockets.TryGetValue(instanceId, out var socket))
|
||||
{
|
||||
string reason = Marshal.PtrToStringAuto(reasonPtr);
|
||||
socket.HandleOnClose((ushort)closeCode, reason);
|
||||
}
|
||||
}
|
||||
|
||||
internal static int AllocateInstance(string address)
|
||||
{
|
||||
if (!isInitialized) Initialize();
|
||||
return WebSocketAllocate(address);
|
||||
}
|
||||
|
||||
internal static void Add(WebSocket socket)
|
||||
{
|
||||
if (!sockets.ContainsKey(socket.instanceId))
|
||||
{
|
||||
sockets.Add(socket.instanceId, socket);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Remove(int instanceId)
|
||||
{
|
||||
if (sockets.ContainsKey(instanceId))
|
||||
{
|
||||
sockets.Remove(instanceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 246cdc66a1e2047148371a8e56e17d3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "UnityWebSocket.Runtime",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b65d8710c3b04373a41cbf6b777ee65
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user