Создание сообщения TCP + recv () [linux]: Хорошие соглашения? - PullRequest
2 голосов
/ 16 ноября 2011

Я пытаюсь создать p2p-приложения в Linux, которые я хочу запускать максимально эффективно.

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

Итак, на данный момент моя структура пакета:

(u16int Packet Length):(Packet Data)

, для которой требуется два вызова recv ();один для получения размера пакета и один для получения пакета.

С этим связаны две основные проблемы:

1. A malicious peer could send a packet with a size header of 
  something large, but not send any more data. The application will 
  hang on the second recv(), waiting for data that will never come.
2. Assuming that calling Recv() has a noticeable performance penalty
  (I actually have no idea, correct me if I am wrong) calling Recv() twice 
  will slow the program down.

Каков наилучший способ структурирования пакетов / системы приема для обоихлучшая эффективность и стабильность?Как это делают другие приложения?Что порекомендуете?

Заранее спасибо.

Ответы [ 5 ]

5 голосов
/ 16 ноября 2011

Я думаю, что ваше "кадрирование" сообщений в потоке TCP правильно.

Вы могли бы рассмотреть вопрос о том, чтобы поместить "волшебный файл cookie" перед каждым кадром (например, написать 32-битное int "0xdeadbeef").в верхней части каждого заголовка кадра в дополнение к длине пакета), так что становится очевидным, что вы читаете заголовок кадра в первой из каждой пары recv ().Если магическое целое число отсутствует в начале сообщения, вы вышли из синхронизации и вам нужно разорвать соединение.

Множественные вызовы recv (), скорее всего, не повлияют на производительность.На самом деле, поскольку сообщения TCP могут быть сегментированы, объединены и заблокированы непредсказуемым образом, вам, вероятно, придется вызывать recv () в цикле, пока вы не получите все ожидаемые данные.Это включает в себя ваш двухбайтовый заголовок, а также для большего чтения байтов полезной нагрузки.Вполне возможно, что вы вызываете "recv" с 2-байтовым буфером, чтобы прочитать "размер" сообщения, но получите только 1 байт назад.(Снова вызовите recv, и вы получите последующие байты).Что я говорю разработчикам в моей команде - кодируйте ваши сетевые парсеры так, как если бы это было возможно, чтобы recv доставлял только 1 байт за раз.

Вы можете использовать неблокирующие сокеты и вызов "select", чтобы избежать зависания,Если данные не поступают в течение разумного промежутка времени (или поступает больше данных, чем ожидалось - так что синхронизация следующего сообщения становится невозможной), вы просто разрываете соединение.

Я работаю надмой собственный P2P-проект.Хотел бы торговать векселями.Следите за мной в автономном режиме, если хотите.

2 голосов
/ 20 ноября 2011

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

Создатьбуфер для каждого клиента и использовать неблокирующие сокеты и select / poll / epoll / kqueue.Если есть данные, доступные от клиента, прочитайте как можно больше, не имеет значения, если вы читаете больше «пакетов».Затем проверьте, достаточно ли вы прочитали, чтобы поле размера было доступно, если это так, проверьте, что вы прочитали весь пакет (или больше).Если так, обработайте пакет.Тогда, если есть больше данных, вы можете повторить эту процедуру.Если остался частичный пакет, вы можете переместить его в начало буфера или использовать циклический буфер, чтобы вам не приходилось делать эти memmove-s.

Время ожидания клиента может быть обработано в вашем select / ... loop.

Это то, что я бы использовал, если вы делаете что-то сложное с полученными пакетными данными.Если все, что вам нужно сделать, это записать результаты в файл (большими блоками), то sendfile / splice даст лучшую производительность.Просто прочитайте длину пакета (может быть несколько операций чтения), затем используйте несколько вызовов для отправки файла, пока вы не прочитаете весь пакет (отследите, сколько осталось прочитать).

1 голос
/ 20 ноября 2011

TCP является потоково-ориентированным протоколом - на самом деле он не имеет никакого понятия о пакетах. Таким образом, в дополнение к получению нескольких пакетов уровня приложения в одном вызове recv() вы также можете получить только часть пакета уровня приложения, а остаток поступит в будущем вызове recv().

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

Чтобы всегда получать как можно больше данных на каждом recv(), без блокировки, вы должны использовать неблокирующие сокеты и вызывать recv() до тех пор, пока не будет возвращено -1 с errno, установленным на EWOULDBLOCK.

1 голос
/ 16 ноября 2011

Вы можете использовать неблокирующие вызовы на recv() (установив SOCK_NONBLOCK на сокете) и ждать, пока они не будут готовы к чтению данных, используя select() (с таймаутом) в цикле.

Тогда, если файловый дескриптор слишком долго находится в состоянии ожидания данных, вы можете просто закрыть сокет.

0 голосов
/ 16 ноября 2011

Как говорили другие, начальное магическое число (OT: man file ) является хорошим (99,999999%) решением для определения границ дейтаграмм, а время ожидания (с использованием неблокирующей функции recv ()) хорошо обнаружение отсутствующего / позднего пакета.

Если вы рассчитываете на злоумышленников, вы должны положить в свой пакет CRC . Если профессиональный злоумышленник действительно хочет, он или она выяснит - рано или поздно - как работает ваш CRC, но это даже сложнее, чем создать пакет без CRC. (Кроме того, если безопасность имеет решающее значение, вы найдете в сети SSL libs / examples / code.)

...