Я должен прочитать первые 9 байтов, которые должны включать в себя протокол и входящий размер пакета данных.
Когда порт завершения возвращается с 9 байтами, что лучше сделать? (производительность / хорошая практика или эстетика мудрые)
Ответ зависит от инфраструктуры, которую вы используете. Как правило, лучше всего ничего не делать. Я знаю, это звучит странно, поэтому позвольте мне объяснить. Когда ОС общается с NIC, она обычно имеет по крайней мере одну пару кольцевых буферов RX / TX и, в случае стандартного оборудования, вероятно, общается с устройством по шине PCIe. В верхней части шины PCIe находится механизм DMA, который позволяет сетевым платам считывать и записывать из / в память хоста без использования ЦП. Другими словами, в то время как NIC активен, он всегда будет считывать и записывать пакеты самостоятельно, с минимальным вмешательством ЦП. Есть, конечно, много деталей, но вы можете думать, что на уровне драйвера это то, что происходит — чтение и запись всегда выполняются NIC с использованием DMA, независимо от того, что ваше приложение что-то читает / пишет или нет. Кроме того, имеется инфраструктура ОС, которая позволяет приложениям пользовательского пространства отправлять и получать данные в / из сетевого адаптера. Когда вы открываете сокет, ОС определит, какие данные интересуют ваше приложение, и добавит запись в список приложений, обращающихся к сетевому интерфейсу. Когда это происходит, приложение начинает получать данные, которые помещаются в какую-то очередь приложения в ядре. Неважно, звоните вы или нет, данные помещаются туда. После размещения данных приложение получает уведомление. Механизмы уведомлений в ядре различны, но все они разделяют схожие идеи — пусть приложение знает, что данные доступны для вызова read()
, Как только данные попадают в эту «очередь», приложение может забрать их, вызвав read()
, Разница между блокирующим и неблокирующим чтением проста — если чтение блокирует, ядро просто приостанавливает выполнение приложения до получения данных. В случае неблокирующего чтения элемент управления возвращается приложению в любом случае — либо с данными, либо без них. Если это произойдет, приложение может либо продолжать попытки (или вращение в сокете), либо дождаться уведомления от ядра о том, что данные доступны, а затем приступить к их чтению. Теперь вернемся к «бездействию». Это означает, что сокет зарегистрирован для получения уведомления только один раз. После регистрации приложение не должно ничего делать, но получает уведомление о том, что «данные есть». Поэтому приложение должно прослушивать это уведомление и выполнять чтение только при наличии данных. Как только получено достаточно данных, приложение может как-то начать их обработку. Зная все это, давайте посмотрим, что из трех подходов лучше …
Опубликовать еще одно перекрытое чтение на сокете, на этот раз с размером пакета, чтобы он получил его в следующем завершении?
Это хороший подход. В идеале вам не нужно ничего «публиковать», но это зависит от того, насколько хорош интерфейс ОС. Если вы не можете «зарегистрировать» свое приложение один раз, а затем продолжать получать уведомления каждый раз, когда новые данные становятся доступными, и вызывать read (), когда это так, то отправка запроса асинхронного чтения является следующим лучшим решением.
Прочитать внутри процедуры весь пакет, используя блокирующие сокеты, а затем опубликовать другой, перекрытый с recv с 9 байтами?
Это хороший подход, если вашему приложению больше нечего делать и у вас есть только один сокет для чтения. Другими словами — это простой способ сделать это, очень легко программировать, ОС заботится о самих завершениях и т. Д. Имейте в виду, что если у вас есть более одного сокета для чтения, вам придется либо выполнить очень глупая вещь, такая как иметь поток на сокет (ужасно!), или переписать ваше приложение, используя первый подход.
Считайте порциями (определите размер), скажем — 4096 и укажите счетчик для продолжения чтения каждого перекрывающегося завершения до тех пор, пока данные не будут прочитаны (скажем, оно завершится 12 раз, пока весь пакет не будет прочитан).
Это путь! Фактически, это почти то же самое, что и подход № 1 с хорошей оптимизацией, позволяющей выполнять как можно меньше циклических обращений к ядру и читать как можно больше за один раз. Сначала я хотел исправить первый подход с этими деталями, но потом я заметил, что вы сделали это сами.
Надеюсь, поможет. Удачи!
Ответ Влада интересный, но несколько агностический и теоретический. Вот кое-что более сфокусированное на соображениях дизайна IOCP.
Похоже, что вы читаете поток сообщений из TCP-соединения, в результате чего сообщение состоит из заголовка, который подробно описывает длину всего сообщения. Заголовок имеет фиксированный размер, 9 байтов.
Пожалуйста, имейте в виду, что каждое перекрывающееся завершение чтения будет возвращаться между 1 байтом и размером вашего буфера, вы НЕ ДОЛЖНЫ предполагать, что вы можете выполнить 9-байтовое чтение и всегда получить полный заголовок, и вы не должны предполагать, что впоследствии вы можете выполнить чтение с достаточно большим буфером для полного сообщения и получения этого сообщения полностью после завершения чтения. Вам нужно иметь дело с завершениями, которые возвращают меньше байтов, чем вы ожидаете, и лучший способ справиться с этим — настроить указатель WSABUF на начало буфера, чтобы при последующем перекрывающемся чтении в буфер помещалось больше данных в позиции только после того, как это чтение закончено
Лучший способ прочитать данные будет зависеть от следующих вещей:
Большинство решений о том, как читать данные с использованием IOCP, зависят от того, где будет происходить копирование данных и насколько удобно создавать обрабатываемые данные. Предполагая, что вы НЕ отключили буферизацию чтения на уровне сокета, тогда, вероятно, будет копия данных всякий раз, когда вы читаете данные. Стек TCP будет накапливать данные в своих буферах чтения на сокет, и ваши перекрывающиеся чтения будут копировать это в ваш собственный буфер и возвращать вам.
Самая простая ситуация, если вы можете обрабатывать сообщения по частям по мере их поступления. В этом случае просто выполните перекрывающееся чтение для вашего полного размера буфера и обработайте завершение (буфер будет содержать от 1 байта до размера буфера данных), выполните новое чтение (возможно, в конец того же буфера), пока не получите достаточно данных для обработки, а затем обрабатывать данные, пока вам не нужно больше читать. Преимущество этого состоит в том, что вы выдаете минимальное количество перекрывающихся операций чтения (для вашего размера буфера), и это уменьшает переходы пользовательского режима в режим ядра.
Если вы ДОЛЖНЫ обрабатывать сообщения как полные сообщения, то как вы их обрабатываете, зависит от того, насколько большими они могут быть и насколько велики ваши буферы. Вы МОЖЕТЕ выполнить чтение для заголовка (указав, что длина буфера составляет всего 9 байтов), а затем выполнить больше перекрывающихся чтений для накопления полного сообщения в один или несколько буферов (путем настройки начала и длины буфера по мере необходимости) и объединение буферов в структуру данных «для каждого соединения». В качестве альтернативы, не выдавайте «специальное» чтение для заголовка и работайте с возможностью одного чтения, возвращающего более одного сообщения.
У меня есть несколько примеров серверов IOCP, которые выполняют большую часть этой работы, вы можете скачать их с Вот и читайте о них в сопроводительных статьях.