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