8

私が理解しているように、TcpListenerを呼び出すと、 は接続をキューに入れますStart()AcceptTcpClient(または)を呼び出すたびにBeginAcceptTcpClient、キューから 1 つのアイテムがデキューされます。

一度に 1,000 の接続をアプリに送信して負荷テストを行うTcpListenerと、キューはクリアするよりもはるかに速く構築され、(最終的には) クライアントからのタイムアウトにつながります。列。ただし、サーバーに負荷がかかっているようには見えず、アプリは CPU 時間をあまり消費しておらず、マシン上で監視されている他のリソースも十分に機能していません。現在、私たちは十分に効率的に運営されていないように感じます。

呼び出しBeginAcceptTcpListenerてすぐにスレッドに引き渡し、ThreadPool実際に作業を実行してから、BeginAcceptTcpClient再度呼び出します。関連する作業は、マシンに圧力をかけるようには見えません。基本的には、3 秒間のスリープとそれに続く辞書の検索、そしてTcpClientのストリームへの 100 バイトの書き込みです。

使用しているTcpListenerコードは次のとおりです。

    // Thread signal.
    private static ManualResetEvent tcpClientConnected = new ManualResetEvent(false);

    public void DoBeginAcceptTcpClient(TcpListener listener)
    {
        // Set the event to nonsignaled state.
        tcpClientConnected.Reset();

        listener.BeginAcceptTcpClient(
            new AsyncCallback(DoAcceptTcpClientCallback),
            listener);

        // Wait for signal
        tcpClientConnected.WaitOne();
    }

    public void DoAcceptTcpClientCallback(IAsyncResult ar)
    {
        // Get the listener that handles the client request, and the TcpClient
        TcpListener listener = (TcpListener)ar.AsyncState;
        TcpClient client = listener.EndAcceptTcpClient(ar);

        if (inProduction)
            ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client, serverCertificate));  // With SSL
        else
            ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client));  // Without SSL

        // Signal the calling thread to continue.
        tcpClientConnected.Set();
    }

    public void Start()
    {
        currentHandledRequests = 0;
        tcpListener = new TcpListener(IPAddress.Any, 10000);
        try
        {
            tcpListener.Start();

            while (true)
                DoBeginAcceptTcpClient(tcpListener);
        }
        catch (SocketException)
        {
            // The TcpListener is shutting down, exit gracefully
            CheckBuffer();
            return;
        }
    }

答えは のSockets代わりにを使用するTcpListenerか、少なくとも を使用することに関連してTcpListener.AcceptSocketいると思いますが、どうやってそれを行うのだろうかと思いました。

私たちが持っていたアイデアの 1 つは、 を呼び出してAcceptTcpClient、すぐに複数のオブジェクトの 1 つに入れるというものでした。そうすれば、別のスレッド (スレッドごとに 1 つのキュー) でこれらのキューをポーリングでき、他の操作を待っている間にスレッドをブロックする可能性のあるモニターに遭遇することはありません。次に、各キュー スレッドを使用して、スレッド内で作業を完了させ、そのキュー内の次のキューからの取り出しに移ることができます。このアプローチをお勧めしますか、それとも私たちが使用している問題であり、迅速なデキューでは解決されませんか?EnqueueTcpClientQueue<TcpClient>DequeueThreadPool.QueueUserWorkItemThreadPoolTcpClientTcpListener

4

5 に答える 5

3

ソケットを直接使用するコードをいくつか作成しましたが、1000クライアントで負荷テストを実行する手段がありません。このコードが現在のソリューションとどのように比較されるかをテストしてみてください。現在、多くの接続も受け入れる必要があるサーバーを構築しているので、結果に非常に興味があります。

static WaitCallback handleTcpRequest = new WaitCallback(HandleTcpRequest);

static void Main()
{
    var e = new SocketAsyncEventArgs();
    e.Completed += new EventHandler<SocketAsyncEventArgs>(e_Completed);

    var socket = new Socket(
        AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    socket.Bind(new IPEndPoint(IPAddress.Loopback, 8181));
    socket.Listen((int)SocketOptionName.MaxConnections);
    socket.AcceptAsync(e);

    Console.WriteLine("--ready--");
    Console.ReadLine();
    socket.Close();
}

static void e_Completed(object sender, SocketAsyncEventArgs e)
{
    var socket = (Socket)sender;
    ThreadPool.QueueUserWorkItem(handleTcpRequest, e.AcceptSocket);
    e.AcceptSocket = null;
    socket.AcceptAsync(e);
}

static void HandleTcpRequest(object state)
{
    var socket = (Socket)state;
    Thread.Sleep(100); // do work
    socket.Close();
}
于 2010-04-30T15:14:56.097 に答える
2

私が何かを見逃していない限り、あなたは非同期のBeingAcceptTcpClientを呼び出していますが、非同期コードが終了するまで待つためにWaitOne()を呼び出しています。これにより、プロセスが効果的に同期されます。コードは、一度に 1 つのクライアントのみを受け入れることができます。それとも私は完全に狂っていますか?少なくとも、これは無駄なコンテキスト切り替えのように思えます。

于 2010-04-30T15:23:31.667 に答える
2

他の質問でほのめかされましたが、tcpListener.Start() メソッドで、バックログを一度に予想される最大接続数よりも高い数に設定できるオーバーロードを使用することをお勧めします:


    public void Start()
    {
        currentHandledRequests = 0;
        tcpListener = new TcpListener(IPAddress.Any, 10000);
        try
        {
            tcpListener.Start(1100);  // This is the backlog parameter

            while (true)
                DoBeginAcceptTcpClient(tcpListener);
        }
        catch (SocketException)
        {
            // The TcpListener is shutting down, exit gracefully
            CheckBuffer();
            return;
        }
    }

基本的に、このオプションは、Accept が呼び出されるのを待っている "保留中" の TCP 接続を許可する数を設定します。接続を受け入れる速度が十分でなく、このバックログがいっぱいになると、TCP 接続は自動的に拒否され、それらを処理する機会さえありません。

他の人が述べたように、他の可能性は、着信接続を処理する速度を高速化することです. ただし、受け入れ時間を短縮できる場合でも、バックログをより高い値に設定する必要があります。

于 2010-05-01T04:21:42.670 に答える
1

最初に自問することは、「一度に 1000 の接続が妥当か」ということです。個人的には、そのような状況に陥る可能性は低いと思います。短期間に 1000 件の接続が発生している可能性が高くなります。

サーバーフレームワークをテストするために使用するTCPテストプログラムがあります。これは、各バッチ間にZミリ秒のギャップがあるYのバッチで合計X接続などを実行できます。私が個人的に見つけたのは、「一度に膨大な数」よりも現実的な世界です。無料です。役立つかもしれません。ここから入手できます: http://www.lenholgate.com/blog/2005/11/windows-tcpip-server-performance.html

他の人が言っているように、リッスンのバックログを増やし、接続をより速く処理し、可能であれば非同期受け入れを使用してください...

于 2010-04-30T16:23:51.953 に答える
1

単なる提案: クライアントを同期的に受け入れ (のAcceptTcpClient代わりに使用してBeginAcceptTcpClient)、新しいスレッドでクライアントを処理してみませんか? そうすれば、次のクライアントを受け入れる前に、クライアントが処理されるのを待つ必要がなくなります。

于 2010-04-30T16:56:09.003 に答える