Currently my Socket's use the standard APM Pattern BeginXXX, EndXXX
Additionally I'm utilizing a BufferPool that completely eliminates allocations that would be present on each receive/send operation.
Part's of the class for illustration
Packets are structs which I copy to a pooled buffer object using unsafe code so no additional allocations are being created.
(de)Serialization:
Outgoing packets are being merged into 850byte buffers, adding a little latency overhead (max 30ms + looptime) but improving the overal performance of the socket system by a huge margin. Usually 100 packets end up being 1-2 packets - especially useful with attack packets. (I'll override the equality/hashcode generation of the Account class to improve performance)
Sending 850bytes would be a waste if the actual content length is less than that, but copying the packet to a smaller buffer would create an additional allocation so I'm using the BeginSend with a Size parameter on my 850byte buffer;
Now, all those IAsyncResults create enough allocations on their own, so I need a way of getting rid of them. Is there something I can do other than implementing SAEA?
Any improvements/flaws I should take care of? Socket's never really been my strength - this is my first attempt on good ones :) Of course you could argue if I should call anything coding related a strength of mine haha
Fang will destroy me for sure :D
Additionally I'm utilizing a BufferPool that completely eliminates allocations that would be present on each receive/send operation.
Part's of the class for illustration
PHP Code:
public static class BufferPool
{
public static int BigPoolCount => BigPool.Count;
public const int MinBigPoolSize = 2500;
public const int MaxBufferSizeBytes = 850;
public static ulong BigRecycleCount;
public static ulong BigRequestCount;
public static ulong CopyCount;
private static readonly ConcurrentStack<byte[]> BigPool = new ConcurrentStack<byte[]>();
public static byte[] GetBigBuffer()
{
BigRequestCount++;
byte[] buffer;
while (!BigPool.TryPop(out buffer))
CreateBufferPool();
return buffer;
}
public static byte[] Copy(byte[] packet)
{
CopyCount++;
var copy = GetBigBuffer();
Buffer.BlockCopy(packet, 0, copy, 0, packet.Length);
return copy;
}
public static void RecycleBuffer(byte[] buffer)
{
Array.Clear(buffer, 0, buffer.Length);
BigPool.Push(buffer);
BigRecycleCount++;
}
private static void CreateBufferPool()
{
if (BigPool.Count <= MinBigPoolSize)
{
for (var i = 0; i < MinBigPoolSize; i++)
BigPool.Push(new byte[MaxBufferSizeBytes]);
Output.WriteLine($"Socket's Big BufferPool Expanded to {BigPoolCount}", ConsoleColor.Green);
}
}
}
(de)Serialization:
PHP Code:
public static unsafe implicit operator byte[](MsgAction msg)
{
var buffer = BufferPool.GetBigBuffer();
fixed (byte* p = buffer)
*(MsgAction*)p = *&msg;
return buffer;
}
public static unsafe implicit operator MsgAction(byte[] msg)
{
MsgAction packet;
fixed (byte* p = msg)
packet = *(MsgAction*)p;
BufferPool.RecycleBuffer(msg);
return packet;
}
PHP Code:
public static class OutgoingPacketQueue
{
public static readonly ConcurrentDictionary<Account, ConcurrentQueue<byte[]>> PacketQueue = new ConcurrentDictionary<Account, ConcurrentQueue<byte[]>>();
public static readonly AutoResetEvent Block = new AutoResetEvent(false);
public static readonly Thread QueueThread = new Thread(WorkLoop);
public static byte[] MergedPacket;
public static byte[] Chunk;
public static int Counter;
public static int Offset;
public static int TempLen;
public static void Add(Account client, byte[] packet)
{
if (!QueueThread.IsAlive)
QueueThread.Start();
if (!PacketQueue.ContainsKey(client))
PacketQueue.TryAdd(client, new ConcurrentQueue<byte[]>());
PacketQueue[client].Enqueue(packet);
}
public static void WorkLoop()
{
while (true)
{
Block.WaitOne(30);
foreach (var q in PacketQueue.Where(q => q.Value.Count > 0))
{
Counter = 0;
Offset = 0;
MergedPacket = BufferPool.GetBigBuffer();
while (q.Value.Count > 0 && q.Value.TryPeek(out Chunk) && BitConverter.ToUInt16(Chunk, 0) + Offset <= BufferPool.MaxBufferSizeBytes)
{
if (q.Value.TryDequeue(out Chunk))
{
TempLen = BitConverter.ToUInt16(Chunk, 0);
if (TempLen == 0)
{
BufferPool.RecycleBuffer(Chunk);
continue;
}
Buffer.BlockCopy(Chunk, 0, MergedPacket, Offset, TempLen);
Offset += TempLen;
Counter++;
BufferPool.RecycleBuffer(Chunk);
}
}
if (Counter > 0)
{
q.Key.ActualPacketsPerSecond += Counter;
q.Key.ForceSend(MergedPacket, Offset);
}
}
}
}
}
PHP Code:
public void Send(byte[] packet, int size)
{
try
{
if (packet == null || BitConverter.ToUInt16(packet, 0) < 4)
return;
if (Crypto!=null)
packet = Crypto.Encrypt(packet, size);
Socket.BeginSend(packet, 0, size, SocketFlags.None, EndSend, packet);
Ref.PacketPerSecond++;
}
catch (Exception e)
{
BufferPool.RecycleBuffer(packet);
Output.WriteLine(e);
Disconnect();
}
}
Any improvements/flaws I should take care of? Socket's never really been my strength - this is my first attempt on good ones :) Of course you could argue if I should call anything coding related a strength of mine haha
Fang will destroy me for sure :D