Почему NetworkStream читается так?

У меня есть приложение, которое отправляет сообщения, которые заканчиваются новой строкой через сокет TCP, используя TCPClient, и он находится в сети NetworkStream.

Данные транслируются примерно в 28k каждые 100 мс из потока данных реального времени для мониторинга.

Я удалил ненужный код, это в основном то, как мы читаем данные:

TcpClient socket; // initialized elsewhere
byte[] bigbuffer = new byte[0x1000000];
socket.ReceiveBufferSize = 0x1000000;
NetworkStream ns = socket.GetStream();
int end = 0;
int sizeToRead = 0x1000000;
while (true)
{
  bytesRead = ns.Read(bigbuffer, end, sizeToRead);
  sizeToRead -= bytesRead;
  end += bytesRead;

  // check for newline in read buffer, and if found, slice it up, and return
  // data for deserialization in another thread

  // circular buffer
  if (sizeToRead == 0)
  {
    sizeToRead = 0x1000000;
    end = 0;
  }
}

Симптом, который мы наблюдали, несколько прерывисто, основываясь на количестве данных, которые мы отправляли назад, заключается в том, что будет "отставание" от записей, где данные, которые мы читаем из потока, постепенно становятся старше и старше по сравнению с то, что мы доставляем (через несколько минут потоковой передачи, отставание составляет порядка 10 секунд), до тех пор, пока в конце концов все это не будет достигнуто одним большим выстрелом, и цикл повторяется.

Мы исправили его с помощью maxing out sizeToRead и (независимо от того, требуется ли это, я не уверен, но мы все равно это сделали), удалил набор ReceiveBufferSize, установленный на TcpClient, и сохранил его по умолчанию 8192 (меняя только ReceiveBufferSize не исправил его).

int sizeForThisRead = sizeToRead > 8192 ? 8192 : sizeToRead;
bytesRead = ns.Read(bigBuffer, end, sizeForThisRead);

Я подумал, что, возможно, это было взаимодействие с nagle и delayed ack, но wirehark показал, что данные поступают очень хорошо, основываясь на временных меток и просматривая данные (это временная метка, а серверные и клиентские часы синхронизируются в пределах второй).

Мы выводим журналы после ns.Read, и, несомненно, проблема связана с вызовом Read, а не с десериализационным кодом.

Так что это заставляет меня поверить, что если вы установите TcpClient ReceiveBufferSize действительно большим, и в своем вызове Read на нем, который находится в сети NetworkStream, pass bytesToRead будет намного больше байтов, чем ожидалось, там будет тайм-аут в Read вызывать ожидание появления этих байтов, но он все равно не возвращает все в потоке? Каждый последующий вызов в этом цикле истекает до тех пор, пока 1 мегабайт не будет заполнен, после чего, когда "end" получит reset обратно в 0, он вдыхает все, что осталось в потоке, заставляя все это догнать - но это должно Это не так, потому что логика для меня похожа на то, что она должна полностью очистить поток на следующей итерации (поскольку следующий размер будет доступен в буфере).

Или, может быть, это то, о чем я не думаю, что я не могу синтезировать, но, возможно, эти умные души здесь могут что-то придумать.

Или, может быть, это ожидаемое поведение - если да, то почему?

Ответ 1

Такое поведение было настолько интересным, что мне просто нужно было это увидеть для себя, и... я не мог.

Этот антиответ дает альтернативную теорию, которая может объяснить отставание, описанное в вопросе. Мне пришлось вывести некоторые детали из вопроса и комментариев.

Целевое приложение представляет собой интерактивное приложение пользовательского интерфейса с тремя потоками:

  • A TcpClient сетевой пользователь данных.
  • Потребительский поток очереди данных, который поставляет результаты в пользовательский интерфейс.
  • Пользовательский интерфейс.

В целях этого обсуждения предположим, что TheDataQueue является экземпляром BlockingCollection<string> (любая потоковая очередь должна делать):

BlockingCollection<string> TheDataQueue = new BlockingCollection<string>(1000);

