Медлительность чтения .NET NetworkStream

У меня есть сетевой код для обработки произвольного TCP-соединения.

Кажется, все работает так, как ожидалось, но кажется медленным. Когда я профилировал код, кажется, что он тратит 600 мс в NetworkStream.Read(), и мне интересно, как его улучшить. Я возился с размерами буфера и чередовал массивный буфер для чтения всех данных за один раз или небольшой буфер, который должен объединять данные в StringBuilder. В настоящее время клиент, который я использую, представляет собой веб-браузер, но этот код является общим, и вполне возможно, что ему отправляются не данные HTTP. Любые идеи?

Мой код таков:

    public void StartListening()
    {
        try
        {
            lock (oSyncRoot)
            {
                oTCPListener = new TcpListener(oIPaddress, nPort);

                // fire up the server
                oTCPListener.Start();

                // set listening bit
                bIsListening = true;
            }

            // Enter the listening loop.
            do
            {
                // Wait for connection
                TcpClient newClient = oTCPListener.AcceptTcpClient();

                // queue a request to take care of the client
                oThreadPool.QueueUserWorkItem(new WaitCallback(ProcessConnection), newClient);
            }
            while (bIsListening);
        }
        catch (SocketException se)
        {
            Logger.Write(new TCPLogEntry("SocketException: " + se.ToString()));
        }
        finally
        {
            // shut it down
            StopListening();
        }
    }

    private void ProcessConnection(object oClient)
    {

        TcpClient oTCPClient = (TcpClient)oClient;
        try
        {
            byte[] abBuffer = new byte[1024];
            StringBuilder sbReceivedData = new StringBuilder();

            using (NetworkStream oNetworkStream = oTCPClient.GetStream())
            {
                // set initial read timeout to nInitialTimeoutMS to allow for connection
                oNetworkStream.ReadTimeout = nInitialTimeoutMS;

                int nBytesRead = 0;

                do
                {
                    try
                    {
                        bool bDataAvailable = oNetworkStream.DataAvailable;

                        while (!bDataAvailable)
                        {
                           Thread.Sleep(5);
                           bDataAvailable = oNetworkStream.DataAvailable;
                        }

                        nBytesRead = oNetworkStream.Read(abBuffer, 0, abBuffer.Length);

                        if (nBytesRead > 0)
                        {
                            // Translate data bytes to an ASCII string and append
                            sbReceivedData.Append(Encoding.UTF8.GetString(abBuffer, 0, nBytesRead));
                            // decrease read timeout to nReadTimeoutMS second now that data is coming in
                            oNetworkStream.ReadTimeout = nReadTimeoutMS;

                        }
                    }
                    catch (IOException)
                    {
                        // read timed out, all data has been retrieved
                        nBytesRead = 0;
                    }
                }
                while (nBytesRead > 0);

                //send the data to the callback and get the response back
                byte[] abResponse = oClientHandlerDelegate(sbReceivedData.ToString(), oTCPClient);
                if (abResponse != null)
                {
                    oNetworkStream.Write(abResponse, 0, abResponse.Length);
                    oNetworkStream.Flush();
                }
            }
        }
        catch (Exception e)
        {
            Logger.Write(new TCPLogEntry("Caught Exception " + e.StackTrace));
        }
        finally
        {
            // stop talking to client
            if (oTCPClient != null)
            {
                oTCPClient.Close();
            }
        }
    }

Изменить: я получаю примерно одинаковые цифры на двух совершенно разных машинах (моя машина для разработки XP и коробка 2003 года в цвете). Я добавил время в код для соответствующих частей (используя System.Diagnostic.StopWatch) и выгрузил его в журнал:

7/6/2009 3:44:50 PM : Debug : While DataAvailable took 0 ms
7/6/2009 3:44:50 PM : Debug : Read took 531 ms
7/6/2009 3:44:50 PM : Debug : ProcessConnection took 577 ms

