mirror of
https://github.com/quasar/Quasar.git
synced 2026-10-26 02:19:49 +08:00
413 lines
14 KiB
C#
413 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Windows.Forms;
|
|
using xServer.Core.Data;
|
|
using xServer.Core.NetSerializer;
|
|
using xServer.Core.Networking.Utilities;
|
|
using xServer.Core.Packets;
|
|
|
|
namespace xServer.Core.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;
|
|
if (handler != null)
|
|
{
|
|
handler(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)
|
|
{
|
|
var handler = ClientState;
|
|
|
|
if (!connected)
|
|
RemoveClient(c);
|
|
|
|
if (handler != null)
|
|
{
|
|
handler(this, c, connected);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when a packet is received by a client.
|
|
/// </summary>
|
|
public event ClientReadEventHandler ClientRead;
|
|
|
|
/// <summary>
|
|
/// Represents a method that will handle a packet 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 packet.</param>
|
|
/// <param name="packet">The packet that received by the client.</param>
|
|
public delegate void ClientReadEventHandler(Server s, Client c, IPacket packet);
|
|
|
|
/// <summary>
|
|
/// Fires an event that informs subscribers that a packet has been
|
|
/// received from the client.
|
|
/// </summary>
|
|
/// <param name="c">The client that has received the packet.</param>
|
|
/// <param name="packet">The packet that received by the client.</param>
|
|
private void OnClientRead(Client c, IPacket packet)
|
|
{
|
|
var handler = ClientRead;
|
|
if (handler != null)
|
|
{
|
|
handler(this, c, packet);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when a packet is sent by a client.
|
|
/// </summary>
|
|
public event ClientWriteEventHandler ClientWrite;
|
|
|
|
/// <summary>
|
|
/// Represents the method that will handle the sent packet by a client.
|
|
/// </summary>
|
|
/// <param name="s">The server, the client is connected to.</param>
|
|
/// <param name="c">The client that has sent the packet.</param>
|
|
/// <param name="packet">The packet that has been sent by the client.</param>
|
|
/// <param name="length">The length of the packet.</param>
|
|
/// <param name="rawData">The packet in raw bytes.</param>
|
|
public delegate void ClientWriteEventHandler(Server s, Client c, IPacket packet, long length, byte[] rawData);
|
|
|
|
/// <summary>
|
|
/// Fires an event that informs subscribers that the client has sent a packet.
|
|
/// </summary>
|
|
/// <param name="c">The client that has sent the packet.</param>
|
|
/// <param name="packet">The packet that has been sent by the client.</param>
|
|
/// <param name="length">The length of the packet.</param>
|
|
/// <param name="rawData">The packet in raw bytes.</param>
|
|
private void OnClientWrite(Client c, IPacket packet, long length, byte[] rawData)
|
|
{
|
|
var handler = ClientWrite;
|
|
if (handler != null)
|
|
{
|
|
handler(this, c, packet, length, rawData);
|
|
}
|
|
}
|
|
|
|
/// <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>
|
|
public int BUFFER_SIZE { get { return 1024 * 16; } } // 16KB
|
|
|
|
/// <summary>
|
|
/// The keep-alive time in ms.
|
|
/// </summary>
|
|
public uint KEEP_ALIVE_TIME { get { return 25000; } } // 25s
|
|
|
|
/// <summary>
|
|
/// The keep-alive interval in ms.
|
|
/// </summary>
|
|
public uint KEEP_ALIVE_INTERVAL { get { return 25000; } } // 25s
|
|
|
|
/// <summary>
|
|
/// The header size in bytes.
|
|
/// </summary>
|
|
public int HEADER_SIZE { get { return 4; } } // 4B
|
|
|
|
/// <summary>
|
|
/// The maximum size of a packet in bytes.
|
|
/// </summary>
|
|
public int MAX_PACKET_SIZE { get { return (1024 * 1024) * 5; } } // 5MB
|
|
|
|
/// <summary>
|
|
/// The buffer manager to handle the receive buffers for the clients.
|
|
/// </summary>
|
|
public PooledBufferManager BufferManager { get; private set; }
|
|
|
|
/// <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>
|
|
/// The packet serializer.
|
|
/// </summary>
|
|
public Serializer Serializer { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// Handle of the Server Socket.
|
|
/// </summary>
|
|
private Socket _handle;
|
|
|
|
/// <summary>
|
|
/// The event to accept new connections asynchronously.
|
|
/// </summary>
|
|
private SocketAsyncEventArgs _item;
|
|
|
|
/// <summary>
|
|
/// List of the clients connected to the server.
|
|
/// </summary>
|
|
private List<Client> _clients;
|
|
|
|
/// <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 variables.
|
|
/// </summary>
|
|
protected Server()
|
|
{
|
|
_clients = new List<Client>();
|
|
BufferManager = new PooledBufferManager(BUFFER_SIZE, 1) { ClearOnReturn = false };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begins listening for clients.
|
|
/// </summary>
|
|
/// <param name="port">Port to listen for clients on.</param>
|
|
public void Listen(ushort port)
|
|
{
|
|
this.Port = port;
|
|
try
|
|
{
|
|
if (!Listening)
|
|
{
|
|
if (_handle != null)
|
|
{
|
|
_handle.Close();
|
|
_handle = null;
|
|
}
|
|
|
|
_handle = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
_handle.Bind(new IPEndPoint(IPAddress.Any, port));
|
|
_handle.Listen(1000);
|
|
|
|
ProcessingDisconnect = false;
|
|
|
|
OnServerState(true);
|
|
|
|
if (_item != null)
|
|
{
|
|
_item.Dispose();
|
|
_item = null;
|
|
}
|
|
|
|
_item = new SocketAsyncEventArgs();
|
|
_item.Completed += AcceptClient;
|
|
|
|
if (!_handle.AcceptAsync(_item))
|
|
AcceptClient(null, _item);
|
|
}
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
if (ex.ErrorCode == 10048)
|
|
{
|
|
MessageBox.Show("The port is already in use.", "Listen Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show(
|
|
string.Format(
|
|
"An unexpected socket error occurred: {0}\n\nError Code: {1}\n\nPlease report this as fast as possible here:\n{2}/issues",
|
|
ex.Message, ex.ErrorCode, Settings.RepositoryURL), "Unexpected Listen Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
Disconnect();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes and accepts an incoming client.
|
|
/// </summary>
|
|
/// <param name="s">Unused, use null.</param>
|
|
/// <param name="e">Asynchronously Socket Event</param>
|
|
private void AcceptClient(object s, SocketAsyncEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
do
|
|
{
|
|
switch (e.SocketError)
|
|
{
|
|
case SocketError.Success:
|
|
if (BufferManager.BuffersAvailable == 0)
|
|
BufferManager.IncreaseBufferCount(1);
|
|
|
|
Client client = new Client(this, e.AcceptSocket);
|
|
AddClient(client);
|
|
OnClientState(client, true);
|
|
break;
|
|
case SocketError.ConnectionReset:
|
|
break;
|
|
default:
|
|
throw new Exception("SocketError");
|
|
}
|
|
|
|
e.AcceptSocket = null; // enable reuse
|
|
} while (!_handle.AcceptAsync(e));
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
} |