2017-01-31 8 views
0

Я читал о TCP-пакете и о том, как их можно разделить сколько угодно раз во время рейса. Я принял это, чтобы предположить, что мне понадобится реализовать какой-то буфер в верхней части буфера, используемого для фактического сетевого трафика, чтобы хранить каждый ReceiveAsync() до тех пор, пока не будет достаточно данных для синтаксического анализа сообщения. Кстати, я отправляю длинные префиксные, протобуф-сериализованные сообщения по TCP.Как поступает пакет TCP при использовании Socket api в C#

Затем я прочитал, что нижние слои (ethernet ?, IP?) Будут фактически собирать пакеты прозрачно.

Вопрос, на C#, я гарантированно получаю полное «сообщение» по TCP? Другими словами, если я отправлю 32 байта, я обязательно получу эти 32 байта в «одноразовом» (один звонок до ReceiveAsync())? Или мне нужно «хранить» каждый прием до тех пор, пока количество полученных байтов не будет равно префиксу длины?

Кроме того, могу ли я получить еще, чем одно сообщение в один звонок по номеру ReceiveAsync()? Скажем, одно сообщение «protobuf» составляет 32 байта. Я отправляю 2 из них. Могу ли я получить 48 байтов в «один раз», а затем 16 в другом?

Я знаю, что этот вопрос легко появляется в Google, но я не могу сказать, соответствует ли он в правильном контексте (речь идет о фактическом протоколе TCP или о том, как C# будет показывать сетевой трафик программисту).

Спасибо.

+0

В то время как данные TCP могут быть разделены и проходить разные маршруты во время рейса, достаточно сказать, что это на 100% прозрачно для приложений. Все, что вам нужно сделать, это получить его в безопасности, зная, что оно собрано для вас. Это НЕ относится к UDP – MickyD

+1

@MickyD Пока ваши утверждения верны, я думаю, что вам не хватает точки вопроса. Он спрашивает о количестве данных, доступных для приложения в любой момент. –

+0

@JonathonReinhart Вот почему это комментарий не ответ – MickyD

ответ

1

TCP - это протокол потока - он передает поток байтов. Это все. Абсолютно не подразумевается кадрирование/группировка сообщений. На самом деле вы должны забыть, что Ethernet-пакеты или IP-датаграммы существуют даже при написании кода с использованием сокета TCP.

Возможно, вам доступно 1 байт или 10 000 байт, доступных для чтения. Красота (синхронного) Berkeley sockets API заключается в том, что вам, как программисту, не нужно беспокоиться об этом. Поскольку вы используете формат сообщений с префиксом длины (хорошая работа!) Просто recv() столько байтов, сколько вы ожидаете. Если есть более байтов, доступных, чем запросы приложений, ядро ​​будет поддерживать остальную буферизацию до следующего вызова. Если есть меньше байт, доступных, чем требуется, поток будет либо блокироваться, либо вызов будет указывать на то, что было получено меньше байтов. В этом случае вы можете просто спать снова, пока не будут доступны данные.

Проблема с асинхронными API заключается в том, что для приложения требуется отслеживать гораздо большее состояние. Даже это Microsoft example of Asynchronous Client Sockets намного сложнее, чем должно быть. С асинхронными API-интерфейсами вы по-прежнему контролируете объем данных, запрашиваемых ядром, но когда ваш асинхронный обратный вызов запущен, вам необходимо знать следующий объем данных для запроса.

Обратите внимание, что C# async/await в 4.5 упрощает асинхронную обработку, так как вы можете сделать это синхронно. Взгляните на this answer, где автор комментирует:

Socket.ReceiveAsync является странным. Это не имеет никакого отношения к функциям async/await в .net4.5. Он был разработан как альтернативный API сокетов, который бы не разбивал память так сильно, как BeginReceive/EndReceive, и ее нужно использовать только в самых хардкорных серверных приложениях.

+0

Спасибо за ответ. Таким образом, это означает, что если я использую новый (er) 'SocketAsyncEventArgs', мне придется реализовать собственное кадрирование сообщений (например, сокеты berkeley api, которые вы упомянули)? –

+0

Более или менее, да. Если вам не требуется серьезное количество параллельных подключений, я бы сначала написал ваш код для использования синхронных API-интерфейсов, создав новый поток для обработки каждого соединения, если это необходимо. Затем рассмотрите возможность использования последней поддержки async в .NET для обработки большего количества подключений в меньшем количестве потоков. –

+0

Когда вы говорите «серьезно массивные», о чем мы говорим? Я создаю (очень простой) игровой сервер MMO, поэтому думаю, что 10k + соединения, все отправляющие 2 или 3 позиции обновляют секунду плюс любую другую информацию. Прежде чем вы скажете мне, что это невозможно, это просто для образовательных целей, а не для фактического производства :) –

