提交
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6183796adc7be374c86975de4519da5c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,848 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AOT;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
public class MainThreadUtil : MonoBehaviour
|
||||
{
|
||||
public static MainThreadUtil Instance { get; private set; }
|
||||
public static SynchronizationContext synchronizationContext { get; private set; }
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
public static void Setup()
|
||||
{
|
||||
Instance = new GameObject("MainThreadUtil")
|
||||
.AddComponent<MainThreadUtil>();
|
||||
synchronizationContext = SynchronizationContext.Current;
|
||||
}
|
||||
|
||||
public static void Run(IEnumerator waitForUpdate)
|
||||
{
|
||||
synchronizationContext.Post(_ => Instance.StartCoroutine(
|
||||
waitForUpdate), null);
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public class WaitForUpdate : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public MainThreadAwaiter GetAwaiter()
|
||||
{
|
||||
var awaiter = new MainThreadAwaiter();
|
||||
MainThreadUtil.Run(CoroutineWrapper(this, awaiter));
|
||||
return awaiter;
|
||||
}
|
||||
|
||||
public class MainThreadAwaiter : INotifyCompletion
|
||||
{
|
||||
Action continuation;
|
||||
|
||||
public bool IsCompleted { get; set; }
|
||||
|
||||
public void GetResult() { }
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
IsCompleted = true;
|
||||
continuation?.Invoke();
|
||||
}
|
||||
|
||||
void INotifyCompletion.OnCompleted(Action continuation)
|
||||
{
|
||||
this.continuation = continuation;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerator CoroutineWrapper(IEnumerator theWorker, MainThreadAwaiter awaiter)
|
||||
{
|
||||
yield return theWorker;
|
||||
awaiter.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
namespace NativeWebSocket
|
||||
{
|
||||
public delegate void WebSocketOpenEventHandler();
|
||||
public delegate void WebSocketMessageEventHandler(byte[] data);
|
||||
public delegate void WebSocketErrorEventHandler(string errorMsg);
|
||||
public delegate void WebSocketCloseEventHandler(WebSocketCloseCode closeCode);
|
||||
|
||||
public enum WebSocketCloseCode
|
||||
{
|
||||
/* Do NOT use NotSet - it's only purpose is to indicate that the close code cannot be parsed. */
|
||||
NotSet = 0,
|
||||
Normal = 1000,
|
||||
Away = 1001,
|
||||
ProtocolError = 1002,
|
||||
UnsupportedData = 1003,
|
||||
Undefined = 1004,
|
||||
NoStatus = 1005,
|
||||
Abnormal = 1006,
|
||||
InvalidData = 1007,
|
||||
PolicyViolation = 1008,
|
||||
TooBig = 1009,
|
||||
MandatoryExtension = 1010,
|
||||
ServerError = 1011,
|
||||
TlsHandshakeFailure = 1015
|
||||
}
|
||||
|
||||
public enum WebSocketState
|
||||
{
|
||||
Connecting,
|
||||
Open,
|
||||
Closing,
|
||||
Closed
|
||||
}
|
||||
|
||||
public interface IWebSocket
|
||||
{
|
||||
event WebSocketOpenEventHandler OnOpen;
|
||||
event WebSocketMessageEventHandler OnMessage;
|
||||
event WebSocketErrorEventHandler OnError;
|
||||
event WebSocketCloseEventHandler OnClose;
|
||||
|
||||
WebSocketState State { get; }
|
||||
}
|
||||
|
||||
public static class WebSocketHelpers
|
||||
{
|
||||
public static WebSocketCloseCode ParseCloseCodeEnum(int closeCode)
|
||||
{
|
||||
|
||||
if (WebSocketCloseCode.IsDefined(typeof(WebSocketCloseCode), closeCode))
|
||||
{
|
||||
return (WebSocketCloseCode)closeCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
return WebSocketCloseCode.Undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static WebSocketException GetErrorMessageFromCode(int errorCode, Exception inner)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case -1:
|
||||
return new WebSocketUnexpectedException("WebSocket instance not found.", inner);
|
||||
case -2:
|
||||
return new WebSocketInvalidStateException("WebSocket is already connected or in connecting state.", inner);
|
||||
case -3:
|
||||
return new WebSocketInvalidStateException("WebSocket is not connected.", inner);
|
||||
case -4:
|
||||
return new WebSocketInvalidStateException("WebSocket is already closing.", inner);
|
||||
case -5:
|
||||
return new WebSocketInvalidStateException("WebSocket is already closed.", inner);
|
||||
case -6:
|
||||
return new WebSocketInvalidStateException("WebSocket is not in open state.", inner);
|
||||
case -7:
|
||||
return new WebSocketInvalidArgumentException("Cannot close WebSocket. An invalid code was specified or reason is too long.", inner);
|
||||
default:
|
||||
return new WebSocketUnexpectedException("Unknown error.", inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class WebSocketException : Exception
|
||||
{
|
||||
public WebSocketException() { }
|
||||
public WebSocketException(string message) : base(message) { }
|
||||
public WebSocketException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WebSocketUnexpectedException : WebSocketException
|
||||
{
|
||||
public WebSocketUnexpectedException() { }
|
||||
public WebSocketUnexpectedException(string message) : base(message) { }
|
||||
public WebSocketUnexpectedException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WebSocketInvalidArgumentException : WebSocketException
|
||||
{
|
||||
public WebSocketInvalidArgumentException() { }
|
||||
public WebSocketInvalidArgumentException(string message) : base(message) { }
|
||||
public WebSocketInvalidArgumentException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WebSocketInvalidStateException : WebSocketException
|
||||
{
|
||||
public WebSocketInvalidStateException() { }
|
||||
public WebSocketInvalidStateException(string message) : base(message) { }
|
||||
public WebSocketInvalidStateException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WaitForBackgroundThread
|
||||
{
|
||||
public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter()
|
||||
{
|
||||
return Task.Run(() => { }).ConfigureAwait(false).GetAwaiter();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket class bound to JSLIB.
|
||||
/// </summary>
|
||||
public class WebSocket : IWebSocket {
|
||||
|
||||
/* 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 WebSocketSendText (int instanceId, string message);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketGetState (int instanceId);
|
||||
|
||||
protected int instanceId;
|
||||
|
||||
public event WebSocketOpenEventHandler OnOpen;
|
||||
public event WebSocketMessageEventHandler OnMessage;
|
||||
public event WebSocketErrorEventHandler OnError;
|
||||
public event WebSocketCloseEventHandler OnClose;
|
||||
|
||||
public WebSocket (string url, Dictionary<string, string> headers = null) {
|
||||
if (!WebSocketFactory.isInitialized) {
|
||||
WebSocketFactory.Initialize ();
|
||||
}
|
||||
|
||||
int instanceId = WebSocketFactory.WebSocketAllocate (url);
|
||||
WebSocketFactory.instances.Add (instanceId, this);
|
||||
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
public WebSocket (string url, string subprotocol, Dictionary<string, string> headers = null) {
|
||||
if (!WebSocketFactory.isInitialized) {
|
||||
WebSocketFactory.Initialize ();
|
||||
}
|
||||
|
||||
int instanceId = WebSocketFactory.WebSocketAllocate (url);
|
||||
WebSocketFactory.instances.Add (instanceId, this);
|
||||
|
||||
WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol);
|
||||
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
public WebSocket (string url, List<string> subprotocols, Dictionary<string, string> headers = null) {
|
||||
if (!WebSocketFactory.isInitialized) {
|
||||
WebSocketFactory.Initialize ();
|
||||
}
|
||||
|
||||
int instanceId = WebSocketFactory.WebSocketAllocate (url);
|
||||
WebSocketFactory.instances.Add (instanceId, this);
|
||||
|
||||
foreach (string subprotocol in subprotocols) {
|
||||
WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol);
|
||||
}
|
||||
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
~WebSocket () {
|
||||
WebSocketFactory.HandleInstanceDestroy (this.instanceId);
|
||||
}
|
||||
|
||||
public int GetInstanceId () {
|
||||
return this.instanceId;
|
||||
}
|
||||
|
||||
public Task Connect () {
|
||||
int ret = WebSocketConnect (this.instanceId);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void CancelConnection () {
|
||||
if (State == WebSocketState.Open)
|
||||
Close (WebSocketCloseCode.Abnormal);
|
||||
}
|
||||
|
||||
public Task Close (WebSocketCloseCode code = WebSocketCloseCode.Normal, string reason = null) {
|
||||
int ret = WebSocketClose (this.instanceId, (int) code, reason);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Send (byte[] data) {
|
||||
int ret = WebSocketSend (this.instanceId, data, data.Length);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SendText (string message) {
|
||||
int ret = WebSocketSendText (this.instanceId, message);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public WebSocketState State {
|
||||
get {
|
||||
int state = WebSocketGetState (this.instanceId);
|
||||
|
||||
if (state < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (state, null);
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
return WebSocketState.Connecting;
|
||||
|
||||
case 1:
|
||||
return WebSocketState.Open;
|
||||
|
||||
case 2:
|
||||
return WebSocketState.Closing;
|
||||
|
||||
case 3:
|
||||
return WebSocketState.Closed;
|
||||
|
||||
default:
|
||||
return WebSocketState.Closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DelegateOnOpenEvent () {
|
||||
this.OnOpen?.Invoke ();
|
||||
}
|
||||
|
||||
public void DelegateOnMessageEvent (byte[] data) {
|
||||
this.OnMessage?.Invoke (data);
|
||||
}
|
||||
|
||||
public void DelegateOnErrorEvent (string errorMsg) {
|
||||
this.OnError?.Invoke (errorMsg);
|
||||
}
|
||||
|
||||
public void DelegateOnCloseEvent (int closeCode) {
|
||||
this.OnClose?.Invoke (WebSocketHelpers.ParseCloseCodeEnum (closeCode));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
public class WebSocket : IWebSocket
|
||||
{
|
||||
public event WebSocketOpenEventHandler OnOpen;
|
||||
public event WebSocketMessageEventHandler OnMessage;
|
||||
public event WebSocketErrorEventHandler OnError;
|
||||
public event WebSocketCloseEventHandler OnClose;
|
||||
|
||||
private Uri uri;
|
||||
private Dictionary<string, string> headers;
|
||||
private List<string> subprotocols;
|
||||
private ClientWebSocket m_Socket = new ClientWebSocket();
|
||||
|
||||
private CancellationTokenSource m_TokenSource;
|
||||
private CancellationToken m_CancellationToken;
|
||||
|
||||
private readonly object OutgoingMessageLock = new object();
|
||||
private readonly object IncomingMessageLock = new object();
|
||||
|
||||
private bool isSending = false;
|
||||
private List<ArraySegment<byte>> sendBytesQueue = new List<ArraySegment<byte>>();
|
||||
private List<ArraySegment<byte>> sendTextQueue = new List<ArraySegment<byte>>();
|
||||
|
||||
public WebSocket(string url, Dictionary<string, string> headers = null)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
|
||||
if (headers == null)
|
||||
{
|
||||
this.headers = new Dictionary<string, string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
subprotocols = new List<string>();
|
||||
|
||||
string protocol = uri.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public WebSocket(string url, string subprotocol, Dictionary<string, string> headers = null)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
|
||||
if (headers == null)
|
||||
{
|
||||
this.headers = new Dictionary<string, string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
subprotocols = new List<string> {subprotocol};
|
||||
|
||||
string protocol = uri.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public WebSocket(string url, List<string> subprotocols, Dictionary<string, string> headers = null)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
|
||||
if (headers == null)
|
||||
{
|
||||
this.headers = new Dictionary<string, string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
this.subprotocols = subprotocols;
|
||||
|
||||
string protocol = uri.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public void CancelConnection()
|
||||
{
|
||||
m_TokenSource?.Cancel();
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_TokenSource = new CancellationTokenSource();
|
||||
m_CancellationToken = m_TokenSource.Token;
|
||||
|
||||
m_Socket = new ClientWebSocket();
|
||||
|
||||
foreach (var header in headers)
|
||||
{
|
||||
m_Socket.Options.SetRequestHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
foreach (string subprotocol in subprotocols) {
|
||||
m_Socket.Options.AddSubProtocol(subprotocol);
|
||||
}
|
||||
|
||||
await m_Socket.ConnectAsync(uri, m_CancellationToken);
|
||||
OnOpen?.Invoke();
|
||||
|
||||
await Receive();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(ex.Message);
|
||||
OnClose?.Invoke(WebSocketCloseCode.Abnormal);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (m_Socket != null)
|
||||
{
|
||||
m_TokenSource.Cancel();
|
||||
m_Socket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketState State
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (m_Socket.State)
|
||||
{
|
||||
case System.Net.WebSockets.WebSocketState.Connecting:
|
||||
return WebSocketState.Connecting;
|
||||
|
||||
case System.Net.WebSockets.WebSocketState.Open:
|
||||
return WebSocketState.Open;
|
||||
|
||||
case System.Net.WebSockets.WebSocketState.CloseSent:
|
||||
case System.Net.WebSockets.WebSocketState.CloseReceived:
|
||||
return WebSocketState.Closing;
|
||||
|
||||
case System.Net.WebSockets.WebSocketState.Closed:
|
||||
return WebSocketState.Closed;
|
||||
|
||||
default:
|
||||
return WebSocketState.Closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Send(byte[] bytes)
|
||||
{
|
||||
// return m_Socket.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
return SendMessage(sendBytesQueue, WebSocketMessageType.Binary, new ArraySegment<byte>(bytes));
|
||||
}
|
||||
|
||||
public Task SendText(string message)
|
||||
{
|
||||
var encoded = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
// m_Socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
return SendMessage(sendTextQueue, WebSocketMessageType.Text, new ArraySegment<byte>(encoded, 0, encoded.Length));
|
||||
}
|
||||
|
||||
private async Task SendMessage(List<ArraySegment<byte>> queue, WebSocketMessageType messageType, ArraySegment<byte> buffer)
|
||||
{
|
||||
// Return control to the calling method immediately.
|
||||
// await Task.Yield ();
|
||||
|
||||
// Make sure we have data.
|
||||
if (buffer.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The state of the connection is contained in the context Items dictionary.
|
||||
bool sending;
|
||||
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
sending = isSending;
|
||||
|
||||
// If not, we are now.
|
||||
if (!isSending)
|
||||
{
|
||||
isSending = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sending)
|
||||
{
|
||||
// Lock with a timeout, just in case.
|
||||
if (!Monitor.TryEnter(m_Socket, 1000))
|
||||
{
|
||||
// If we couldn't obtain exclusive access to the socket in one second, something is wrong.
|
||||
await m_Socket.CloseAsync(WebSocketCloseStatus.InternalServerError, string.Empty, m_CancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the message synchronously.
|
||||
var t = m_Socket.SendAsync(buffer, messageType, true, m_CancellationToken);
|
||||
t.Wait(m_CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(m_Socket);
|
||||
}
|
||||
|
||||
// Note that we've finished sending.
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
isSending = false;
|
||||
}
|
||||
|
||||
// Handle any queued messages.
|
||||
await HandleQueue(queue, messageType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the message to the queue.
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
queue.Add(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleQueue(List<ArraySegment<byte>> queue, WebSocketMessageType messageType)
|
||||
{
|
||||
var buffer = new ArraySegment<byte>();
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
// Check for an item in the queue.
|
||||
if (queue.Count > 0)
|
||||
{
|
||||
// Pull it off the top.
|
||||
buffer = queue[0];
|
||||
queue.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Send that message.
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
await SendMessage(queue, messageType, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private List<byte[]> m_MessageList = new List<byte[]>();
|
||||
|
||||
// simple dispatcher for queued messages.
|
||||
public void DispatchMessageQueue()
|
||||
{
|
||||
if (m_MessageList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<byte[]> messageListCopy;
|
||||
|
||||
lock (IncomingMessageLock)
|
||||
{
|
||||
messageListCopy = new List<byte[]>(m_MessageList);
|
||||
m_MessageList.Clear();
|
||||
}
|
||||
|
||||
var len = messageListCopy.Count;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
OnMessage?.Invoke(messageListCopy[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Receive()
|
||||
{
|
||||
WebSocketCloseCode closeCode = WebSocketCloseCode.Abnormal;
|
||||
await new WaitForBackgroundThread();
|
||||
|
||||
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[8192]);
|
||||
try
|
||||
{
|
||||
while (m_Socket.State == System.Net.WebSockets.WebSocketState.Open)
|
||||
{
|
||||
WebSocketReceiveResult result = null;
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
do
|
||||
{
|
||||
result = await m_Socket.ReceiveAsync(buffer, m_CancellationToken);
|
||||
ms.Write(buffer.Array, buffer.Offset, result.Count);
|
||||
}
|
||||
while (!result.EndOfMessage);
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
lock (IncomingMessageLock)
|
||||
{
|
||||
m_MessageList.Add(ms.ToArray());
|
||||
}
|
||||
|
||||
//using (var reader = new StreamReader(ms, Encoding.UTF8))
|
||||
//{
|
||||
// string message = reader.ReadToEnd();
|
||||
// OnMessage?.Invoke(this, new MessageEventArgs(message));
|
||||
//}
|
||||
}
|
||||
else if (result.MessageType == WebSocketMessageType.Binary)
|
||||
{
|
||||
lock (IncomingMessageLock)
|
||||
{
|
||||
m_MessageList.Add(ms.ToArray());
|
||||
}
|
||||
}
|
||||
else if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await Close();
|
||||
closeCode = WebSocketHelpers.ParseCloseCodeEnum((int)result.CloseStatus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
m_TokenSource.Cancel();
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new WaitForUpdate();
|
||||
OnClose?.Invoke(closeCode);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Close()
|
||||
{
|
||||
if (State == WebSocketState.Open)
|
||||
{
|
||||
await m_Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, m_CancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
///
|
||||
/// Factory
|
||||
///
|
||||
|
||||
/// <summary>
|
||||
/// Class providing static access methods to work with JSLIB WebSocket or WebSocketSharp interface
|
||||
/// </summary>
|
||||
public static class WebSocketFactory
|
||||
{
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
/* Map of websocket instances */
|
||||
public static Dictionary<Int32, WebSocket> instances = new Dictionary<Int32, WebSocket> ();
|
||||
|
||||
/* Delegates */
|
||||
public delegate void OnOpenCallback (int instanceId);
|
||||
public delegate void OnMessageCallback (int instanceId, System.IntPtr msgPtr, int msgSize);
|
||||
public delegate void OnErrorCallback (int instanceId, System.IntPtr errorPtr);
|
||||
public delegate void OnCloseCallback (int instanceId, int closeCode);
|
||||
|
||||
/* 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 subprotocol);
|
||||
|
||||
[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 WebSocketSetOnError (OnErrorCallback callback);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern void WebSocketSetOnClose (OnCloseCallback callback);
|
||||
|
||||
/* If callbacks was initialized and set */
|
||||
public static bool isInitialized = false;
|
||||
|
||||
/*
|
||||
* Initialize WebSocket callbacks to JSLIB
|
||||
*/
|
||||
public static void Initialize () {
|
||||
|
||||
WebSocketSetOnOpen (DelegateOnOpenEvent);
|
||||
WebSocketSetOnMessage (DelegateOnMessageEvent);
|
||||
WebSocketSetOnError (DelegateOnErrorEvent);
|
||||
WebSocketSetOnClose (DelegateOnCloseEvent);
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when instance is destroyed (by destructor)
|
||||
/// Method removes instance from map and free it in JSLIB implementation
|
||||
/// </summary>
|
||||
/// <param name="instanceId">Instance identifier.</param>
|
||||
public static void HandleInstanceDestroy (int instanceId) {
|
||||
|
||||
instances.Remove (instanceId);
|
||||
WebSocketFree (instanceId);
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnOpenCallback))]
|
||||
public static void DelegateOnOpenEvent (int instanceId) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
instanceRef.DelegateOnOpenEvent ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnMessageCallback))]
|
||||
public static void DelegateOnMessageEvent (int instanceId, System.IntPtr msgPtr, int msgSize) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
byte[] msg = new byte[msgSize];
|
||||
Marshal.Copy (msgPtr, msg, 0, msgSize);
|
||||
|
||||
instanceRef.DelegateOnMessageEvent (msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnErrorCallback))]
|
||||
public static void DelegateOnErrorEvent (int instanceId, System.IntPtr errorPtr) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
|
||||
string errorMsg = Marshal.PtrToStringAuto (errorPtr);
|
||||
instanceRef.DelegateOnErrorEvent (errorMsg);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnCloseCallback))]
|
||||
public static void DelegateOnCloseEvent (int instanceId, int closeCode) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
instanceRef.DelegateOnCloseEvent (closeCode);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Create WebSocket client instance
|
||||
/// </summary>
|
||||
/// <returns>The WebSocket instance.</returns>
|
||||
/// <param name="url">WebSocket valid URL.</param>
|
||||
public static WebSocket CreateInstance(string url)
|
||||
{
|
||||
return new WebSocket(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b775e14fc5a58124e8dbe558d81b5824
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,333 @@
|
||||
|
||||
var LibraryWebSocket = {
|
||||
$webSocketState: {
|
||||
/*
|
||||
* Map of instances
|
||||
*
|
||||
* Instance structure:
|
||||
* {
|
||||
* url: string,
|
||||
* ws: WebSocket
|
||||
* }
|
||||
*/
|
||||
instances: {},
|
||||
|
||||
/* Last instance ID */
|
||||
lastId: 0,
|
||||
|
||||
/* Event listeners */
|
||||
onOpen: null,
|
||||
onMesssage: null,
|
||||
onError: null,
|
||||
onClose: null,
|
||||
|
||||
/* Debug mode */
|
||||
debug: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onOpen callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnOpen: function(callback) {
|
||||
|
||||
webSocketState.onOpen = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onMessage callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnMessage: function(callback) {
|
||||
|
||||
webSocketState.onMessage = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onError callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnError: function(callback) {
|
||||
|
||||
webSocketState.onError = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onClose callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnClose: function(callback) {
|
||||
|
||||
webSocketState.onClose = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Allocate new WebSocket instance struct
|
||||
*
|
||||
* @param url Server URL
|
||||
*/
|
||||
WebSocketAllocate: function(url) {
|
||||
|
||||
var urlStr = UTF8ToString(url);
|
||||
var id = webSocketState.lastId++;
|
||||
|
||||
webSocketState.instances[id] = {
|
||||
subprotocols: [],
|
||||
url: urlStr,
|
||||
ws: null
|
||||
};
|
||||
|
||||
return id;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Add subprotocol to instance
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param subprotocol Subprotocol name to add to instance
|
||||
*/
|
||||
WebSocketAddSubProtocol: function(instanceId, subprotocol) {
|
||||
|
||||
var subprotocolStr = UTF8ToString(subprotocol);
|
||||
webSocketState.instances[instanceId].subprotocols.push(subprotocolStr);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 = webSocketState.instances[instanceId];
|
||||
|
||||
if (!instance) return 0;
|
||||
|
||||
// Close if not closed
|
||||
if (instance.ws && instance.ws.readyState < 2)
|
||||
instance.ws.close();
|
||||
|
||||
// Remove reference
|
||||
delete webSocketState.instances[instanceId];
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Connect WebSocket to the server
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketConnect: function(instanceId) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (instance.ws !== null)
|
||||
return -2;
|
||||
|
||||
instance.ws = new WebSocket(instance.url, instance.subprotocols);
|
||||
|
||||
instance.ws.binaryType = 'arraybuffer';
|
||||
|
||||
instance.ws.onopen = function() {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Connected.");
|
||||
|
||||
if (webSocketState.onOpen)
|
||||
Module.dynCall_vi(webSocketState.onOpen, instanceId);
|
||||
|
||||
};
|
||||
|
||||
instance.ws.onmessage = function(ev) {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Received message:", ev.data);
|
||||
|
||||
if (webSocketState.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(webSocketState.onMessage, instanceId, buffer, dataBuffer.length);
|
||||
} finally {
|
||||
_free(buffer);
|
||||
}
|
||||
|
||||
} else {
|
||||
var dataBuffer = (new TextEncoder()).encode(ev.data);
|
||||
|
||||
var buffer = _malloc(dataBuffer.length);
|
||||
HEAPU8.set(dataBuffer, buffer);
|
||||
|
||||
try {
|
||||
Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length);
|
||||
} finally {
|
||||
_free(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
instance.ws.onerror = function(ev) {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Error occured.");
|
||||
|
||||
if (webSocketState.onError) {
|
||||
|
||||
var msg = "WebSocket error.";
|
||||
var length = lengthBytesUTF8(msg) + 1;
|
||||
var buffer = _malloc(length);
|
||||
stringToUTF8(msg, buffer, length);
|
||||
|
||||
try {
|
||||
Module.dynCall_vii(webSocketState.onError, instanceId, buffer);
|
||||
} finally {
|
||||
_free(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
instance.ws.onclose = function(ev) {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Closed.");
|
||||
|
||||
if (webSocketState.onClose)
|
||||
Module.dynCall_vii(webSocketState.onClose, instanceId, ev.code);
|
||||
|
||||
delete instance.ws;
|
||||
|
||||
};
|
||||
|
||||
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 = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (!instance.ws)
|
||||
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 = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (!instance.ws)
|
||||
return -3;
|
||||
|
||||
if (instance.ws.readyState !== 1)
|
||||
return -6;
|
||||
|
||||
instance.ws.send(HEAPU8.buffer.slice(bufferPtr, bufferPtr + length));
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Send text message over WebSocket
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param bufferPtr Pointer to the message buffer
|
||||
* @param length Length of the message in the buffer
|
||||
*/
|
||||
WebSocketSendText: function(instanceId, message) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (!instance.ws)
|
||||
return -3;
|
||||
|
||||
if (instance.ws.readyState !== 1)
|
||||
return -6;
|
||||
|
||||
instance.ws.send(UTF8ToString(message));
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Return WebSocket readyState
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketGetState: function(instanceId) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (instance.ws)
|
||||
return instance.ws.readyState;
|
||||
else
|
||||
return 3;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
autoAddDeps(LibraryWebSocket, '$webSocketState');
|
||||
mergeInto(LibraryManager.library, LibraryWebSocket);
|
||||
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4e1195d1a5508b419c5fc7120cba0ed
|
||||
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:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "endel.nativewebsocket"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3cf245a6f30d9740a037cf5cd3dfb43
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c66c722e6468770478c807f30bd55575
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "com.endel.nativewebsocket",
|
||||
"version": "1.1.4",
|
||||
"description": "WebSocket client for Unity - with no external dependencies (WebGL, Native, Android, iOS, UWP).",
|
||||
"license": "Apache 2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/endel/NativeWebSocket.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Endel Dreyer",
|
||||
"email": "endel.dreyer@gmail.com",
|
||||
"url": "https://github.com/endel/NativeWebSocket"
|
||||
},
|
||||
"keywords": [
|
||||
"websocket",
|
||||
"websockets",
|
||||
"native websocket",
|
||||
"native websockets"
|
||||
],
|
||||
"displayName": "Native WebSockets",
|
||||
"unity": "2019.1",
|
||||
"dependencies": {},
|
||||
"samples": [
|
||||
{
|
||||
"displayName": "Example",
|
||||
"description": "WebSocket Example",
|
||||
"path": "Samples~/WebSocketExample"
|
||||
}
|
||||
],
|
||||
"_fingerprint": "c5101c07762251edc82caad9e8a39d51b1229c31"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de5c97ce7ae5a4a4b9da45566ae46222
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user