IOCP - отправить перекрывающийся или прочитанный пакет?

Я должен прочитать первые 9 байтов, которые должны включать протокол и входящий размер пакета данных.

Когда порт завершения возвращается с 9 байтами, что лучше сделать? (производительность/хорошая практика или эстетика)

  1. Отправить еще одно перекрывающееся чтение в сокете, на этот раз с размером пакета, чтобы он получил его при следующем завершении?
  2. Прочитать внутри подпрограммы весь пакет, используя блокирующие сокеты, а затем опубликовать другой, перекрывающийся с recv с 9 байтами?
  3. Чтение кусками (определить размер), скажем, - 4096, и иметь счетчик для продолжения чтения каждого перекрывающегося завершения до тех пор, пока данные не будут прочитаны (скажем, это будет завершено 12 раз, пока не будет прочитан весь пакет).

person user1255454    schedule 16.02.2013    source источник


Ответы (2)


Отправить еще одно перекрывающееся чтение в сокете, на этот раз с размером пакета, чтобы он получил его при следующем завершении?

Это хороший подход. В идеале вам не нужно было бы ничего «выкладывать», но это зависит от того, насколько хорош интерфейс ОС. Если вы не можете «зарегистрировать» свое приложение один раз, а затем продолжать получать уведомления каждый раз, когда доступны новые данные, и вызывать read(), когда это возможно, то отправка асинхронного запроса на чтение является следующим лучшим решением.

Прочитать внутри подпрограммы весь пакет, используя блокирующие сокеты, а затем опубликовать другой, перекрывающийся с recv с 9 байтами?

Это хороший подход, если вашему приложению абсолютно нечего делать и у вас есть только один сокет для чтения. Другими словами, это простой способ сделать это, его очень легко запрограммировать, ОС сама позаботится о завершении и т. д. Имейте в виду, что если у вас есть более одного сокета для чтения, вам придется либо выполнить очень глупая вещь, например, иметь поток для каждого сокета (ужасно!), или переписать свое приложение, используя первый подход.

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

Это путь! На самом деле, это почти то же самое, что и подход № 1, но с хорошей оптимизацией, чтобы выполнять как можно меньше обращений к ядру и как можно больше читать за один раз. Сначала я хотел исправить первый подход с этими деталями, но потом заметил, что ты сделал это сам.

Надеюсь, поможет. Удачи!

Ответ Влада интересен, но несколько не зависит от ОС и теоретичен. Вот кое-что, немного более сосредоточенное на соображениях дизайна для IOCP.

person Community    schedule 16.02.2013

Кажется, что вы читаете поток сообщений из TCP-соединения, при этом сообщение состоит из заголовка, в котором подробно описывается длина всего сообщения. Заголовок имеет фиксированный размер, 9 байт.

Имейте в виду, что каждое завершение чтения с перекрытием будет возвращать от 1 байта до размера вашего буфера, вы НЕ должны предполагать, что вы можете выполнить чтение 9 байтов и всегда получить полный заголовок, и вы не должны предполагать, что впоследствии вы можете выполнить чтение с буфером, достаточно большим для полного сообщения, и получить это сообщение полностью, когда чтение завершится. Вам БУДУТ иметь дело с завершениями, которые возвращают меньше байтов, чем вы ожидаете, и лучший способ справиться с этим — настроить указатель WSABUF на начало буфера, чтобы последующее чтение с перекрытием считывало больше данных в буфер в позиции только после того, как это чтение закончилось ...

Наилучший способ чтения данных будет зависеть от следующих вещей:

Большинство решений о том, как читать данные с помощью IOCP, сводится к тому, где будет происходить копирование данных и насколько удобными вы хотите сделать данные, которые вы обрабатываете. Предполагая, что вы НЕ отключили буферизацию чтения на уровне сокета, тогда, вероятно, будет копия данных всякий раз, когда вы читаете данные. Стек TCP будет накапливать данные в своих буферах чтения для каждого сокета, а ваши перекрывающиеся операции чтения будут копировать их в ваш собственный буфер и возвращать их вам.

  • сколько соединений вы, вероятно, будете иметь дело с
  • можете ли вы обрабатывать сообщения по частям или только как полные сообщения.
  • может ли одноранговый узел отправлять несколько сообщений без ответа от вас или это протокол в стиле «сообщение-ответ».
  • Ответ зависит от инфраструктуры, которую вы используете. В общем, лучше всего ничего не делать. Я знаю, это звучит странно, поэтому позвольте мне объяснить. Когда ОС взаимодействует с сетевой картой, она обычно имеет как минимум одну пару кольцевых буферов RX/TX и, в случае стандартного оборудования, скорее всего, взаимодействует с устройством по шине PCIe. В верхней части шины PCIe находится механизм прямого доступа к памяти, который позволяет сетевой карте читать и записывать из/в память хоста без использования ЦП. Другими словами, пока сетевая карта активна, она всегда будет считывать и записывать пакеты самостоятельно с минимальным вмешательством процессора. Деталей, конечно, много, но в целом можно подумать, что на уровне драйвера именно это и происходит — чтение и запись всегда выполняются сетевым адаптером с использованием DMA, независимо от того, читает/пишет ли что-нибудь ваше приложение. или не. Теперь, помимо этого, есть инфраструктура ОС, которая позволяет приложениям пользовательского пространства отправлять и получать данные в/из сетевой карты. Когда вы открываете сокет, ОС определяет, какие данные интересуют ваше приложение, и добавляет запись в список приложений, взаимодействующих с сетевым интерфейсом. Когда это происходит, приложение начинает получать данные, которые помещаются в своего рода очередь приложения в ядре. Неважно, вызываете ли вы чтение или нет, данные помещаются туда. После размещения данных приложение получает уведомление. Механизмы уведомлений в ядре различаются, но все они имеют схожие идеи — сообщить приложению, что данные доступны для вызова _1_. Как только данные окажутся в этой «очереди», приложение может их забрать, вызвав _2_. Разница между блокирующим и неблокирующим чтением проста — если чтение блокируется, ядро ​​просто приостановит выполнение приложения до получения данных. При неблокирующем чтении управление возвращается приложению в любом случае — либо с данными, либо без них. Если произойдет последнее, приложение может либо продолжать попытки (т. е. вращаться в сокете), либо дождаться уведомления от ядра о том, что данные доступны, а затем приступить к их чтению. Теперь вернемся к «ничегонеделанию». Это означает, что сокет зарегистрирован для получения уведомления только один раз. После регистрации приложению ничего не нужно делать, кроме как получать уведомление о том, что «данные есть». Итак, что приложение должно делать, так это прослушивать это уведомление и выполнять чтение только тогда, когда данные есть. Как только будет получено достаточно данных, приложение может начать их обработку. Зная все это, давайте посмотрим, какой из трех подходов лучше...

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

Если вы ДОЛЖНЫ обрабатывать сообщения как полные сообщения, то то, как вы их обрабатываете, зависит от того, насколько они могут быть большими и насколько велики ваши буферы. Вы МОЖЕТЕ выполнить чтение для заголовка (указав, что длина буфера составляет всего 9 байт), а затем выполнить больше перекрывающихся операций чтения для накопления полного сообщения в один или несколько буферов (путем корректировки начала и длины буфера по мере продвижения) и объединение буферов вместе внутри вашей структуры данных «для каждого соединения». В качестве альтернативы не запускайте «специальное» чтение для заголовка и учитывайте возможность того, что одно чтение возвращает более одного сообщения.

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

насколько он велик (в среднем и максимально возможный размер сообщения)

person Len Holgate    schedule 20.11.2013