- Initial commit
This commit is contained in:
262
Runtime/Implementations/WebSocketBaseImplementation.cs
Normal file
262
Runtime/Implementations/WebSocketBaseImplementation.cs
Normal file
@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Best.HTTP.Shared;
|
||||
using Best.HTTP.Shared.Extensions;
|
||||
using Best.HTTP.Shared.PlatformSupport.Memory;
|
||||
using Best.HTTP.Shared.Streams;
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
using Best.WebSockets.Implementations.Frames;
|
||||
#endif
|
||||
|
||||
namespace Best.WebSockets.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// States of the underlying implementation's state.
|
||||
/// </summary>
|
||||
public enum WebSocketStates : byte
|
||||
{
|
||||
Connecting = 0,
|
||||
Open = 1,
|
||||
Closing = 2,
|
||||
Closed = 3,
|
||||
Unknown
|
||||
};
|
||||
|
||||
public delegate void OnWebSocketOpenDelegate(WebSocket webSocket);
|
||||
public delegate void OnWebSocketMessageDelegate(WebSocket webSocket, string message);
|
||||
public delegate void OnWebSocketBinaryNoAllocDelegate(WebSocket webSocket, BufferSegment data);
|
||||
public delegate void OnWebSocketClosedDelegate(WebSocket webSocket, WebSocketStatusCodes code, string message);
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
public delegate void OnWebSocketIncompleteFrameDelegate(WebSocket webSocket, WebSocketFrameReader frame);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class for concrete websocket communication implementations.
|
||||
/// </summary>
|
||||
public abstract class WebSocketBaseImplementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Capacity of the RTT buffer where the latencies are kept.
|
||||
/// </summary>
|
||||
public static int RTTBufferCapacity = 5;
|
||||
|
||||
public const string Timing_Name = "Websocket";
|
||||
|
||||
public virtual WebSocketStates State { get; protected set; }
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
public virtual bool IsOpen { get; protected set; }
|
||||
|
||||
public virtual int BufferedAmount { get; protected set; }
|
||||
#else
|
||||
public bool IsOpen => this.State == WebSocketStates.Open;
|
||||
|
||||
public int BufferedAmount { get => this._bufferedAmount; }
|
||||
protected volatile int _bufferedAmount;
|
||||
|
||||
public HTTP.HTTPRequest InternalRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this._internalRequest == null)
|
||||
CreateInternalRequest();
|
||||
|
||||
return this._internalRequest;
|
||||
}
|
||||
}
|
||||
protected HTTP.HTTPRequest _internalRequest;
|
||||
|
||||
public virtual int Latency { get; protected set; }
|
||||
public virtual DateTime LastMessageReceived { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// A circular buffer to store the last N rtt times calculated by the pong messages.
|
||||
/// </summary>
|
||||
protected CircularBuffer<int> rtts = new CircularBuffer<int>(WebSocketBaseImplementation.RTTBufferCapacity);
|
||||
|
||||
/// <summary>
|
||||
/// When we sent out the last ping.
|
||||
/// </summary>
|
||||
protected DateTime lastPing = DateTime.MinValue;
|
||||
|
||||
protected bool waitingForPong = false;
|
||||
|
||||
protected List<WebSocketFrameReader> IncompleteFrames = new List<WebSocketFrameReader>();
|
||||
protected PeekableIncomingSegmentStream incomingSegmentStream = new PeekableIncomingSegmentStream();
|
||||
protected ConcurrentQueue<WebSocketFrameReader> CompletedFrames = new ConcurrentQueue<WebSocketFrameReader>();
|
||||
protected ConcurrentQueue<WebSocketFrame> frames = new ConcurrentQueue<WebSocketFrame>();
|
||||
|
||||
/// <summary>
|
||||
/// True if we sent out a Close message to the server
|
||||
/// </summary>
|
||||
internal volatile bool _closeSent;
|
||||
internal volatile bool _closeReceived;
|
||||
#endif
|
||||
|
||||
public WebSocket Parent { get; }
|
||||
public Uri Uri { get; protected set; }
|
||||
public string Origin { get; }
|
||||
public string Protocol { get; }
|
||||
|
||||
public WebSocketBaseImplementation(WebSocket parent, Uri uri, string origin, string protocol)
|
||||
{
|
||||
this.Parent = parent;
|
||||
this.Uri = uri;
|
||||
this.Origin = origin;
|
||||
this.Protocol = protocol;
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
this.LastMessageReceived = DateTime.MinValue;
|
||||
|
||||
// Set up some default values.
|
||||
this.Parent.PingFrequency = TimeSpan.FromMilliseconds(10_000);
|
||||
this.Parent.CloseAfterNoMessage = TimeSpan.FromSeconds(2);
|
||||
#endif
|
||||
}
|
||||
|
||||
public abstract void StartOpen();
|
||||
public abstract void StartClose(WebSocketStatusCodes code, string message);
|
||||
|
||||
public abstract void Send(string message);
|
||||
public abstract void Send(byte[] buffer);
|
||||
public abstract void Send(byte[] buffer, ulong offset, ulong count);
|
||||
public abstract void SendAsBinary(BufferSegment data);
|
||||
public abstract void SendAsText(BufferSegment data);
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
|
||||
protected void ParseExtensionResponse(HTTP.HTTPResponse resp)
|
||||
{
|
||||
if (this.Parent.Extensions != null)
|
||||
{
|
||||
for (int i = 0; i < this.Parent.Extensions.Length; ++i)
|
||||
{
|
||||
var ext = this.Parent.Extensions[i];
|
||||
|
||||
try
|
||||
{
|
||||
if (ext != null && !ext.ParseNegotiation(resp))
|
||||
this.Parent.Extensions[i] = null; // Keep extensions only that successfully negotiated
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("WebSocketBaseImplementation", "ParseNegotiation", ex, this.Parent.Context);
|
||||
|
||||
// Do not try to use a defective extension in the future
|
||||
this.Parent.Extensions[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void CreateInternalRequest();
|
||||
|
||||
/// <summary>
|
||||
/// It will send the given frame to the server.
|
||||
/// </summary>
|
||||
public abstract void Send(WebSocketFrame frame);
|
||||
|
||||
protected virtual void Cleanup()
|
||||
{
|
||||
for (int i = 0; i < this.IncompleteFrames.Count; ++i)
|
||||
{
|
||||
var frame = this.IncompleteFrames[i];
|
||||
BufferPool.Release(frame.Data);
|
||||
}
|
||||
this.IncompleteFrames.Clear();
|
||||
this.Parent.DisposeExtensions();
|
||||
}
|
||||
|
||||
protected int CalculateLatency()
|
||||
{
|
||||
if (this.rtts.Count == 0)
|
||||
return 0;
|
||||
|
||||
int sumLatency = 0;
|
||||
for (int i = 0; i < this.rtts.Count; ++i)
|
||||
sumLatency += this.rtts[i];
|
||||
|
||||
return sumLatency / this.rtts.Count;
|
||||
}
|
||||
|
||||
public static bool CanReadFullFrame(PeekableStream stream)
|
||||
{
|
||||
if (stream.Length < 2)
|
||||
return false;
|
||||
|
||||
stream.BeginPeek();
|
||||
|
||||
int headerLength = 2;
|
||||
|
||||
int header = stream.PeekByte();
|
||||
if (header == -1)
|
||||
return false;
|
||||
|
||||
int maskAndLength = stream.PeekByte();
|
||||
if (maskAndLength == -1)
|
||||
return false;
|
||||
|
||||
// The second byte is the Mask Bit and the length of the payload data
|
||||
var HasMask = (maskAndLength & 0x80) != 0;
|
||||
if (HasMask)
|
||||
throw new NotSupportedException("Server-sent frames must not be masked!");
|
||||
|
||||
// if 0-125, that is the payload length.
|
||||
int payloadLength = (int)(maskAndLength & 127);
|
||||
|
||||
// If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length.
|
||||
if (payloadLength == 126)
|
||||
{
|
||||
byte[] rawLen = BufferPool.Get(2, true);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
int data = stream.PeekByte();
|
||||
if (data < 0)
|
||||
return false;
|
||||
|
||||
rawLen[i] = (byte)data;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(rawLen, 0, 2);
|
||||
|
||||
payloadLength = (int)BitConverter.ToUInt16(rawLen, 0);
|
||||
headerLength += 2;
|
||||
|
||||
BufferPool.Release(rawLen);
|
||||
}
|
||||
else if (payloadLength == 127)
|
||||
{
|
||||
// If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the
|
||||
// most significant bit MUST be 0) are the payload length.
|
||||
|
||||
byte[] rawLen = BufferPool.Get(8, true);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int data = stream.PeekByte();
|
||||
if (data < 0)
|
||||
return false;
|
||||
|
||||
rawLen[i] = (byte)data;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(rawLen, 0, 8);
|
||||
|
||||
payloadLength = (int)BitConverter.ToUInt64(rawLen, 0);
|
||||
headerLength += 8;
|
||||
|
||||
BufferPool.Release(rawLen);
|
||||
}
|
||||
|
||||
return stream.Length >= (headerLength + payloadLength);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user