Приложение имеет две синхронные операции, которые блокируются при ожидании данных. Первый - вызов NetworkStream.Read, который является основным предметом вопроса:

bytesRead = ns.Read(bigbuffer, end, sizeToRead);

Вторая операция блокировки происходит, когда данные в рабочей очереди сопоставляются с пользовательским интерфейсом для отображения. Предположим, что код выглядит следующим образом:

// A member method on the derived class of `System.Windows.Forms.Form` for the UI.
public void MarshallDataToUI()
{
    // Current thread: data queue consumer thread.
    // This call blocks if the data queue is empty.
    string text = TheDataQueue.Take();

    // Marshall the text to the UI thread.
    Invoke(new Action<string>(ReceiveText), text);
}

private void ReceiveText(string text)
{
    // Display the text.
    textBoxDataFeed.Text = text;

    // Explicitly process all Windows messages currently in the message queue to force
    // immediate UI refresh.  We want the UI to display the very latest data, right?
    // Note that this can be relatively slow...
    Application.DoEvents();
}

В этом дизайне приложения наблюдаемое отставание происходит, когда сеть доставляет данные до TheWorkQueue быстрее, чем пользовательский интерфейс может отображать его.

Почему журналы @paquetp отображают проблему с NetworkStream.Read?

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

Как вы объясните, что в конечном итоге все это настигает один большой выстрел?

Это естественное следствие потребительского потока очереди данных, работающего через отставание в TheDataQueue.

Но как насчет времени захвата пакетов и временных данных?

Если элемент не зарегистрирован в TheDataQueue, отметки времени данных верны. Но вы еще не видите их в пользовательском интерфейсе. Временные метки захвата пакетов являются своевременными, поскольку сетевые данные были получены и быстро поставлены в очередь сетевым потоком.

Разве это не просто догадки?

Неа. Существует пара настраиваемых приложений (производитель и потребитель), которые демонстрируют это поведение.

Network Consumer App Screenshot

Снимок экрана показывает, что очередь данных отстает на 383 элемента. Временная метка данных задерживает текущую временную метку примерно на 41 секунду. Несколько раз я приостановил работу продюсера, чтобы имитировать всплеск сетевых данных.

Однако я никогда не мог заставить NetworkStream.Read вести себя как предполагаемый вопрос.

Ответ 2

TcpClient.NoDelay Свойство получает или устанавливает значение, которое отключает задержку, когда буферы отправки или получения не заполняются.

Когда NoDelay - false, a TcpClient не отправляет пакет по сети, пока не собрал значительное количество исходящих данных. Из-за количества накладных расходов в сегменте TCP передача небольших объемов данных неэффективна. Однако существуют ситуации, когда вам нужно отправлять очень маленькие объемы данных или ожидать немедленных ответов от каждого отправляемого вами пакета. Ваше решение должно учитывать относительную важность эффективности сети и требований приложения.

Источник: http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.nodelay(v = vs. 110).aspx

Интерпретация Push Bit По умолчанию TCP/IP Windows Server 2003 завершает вызов recv(), когда выполняется одно из следующих условий:

  • Данные поступают с установленным битом PUSH
  • Пользовательский recv-буфер заполнен.
  • Прошло 0,5 секунды с момента поступления каких-либо данных.

Если клиентское приложение запускается на компьютере с реализацией TCP/IP, которая не устанавливает бит push при отправке, может возникнуть задержка ответа. Лучше всего исправить это на клиенте; однако в Afd.sys был добавлен параметр конфигурации (IgnorePushBitOnReceives), чтобы заставить его обрабатывать все поступающие пакеты, как если бы был установлен бит push.

Попробуйте уменьшить размер буфера, чтобы принудительная реализация сети поставщика установила бит PSH.

Источник: http://technet.microsoft.com/en-us/library/cc758517 (WS.10).aspx (в разделе Push Bit Interpretation) Источник: http://technet.microsoft.com/en-us/library/cc781532 (WS.10).aspx (под IgnorePushBitOnReceives)