person Gordon Thompson    schedule 06.07.2009    source источник
comment
У меня такая же проблема. Я вижу ваше решение в этом посте, но как насчет nReadTimeOutMS и bTurboMode. Не могли бы вы дать мне полное описание? Я очень заинтересован и признателен, если вы поделитесь со мной этим классом. Заранее спасибо.   -  person olidev    schedule 08.06.2011
comment
Моя реализация была очень простой, я переписал большую ее часть после этого поста, чтобы сделать это правильно. Суть дела в том, что вы полагаетесь только на тайм-ауты и т. д., если отправляющий клиент не сообщает вам, сколько данных отправляется. Моя недавняя реализация проверяла заголовки, чтобы увидеть, сколько данных было отправлено, и когда все было прочитано, я перестал читать.   -  person Gordon Thompson    schedule 09.06.2011


Ответы (3)


Я рекомендую вам использовать Microsoft Network Monitor или что-то подобное, чтобы увидеть, что происходит в течение этих 600 мс. NetworkStream — это сетевое программное обеспечение, и, глядя на его поведение, всегда учитывайте, что делает сеть.

person John Saunders    schedule 06.07.2009

Еще один голос за использование программного обеспечения для мониторинга сети. Подойдет либо сетевой монитор, либо WireShark. Убедитесь, что вы записали время начала и завершения вызова networkstream.read в вашей программе, чтобы вы могли знать, где в записанном сетевом трафике произошли события вашей программы.

Кроме того, я бы рекомендовал подождать, пока свойство NetworkStream.DataAvailable станет истинным, прежде чем вызывать метод Read, а также записать время, когда оно станет истинным. Если ваш сетевой монитор показывает, что данные прибывают за 600 мс до того, как ваша программа указывает, что они могут быть прочитаны, что-то еще на вашем компьютере может задерживать пакет, например. антивирус или ваш брандмауэр.

Приложение 06.07.2009, 15:12 по восточному поясному времени:

Дополнительная информация о времени, которую вы опубликовали, интересна. Если данные доступны, почему их чтение занимает так много времени? Я запустил ваш код на своей машине для разработки, и как ожидание доступных данных, так и сама функция чтения выходят за 0 миллисекунд. Вы уверены, что у вас установлены последние пакеты обновлений и т. д.? Я использую Visual Studio Professional 2005 с .NET 2.0.50727. У меня также установлены .NET 3.0 и 3.5, но я не думаю, что VS 2005 их использует. У вас есть свежая установка ОС (реальная или виртуальная машина) без дополнительных программ (даже/особенно тех, которые «требуются» корпоративным ИТ), которые вы могли бы попробовать?

Вот код, который я запустил:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Diagnostics;

namespace stackoverflowtest
{
    class Program
    {

        static private object oSyncRoot = new object();

        static private TcpListener oTCPListener;

        static private IPAddress oIPaddress = IPAddress.Parse("10.1.1.109");

        static private int nPort = 8009;

        static bool bIsListening = true;





        static void Main(string[] args)
        {
            StartListening();
            Thread.Sleep(500000);
            bIsListening = false;
        }

