C# ожидающая оболочки SocketAsyncEventArgs для UDP-сервера; socket.ReceiveAsyn c возвращает недопустимый RemoteEndPoint (0.0.0.0:0) - PullRequest
1 голос
/ 25 февраля 2020

Как видно из названия, я пишу сервер UDP на сырых сокетах и ​​использую SocketAsyncEventArgs, потому что я хотел бы написать что-то быстрое.

Я знаю, что UdpClient существует, и что go есть более простые способы написания сервера, но я хотел бы узнать, как правильно использовать SocketAsyncEventArgs и socket.ReceiveFromAsyn c / socket. SendToAsyn c методы для заявленной им «повышенной пропускной способности» и «лучшей масштабируемости» ( MSDN Docs для SocketAsyncEventArgs ).

Я по сути следовал примеру MSDN, так как считал, что это был достойная отправная точка, чтобы узнать, как работают эти методы, и столкнулась с небольшой проблемой. Первоначально сервер работает и может возвращать полученные байты, но случайным образом «не сможет» получить байты с правильного адреса. Вместо правильного адреса клиента локального хоста (например, 127.0.0.1:7007) RemoteEndPoint будет заполняться конечной точкой UDP-заполнителя {0.0.0.0:0} (из-за отсутствия лучшего термина). Изображение, показывающее проблему (консоль в Poli sh, извините. Просто поверьте мне, что сообщение SocketException "Требуемый адрес недопустим в этом контексте").

Я уже разорвал порой фрагменты из примера MSDN, изменяя только те поля, которые были заполнены в экземпляре SocketAsyncEventArgs для вызова socket.ReceiveFromAsyn c (в соответствии с документами MSDN -> socket.ReceiveFromAsyn c Docs ), и конечный результат остался прежним. Кроме того, это временная проблема, а не постоянная. Как я уже заметил, сервер никогда не будет постоянно выдавать ошибки.

Пока что я думаю о проблеме с состоянием UdpServer, некоторой несогласованности на стороне UdpClient или неправильном использовании TaskCompletionSource.

Редактировать 1:

Я чувствую, что должен рассмотреть, почему я использую SocketAsyncEventArgs. Я полностью понимаю, что есть более простые способы go об отправке и получении данных. Расширения сокетов async / await - хороший способ узнать об этом go, и именно так я и сделал изначально. Я хотел бы сравнить async / await со старым API, SocketArgs, чтобы увидеть, насколько эти два метода различаются.


Код для UdpClient, UdpServer и общих структур приведен ниже. Я также могу попытаться предоставить больше кода по требованию, если StackOverflow позволит мне.

Спасибо, что нашли время помочь мне.

Тестовый код

private static async Task TestNetworking()
        {
            EndPoint serverEndPoint = new IPEndPoint(IPAddress.Loopback, 12345);

            await Task.Factory.StartNew(async () =>
            {
                SocketClient client = new UdpClient();
                bool bound = client.Bind(new IPEndPoint(IPAddress.Any, 7007));
                if (bound)
                {
                    Console.WriteLine($"[Client] Bound client socket!");
                }

                if (bound && client.Connect(serverEndPoint))
                {
                    Console.WriteLine($"[Client] Connected to {serverEndPoint}!");

                    byte[] message = Encoding.UTF8.GetBytes("Hello World!");

                    Stopwatch stopwatch = new Stopwatch();

                    const int packetsToSend = 1_000_000;

                    for (int i = 0; i < packetsToSend; i++)
                    {
                        try
                        {
                            stopwatch.Start();

                            int sentBytes = await client.SendAsync(serverEndPoint, message, SocketFlags.None);

                            //Console.WriteLine($"[Client] Sent {sentBytes} to {serverEndPoint}");

                            ReceiveResult result = await client.ReceiveAsync(serverEndPoint, SocketFlags.None);

                            //Console.WriteLine($"[{result.RemoteEndPoint} > Client] : {Encoding.UTF8.GetString(result.Contents)}");
                            serverEndPoint = result.RemoteEndPoint;

                            stopwatch.Stop();
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex);
                            i--;
                            await Task.Delay(1);
                        }
                    }

                    double approxBandwidth = (packetsToSend * message.Length) / (1_000_000.0 * (stopwatch.ElapsedMilliseconds / 1000.0));

                    Console.WriteLine($"Sent {packetsToSend} packets of {message.Length} bytes in {stopwatch.ElapsedMilliseconds:N} milliseconds.");
                    Console.WriteLine($"Approximate bandwidth: {approxBandwidth} MBps");
                }
            }, TaskCreationOptions.LongRunning);

            await Task.Factory.StartNew(async () =>
            {
                try
                {
                    SocketServer server = new UdpServer();
                    bool bound = server.Bind(serverEndPoint);
                    if (bound)
                    {
                        //Console.WriteLine($"[Server] Bound server socket!");

                        //Console.WriteLine($"Starting server at {serverEndPoint}!");

                        await server.StartAsync();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }).Result;
        }

Общий код

public readonly struct ReceiveResult
    {
        public const int PacketSize = 1024;

        public readonly Memory<byte> Contents;

        public readonly int ReceivedBytes;

        public readonly EndPoint RemoteEndPoint;

        public ReceiveResult(Memory<byte> contents, int receivedBytes, EndPoint remoteEndPoint)
        {
            Contents = contents;

            ReceivedBytes = receivedBytes;

            RemoteEndPoint = remoteEndPoint;
        }
    }

UDP-клиент

public class UdpClient : SocketClient
    {
        /*
        public abstract class SocketClient
        {
            protected readonly Socket socket;

            protected SocketClient(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
            {
                socket = new Socket(addressFamily, socketType, protocolType);
            }

            public bool Bind(in EndPoint localEndPoint)
            {
                try
                {
                    socket.Bind(localEndPoint);

                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);

                    return false;
                }
            }

            public bool Connect(in EndPoint remoteEndPoint)
            {
                try
                {
                    socket.Connect(remoteEndPoint);

                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);

                    return false;
                }
            }

            public abstract Task<ReceiveResult> ReceiveAsync(EndPoint remoteEndPoint, SocketFlags socketFlags);

            public abstract Task<int> SendAsync(EndPoint remoteEndPoint, ArraySegment<byte> buffer, SocketFlags socketFlags);
        }
        */
        /// <inheritdoc />
        public UdpClient() : base(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
        {
        }

        public override async Task<ReceiveResult> ReceiveAsync(EndPoint remoteEndPoint, SocketFlags socketFlags)
        {
            byte[] buffer = new byte[ReceiveResult.PacketSize];

            SocketReceiveFromResult result =
                await socket.ReceiveFromAsync(new ArraySegment<byte>(buffer), socketFlags, remoteEndPoint);

            return new ReceiveResult(buffer, result.ReceivedBytes, result.RemoteEndPoint);

            /*
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SetBuffer(new byte[ReceiveResult.PacketSize]);
            args.SocketFlags = socketFlags;
            args.RemoteEndPoint = remoteEndPoint;
            SocketTask awaitable = new SocketTask(args);

            while (ReceiveResult.PacketSize > args.BytesTransferred)
            {
                await socket.ReceiveFromAsync(awaitable);
            }

            return new ReceiveResult(args.MemoryBuffer, args.RemoteEndPoint);
            */
        }

        public override async Task<int> SendAsync(EndPoint remoteEndPoint, ArraySegment<byte> buffer, SocketFlags socketFlags)
        {
            return await socket.SendToAsync(buffer.ToArray(), socketFlags, remoteEndPoint);

            /*
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SetBuffer(buffer);
            args.SocketFlags = socketFlags;
            args.RemoteEndPoint = remoteEndPoint;
            SocketTask awaitable = new SocketTask(args);

            while (buffer.Length > args.BytesTransferred)
            {
                await socket.SendToAsync(awaitable);
            }

            return args.BytesTransferred;
            */
        }
    }

UDP-сервер

public class UdpServer : SocketServer
    {
        /*
        public abstract class SocketServer
        {
            protected readonly Socket socket;

            protected SocketServer(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
            {
                socket = new Socket(addressFamily, socketType, protocolType);
            }

            public bool Bind(in EndPoint localEndPoint)
            {
                try
                {
                    socket.Bind(localEndPoint);

                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);

                    return false;
                }
            }

            public abstract Task StartAsync();
        }
         */

        private const int MaxPooledObjects = 100;
        private readonly ConcurrentDictionary<EndPoint, ConcurrentQueue<byte[]>> clients;

        private readonly ArrayPool<byte> receiveBufferPool = ArrayPool<byte>.Create(ReceiveResult.PacketSize, MaxPooledObjects);

        private readonly ObjectPool<SocketAsyncEventArgs> receiveSocketAsyncEventArgsPool =
            new DefaultObjectPool<SocketAsyncEventArgs>(new DefaultPooledObjectPolicy<SocketAsyncEventArgs>(), MaxPooledObjects);

        private readonly ObjectPool<SocketAsyncEventArgs> sendSocketAsyncEventArgsPool =
            new DefaultObjectPool<SocketAsyncEventArgs>(new DefaultPooledObjectPolicy<SocketAsyncEventArgs>(), MaxPooledObjects);

        private void HandleIOCompleted(object? sender, SocketAsyncEventArgs eventArgs)
        {
            eventArgs.Completed -= HandleIOCompleted;
            bool closed = false;

            /*
             Original (local) methods in ReceiveAsync and SendAsync,
             these were assigned to eventArgs.Completed instead of HandleIOCompleted
             =======================================================================

            void ReceiveCompletedHandler(object? sender, SocketAsyncEventArgs eventArgs)
            {
                AsyncReadToken asyncReadToken = (AsyncReadToken)eventArgs.UserToken;
                eventArgs.Completed -= ReceiveCompletedHandler;

                if (eventArgs.SocketError != SocketError.Success)
                {
                    asyncReadToken.CompletionSource.TrySetException(new SocketException((int)eventArgs.SocketError));
                }
                else
                {
                    eventArgs.MemoryBuffer.CopyTo(asyncReadToken.OutputBuffer);

                    asyncReadToken.CompletionSource.TrySetResult(
                        new ReceiveResult(asyncReadToken.OutputBuffer, eventArgs.BytesTransferred, eventArgs.RemoteEndPoint));
                }

                receiveBufferPool.Return(asyncReadToken.RentedBuffer);
                receiveSocketAsyncEventArgsPool.Return(eventArgs);
            }

            void SendCompletedHandler(object? sender, SocketAsyncEventArgs eventArgs)
            {
                AsyncWriteToken asyncWriteToken = (AsyncWriteToken)eventArgs.UserToken;
                eventArgs.Completed -= SendCompletedHandler;

                if (eventArgs.SocketError != SocketError.Success)
                {
                    asyncWriteToken.CompletionSource.TrySetException(new SocketException((int)eventArgs.SocketError));
                }
                else
                {
                    asyncWriteToken.CompletionSource.TrySetResult(eventArgs.BytesTransferred);
                }

                sendSocketAsyncEventArgsPool.Return(eventArgs);
            }
            */

            switch (eventArgs.LastOperation)
            {
                case SocketAsyncOperation.SendTo:
                    AsyncWriteToken asyncWriteToken = (AsyncWriteToken)eventArgs.UserToken;

                    if (eventArgs.SocketError != SocketError.Success)
                    {
                        asyncWriteToken.CompletionSource.TrySetException(new SocketException((int)eventArgs.SocketError));
                    }
                    else
                    {
                        asyncWriteToken.CompletionSource.TrySetResult(eventArgs.BytesTransferred);
                    }

                    sendSocketAsyncEventArgsPool.Return(eventArgs);

                    break;

                case SocketAsyncOperation.ReceiveFrom:
                    AsyncReadToken asyncReadToken = (AsyncReadToken)eventArgs.UserToken;

                    if (eventArgs.SocketError != SocketError.Success)
                    {
                        asyncReadToken.CompletionSource.TrySetException(new SocketException((int)eventArgs.SocketError));
                    }
                    else
                    {
                        eventArgs.MemoryBuffer.CopyTo(asyncReadToken.OutputBuffer);

                        asyncReadToken.CompletionSource.TrySetResult(
                            new ReceiveResult(asyncReadToken.OutputBuffer, eventArgs.BytesTransferred, eventArgs.RemoteEndPoint));
                    }

                    receiveBufferPool.Return(asyncReadToken.RentedBuffer);
                    receiveSocketAsyncEventArgsPool.Return(eventArgs);

                    break;

                case SocketAsyncOperation.Disconnect:
                    closed = true;
                    break;

                case SocketAsyncOperation.Accept:
                case SocketAsyncOperation.Connect:
                case SocketAsyncOperation.None:
                    break;
            }

            if (closed)
            {
                // handle the client closing the connection on tcp servers at some point
            }
        }

        private Task<ReceiveResult> ReceiveAsync(EndPoint remoteEndPoint, SocketFlags socketFlags, Memory<byte> outputBuffer)
        {
            TaskCompletionSource<ReceiveResult> tcs = new TaskCompletionSource<ReceiveResult>();

            byte[] buffer = receiveBufferPool.Rent(ReceiveResult.PacketSize);
            Memory<byte> memoryBuffer = new Memory<byte>(buffer);

            SocketAsyncEventArgs args = receiveSocketAsyncEventArgsPool.Get();
            args.SetBuffer(memoryBuffer);
            args.SocketFlags = socketFlags;
            args.RemoteEndPoint = remoteEndPoint;
            args.UserToken = new AsyncReadToken(buffer, outputBuffer, tcs);
            args.Completed += HandleIOCompleted;

            if (socket.ReceiveFromAsync(args)) return tcs.Task;

            byte[] bufferCopy = new byte[ReceiveResult.PacketSize];

            args.MemoryBuffer.CopyTo(bufferCopy);

            ReceiveResult result = new ReceiveResult(bufferCopy, args.BytesTransferred, args.RemoteEndPoint);

            receiveBufferPool.Return(buffer);
            receiveSocketAsyncEventArgsPool.Return(args);

            return Task.FromResult(result);
        }

        private Task<int> SendAsync(EndPoint remoteEndPoint, Memory<byte> buffer, SocketFlags socketFlags)
        {
            TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();

            SocketAsyncEventArgs args = sendSocketAsyncEventArgsPool.Get();
            args.SetBuffer(buffer);
            args.SocketFlags = socketFlags;
            args.RemoteEndPoint = remoteEndPoint;
            args.UserToken = new AsyncWriteToken(buffer, tcs);
            args.Completed += HandleIOCompleted;

            if (socket.SendToAsync(args)) return tcs.Task;

            int result = args.BytesTransferred;
            sendSocketAsyncEventArgsPool.Return(args);

            return Task.FromResult(result);
        }

        private readonly struct AsyncReadToken
        {
            public readonly TaskCompletionSource<ReceiveResult> CompletionSource;

            public readonly Memory<byte> OutputBuffer;
            public readonly byte[] RentedBuffer;

            public AsyncReadToken(byte[] rentedBuffer, Memory<byte> outputBuffer, TaskCompletionSource<ReceiveResult> tcs)
            {
                RentedBuffer = rentedBuffer;
                OutputBuffer = outputBuffer;

                CompletionSource = tcs;
            }
        }

        private readonly struct AsyncWriteToken
        {
            public readonly TaskCompletionSource<int> CompletionSource;

            public readonly Memory<byte> OutputBuffer;

            public AsyncWriteToken(Memory<byte> outputBuffer, TaskCompletionSource<int> tcs)
            {
                OutputBuffer = outputBuffer;

                CompletionSource = tcs;
            }
        }

        public UdpServer() : base(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
        {
            clients = new ConcurrentDictionary<EndPoint, ConcurrentQueue<byte[]>>();
        }

        /// <inheritdoc />
        public override async Task StartAsync()
        {
            EndPoint nullEndPoint = new IPEndPoint(IPAddress.Any, 0);
            byte[] receiveBuffer = new byte[ReceiveResult.PacketSize];
            Memory<byte> receiveBufferMemory = new Memory<byte>(receiveBuffer);

            while (true)
            {
                ReceiveResult result = await ReceiveAsync(nullEndPoint, SocketFlags.None, receiveBufferMemory);

                Console.WriteLine($"[{result.RemoteEndPoint} > Server] : {Encoding.UTF8.GetString(result.Contents.Span)}");

                int sentBytes = await SendAsync(result.RemoteEndPoint, result.Contents, SocketFlags.None);
                Console.WriteLine($"[Server > {result.RemoteEndPoint}] Sent {sentBytes} bytes to {result.RemoteEndPoint}");
            }
        }
    }

1 Ответ

1 голос
/ 26 февраля 2020

Мне удалось решить проблему!

В итоге мне пришлось объединить пул SocketAsyncEventArgs, поскольку выясняется, что вам необходимо сохранить один объект args на время вызовов приема и отправки. Теперь моя функция SendToAsyn c принимает объект SocketAsyncEventArgs (арендованный в вызове ReceiveFromAsyn c), который содержит RemoteEndPoint для клиента, которому нужно отправить ответ. Функция SendToAsyn c предназначена для очистки SocketAsyncEventArgs и возврата их в пул.

Другая проблема, связанная с моим более ранним решением, заключалась в многократном назначении события. Когда я объединил два пула аргументов сокета, я оставил назначение обработчику нескольких событий, что в итоге вызвало проблемы. Как только это было исправлено, решение работало полностью, как и предполагалось, и может отправлять 1 000 000 пакетов (объемом 1 КБ) без каких-либо проблем. Действительно раннее тестирование (возможно, немного) показало пропускную способность около 5 мегабайт в секунду (около 40 мегабит в секунду), что является приемлемым и намного лучше, чем я получал с моей собственной сверхсложной версией fast asyn c. code.

Что касается пропускной способности, моя быстрая версия asyn c была чрезмерно усложнена и, следовательно, не очень сопоставима, но я считаю, что эта версия SocketAsyncEventArgs может послужить хорошей отправной точкой как для тестирования, так и для выжмите как можно больше производительности из сокета. Я все еще хотел бы получить отзыв об этом, и, вероятно, в какой-то момент опубликую его на бирже стека Code Review, так как сомневаюсь, что в решении все еще нет никаких тонких ошибок.

Кто бы ни хотел использовать этот код он свободен, в итоге его стало намного проще и проще, чем предполагалось, но я не несу никакой ответственности, если вы достаточно глупы, чтобы использовать его в производстве без всестороннего тестирования (это ведь учебный проект).


Код теста:

private static async Task TestNetworking()
        {
            EndPoint serverEndPoint = new IPEndPoint(IPAddress.Loopback, 12345);

            await Task.Factory.StartNew(async () =>
            {
                try
                {
                    SocketServer server = new UdpServer();
                    bool bound = server.Bind(serverEndPoint);
                    if (bound)
                    {
                        Console.WriteLine($"[Server] Bound server socket!");

                        Console.WriteLine($"[Server] Starting server at {serverEndPoint}!");

                        await server.StartAsync();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);

            await Task.Factory.StartNew(async () =>
            {
                SocketClient client = new UdpClient();
                bool bound = client.Bind(new IPEndPoint(IPAddress.Any, 7007));
                if (bound)
                {
                    Console.WriteLine($"[Client] Bound client socket!");
                }

                if (bound && client.Connect(serverEndPoint))
                {
                    Console.WriteLine($"[Client] Connected to {serverEndPoint}!");

                    byte[] message = Encoding.UTF8.GetBytes("Hello World!");
                    Memory<byte> messageBuffer = new Memory<byte>(message);

                    byte[] response = new byte[ReceiveResult.PacketSize];
                    Memory<byte> responseBuffer = new Memory<byte>(response);

                    Stopwatch stopwatch = new Stopwatch();

                    const int packetsToSend = 1_000_000, statusPacketThreshold = 10_000;

                    Console.WriteLine($"Started sending packets (total packet count: {packetsToSend})");

                    for (int i = 0; i < packetsToSend; i++)
                    {
                        if (i % statusPacketThreshold == 0)
                        {
                            Console.WriteLine($"Sent {i} packets out of {packetsToSend} ({((double)i / packetsToSend) * 100:F2}%)");
                        }

                        try
                        {
                            //Console.WriteLine($"[Client > {serverEndPoint}] Sending packet {i}");
                            stopwatch.Start();

                            int sentBytes = await client.SendAsync(serverEndPoint, messageBuffer, SocketFlags.None);

                            //Console.WriteLine($"[Client] Sent {sentBytes} to {serverEndPoint}");

                            ReceiveResult result = await client.ReceiveAsync(serverEndPoint, SocketFlags.None, responseBuffer);

                            //Console.WriteLine($"[{result.RemoteEndPoint} > Client] : {Encoding.UTF8.GetString(result.Contents)}");
                            serverEndPoint = result.RemoteEndPoint;

                            stopwatch.Stop();
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex);
                            i--;
                            await Task.Delay(1);
                        }
                    }

                    double approxBandwidth = (packetsToSend * ReceiveResult.PacketSize) / (1_000_000.0 * (stopwatch.ElapsedMilliseconds / 1000.0));

                    Console.WriteLine($"Sent {packetsToSend} packets of {ReceiveResult.PacketSize} bytes in {stopwatch.ElapsedMilliseconds:N} milliseconds.");
                    Console.WriteLine($"Approximate bandwidth: {approxBandwidth} MBps");
                }
            }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result;
        }

Общий код:

internal readonly struct AsyncReadToken
    {
        public readonly CancellationToken CancellationToken;
        public readonly TaskCompletionSource<ReceiveResult> CompletionSource;
        public readonly byte[] RentedBuffer;
        public readonly Memory<byte> UserBuffer;

        public AsyncReadToken(byte[] rentedBuffer, Memory<byte> userBuffer, TaskCompletionSource<ReceiveResult> tcs,
            CancellationToken cancellationToken = default)
        {
            RentedBuffer = rentedBuffer;
            UserBuffer = userBuffer;

            CompletionSource = tcs;
            CancellationToken = cancellationToken;
        }
    }

internal readonly struct AsyncWriteToken
    {
        public readonly CancellationToken CancellationToken;
        public readonly TaskCompletionSource<int> CompletionSource;
        public readonly byte[] RentedBuffer;
        public readonly Memory<byte> UserBuffer;

        public AsyncWriteToken(byte[] rentedBuffer, Memory<byte> userBuffer, TaskCompletionSource<int> tcs,
            CancellationToken cancellationToken = default)
        {
            RentedBuffer = rentedBuffer;
            UserBuffer = userBuffer;

            CompletionSource = tcs;
            CancellationToken = cancellationToken;
        }
    }

public readonly struct ReceiveResult
    {
        public const int PacketSize = 1024;

        public readonly SocketAsyncEventArgs ClientArgs;

        public readonly Memory<byte> Contents;

        public readonly int Count;

        public readonly EndPoint RemoteEndPoint;

        public ReceiveResult(SocketAsyncEventArgs clientArgs, Memory<byte> contents, int count, EndPoint remoteEndPoint)
        {
            ClientArgs = clientArgs;

            Contents = contents;
            Count = count;
            RemoteEndPoint = remoteEndPoint;
        }
    }

Код сервера:

public abstract class SocketServer
    {
        protected readonly Socket socket;

        protected SocketServer(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
        {
            socket = new Socket(addressFamily, socketType, protocolType);
        }

        public bool Bind(in EndPoint localEndPoint)
        {
            try
            {
                socket.Bind(localEndPoint);

                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);

                return false;
            }
        }

        public abstract Task StartAsync();
    }

public class UdpServer : SocketServer
    {
        private const int MaxPooledObjects = 1;
        private readonly ConcurrentDictionary<EndPoint, ConcurrentQueue<byte[]>> clients;

        private readonly ArrayPool<byte> receiveBufferPool =
            ArrayPool<byte>.Create(ReceiveResult.PacketSize, MaxPooledObjects);

        private readonly ArrayPool<byte> sendBufferPool =
            ArrayPool<byte>.Create(ReceiveResult.PacketSize, MaxPooledObjects);

        private readonly ObjectPool<SocketAsyncEventArgs> socketAsyncEventArgsPool =
            new DefaultObjectPool<SocketAsyncEventArgs>(new DefaultPooledObjectPolicy<SocketAsyncEventArgs>(),
                MaxPooledObjects);

        private void HandleIOCompleted(object? sender, SocketAsyncEventArgs eventArgs)
        {
            bool closed = false;

            switch (eventArgs.LastOperation)
            {
                case SocketAsyncOperation.SendTo:
                    AsyncWriteToken asyncWriteToken = (AsyncWriteToken)eventArgs.UserToken;

                    if (asyncWriteToken.CancellationToken.IsCancellationRequested)
                    {
                        asyncWriteToken.CompletionSource.TrySetCanceled();
                    }
                    else
                    {
                        if (eventArgs.SocketError != SocketError.Success)
                        {
                            asyncWriteToken.CompletionSource.TrySetException(
                                new SocketException((int)eventArgs.SocketError));
                        }
                        else
                        {
                            asyncWriteToken.CompletionSource.TrySetResult(eventArgs.BytesTransferred);
                        }
                    }

                    sendBufferPool.Return(asyncWriteToken.RentedBuffer, true);
                    socketAsyncEventArgsPool.Return(eventArgs);

                    break;

                case SocketAsyncOperation.ReceiveFrom:
                    AsyncReadToken asyncReadToken = (AsyncReadToken)eventArgs.UserToken;

                    if (asyncReadToken.CancellationToken.IsCancellationRequested)
                    {
                        asyncReadToken.CompletionSource.SetCanceled();
                    }
                    else
                    {
                        if (eventArgs.SocketError != SocketError.Success)
                        {
                            asyncReadToken.CompletionSource.SetException(
                                new SocketException((int)eventArgs.SocketError));
                        }
                        else
                        {
                            eventArgs.MemoryBuffer.CopyTo(asyncReadToken.UserBuffer);
                            ReceiveResult result = new ReceiveResult(eventArgs, asyncReadToken.UserBuffer,
                                eventArgs.BytesTransferred, eventArgs.RemoteEndPoint);

                            asyncReadToken.CompletionSource.SetResult(result);
                        }
                    }

                    receiveBufferPool.Return(asyncReadToken.RentedBuffer, true);

                    break;

                case SocketAsyncOperation.Disconnect:
                    closed = true;
                    break;

                case SocketAsyncOperation.Accept:
                case SocketAsyncOperation.Connect:
                case SocketAsyncOperation.None:
                case SocketAsyncOperation.Receive:
                case SocketAsyncOperation.ReceiveMessageFrom:
                case SocketAsyncOperation.Send:
                case SocketAsyncOperation.SendPackets:
                    throw new NotImplementedException();

                default:
                    throw new ArgumentOutOfRangeException();
            }

            if (closed)
            {
                // handle the client closing the connection on tcp servers at some point
            }
        }

        private Task<ReceiveResult> ReceiveAsync(EndPoint remoteEndPoint, SocketFlags socketFlags,
            Memory<byte> outputBuffer, CancellationToken cancellationToken = default)
        {
            TaskCompletionSource<ReceiveResult> tcs = new TaskCompletionSource<ReceiveResult>();

            byte[] rentedBuffer = receiveBufferPool.Rent(ReceiveResult.PacketSize);
            Memory<byte> memoryBuffer = new Memory<byte>(rentedBuffer);

            SocketAsyncEventArgs args = socketAsyncEventArgsPool.Get();

            args.SetBuffer(memoryBuffer);
            args.SocketFlags = socketFlags;
            args.RemoteEndPoint = remoteEndPoint;
            args.UserToken = new AsyncReadToken(rentedBuffer, outputBuffer, tcs, cancellationToken);

            // if the receive operation doesn't complete synchronously, returns the awaitable task
            if (socket.ReceiveFromAsync(args)) return tcs.Task;

            args.MemoryBuffer.CopyTo(outputBuffer);

            ReceiveResult result = new ReceiveResult(args, outputBuffer, args.BytesTransferred, args.RemoteEndPoint);

            receiveBufferPool.Return(rentedBuffer, true);

            return Task.FromResult(result);
        }

        private Task<int> SendAsync(SocketAsyncEventArgs clientArgs, Memory<byte> inputBuffer, SocketFlags socketFlags,
            CancellationToken cancellationToken = default)
        {
            TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();

            byte[] rentedBuffer = sendBufferPool.Rent(ReceiveResult.PacketSize);
            Memory<byte> memoryBuffer = new Memory<byte>(rentedBuffer);

            inputBuffer.CopyTo(memoryBuffer);

            SocketAsyncEventArgs args = clientArgs;
            args.SetBuffer(memoryBuffer);
            args.SocketFlags = socketFlags;
            args.UserToken = new AsyncWriteToken(rentedBuffer, inputBuffer, tcs, cancellationToken);

            // if the send operation doesn't complete synchronously, return the awaitable task
            if (socket.SendToAsync(args)) return tcs.Task;

            int result = args.BytesTransferred;

            sendBufferPool.Return(rentedBuffer, true);
            socketAsyncEventArgsPool.Return(args);

            return Task.FromResult(result);
        }

        public UdpServer() : base(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
        {
            clients = new ConcurrentDictionary<EndPoint, ConcurrentQueue<byte[]>>();

            for (int i = 0; i < MaxPooledObjects; i++)
            {
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                args.Completed += HandleIOCompleted;

                socketAsyncEventArgsPool.Return(args);
            }
        }

        /// <inheritdoc />
        public override async Task StartAsync()
        {
            EndPoint nullEndPoint = new IPEndPoint(IPAddress.Any, 0);
            byte[] receiveBuffer = new byte[ReceiveResult.PacketSize];
            Memory<byte> receiveBufferMemory = new Memory<byte>(receiveBuffer);

            while (true)
            {
                ReceiveResult result = await ReceiveAsync(nullEndPoint, SocketFlags.None, receiveBufferMemory);

                //Console.WriteLine($"[{result.RemoteEndPoint} > Server] : {Encoding.UTF8.GetString(result.Contents.Span)}");

                int sentBytes = await SendAsync(result.ClientArgs, result.Contents, SocketFlags.None);

                //Console.WriteLine($"[Server > {result.RemoteEndPoint}] Sent {sentBytes} bytes to {result.RemoteEndPoint}");
            }
        }

Код клиента:

public abstract class SocketClient
    {
        protected readonly Socket socket;

        protected SocketClient(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
        {
            socket = new Socket(addressFamily, socketType, protocolType);
        }

        public bool Bind(in EndPoint localEndPoint)
        {
            try
            {
                socket.Bind(localEndPoint);

                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);

                return false;
            }
        }

        public bool Connect(in EndPoint remoteEndPoint)
        {
            try
            {
                socket.Connect(remoteEndPoint);

                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);

                return false;
            }
        }

        public abstract Task<ReceiveResult> ReceiveAsync(EndPoint remoteEndPoint, SocketFlags socketFlags,
            Memory<byte> outputBuffer);

        public abstract Task<int> SendAsync(EndPoint remoteEndPoint, Memory<byte> inputBuffer, SocketFlags socketFlags);
    }

public class UdpClient : SocketClient
    {
        /// <inheritdoc />
        public UdpClient() : base(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
        {
        }

        public override async Task<ReceiveResult> ReceiveAsync(EndPoint remoteEndPoint, SocketFlags socketFlags,
            Memory<byte> outputBuffer)
        {
            byte[] buffer = new byte[ReceiveResult.PacketSize];

            SocketReceiveFromResult result =
                await socket.ReceiveFromAsync(new ArraySegment<byte>(buffer), socketFlags, remoteEndPoint);

            buffer.CopyTo(outputBuffer);

            return new ReceiveResult(default, outputBuffer, result.ReceivedBytes, result.RemoteEndPoint);

            /*
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SetBuffer(new byte[ReceiveResult.PacketSize]);
            args.SocketFlags = socketFlags;
            args.RemoteEndPoint = remoteEndPoint;
            SocketTask awaitable = new SocketTask(args);

            while (ReceiveResult.PacketSize > args.BytesTransferred)
            {
                await socket.ReceiveFromAsync(awaitable);
            }

            return new ReceiveResult(args.MemoryBuffer, args.RemoteEndPoint);
            */
        }

        public override async Task<int> SendAsync(EndPoint remoteEndPoint, Memory<byte> buffer, SocketFlags socketFlags)
        {
            return await socket.SendToAsync(buffer.ToArray(), socketFlags, remoteEndPoint);

            /*
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SetBuffer(buffer);
            args.SocketFlags = socketFlags;
            args.RemoteEndPoint = remoteEndPoint;
            SocketTask awaitable = new SocketTask(args);

            while (buffer.Length > args.BytesTransferred)
            {
                await socket.SendToAsync(awaitable);
            }

            return args.BytesTransferred;
            */
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...