Today I was posting a question to get a bit of information on how to optimize my code for maximum performance. I'm a beginner to C# (in a sense) and I don't really know as much as all of you other developers on this site.
I'm looking for just any improvements that you think I should make to write better quality code for higher performance, I'm not saying the performance is bad but what I have mostly learnt is there is always room for improved with code.
So, what I am posting is a Socket Listener class which listens for players connecting to the game server. When they have connected the player gets a new instance of 'PlayerConnection' with their socket and added to the PlayerManager's dictionary of players.
The purpose for this is each player will send and receive data to the server. We do it in packets and each packet has a unique identifier.
As of now I have only coded the receiving from Player side of things, I am yet to code sending data to the Player. You'll see I have coded something to do with sending because I have to first of all send a crossdomain policy but I haven't implemented a proper system for sending packets to the player from the server yet.
I've included a fair amount of detail, the rest is pretty much self explanatory or I've added comments in certain places.
ServerListener:
public class SocketListener : IDisposable, IPandaClass
{
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
private readonly Socket _listener;
public SocketListener()
{
_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public void Load()
{
var panda = Program.GetPandaServer();
var config = panda.GetHandler("configuration") as ConfigurationData;
if (config == null)
{
return;
}
var socketPort = config.GetValueByKeyInt("game.socket.port");
var socketBacklog = config.GetValueByKeyInt("game.socket.backlog");
_listener.Bind(new IPEndPoint(IPAddress.Any, socketPort));
_listener.Listen(socketBacklog);
_listener.BeginAccept(OnAccept, _listener);
}
private void OnAccept(IAsyncResult asyncResult)
{
try
{
if (_listener == null)
{
return;
}
var server = (Socket)asyncResult.AsyncState;
var client = server.EndAccept(asyncResult);
var player = new PlayerConnection(client);
var panda = Program.GetPandaServer();
var pandaBase = panda.GetHandler("base") as BaseHandler;
if (pandaBase == null)
{
return;
}
var playerHandler = pandaBase.GetHandler("player") as PlayerHandler;
if (playerHandler != null && !playerHandler.TryAddPlayer(player))
{
Logger.Error($"Failed to register players connection on: {client.RemoteEndPoint}");
}
}
catch (SocketException socketException)
{
Logger.Error(socketException, $"Failed to accept socket connection: {socketException.Message}");
}
finally
{
_listener?.BeginAccept(OnAccept, _listener);
}
}
public void Dispose()
{
_listener.Shutdown(SocketShutdown.Both);
_listener = null;
}
PlayerManager: There will be many more methods like GetPlayer DisconnectPlayer MassPacket and more once finished.
internal class PlayerHandler : IPandaClass
{
private readonly ConcurrentDictionary<int, PlayerConnection> _players;
public PlayerHandler()
{
_players = new ConcurrentDictionary<int, PlayerConnection>();
}
public bool TryAddPlayer(PlayerConnection player)
{
return _players.TryAdd(_players.Count, player);
}
}
PlayerConnection:
public sealed class PlayerConnection : PlayerData, IDisposable
{
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
private readonly Socket _playerSocket;
private readonly byte[] _buffer;
private bool _halfDataReceived;
private byte[] _halfData;
public bool _decrypted;
public PlayerConnection(Socket socket)
{
_playerSocket = socket;
_buffer = new byte[8192];
try
{
_playerSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceivedData, _playerSocket);
}
catch (Exception exception)
{
Logger.Error(exception, "Failed to start receiving on players socket.");
}
}
private void OnReceivedData(IAsyncResult asyncResult)
{
try
{
var bytesReceived = _playerSocket.EndReceive(asyncResult);
if (bytesReceived == 0)
{
Dispose();
return;
}
var packet = new byte[bytesReceived];
Array.Copy(_buffer, packet, bytesReceived);
ProcessReceivedData(packet);
}
catch (SocketException socketException)
{
Logger.Error(socketException, "Failed while receiving data from players socket.");
}
finally
{
_playerSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceivedData, _playerSocket);
}
}
private void ProcessReceivedData(byte[] receivedBytes)
{
if (!_decrypted)
{
// todo: decrypt here
_decrypted = true;
}
var packetId = receivedBytes[0];
if (packetId == 67)
{
return;
}
if (_halfDataReceived)
{
var fullDataRcv = new byte[_halfData.Length + receivedBytes.Length];
Buffer.BlockCopy(_halfData, 0, fullDataRcv, 0, _halfData.Length);
Buffer.BlockCopy(receivedBytes, 0, fullDataRcv, _halfData.Length, receivedBytes.Length);
_halfDataReceived = false;
ProcessReceivedData(fullDataRcv); // todo: repeat now we have the combined array
return;
}
if (packetId == 60)
{
const string crossDomainPolicy = "<?xml version=\"1.0\"?>\r\n"
+ "<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\r\n"
+ "<cross-domain-policy>\r\n"
+ "<site-control permitted-cross-domain-policies=\"master-only\"/>\r\n"
+ "<allow-access-from domain=\"*\" to-ports=\"*\" />\r\n"
+ "</cross-domain-policy>\x0";
SendString(crossDomainPolicy);
}
else
{
using (var reader = new BinaryReader(new MemoryStream(receivedBytes)))
{
if (receivedBytes.Length < 4)
{
return;
}
var packetLength = PacketUtilities.DecodeInt32(reader.ReadBytes(4));
if (reader.BaseStream.Length - 4 < packetLength)
{
_halfData = receivedBytes;
_halfDataReceived = true;
return;
}
if (packetLength < 0 || packetLength > 5120)
{
return;
}
var packetBytes = reader.ReadBytes(packetLength);
using (var binaryReader = new BinaryReader(new MemoryStream(packetBytes)))
{
var packetHeader = PacketUtilities.DecodeInt16(binaryReader.ReadBytes(2));
var packetBodyBytes = new byte[packetBytes.Length - 2];
Buffer.BlockCopy(packetBytes, 2, packetBodyBytes, 0, packetBytes.Length - 2);
// now we handle the packet: packetHeader and packetBodyBytes
_decrypted = false;
}
if (reader.BaseStream.Length - 4 <= packetLength)
{
return;
}
var extra = new byte[reader.BaseStream.Length - reader.BaseStream.Position];
Buffer.BlockCopy(receivedBytes, (int)reader.BaseStream.Position, extra, 0, (int)(reader.BaseStream.Length - reader.BaseStream.Position));
_decrypted = true;
ProcessReceivedData(extra);
}
}
}
private void SendString(string data)
{
SendData(Encoding.UTF8.GetBytes(data));
}
private void SendData(byte[] data)
{
try
{
_playerSocket.BeginSend(data, 0, data.Length, 0, OnSend, null);
}
catch (SocketException socketException)
{
Logger.Error(socketException, "Error sending message to players socket.");
Dispose();
}
}
private void OnSend(IAsyncResult asyncResult)
{
try
{
if (_playerSocket == null)
{
return;
}
_playerSocket.EndSend(asyncResult);
}
catch (SocketException)
{
Dispose();
}
}
public void Dispose()
{
if (_playerSocket == null || !_playerSocket.Connected)
{
return;
}
_playerSocket.Shutdown(SocketShutdown.Both);
_playerSocket.Close();
}
}
I'll most the other methods I used (Utility methods) below also.
PacketUtilities:
internal static int DecodeInt32(byte[] bytes)
{
if ((bytes[0] | bytes[1] | bytes[2] | bytes[3]) < 0)
{
return -1;
}
return (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
}
internal static int DecodeInt16(byte[] bytes)
{
if ((bytes[0] | bytes[1]) < 0)
{
return -1;
}
return (bytes[0] << 8) + bytes[1];
}