        public static void StartListening()
        {
            try
            {
                lock (oSyncRoot)
                {
                    oTCPListener = new TcpListener(oIPaddress, nPort);

                    // fire up the server
                    oTCPListener.Start();

                    // set listening bit
                    bIsListening = true;
                }

                // Enter the listening loop.
                do
                {
                    // Wait for connection
                    TcpClient newClient = oTCPListener.AcceptTcpClient();



                    // queue a request to take care of the client
                    ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessConnection), newClient);
                }
                while (bIsListening);
            }
            catch (SocketException se)
            {
                Console.WriteLine("SocketException: " + se.ToString());
            }
            finally
            {
                // shut it down
                //StopListening();
            }
        }

        private static void ProcessConnection(object oClient)
        {

            TcpClient oTCPClient = (TcpClient)oClient;
            try
            {
                byte[] abBuffer = new byte[1024];
                StringBuilder sbReceivedData = new StringBuilder();

                using (NetworkStream oNetworkStream = oTCPClient.GetStream())
                {
                    int nInitialTimeoutMS = 1000;
                    // set initial read timeout to nInitialTimeoutMS to allow for connection
                    oNetworkStream.ReadTimeout = nInitialTimeoutMS;

                    int nBytesRead = 0;

                    do
                    {
                        try
                        {
                            bool bDataAvailable = oNetworkStream.DataAvailable;
                            Stopwatch sw = new Stopwatch();
                            while (!bDataAvailable)
                            {
                                Thread.Sleep(5);
                                bDataAvailable = oNetworkStream.DataAvailable;
                            }
                            Console.WriteLine("DataAvailable loop took " + sw.ElapsedMilliseconds);

                            sw.Reset();
                            nBytesRead = oNetworkStream.Read(abBuffer, 0, abBuffer.Length);
                            Console.WriteLine("Reading " + nBytesRead + " took " + sw.ElapsedMilliseconds);
                            if (nBytesRead > 0)
                            {
                                // Translate data bytes to an ASCII string and append
                                sbReceivedData.Append(Encoding.UTF8.GetString(abBuffer, 0, nBytesRead));
                                // decrease read timeout to nReadTimeoutMS second now that data is coming in
                                int nReadTimeoutMS = 100;
                                oNetworkStream.ReadTimeout = nReadTimeoutMS;

                            }
                        }
                        catch (IOException)
                        {
                            // read timed out, all data has been retrieved
                            nBytesRead = 0;
                        }
                    }
                    while (nBytesRead > 0);

                    byte[] abResponse = new byte[1024];
                    for (int i = 0; i < abResponse.Length; i++)
                    {
                        abResponse[i] = (byte)i;
                    }
                    oNetworkStream.Write(abResponse, 0, abResponse.Length);
                    oNetworkStream.Flush();

                    //send the data to the callback and get the response back
                    //byte[] abResponse = oClientHandlerDelegate(sbReceivedData.ToString(), oTCPClient);
                    //if (abResponse != null)
                    //{
                    //    oNetworkStream.Write(abResponse, 0, abResponse.Length);
                    //    oNetworkStream.Flush();
                    //}
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Caught Exception " + e.StackTrace);
            }
            finally
            {
                // stop talking to client
                if (oTCPClient != null)
                {
                    oTCPClient.Close();
                }
            }
        }

    }
}
person Sam Skuce    schedule 06.07.2009
comment
Я пробовал работать на абсолютно чистой машине и все равно получаю то же отставание. Однако запуск вашего кода не отстает, поэтому он должен быть таким, как я его называю. Кстати; ваш код не запускает секундомер снова после сброса, когда я думаю, что должен? - person Gordon Thompson; 07.07.2009
comment
Вы работаете на другой машине, но в той же сети... Посмотрите с помощью сетевого монитора! - person John Saunders; 07.07.2009
comment
Запуск кода sskuce на той же машине не отстает, так что, конечно, это не может быть сеть? - person Gordon Thompson; 07.07.2009
comment
Кстати, он работает на локальном хосте, наверняка это исключает проблемы с сетью? - person Gordon Thompson; 07.07.2009
comment
Упс, я неправильно использовал секундомер. Я даже не знал, что он существует, пока вы не упомянули об этом (спасибо!), поэтому я не знал, что для выполнения своей работы требуется так много няни. Однако это не имеет значения, все равно получается 0 мс для всего. Я вижу ниже, что вы не застрахованы от подобных проблем, поэтому я не чувствую себя так плохо. - person Sam Skuce; 07.07.2009

После некоторых дополнительных исследований кажется, что единственный способ ускорить это - прерваться после прочтения первых x байтов. Задержка, кажется, на втором чтении. Если я изменю буфер на 8096 байт (вероятно, максимальное мое приложение будет отправлено за один раз) и сломаюсь здесь:

        if (nBytesRead > 0)
        {
             // Translate data bytes to an ASCII string and append
             sbReceivedData.Append(Encoding.UTF8.GetString(abBuffer, 0, nBytesRead));

            if (bTurboMode)
            {
                  break;
            }
            else
            {
                  // decrease read timeout to nReadTimeoutMS second now that data is coming in
                  oNetworkStream.ReadTimeout = nReadTimeoutMS;
            }
        }

Затем время отклика увеличивается с 600 мс до примерно 80 мс. Это приемлемое решение для меня в настоящее время. Я могу переключить bTurboMode из вызывающего приложения и существенно ускорить процесс для этого случая.

person Gordon Thompson    schedule 07.07.2009
comment
Это хакерский способ сделать это, и его не следует делать таким образом. Я переписал все это, чтобы использовать переменные заголовка HTTP, чтобы я знал, сколько данных я ожидал... - person Gordon Thompson; 09.06.2011