1

Мой вопрос, в C#, я гарантированно получить полный "сообщение" через TCP?

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

Смотрите пример здесь, он хранит данные чтения в буфере и продолжает проверять, если есть больше данных для чтения:

private static void ReceiveCallback(IAsyncResult ar) 
{ 
    try 
    { 
     // Retrieve the state object and the client socket 
     // from the asynchronous state object. 
     StateObject state = (StateObject)ar.AsyncState; 
     Socket client = state.workSocket; 
     // Read data from the remote device. 
     int bytesRead = client.EndReceive(ar); 
     if (bytesRead > 0) 
     { 
      // There might be more data, so store the data received so far. 
      state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead)); 
      // Get the rest of the data. 
      client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, 
       new AsyncCallback(ReceiveCallback), state); 
     } 
     else 
     { 
      // All the data has arrived; put it in response. 
      if (state.sb.Length > 1) 
      { 
       response = state.sb.ToString(); 
      } 
      // Signal that all bytes have been received. 
      receiveDone.Set(); 
     } 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e.ToString()); 
    } 
} 

См this MSDN статьи и this статьи для более подробной информации. Вторая ссылка идет на более подробные сведения, а также содержит пример кода.

0

TCP - это протокол октета, основанный на потоке. Таким образом, с точки зрения приложения вы можете читать или писать только байт.

Я читал о TCP-пакете и о том, как их можно разделить сколько угодно раз во время рейса.

TCP-пакеты представляют собой детали реализации сети. Они используются для повышения эффективности (было бы очень неэффективно отправлять один байт за раз). Фрагментация пакетов выполняется на уровне драйвера/аппаратного уровня устройства и никогда не подвергается действиям приложений. Приложение никогда не знает, что такое «пакет» или где его границы.

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

Да. Потому что «сообщение» не является концепцией TCP. Это чисто концепция приложения. Большинство протоколов приложений определяют своеобразное «сообщение», потому что это легче рассуждать.

Некоторые протоколы приложений, однако, не определяют концепцию «сообщения»; они обрабатывают поток TCP как фактический поток, а не последовательность сообщений.

Чтобы поддерживать протоколы приложений обоих типов, API TCP/IP имеют, которые должны быть потоковыми.

К сожалению, я отправляю длинные префиксные, протобуф-сериализованные сообщения по TCP.

Это хорошо. Префикс длины намного легче справиться, чем альтернативы, ИМО.

Вопрос, на C#, я гарантированно получаю полное «сообщение» по TCP?

No.

Или я должен «магазин» каждый получит, пока количество байт, полученных не равна длине префикса?Кроме того, могу ли я получить более одного сообщения в одном вызове ReceiveAsync()?

Да, и да.

Еще больше удовольствия:

  • Вы можете получить только часть вашего префикса длины (предполагается, что префикс длины многобайтного).
  • Вы можете получить любое количество сообщений одновременно.
  • Ваш буфер может содержать часть сообщения или часть префикса длины сообщения.
  • Следующее чтение не может завершить текущее сообщение или даже префикс длины текущего сообщения.

Для получения дополнительной информации о деталях, увидеть мой TCP/IP .NET FAQ, в частности, разделы о message framing и некоторых example code for length-prefixed messages.

Я настоятельно рекомендую использовать только асинхронные API-интерфейсы; синхронная альтернатива наличия двух потоков на одно соединение отрицательно влияет на масштабируемость.

О, и я также всегда рекомендую использовать SignalR, если это возможно. Сырое программирование сокетов TCP/IP всегда сложное.