NBC修改

This commit is contained in:
bob
2025-07-03 14:16:18 +08:00
parent 4febfadd56
commit 800e96aac7
2083 changed files with 60081 additions and 2942 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b3a2a8f55d4a47f599b1fa3ed612389
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29b987d07ba15434cb1744135a7a5416
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e34ee317292e4225a10427cc35f85ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 884e7db60b6444154b7200e0e436f2de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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.Closed"/>.
/// </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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 37ee2146eb8c34ffab8b081a632b05cf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b44eda173b4924081bab76ae9d1b0a9c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eeac0ef90273544ebbae046672caf362
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using System;
namespace UnityWebSocket
{
public class OpenEventArgs : EventArgs
{
internal OpenEventArgs()
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5fb6fd704bd4e4b8ba63cd0b28712955
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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.8.6";
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e268303c7a605e343b1b132e5559f01f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f6567ad13cb147a59f8af784f1c5f60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 396c66b333d624d539153070900bb73b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6c110a898ae8b0b41bcf4da49c2b0425
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,345 @@
#if !NET_LEGACY && (UNITY_EDITOR || !UNITY_WEBGL)
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.WebSockets;
using System.IO;
using System.Collections.Concurrent;
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;
private ConcurrentQueue<SendBuffer> sendQueue = new ConcurrentQueue<SendBuffer>();
private ConcurrentQueue<EventArgs> eventQueue = new ConcurrentQueue<EventArgs>();
private bool closeProcessing;
private CancellationTokenSource cts = null;
#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 (socket != null)
{
HandleError(new Exception("Socket is busy."));
return;
}
WebSocketManager.Instance.Add(this);
socket = new ClientWebSocket();
cts = new CancellationTokenSource();
// support sub protocols
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;
}
closeProcessing = true;
}
public void SendAsync(byte[] data, int offset, int len)
{
if (!isOpening) return;
var buffer = new SendBuffer(data, offset, len, WebSocketMessageType.Binary);
sendQueue.Enqueue(buffer);
}
public void SendAsync(byte[] data)
{
if (!isOpening) return;
var buffer = new SendBuffer(data, 0, data.Length, WebSocketMessageType.Binary);
sendQueue.Enqueue(buffer);
}
public void SendAsync(string text)
{
if (!isOpening) return;
var data = Encoding.UTF8.GetBytes(text);
var buffer = new SendBuffer(data, 0, data.Length, WebSocketMessageType.Text);
sendQueue.Enqueue(buffer);
}
#endregion
class SendBuffer
{
public int offset;
public int len;
public byte[] data;
public WebSocketMessageType type;
public SendBuffer(byte[] data, int offset, int len, WebSocketMessageType type)
{
this.offset = offset;
this.len = len;
this.data = data;
this.type = type;
}
}
private void CleanSendQueue()
{
while (sendQueue.TryDequeue(out var _)) ;
}
private void CleanEventQueue()
{
while (eventQueue.TryDequeue(out var _)) ;
}
private async Task ConnectTask()
{
Log("Connect Task Begin ...");
try
{
var uri = new Uri(Address);
await socket.ConnectAsync(uri, cts.Token);
}
catch (Exception e)
{
HandleError(e);
HandleClose((ushort)CloseStatusCode.Abnormal, e.Message);
return;
}
HandleOpen();
Log("Connect Task Success !");
StartReceiveTask();
StartSendTask();
}
private async void StartSendTask()
{
Log("Send Task Begin ...");
try
{
while (!closeProcessing && socket != null && cts != null && !cts.IsCancellationRequested)
{
while (!closeProcessing && sendQueue.Count > 0 && sendQueue.TryDequeue(out var buffer))
{
Log($"Send, type: {buffer.type}, size: {buffer.data.Length}, queue left: {sendQueue.Count}");
await socket.SendAsync(new ArraySegment<byte>(buffer.data,buffer.offset, buffer.len), buffer.type, true, cts.Token);
}
Thread.Sleep(3);
}
if (closeProcessing && socket != null && cts != null && !cts.IsCancellationRequested)
{
CleanSendQueue();
Log($"Close Send Begin ...");
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Normal Closure", cts.Token);
Log($"Close Send Success !");
}
}
catch (Exception e)
{
HandleError(e);
}
finally
{
closeProcessing = false;
}
Log("Send Task End !");
}
private async void StartReceiveTask()
{
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 && !cts.IsCancellationRequested)
{
var result = await socket.ReceiveAsync(segment, cts.Token);
ms.Write(segment.Array, 0, result.Count);
if (!result.EndOfMessage) continue;
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);
Log("Receive Task End !");
}
private void SocketDispose()
{
Log("Dispose");
WebSocketManager.Instance.Remove(this);
CleanSendQueue();
CleanEventQueue();
socket.Dispose();
socket = null;
cts.Dispose();
cts = null;
}
private void HandleOpen()
{
Log("OnOpen");
eventQueue.Enqueue(new OpenEventArgs());
}
private void HandleMessage(Opcode opcode, byte[] rawData)
{
Log($"OnMessage, type: {opcode}, size: {rawData.Length}");
eventQueue.Enqueue(new MessageEventArgs(opcode, rawData));
}
private void HandleClose(ushort code, string reason)
{
Log($"OnClose, code: {code}, reason: {reason}");
eventQueue.Enqueue(new CloseEventArgs(code, reason));
}
private void HandleError(Exception exception)
{
Log("OnError, error: " + exception.Message);
eventQueue.Enqueue(new ErrorEventArgs(exception.Message));
}
internal void Update()
{
while (eventQueue.Count > 0 && eventQueue.TryDequeue(out var e))
{
if (e is CloseEventArgs)
{
OnClose?.Invoke(this, e as CloseEventArgs);
SocketDispose();
break;
}
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);
}
}
}
internal void Abort()
{
Log("Abort");
if (cts != null)
{
cts.Cancel();
}
}
[System.Diagnostics.Conditional("UNITY_WEB_SOCKET_LOG")]
static void Log(string msg)
{
var time = DateTime.Now.ToString("HH:mm:ss.fff");
var thread = Thread.CurrentThread.ManagedThreadId;
UnityEngine.Debug.Log($"[{time}][UnityWebSocket][T-{thread:D3}] {msg}");
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d10f88a23641b4beb8df74460fb7f705
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
#if !NET_LEGACY && (UNITY_EDITOR || !UNITY_WEBGL)
using System.Collections.Generic;
using UnityEngine;
namespace UnityWebSocket
{
[DisallowMultipleComponent]
[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();
}
}
private void OnDisable()
{
SocketAbort();
}
private void SocketAbort()
{
for (int i = sockets.Count - 1; i >= 0; i--)
{
sockets[i].Abort();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99157fb5def394c83a9e5342036c92b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1fb37927ec1ce4def9c5e7cff883f9f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,154 @@
#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, 0, data.Length);
if (code < 0) HandleOnError(GetErrorMessageFromCode(code));
}
public void SendAsync(byte[] data, int offset, int len)
{
Log($"Send, type: {Opcode.Binary}, offset: {offset}, len: {len}, size: {data.Length}");
int code = WebSocketManager.WebSocketSend(instanceId, data, offset, len);
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.";
case -8: return "Not support buffer slice. ";
default: return $"Unknown error code {errorCode}.";
}
}
[System.Diagnostics.Conditional("UNITY_WEB_SOCKET_LOG")]
static void Log(string msg)
{
var time = DateTime.Now.ToString("HH:mm:ss.fff");
UnityEngine.Debug.Log($"[{time}][UnityWebSocket] {msg}");
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 74a5b3c22251243d2a2f33e74741559d
timeCreated: 1466578513
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,159 @@
#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 offset, int dataLength);
[DllImport("__Internal")]
public static extern int WebSocketSendStr(int instanceId, string data);
[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);
[DllImport("__Internal")]
public static extern void WebSocketSetSupport6000();
/* 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);
#if UNITY_6000_0_OR_NEWER
WebSocketSetSupport6000();
#endif
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(OnMessageStrCallback))]
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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 246cdc66a1e2047148371a8e56e17d3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: