Шрифт:
Это довольно изящный трюк для записи неизвестного количества данных, когда вы узнаете, сколько данных нужно было записать, только когда запись уже закончена. Главное — если вы будете использовать второй метод, с записью заголовка после записи данных, не забудьте зарезервировать место под заголовок в начале клиентского буфера!
Составные сообщения
До сих пор мы демонстрировали только обмен сообщениями, когда данные передаются из одного буфера в адресном пространстве клиента в другой буфер в адресном пространстве сервера (и наоборот — в случае ответа на сообщение).
При том, что данный подход вполне приемлем для большинства приложений, его применение далеко не всегда эффективно. Вспомните: наша функция write из Си-библиотеки берет переданный ей буфер и добавляет в его начало небольшой заголовок. Используя то, что мы уже изучили ранее, вы могли бы ожидать, что реализация write в Си-библиотеке может выглядеть примерно так (это не реальный код!):
Понимаете, что произошло? Несколько неприятных вещей:
• Функция write теперь должна быть способна выделить память под буфер достаточно большого размера как для данных клиента (которые могут быть довольно значительными по объему), так и для заголовка. Размер заголовка не имеет значения — в этом случае он был равен 12 байтам.
• Мы были должны скопировать данные дважды: в первый раз — при использовании функции memcpy, и затем еще раз, снова — уже при осуществлении передачи сообщения.
• Мы должны были предусмотреть указатель на тип
Поскольку ядро намерено копировать данные в любом случае, было бы хорошо, если бы мы смогли сообщить ему о том, что одна часть данных (заголовок) фиксирована по некоторому адресу, а другая часть (собственно данные) фиксирована где- нибудь еще, без необходимости самим вручную собирать буферы из частей и копировать данные.
На наше счастье, в QNX/Neutrino реализован механизм, который позволяет нам сделать именно так! Механизм этот называется IOV (i/o vector), или «вектор ввода/вывода».
Давайте для начала рассмотрим некоторую программу, а затем обсудим, что происходит с применением такого вектора.
Прежде всего, обратите внимание на то, что не применяется никакой функции malloc и никакой функции memcpy. Затем обратим внимание на тип применяемого вектора IOV —
Определение типа вектора
Мы заполняем в этой структуре пары «адрес — длина» для заголовка операции записи (первая часть) и для данных клиента (вторая часть). Существует удобная макрокоманда, SETIOV, которая выполняет за нас необходимые присвоения. Она формально определена следующим образом: