2

プールされたSocketAsyncEventArgsスタイルが、多くの同時接続を提供するサーバーのメモリ消費を削減するのにどのように役立つかわかりません。

はい、前述の MSDN ページでa System.IAsyncResult object be allocated for each asynchronous socket operation.

そして、初期の調査では、何らかの理由で、せいぜい一握りのバイト配列のみを割り当てて、同時に接続されている何千ものクライアント間でそれらを共有できると信じていました。

しかし、何千ものクライアント接続でデータを待ちたい場合はReceiveAsync、毎回異なるバイト配列 (SocketAsyncEventArgs にラップされる) を提供して、何千回も呼び出す必要があるようです。クライアントが送信することを決定する時間。おそらく 10 秒です。

したがって、クライアントがデータを送信する直前に ReceiveAsync を呼び出さない限り (または、その後、いくつかのネットワーク スタック バッファーに依存しますか?)、これはクライアントの裁量であり、サーバーにとって予測不可能です。運が悪く、バイト配列がそこに座って、クライアントがお尻を動かすのをぼんやりと待っています。

私は、単一のバイト配列(または、並列化が理にかなっている場合は、リスニングスレッドごとに単一の配列)を使用して数千の接続をリッスンし、それらの接続のいずれかが何かを送信すると(ネットワークスタックバッファに入る必要があります)とにかく最初に)、その配列にコピーされ、リスナーが呼び出され、リスナーが完了すると、配列を再利用できます。

これは本当に Socket.*Async() メソッドでは不可能ですか?

このようなことは、.net のソケット ライブラリでまったく可能ですか?

4

3 に答える 3

1

複数のソケット操作で同じメモリを共有することはできません (または共有した場合、未定義の結果が返されます)。

最初に 1 バイトだけを読み取ることで、この問題を回避できます。その読み取りが完了すると、さらに多くのデータが来る可能性があります。したがって、次の読み取りでは、4KB などのより効率的なサイズを使用します (または、プロパティを調べますDataAvailable。これは、そのプロパティの唯一の有効な使用例です)。

于 2015-04-08T13:03:30.350 に答える
0

MSDN の記事では、プーリングのしくみについて説明しています。基本的に:

a) 利用可能なプール インスタンスがある場合はそれを使用し、そうでない場合は新しいインスタンスを作成します。

b) 完了したら、インスタンスをプールに戻して再利用できるようにします。

最終的には、すべてのリクエストに対応できるようにプール サイズが大きくなります。または、たとえば、インスタンスに対するリクエストがあり、最大プール サイズに達し、プールが現在空である場合に、インスタンス数を最大にしてブロックするようにプールを構成することができます。この戦略により、プールが制御されない方法で成長するのを防ぎます。

于 2015-04-08T13:07:44.367 に答える
0

これは、usr の優れた回避策の提案を組み込んだbyte[1]実装のスケッチであり、パフォーマンスを犠牲にすることなく、やや面倒なSocket.xxxAsyncメソッドを に完全に隠す方法を示しています。SimpleAsyncSocket

を使用する単純な非同期エコー サーバーSimpleAsyncSocketは、次のようになります。

readonly static Encoding Enc = new UTF8Encoding(false);
SimpleAsyncSocket _simpleSocket;

void StartEchoServer(Socket socket)
{
    _simpleSocket = new SimpleAsyncSocket(socket, OnSendCallback,
        _receiveBufferPool, OnReceiveCallback);
}

bool OnReceiveCallback(SimpleAsyncSocket socket,
    ArraySegment<byte> bytes)
{
    var str = Enc.GetString(bytes.Array, bytes.Offset, bytes.Count);
    _simpleSocket.SendAsync(new ArraySegment<byte>(Enc.GetBytes(str)));
    return false;
}

void OnSendCallback(SimpleAsyncSocket asyncSocket,
    ICollection<ArraySegment<byte>> collection, SocketError arg3)
{
    var bytes = collection.First();
    var str = Enc.GetString(bytes.Array, bytes.Offset, bytes.Count);
}

実装のスケッチは次のとおりです。

class SimpleAsyncSocket
{
    private readonly Socket _socket;
    private readonly Pool<byte[]> _receiveBufferPool;
    private readonly SocketAsyncEventArgs _recvAsyncEventArgs;
    private readonly SocketAsyncEventArgs _sendAsyncEventArgs;
    private readonly byte[] _waitForReceiveEventBuffer = new byte[1];
    private readonly Queue<ArraySegment<byte>> _sendBuffers = new Queue<ArraySegment<byte>>();

    public SimpleAsyncSocket(Socket socket, Action<SimpleAsyncSocket, ICollection<ArraySegment<byte>>, SocketError> sendCallback,
        Pool<byte[]> receiveBufferPool, Func<SimpleAsyncSocket, ArraySegment<byte>, bool> receiveCallback)
    {
        if (socket == null) throw new ArgumentNullException("socket");
        if (sendCallback == null) throw new ArgumentNullException("sendCallback");
        if (receiveBufferPool == null) throw new ArgumentNullException("receiveBufferPool");
        if (receiveCallback == null) throw new ArgumentNullException("receiveCallback");

        _socket = socket;

        _sendAsyncEventArgs = new SocketAsyncEventArgs();
        _sendAsyncEventArgs.UserToken = sendCallback;
        _sendAsyncEventArgs.Completed += SendCompleted;

        _receiveBufferPool = receiveBufferPool;
        _recvAsyncEventArgs = new SocketAsyncEventArgs();
        _recvAsyncEventArgs.UserToken = receiveCallback;
        _recvAsyncEventArgs.Completed += ReceiveCompleted;
        _recvAsyncEventArgs.SetBuffer(_waitForReceiveEventBuffer, 0, 1);
        ReceiveAsyncWithoutTheHassle(_recvAsyncEventArgs);
    }

    public void SendAsync(ArraySegment<byte> buffer)
    {
        lock (_sendBuffers)
            _sendBuffers.Enqueue(buffer);
        StartOrContinueSending();
    }
    private void StartOrContinueSending(bool calledFromCompleted = false)
    {
        lock (_waitForReceiveEventBuffer) // reuse unrelated object for locking
        {
            if (!calledFromCompleted && _sendAsyncEventArgs.BufferList != null)
                return; // still sending
            List<ArraySegment<byte>> buffers = null;
            lock (_sendBuffers)
            {
                if (_sendBuffers.Count > 0)
                {
                    buffers = new List<ArraySegment<byte>>(_sendBuffers);
                    _sendBuffers.Clear();
                }
            }
            _sendAsyncEventArgs.BufferList = buffers; // nothing left to send
            if (buffers == null)
                return;
        }

        if (!_socket.SendAsync(_sendAsyncEventArgs))
            // Someone on stackoverflow claimed that invoking the Completed
            // handler synchronously might end up blowing the stack, which
            // does sound possible. To avoid that guy finding my code and
            // downvoting me for it (and maybe just because it's the right
            // thing to do), let's leave the call stack via the ThreadPool
            ThreadPool.QueueUserWorkItem(state => SendCompleted(this, _sendAsyncEventArgs));
    }
    private void SendCompleted(object sender, SocketAsyncEventArgs args)
    {
        switch (args.LastOperation)
        {
            case SocketAsyncOperation.Send:
                {
                    try
                    {
                        var bytesTransferred = args.BytesTransferred;
                        var sendCallback = (Action<SimpleAsyncSocket, ICollection<ArraySegment<byte>>, SocketError>)args.UserToken;
                        // for the moment, I believe the following commented-out lock is not
                        // necessary, but still have to think it through properly
                        // lock (_waitForReceiveEventBuffer) // reuse unrelated object for locking
                        {
                            sendCallback(this, args.BufferList, args.SocketError);
                        }
                        StartOrContinueSending(true);
                    }
                    catch (Exception e)
                    {
                        args.BufferList = null;
                        // todo: log and disconnect
                    }


                    break;
                }
            case SocketAsyncOperation.None:
                break;
            default:
                throw new Exception("Unsupported operation: " + args.LastOperation);
        }
    }
    private void ReceiveCompleted(object sender, SocketAsyncEventArgs args)
    {
        switch (args.LastOperation)
        {
            case SocketAsyncOperation.Receive:
                {
                    var bytesTransferred = args.BytesTransferred;
                    var buffer = args.Buffer;
                    if (args.BytesTransferred == 0) // remote end closed connection
                    {
                        args.SetBuffer(null, 0, 0);
                        if (buffer != _waitForReceiveEventBuffer)
                            _receiveBufferPool.Return(buffer);

                        // todo: disconnect event
                        return;
                    }
                    if (buffer == _waitForReceiveEventBuffer)
                    {
                        if (args.BytesTransferred == 1)
                        {
                            // we received one byte, there's probably more!
                            var biggerBuffer = _receiveBufferPool.Take();
                            biggerBuffer[0] = _waitForReceiveEventBuffer[0];
                            args.SetBuffer(biggerBuffer, 1, biggerBuffer.Length - 1);
                            ReceiveAsyncWithoutTheHassle(args);
                        }
                        else
                            throw new Exception("What the heck");
                    }
                    else
                    {
                        var callback = (Func<SimpleAsyncSocket, ArraySegment<byte>, bool>)args.UserToken;
                        bool calleeExpectsMoreDataImmediately = false;
                        bool continueReceiving = false;
                        try
                        {
                            var count = args.Offset == 1
                                            // we set the first byte manually from _waitForReceiveEventBuffer
                                            ? bytesTransferred + 1
                                            : bytesTransferred;
                            calleeExpectsMoreDataImmediately = callback(this, new ArraySegment<byte>(buffer, 0, count));
                            continueReceiving = true;
                        }
                        catch (Exception e)
                        {
                            // todo: log and disconnect
                        }
                        finally
                        {
                            if (!calleeExpectsMoreDataImmediately)
                            {
                                args.SetBuffer(_waitForReceiveEventBuffer, 0, 1);
                                _receiveBufferPool.Return(buffer);
                            }
                        }
                        if (continueReceiving)
                            ReceiveAsyncWithoutTheHassle(args);
                    }
                    break;
                }
            case SocketAsyncOperation.None:
                break;
            default:
                throw new Exception("Unsupported operation: " + args.LastOperation);
        }
    }

    private void ReceiveAsyncWithoutTheHassle(SocketAsyncEventArgs args)
    {
        if (!_socket.ReceiveAsync(args))
            // Someone on stackoverflow claimed that invoking the Completed
            // handler synchronously might end up blowing the stack, which
            // does sound possible. To avoid that guy finding my code and
            // downvoting me for it (and maybe just because it's the right
            // thing to do), let's leave the call stack via the ThreadPool
            ThreadPool.QueueUserWorkItem(state => ReceiveCompleted(this, args));
    }
}
于 2015-04-09T04:23:56.023 に答える