Глава 4 Использование протокола XDR ───────────────────────────────────────────────────────────── Введение в XDR Компиляция программ, включающих подпрограммы XDR Создание переносимых данных с помощью XDR Элементы библиотеки XDR Числовые фильтры Фильтры с плавающей точкой Фильтры для перечислимых типов Отсутствие данных Фильтры для конструируемых типов данных Строки Массивы байт Списки Примеры Конструируемые "непрозрачные" данные Массивы фиксированного размера Размеченные объединения Указатели Семантика указателей и XDR Безфильтровые элементы Направления работы XDR Потоковый доступ в XDR Стандартные потоки ввода-вывода Потоки в памяти Потоки на уровне записей (TCP/IP) Реализация потоков в XDR Объект XDR Стандарт XDR Размер основного блока Целые Целые без знака Перечисление типов Булевы переменные Расширение целых чисел и чисел без знака Плавающая запятая и двойная точность "Непрозрачные" данные Строки байтов заданного размера Массивы фиксированной размерности Массивы, содержащие счетчик элементов Структуры Размеченные объединения Пропуски в спецификациях Перекрестные ссылки с использованием элементов библиотек Маркирование записей Перспективные вопросы - списки с указателями Краткий обзор подпрограмм XDR Введение в XDR ───────────────────────────────────────────────────────────── Настоящая глава посвящена рассмотрению библиотечных подпрог- рамм, с помощью которых программист на Си выполняет описание про- извольных структур данных машинно-независимым способом. Стандарт XDR (внешнее представление данных) является базой для пакета RPC (обращения к удаленным процедурам) в том смысле, что передача данных при работе с удаленными процедурами использует этот стан- дарт. Библиотечные подпрограммы XDR следует использовать для пе- редачи тех данных, к которым обращение будет производиться разно- типными машинами. Глава содержит описание подпрограмм библиотеки XDR, рекомен- дации по работе с потоками, по описанию новых потоков и типов данных, а также формальное определение стандарта XDR. XDR может работать с разными языками программирования, с разными операцион- ными системами и машинами с разной архитектурой. Большинству пользователей (в особенности пользователям RPC) нужна только та информация, которая содержится в разделах "Компиляция программ, включающих подпрограммы XDR", "Создание переносимых данных с по- мощью XDR" и "Элементы библиотеки XDR". Программисты, которые ин- тересуются реализацией RPC и XDR на новых машинах, должны ознако- миться с разделами "Потоковый доступ в XDR", "Потоковая версия XDR" и "Стандарт XDR". Перспективные вопросы, касающиеся не всех реализаций, рассматриваются в соответствующем разделе. Перечень всех подпрограмм XDR вместе с краткой характеристикой каждой из них дается в разделе "Краткий обзор подпрограмм XDR". Компиляция программ, включающих подпрограммы XDR ───────────────────────────────────────────────────────────── Если в вашей программе на Си используются подпрограммы XDR, включите в нее файл , содержащий описание всех необхо- димых интерфейсов с системой XDR. Если библиотека модулей на Си libc.a содержит подпрограммы XDR, компиляция программ не потребу- ет указания дополнительных опций, например: cc program.c В противном случае используйте при компиляции флаг .lrpc, требующий включения библиотеки RPC, например: cc .lrpc program.c Создание переносимых данных с помощью XDR ───────────────────────────────────────────────────────────── Рассмотрим две программы: writer ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ │ │ main() /* writer.c */ │ │ { │ │ long i; │ │ │ │ for (i = 0; i < 8; i++) { │ │ if (fwrite((char *)&i, sizeof(i), 1, stdout) != 1) {│ │ fprintf(stderr, "failed!\n"); │ │ exit(1); │ │ } │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ reader ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ │ │ main() /* reader.c */ │ │ { │ │ long i, j; │ │ │ │ for (j = 0; j < 8; j++) { │ │ if (fread((char *)&i, sizeof(i), 1, stdin) != 1) { │ │ fprintf(stderr, "failed!\n"); │ │ exit(1); │ │ } │ │ printf("%1d", i); │ │ } │ │ printf("\n"); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Обе программы кажутся мобильными (переносимыми) по следующим причинам: * Они проходят проверку по типу lint. * Они ведут себя одинаково в системах с различной аппаратной архитектурой, например, на машине, работающей в среде XENIX /UNIX, и на машине Sun. Направление выводного потока программы writer в канал прог- раммы reader дает идентичные результаты на обеих машинах: ───────────────────────────────────────────────────────────── │ nix% writer│reader │ 0 1 2 3 4 5 6 7 │ nix% ───────────────────────────────────────────────────────────── │ sun% writer│reader │ 0 1 2 3 4 5 6 7 │ sun% С появлением локальных сетей получила распространение концеп- ция "сетевых каналов", по которой данные, генерируемые процессом на одной машине, поступают на вторую машину другому процессу. Се- тевой канал можно создать с помощью программ writer и reader. Рассмотрим пример сетевого канала, соединяющего между собой маши- ну, работающую в среде XENIX/UNIX, и машину фирмы Sun: ───────────────────────────────────────────────────────────── │ nix% writer│rsh sun reader │ 0 16777216 33554432 50331648 67108864 83886080 100663296 │ nix% Аналогичные результаты можно получить, исполняя программу writer на машине Sun, а программу reader на машине, которая рабо- тает в среде XENIX/UNIX. Это имеет место благодаря различию байтного порядка длинных целых величин на таких машинах. Данные других типов могут иметь различные: размер, байтный порядок, представление и способ выравнивания. Так, например, в среде XENIX /UNIX число 01234567 хранится в следующем виде: ┌─────────────────┬──────────────────┐ │ байт │ содержимое │ ╞═════════════════╪══════════════════╡ │ 0 │ 67 │ │ 1 │ 45 │ │ 2 │ 23 │ │ 3 │ 01 │ └─────────────────┴──────────────────┘ То же самое число в машине Sun представляется следующим обра- зом: ┌─────────────────┬──────────────────┐ │ байт │ содержимое │ ╞═════════════════╪══════════════════╡ │ 0 │ 01 │ │ 1 │ 23 │ │ 2 │ 45 │ │ 3 │ 67 │ └─────────────────┴──────────────────┘ Возьмем число 16777216 = 2**24; при инверсии в 24-й бит зано- сится 1. Этот пример свидетельствует в пользу переносимости данных, особенно необходимой тогда, когда данные используются совместно машинами разного типа. В целях ее обеспечения те строки програм- мы, которые содержат обращение к функциям read() и write(), заме- няются на вызов библиотечной подпрограммы xdr_long(). Указанная подпрограмма выполняет роль фильтра, опознающего стандартное представление длинных целых в их внешней форме. После включения подпрограммы xdr_long() программы writer и reader принимают следующий вид: writer ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include /* xdr - это подраздел библиотеки │ │ * rpc */ │ │ main() /* writer.c */ │ │ { │ │ XDR xdrs; │ │ long i; │ │ │ │ xdrstdio_create(&xdrs, stdout, XDR_ENCODE); │ │ for (i = 0; i < 8; i++) { │ │ if (!xdr_long(&xdrs, &i)) { │ │ fprintf(stderr, "failed!\n"); │ │ exit(1); │ │ } │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ reader ┌────────────────────────────────────────────────────────────┐ │ │ │ #include │ │ #include /* xdr - это подраздел библиотеки │ │ * rpc */ │ │ main() /* reader.c */ │ │ { │ │ XDR xdrs; │ │ long i, j; │ │ │ │ xdrstdio_create(&xdrs, stdin, XDR_DECODE); │ │ for (j = 0; j < 8; j++) { │ │ if (!xdr_long(&xdrs, &i)) { │ │ fprintf(stderr, "failed!\n"); │ │ exit(1); │ │ } │ │ printf("%1d", i); │ │ } │ │ printf("\n"); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Ниже приводятся результаты выполнения новых программ в среде XENIX/UNIX, на машине Sun, а также передачи данных из XENIX/UNIX в Sun. ───────────────────────────────────────────────────────────── │ nix% writer│reader │ 0 1 2 3 4 5 6 7 │ nix% ───────────────────────────────────────────────────────────── │ sun% writer│reader │ 0 1 2 3 4 5 6 7 │ sun% ───────────────────────────────────────────────────────────── │ xenix% writer│rsh sun reader │ 0 1 2 3 4 5 6 7 │ xenix% Работа с целыми числами - только небольшая часть проблемы пе- реносимости данных. Другими вопросами, которые при этом приходит- ся решать, являются выравнивание и обработка указателей. Выравни- вание на границе слова порождает варьирование размера структуры данных на разных машинах. Указатели, с одной стороны, удобны для использования, но с другой стороны не имеют смысла вне той маши- ны, где они были определены. Решение этих проблем возложено на пакет библиотечных подпрог- рамм XDR. Благодаря этому пакету пользователь может вести запись и считывание произвольных конструкций, принятых в языке Си, с по- мощью согласованной, отлаженной, хорошо описанной методологии. Таким образом, использование библиотеки имеет смысл даже в том случае, когда данные не участвуют в сетевой обработке на несколь- ких машинах. Библиотека XDR содержит подпрограммы фильтрации для большого количества объектов, включая строки (массивы байт с пустыми сим- волами в конце), структуры, объединения, списки. Из простых подп- рограмм вы можете создавать свои XDR-процедуры для описания про- извольных структур данных, в том числе элементов массива, корте- жей, входящих в объединения, или объектов, на которые делаются ссылки из других структур. Сами структуры могут включать в себя массивы, состоящие из элементов произвольного типа, и указатели на другие структуры. Перейдем к более подробному рассмотрению обеих программ. Подробности реализации Семейство подпрограмм XDR, имеющих отношение к созданию пото- ков, состоит из элементов, каждый из которых трактует поток дво- ичных данных по своему. В нашем примере данные обрабатываются с помощью стандартных подпрограмм ввода-вывода, поэтому использует- ся xdrstdio_create(). Параметры потоковых подпрограмм варьируются в соответствии с их назначением и механизмом действия. В нашем примере подпрограмма xdrstdio_create() получает ука- затель на структуру XDR, подлежащую инициализации, указатель на файл (FILE), по отношению к которому выполняется ввод-вывод, и действие, которое требуется выполнить. Требуемым действием может быть XDR_ENCODE (сериализация в программе writer) или XDR_DECODE (десериализация в программе reader). ----------------------------------------------------------------- Замечание. Клиентам RPC нет необходимости создавать потоки XDR. Система RPC создает их сама и передает клиентам. ----------------------------------------------------------------- Вызов подпрограммы xdr_long() присутствует в большинстве подпрограмм библиотеки XDR и во всех XDR-процедурах, созданных клиентами: * Процедура в случае успеха возвращает значение TRUE (1) и FALSE (0) в случае неудачи. * С каждым типом данных (xxx) связана подпрограмма XDR, имею- щая следующую форму: xdr_xxx(xdrs, fp) xdr_xxx(xdrs, fp) XDR *xdrs; xxx *fp; { } В данном случае xxx занимает длинное слово, поэтому ему соот- ветствует подпрограмма xdr_long. Если xxx - структура, состоящая из элементов произвольного типа, клиент может также описать про- цедуру xdr_xxx, которая для каждого поля будет вызывать подпрог- рамму XDR соответствующего типа. Во всех случаях первый параметр, xdrs, можно трактовать как неявный выход, передаваемый простым подпрограммам. Подпрограммы XDR инвариантны к способу их использования; дру- гими словами, одни и те же подпрограммы вызываются как для сериа- лизации, так и для десериализации данных. Эта особенность являет- ся решающей при реализации проблемы переносимости данных. Нужно стремиться к тому, чтобы для любой операции вызывать одну и ту же подпрограмму; это послужит порукой тому, что данные, прошедшие сериализацию, пройдут и десериализацию. Одна подпрограмма исполь- зуется и генератором циркулирующей в сети информации, и ее поль- зователем. Это возможно благодаря передаче адреса объекта, а не самого объекта; объект подвергается изменениям только в случае десериализации. Указанная особенность в нашем примере не видна, тем не менее ее значимость становится очевидной, если в межмашин- ном информационном обмене принимают участие нетривиальные струк- туры данных. Подробности см. в разделе "Краткий обзор подпрограмм XDR". Рассмотрим несколько более сложный пример. Предположим, что суммарный актив и пассив клиента нужно распределить между процес- сами. Допустим также, что эти значения достаточно чувствительны к типу данных и каждое из них требует описания собственного типа: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct gnumbers { │ │ long g_assets; │ │ long g_liabilities; │ │ }; │ │ │ └────────────────────────────────────────────────────────────┘ Соответствующая подпрограмма XDR, описывающая эту структуру, имеет вид: bool_t /* TRUE в случае успеха, FALSE в случае неудачи */ xdr_gnumbers(xdrs, gp) XDR *xdrs; struct gnumbers *gp; { if (xdr_long(xdrs, &gp->g_assets) && xdr_long(xdrs, &gp->g_liabilities)) return(TRUE); return(FALSE); } Параметр xdrs не подлежит ревизии или модифицированию; он только передается подкомпонентным подпрограммам. Обязательной проверке подлежит значение, возвращаемое каждой из подпрограмм XDR; в случае неудачи следует немедленно прервать работу и вер- нуть вызывающему процессу значение FALSE. Из примера видно, что bool_t объявлено как целое, единствен- ными значениями которого являются только TRUE (1) и FALSE (0). В настоящем разделе используются следующие определения: #define bool_t int #define TRUE 1 #define FALSE 0 #define enum_t int /* enum_t используется для генерации дан- * ных типа enum */ При указанных условиях подпрограмма xdr_gnumbers() примет следующий вид: ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_gnumbers(xdrs, gp) │ │ XDR *xdrs; │ │ struct gnumbers *gp; │ │ { │ │ return (xdr_long(xdrs, &gp->g_assets) && │ │ xdr_long(xdrs, &gp->g_liabilities)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ В разделе используются оба способа кодирования. Элементы библиотеки XDR ───────────────────────────────────────────────────────────── В разделе содержится обзор всех элементов XDR, начиная с эле- ментарных типов и кончая структурами, конструируемыми из них. Рассматриваются также утилиты XDR. Взаимодействие с этими элемен- тами и утилитами описывается в файле , который автома- тически включается файлом . Числовые фильтры Библиотека XDR содержит элементы, устанавливающие соответс- твие между числами, принятыми в Си, и их внешним представлением. Диапазон используемых типов: [signed, unsigned] * [short, int, long] Им соответствуют шесть элементарных функций: ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_int(xdrs, ip) │ │ XDR *xdrs; │ │ int *ip; │ │ │ │ bool_t xdr_u_int(xdrs, up) │ │ XDR *xdrs; │ │ unsigned *up; │ │ │ │ bool_t xdr_long(xdrs, lip) │ │ XDR *xdrs; │ │ long *lip; │ │ │ │ bool_t xdr_u_long(xdrs, lup) │ │ XDR *xdrs; │ │ ulong *lup; │ │ │ │ bool_t xdr_short(xdrs, sip) │ │ XDR *xdrs; │ │ short *sip; │ │ │ │ bool_t xdr_u_short(xdrs, sup) │ │ XDR *xdrs; │ │ ushort *sup; │ │ │ └────────────────────────────────────────────────────────────┘ Первый параметр, xdrs, является потоковым выходом XDR. Вторым параметром выступает адрес элемента, используемого для передачи данных в поток или получения данных из потока. В случае успешного завершения все функции возвращают значение TRUE, в противном случае - FALSE. Фильтры с плавающей точкой Библиотека XDR содержит также элементарные функции обработки чисел с плавающей точкой: ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_float(xdrs, fp) │ │ XDR *xdrs; │ │ float *fp; │ │ │ │ bool_t xdr_double(xdrs, dp) │ │ XDR *xdrs; │ │ double *dp; │ │ │ └────────────────────────────────────────────────────────────┘ Первый параметр, xdrs, является потоковым выходом XDR. Вторым параметром выступает адрес элемента с плавающей точкой, использу- емого для передачи данных в поток или получения данных из потока. В случае успешного завершения все функции возвращают значение TRUE, в противном случае - FALSE. ----------------------------------------------------------------- Замечание. Поскольку представление чисел с плавающей точкой подчиняется стандарту IEEE, процесс его преобразования в машинное представле- ние и обратно на процедурном уровне может завершиться неудачно. ------------------------------------------------------------------ Фильтры для перечислимых типов В библиотеке XDR имеются также подпрограммы для обобщающих перечислений. При этом предполагается, что машинное представление данных типа enum и типа integer совпадает между собой. Логический тип (boolean) является особой разновидностью типа enum. Во внеш- нем представлении логическая переменная всегда имеет одно из зна- чений: единица (TRUE) или ноль (FALSE). ┌────────────────────────────────────────────────────────────┐ │ │ │ #define bool_t int │ │ #define FALSE 0 │ │ #define TRUE 1 │ │ │ │ #define enum_t int │ │ │ │ bool_t xdr_enum(xdrs, ep) │ │ XDR *xdrs; │ │ enum_t *ep; │ │ │ │ bool_t xdr_bool(xdrs, bp) │ │ XDR *xdrs; │ │ bool_t *bp; │ │ │ └────────────────────────────────────────────────────────────┘ Второй параметр, ep или bp, является адресом элемента соот- ветствующего типа, используемого для передачи или получения данных из потока xdrs. В случае успешного завершения все функции возвращают значение TRUE, в противном случае - FALSE. Отсутствие данных Иногда подпрограмма XDR может использоваться системой RPC, хотя никакой передачи информации и не предполагалось. Для этого случая имеется функция: ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_void(); /* всегда возвращает TRUE */ │ │ │ └────────────────────────────────────────────────────────────┘ Фильтры для конструируемых типов данных Функции обработки конструируемых или составных типов данных требуют указания большего числа параметров и выполняют более сложные действия по сравнению с теми функциями, которые мы уже рассмотрели. Здесь мы рассмотрим функции для строк, массивов, объединений и указателей на структуры. Эти функции могут иметь отношение к распределению памяти. В большинстве случаев память выделяется при десериализации данных с указанием действия XDR_DECODE. Отсюда следует, что в пакете XDR должны быть средства, позволяющие освобождать память. Для этого в XDR предусмотрено действие XDR_FREE. Таким образом, в XDR имеются три руководящих действия: XDR_ENCODE, XDR_DECODE и XDR_FREE. Строки В языке Си строка определяется как последовательность байт, оканчивающаяся пустым байтом, который при расчете длины строки не берется во внимание. При передаче и обработке строки используется указатель на нее. Таким образом, библиотека XDR описывает строку как char *, а не как последовательность символов. Внешнее предс- тавление строки существенно отличается от ее внутреннего предс- тавления. Внешне строки представляются набором символов ASCII, а внутренне - набором символьных указателей. Превращение одного представления в другое обеспечивает подпрограмма xdr_string(): ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_string(xdrs, sp, maxlength) │ │ XDR *xdrs; │ │ char **sp; │ │ uint maxlength; │ │ │ └────────────────────────────────────────────────────────────┘ Параметры имеют следующий смысл: * Первый параметр, xdrs, является потоковым выходом XDR. * Второй параметр, sp, является указателем на строку (тип char **). * Третий параметр, maxlength, содержит максимальное количест- во байт, доступных для обработки при кодировании или деко- дировании; его значение обычно определяется протоколом. Так, например, согласно спецификации протокола имя файла не должно превышать 255 символов. Если количество символов превышает значение maxlength, подп- рограмма возвращает FALSE, в противном случае - TRUE. Механизм работы подпрограммы xdr_string() похож на механизм работы других функций и подпрограмм, рассматриваемых в этом раз- деле. Действие XDR_ENCODE понять проще. Параметр sp указывает на строку заданной длины; если эта длина не превышает значение maxlength, байты будут преобразованы в последовательную форму (то есть пройдут сериализацию). Эффект десериализации строки передать труднее. * Сначала устанавливается длина поступающей строки; она не должна превышать значение maxlength. * Затем снимается косвенность на sp (ищется значение, на ко- торое ссылается указатель); если это значение равно NULL, выделяется строка соответствующей длины и указатель *sp ус- танавливается на эту строку. * Если первоначальное значение *sp отлично от NULL, то пред- полагается, что область адресата, предназначенная для раз- мещения строк размером не более maxlength, уже выделена. В любом случае строка декодируется в области адресата. * Подпрограмма добавляет в конец строки пустой символ. При выполнении действия XDR_FREE строка ищется по указателю sp. Если строка не является строкой NULL, она освобождается и указатель *sp устанавливается на NULL. При этом действии xdr_string игнорирует параметр maxlength. Массивы байт Использование массивов, состоящих из переменного числа байт, зачастую предпочтительнее, нежели использование строк. Массивы байт отличаются от строк в следующих моментах: * Размер массива (количество байт) явно запоминается как це- лое число без знака. * Последовательность байт не завершается пустым символом. * Внешнее представление байт совпадает с их внутренним предс- тавлением. Превращение массива байт из одного представления в другое обеспечивает функция xdr_bytes(): ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_bytes(xdrs, bpp, lp, maxlength) │ │ XDR *xdrs; │ │ char **bpp; │ │ uint *lp; │ │ uint maxlength; │ │ │ └────────────────────────────────────────────────────────────┘ Смысл первого, второго и четвертого параметров идентичен смыслу первого, второго и третьего параметров функции xdr_string(), соответственно. Длина области (в байтах) ищется при сериализации по указателю lp; при десериализации она заносит- ся по адресу *lp. Списки В библиотеке XDR имеется средство обработки списка (массива) элементов произвольного вида. Функция xdr_bytes() обрабатывает подмножество массивов, каждый элемент которых имеет длину, равную 1, и внешнее представление, совпадающее с внутренним. Функция xdr _array() работает с теми же параметрами, что и функция xdr_bytes(), плюс еще два параметра: размер элемента массива и XDR-процедура обработки каждого элемента. Эта процедура должна обеспечивать кодирование или декодирование каждого элемента мас- сива. ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_array(xdrs, ap, lp, maxlength, elementsize, │ │ xdr_element) │ │ XDR *xdrs; │ │ char **ap; │ │ uint *lp; │ │ uint maxlength; │ │ uint elementsize; │ │ bool_t (*xdr_element) (); │ │ │ └────────────────────────────────────────────────────────────┘ Параметр ap является адресом указателя на массив. Если *ap при десериализации массива равен NULL, XDR заводит массив соот- ветствующего размера и устанавливает на него указатель *ap. При этом количество элементов в массиве выбирается по адресу *lp; при десериализации по этому адресу заносится размерность массива. Па- раметр maxlength содержит максимально-допустимое число элементов в массиве; elementsize - размер каждого элемента массива в бай- тах. (Для получения последнего значения можно воспользоваться конструкцией Си sizeof().) Подпрограмма xdr_element используется для сериализации, десериализации и освобождения каждого элемента массива. Примеры Используя уже рассмотренные типы данных, опишем одного поль- зователя, нескольких пользователей и создадим командную историю (список поданных к этому моменту команд). Описание одного пользователя Пользователь, работающий в сети, идентифицируется следующими параметрами: * именем машины, например jupiter; см. hostname(NC) * кодом идентификации пользователя (UID); см. getuid(S) * номерами групп, к которым принадлежит пользователь; см. getgrent(S). Структура, содержащая эту информацию, и связанная с ней XDR-процедура: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct netuser { │ │ char *nu_machinename; │ │ int nu_uid; │ │ uint nu_glen; │ │ int *nu_gids; │ │ }; │ │ #define NLEN 255 /* имя машины не должно превышать 256 │ │ * символов */ │ │ #define NGRPS 20 /* пользователь может быть членом не │ │ * более 20 групп */ │ │ │ │ bool_t │ │ xdr_netuser(xdrs, nup) │ │ XDR *xdrs; │ │ struct netuser *nup; │ │ { │ │ return (xdr_string(xdrs, &nup->nu_machinename, │ │ NLEN) && xdr_int(xdrs, &nup->nu_uid) && │ │ xdr_array(xdrs, &nup->nu_gids, │ │ &nup->nu_glen, NGRPS, sizeof(int), │ │ xdr_int)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Описание нескольких пользователей Группу работающих в сети пользователей можно описать как мас- сив структурой netuser. Определение процедуры и связанной с ней XDR-процедуры: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct party { │ │ uint p_len; │ │ struct netuser *p_nusers; │ │ }; │ │ #define PLEN 500 /* максимальное число пользователей в │ │ * группе */ │ │ │ │ bool_t │ │ xdr_party(xdrs, pp) │ │ XDR *xdrs; │ │ struct party *pp; │ │ { │ │ return (xdr_array(xdrs, &pp->p_nusers, &pp->p_len, │ │ PLEN, sizeof(struct netuser),xdr_netuser));│ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Создание командной истории Традиционные параметры main(), argc и argv, можно объединить в структуру, после чего массив этих структур использовать при создании истории команд. Определения структур и связанных с ними XDR-процедур: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct cmd { │ │ uint c_argc; │ │ char **c_argv; │ │ }; │ │ #define ALEN 1000 /* аргументы не должны выходить за пре- │ │ * делы 1000 символов */ │ │ #define NARGC 100 /* команды не должны иметь более 100 │ │ * аргументов */ │ │ │ │ struct history { │ │ uint h_len; │ │ struct cmd *h_cmds; │ │ }; │ │ #define NCMDS 75 /* история хранит не более 75 команд */ │ │ │ │ bool_t │ │ xdr_wrap_string(xdrs, sp) │ │ XDR *xdrs; │ │ char **sp; │ │ { │ │ return (xdr_string(xdrs, sp, ALEN)); │ │ } │ │ │ │ bool_t │ │ xdr_cmd(xdrs, cp) │ │ XDR *xdrs; │ │ struct cmd *cp; │ │ { │ │ return (xdr_array(xdrs, &cp->c_argv, &cp->c_argc, │ │ NARGC, sizeof (char *), xdr_wrap_string)); │ │ } │ │ │ │ bool_t │ │ xdr_history(xdrs, hp) │ │ XDR *xdrs; │ │ struct history *hp; │ │ { │ │ return (xdr_array(xdrs, &hp->h_cmds, &hp->h_len, │ │ NCMDS, sizeof (struct cmd), xdr_cmd)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Поскольку функция xdr_array() передает процедуре определения элемента массива только два параметра, возникает необходимость в функции xdr_wrap_string(), которая формирует третий параметр для xdr_string(). Теперь рекурсивная природа библиотеки XDR становится ясна с полной очевидностью. Конструируемые "непрозрачные" данные В некоторых протоколах выходы передаются от сервера к клиен- ту. Позже клиент возвращает их обратно серверу. Клиент не может подвергнуть выходы ревизии, ему доступна только их инициация. Та- ким образом, их можно назвать "непрозрачными". Описание "непроз- рачной" последовательности байт фиксированного размера выполняет- ся с помощью элементарной функции xdr_opaque(): ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_opaque(xdrs, p, len) │ │ XDR *xdrs; │ │ char *p; │ │ uint len; │ │ │ └────────────────────────────────────────────────────────────┘ Параметр p является адресом последовательности; len - число байт в "непрозрачном" объекте. По определению данные, входящие в "непрозрачный" объект, не могут переноситься с машины на машину (не являются мобильными). Массивы фиксированного размера В библиотеке XDR отсутствует функция для массивов фиксирован- ной длины. (Функция xdr_array() предназначена для массивов пере- менной длины.) Перепишем пример описания одного пользователя, ис- пользуя массивы фиксированного размера: ┌────────────────────────────────────────────────────────────┐ │ │ │ #define NLEN 255 /* имя машины не должно превышать 256 │ │ * символов */ │ │ #define NGRPS 20 /* пользователь может быть членом не │ │ * более 20 групп */ │ │ │ │ struct netuser { │ │ char *nu_machinename; │ │ int nu_uid; │ │ int nu_gids[NGRPS]; │ │ }; │ │ │ │ bool_t │ │ xdr_netuser(xdrs, nup) │ │ XDR *xdrs; │ │ struct netuser *nup; │ │ { │ │ int i; │ │ │ │ if (!xdr_string(xdrs, &nup->nu_machinename, NLEN)) │ │ return (FALSE); │ │ if (!xdr_int(xdrs, &nup->nu_uid)) │ │ return (FALSE); │ │ for (i = 0; i < NGRPS; i++) { │ │ if (!xdr_int(xdrs, &nup->nu_gids[i])) │ │ return (FALSE); │ │ } │ │ return (TRUE); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Конструируемые размеченные объединения Библиотека XDR поддерживает размеченные объединения. Разме- ченным объединением называется аналог типа данных union в Си со значением enum_t: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct xdr_discrim { │ │ enum_t value; │ │ bool_t (*proc) (); │ │ }; │ │ │ │ bool_t xdr_union(xdrs, dscmp, unp, arms, defaultarm) │ │ XDR *xdrs; │ │ enum_t *dscmp; │ │ char *unp; │ │ struct xdr_discrim *arms; │ │ bool_t (*defaultarm) (); /* может быть равно NULL*/│ │ │ └────────────────────────────────────────────────────────────┘ Сначала подпрограмма преобразует дискриминант объединения, расположенный по адресу *dscmp. Дискриминант всегда имеет тип enum_t. Затем обрабатывается объединение, расположенное по адресу *unp. Параметр arms является указателем на массив структур xdr_discrim. Каждая структура содержит пару [value, proc]. Если дискриминант объединения равен одному из value, для обработки объединения будет вызвана соответствующая этому значению proc (процедура). Конец массива структур xdr_discrim идентифицируется значением NULL (0). Если дискриминант в массиве arms не обнару- жен, вызывается процедура defaultarm, при условии что соответс- твующий параметр имеет значение, отличное от NULL; в противном случае подпрограмма возвращает значение FALSE. Использование конструируемых размеченных объединений Предположим, что объединение имеет тип целого (integer), сим- вольного указателя (строки) или структуры gnumbers. Предположим также, что объединение и его текущий тип объявлены в структуре: ┌────────────────────────────────────────────────────────────┐ │ │ │ enum utype { INTEGER=1, STRING=2, GNUMBERS=3 }; │ │ │ │ struct u_tag { │ │ enum utype utype; /* дискриминант объединения */ │ │ union { │ │ int ival; │ │ char *pval; │ │ struct gnumbers gn; │ │ } uval; │ │ }; │ │ │ └────────────────────────────────────────────────────────────┘ Конструкции и XDR-процедура, связанные с размеченным объеди- нением: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct xdr_discrim u_tag_arms[4] = { │ │ { INTEGER, xdr_int ), │ │ { GNUMBERS, xdr_gnumbers } │ │ { STRING, xdr_wrap_string }, │ │ { __dontcare__, NULL } │ │ } │ │ │ │ bool_t │ │ xdr_u_tag(xdrs, utp) │ │ XDR *xdrs; │ │ struct u_tag *utp; │ │ { │ │ return (xdr_union(xdrs, &utp->utype, &utp->uval, │ │ u_tag_arms, NULL)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограммы были ранее рассмотрены в следующих разделах: * Подпрограмма xdr_gnumbers() была представлена в разделе "Подробности реализации". * Подпрограмма xdr_wrap_string() была представлена в примере "Создание командной истории". * Последний параметр для xdr_union() в данном примере имеет значение NULL. Таким образом, значение дискриминанта объединения может нахо- диться только в списке, содержащемся в массиве u_tag_arms. При этом из примера видно, что элементы массива могут быть неотсорти- рованы. Следует заметить, что расположение значений дискриминанта в массиве может быть разреженным. Рекомендуется присваивать каждому элементу целое значение, чтобы облегчить работу разнотипных Си-компиляторов и внешнее представление дискриминанта. Указатели В языке Си часто бывает удобно внутри одной структуры разме- щать указатели на другую структуру. Сериализация, десериализация и освобождение таких структур облегчается благодаря функции xdr_reference(): ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t xdr_reference(xdrs, pp, size, proc) │ │ XDR *xdrs; │ │ char **pp; │ │ uint ssize; │ │ bool_t (*proc) (); │ │ │ └────────────────────────────────────────────────────────────┘ Параметр pp является адресом указателя на структуру. Параметр ssize означает размер структуры в байтах, который можно получить с помощью функции Си sizeof(). Параметр proc является именем XDR- процедуры, описывающей структуру. При декодировании данных память выделяется, если *pp равен NULL. Функции xdr_struct() нет необходимости описывать структуры внутри структур, для этого вполне достаточно указателей. Использование указателей Предположим, что имеется структура, содержащая имя лица и указатель на структуру gnumbers с его активами и пассивами: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct pgn { │ │ char *name; │ │ struct gnumbers *gnp; │ │ }; │ │ │ └────────────────────────────────────────────────────────────┘ Соответствующая XDR-процедура: ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t │ │ xdr_pgn(xdrs, pp) │ │ XDR *xdrs; │ │ struct pgn *pp; │ │ { │ │ if (xdr_string(xdrs, &pp->name, NLEN) && │ │ xdr_reference(xdrs, &pp->gnp, │ │ sizeof(struct gnumbers), xdr_gnumbers)) │ │ return(TRUE); │ │ return(FALSE); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Семантика указателей и XDR Во многих программах на Си разработчики придают значению ука- зателя двойной смысл. Обычно значение NULL или ноль говорит об отсутствии данных, хотя еще возможна какая-то дополнительная ин- терпретация этого значения, определяемая задачей. По существу программист на Си в целях эффективного кодирования размеченного объединения перегружает интерпретацию значения указателя. Напри- мер, в следующем примере нулевое значение указателя на gnp может означать то, что активы и пассивы клиента неизвестны. Таким обра- зом, в значении указателя закодированы две вещи: известны ли дан- ные или нет, а также если известны, то где они находятся в памя- ти. Ярким примером дополнительной интерпретации значения указате- ля, которая определяется задачей, являются связанные списки (списки, основанные на использовании указателей). В процессе сериализации функция xdr_reference() не придает нулевому значению указателя какой-то особый смысл. Другими слова- ми, передача функции xdr_reference() адреса указателя, который имеет значение NULL, при сериализации данных с большой долей ве- роятности породит ошибку памяти, а в системе XENIX/UNIX и распе- чатку содержимого памяти (core dump). Как программисту вам следует расширять семантику значений указателей. Это обычно подразумевает описание данных с использо- ванием двуэлементного размеченного объединения. Один элемент (смысл указателя) используется тогда, когда указатель имеет нену- левое значение, второй смысл предназначен для нулевых значений. Пример использования связанных списков будет приведен в разделе "Перспективные вопросы". Безфильтровые элементы Потоки XDR могут обрабатываться с помощью функций, обсуждае- мых в этом разделе. ┌────────────────────────────────────────────────────────────┐ │ │ │ uint xdr_getpos(xdrs) │ │ XDR *xdrs; │ │ │ │ bool_t xdr_setpos(xdrs, pos) │ │ XDR *xdrs; │ │ uint pos; │ │ │ │ xdr_destroy(xdrs) │ │ XDR *xdrs; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма xdr_getpos() возвращает целое без знака, описы- вающее текущую позицию в информационном потоке. ----------------------------------------------------------------- Замечание. В некоторых потоках XDR значение, возвращаемое xdr_getpos(), не несет смысловой нагрузки; подпрограмма возвращает в этом слу- чае -1, несмотря на то, что -1 может быть допустимым значением. ----------------------------------------------------------------- Подпрограмма xdr_setpos() делает позицию потока равной pos. ----------------------------------------------------------------- Замечание. В некоторых потоках XDR установка позиции невозможна; в таких случаях xdr_setpos() возвращает FALSE. Указанная подпрограмма также закончится неудачей, если запрашиваемая позиция находится вне границ досягаемости. Описание границ варьируется от потока к потоку. ----------------------------------------------------------------- Функция xdr_destroy() уничтожает поток XDR. После ее вызова поток становится неопределенным. Направления работы XDR Время от времени перед вами будет вставать задача оптимизации работы подпрограмм XDR на основе использования направления выпол- нения действия (XDR_ENCODE, XDR_DECODE или XDR_FREE). На направ- ление выполнения XDR-операции всегда указывает значение xdrs->x_op. Программистам не следует пользоваться этой информаци- ей, поэтому здесь мы обойдемся без примера. Тем не менее, в раз- деле "Перспективные вопросы" полезность поля xdrs->x_op будет продемонстрирована. Потоковый доступ в XDR ───────────────────────────────────────────────────────────── Поток XDR создается при вызове соответствующей подпрограммы. Аргументы таких подпрограмм настраиваются на специфические свойс- тва потока. Потоки в основном предназначены для (де)сериализации данных, циркулирующих в стандартных потоках ввода-вывода, соединениях TCP /IP, файлах ОС XENIX/UNIX и памяти. В разделе "Реализация потоков в XDR" описывается объект XDR и процедура создания потоков XDR по запросу. Стандартные потоки ввода-вывода Взаимодействие потоков XDR со стандартным вводом-выводом обс- луживается подпрограммой xdrstdio_create(): ┌────────────────────────────────────────────────────────────┐ │     │ │ #include  │ │ #include  /* потоки xdr - часть библиотеки │ │   * rpc */  │ │    │ │ void    │ │ xdrstdio_create(xdrs, fp, x_op)  │ │  XDR *xdrs;   │ │  FILE *fp;   │ │  enum xdr_op x_op;   │ │    │ └────────────────────────────────────────────────────────────┘ Потоки в памяти Потоки в памяти обслуживают циркуляцию данных, связанную с конкретной областью памяти: ┌────────────────────────────────────────────────────────────┐ │    │ │ #include     │ │    │ │ void    │ │ xdrmem_create(xdrs, addr, len, x_op)   │ │  XDR *xdrs;   │ │  char *addr;   │ │  uint len;   │ │  enum xdr_op x_op;   │ │    │ └────────────────────────────────────────────────────────────┘ Подпрограмма xdrmem_create() инициализирует поток XDR в ло- кальной памяти. Адрес области памяти содержится в параметре addr; параметр len содержит размер области в байтах. Параметры xdrs и x _op идентичны соответствующим параметрам xdrstdio_create(). В ре- ализации RPC с протоколом UDP/IP используется xdrmem_create(). Перед вызовом системной подпрограммы sendto() результирующие со- общения встраиваются в память. Потоки на уровне записей (TCP/IP) Поток на уровне записей представляет собой поток XDR, базиру- ющийся на стандарте маркирования записей в файлах XENIX/UNIX или интерфейсе связи 4.2 BSD. ┌────────────────────────────────────────────────────────────┐ │    │ │ #include   /* потоки xdr - часть библиотеки │ │   * rpc */   │ │    │ │ xdrrec_create(xdrs, sendsize, recvsize, iohandle,  │ │ readproc, writeproc)  │ │  XDR *xdrs;   │ │  uint sendsize, recvsize;  │ │  char *iohandle;  │ │  int (*readproc) (), (*writeproc) ();  │ │    │ └────────────────────────────────────────────────────────────┘ Подпрограмма xdrrec_create() обеспечивает в рамках XDR двуна- правленный поток записей произвольной длины. Содержимым записей являются данные в формате XDR. В первую очередь поток призван обеспечить интерфейс RPC с TCP. Тем не менее, он может использо- ваться для обмена данными с обычными файлами системы XENIX/UNIX. Параметр xdrs аналогичен соответствующему параметру, описан- ному выше. Поток располагает своей собственной буферизацией дан- ных, подобной той, которая используется в стандартном вводе-выво- де. Параметры sendsize и recvsize определяют размеры в байтах буферов вывода и ввода, соответственно; если их значения равны нулю, используются значения по умолчанию. Наполнение и опустошение буфера обеспечивают подпрограммы readproc и writeproc, соответственно. Порядок вызова и механизм работы этих подпрограмм схожи с теми характеристиками, которыми обладают системные функции XENIX/UNIX read() и write(). Однако, первым параметром указанных подпрограмм выступает "неявный" пара- метр iohandle. Остальные два параметра (адрес буфера и его размер в байтах) и результаты (количество обработанных байт) - стандарт- ные. Если наименования readproc и writeproc заменить на xxx, фор- ма вызова подпрограмм будет следующей: ┌────────────────────────────────────────────────────────────┐ │ │ │ /* возвращает количество фактически переданных байт. │ │ * -1 в случае ошибки │ │ */ │ │ int │ │ xxx(iohandle, buf, len) │ │ char *iohandle; │ │ char *buf; │ │ int nbytes; │ │ │ └────────────────────────────────────────────────────────────┘ Поток XDR располагает средствами ограничения записей в байто- вом потоке. Подробности реализации этого механизма обсуждаются в разделе "Краткий обзор подпрограмм XDR". Перечислим функции, име- ющие отношение к потокам на уровне записей: ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t │ │ xdrrec_endofrecord(xdrs, flushnow) │ │ XDR *xdrs; │ │ bool_t flushnow; │ │ │ │ bool_t │ │ xdrrec_skiprecord(xdrs) │ │ XDR *xdrs; │ │ │ │ bool_t │ │ xdrrec_eof(xdrs) │ │ XDR *xdrs; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма xdrrec_endofrecord() вызывает оформление текущих данных в виде записи. Если параметр flushnow имеет значение TRUE, вызывается подпрограмма записи в поток writeproc(); иначе эта подпрограмма будет вызвана в случае заполнения буфера. Подпрограмма xdrrec_skiprecord() вызывает сдвиг позиции вход- ного потока с границы текущей записи к началу следующей записи в потоке. Если содержимое вводного буфера исчерпано, подпрограмма xdrrec_eof() возвращает значение TRUE. Это не означает, тем не менее, исчерпания данных, связанных с текущим файловым дескрипто- ром. Реализация потоков в XDR ───────────────────────────────────────────────────────────── Раздел посвящен рассмотрению абстрактных типов данных, необ- ходимых для реализации новых потоков в XDR. Объект XDR Интерфейс с потоком XDR описывается следующей структурой: ┌────────────────────────────────────────────────────────────┐ │ │ │ enum xdr_op { XDR_ENCODE = 0, XDR_DECODE = 1, │ │ XDR_FREE = 2 }; │ │ │ │ typedef struct { │ │ enum xdr_op x_op; /* действие */ │ │ struct xdr_ops { │ │ bool_t (*x_getlong) (); /* получить длинное слово из │ │ * потока */ │ │ bool_t (*x_putlong) (); /* поместить длинное слово │ │ * в поток */ │ │ bool_t (*x_getbytes) (); /* получить байты из потока *│ │ bool_t (*x_putbytes) (); /* поместить байты в поток */│ │ uint (*x_getpostn) (); /* возвращает смещение в │ │ * байтах от начала */ │ │ bool_t (*x_setpostn) (); /* смещение позиции в потоке │ │ caddr_t (*x_inline) (); /* указатель на буферизован- │ │ * ные данные */ │ │ VOID (*x_destroy) (); /* освобождает частные струк-│ │ * туры xdr_потока */ │ │ } *x_ops; │ │ caddr_t x_public; /* данные пользователя */ │ │ caddr_t x_private; /* указатель на частные данные */ │ │ caddr_t x_base; /* позиция info */ │ │ int x_handy; /* дополнительное слово */ │ │ } XDR; │ │ │ └────────────────────────────────────────────────────────────┘ Поле x_op определяет текущее действие, выполняемое над пото- ком. Значение этого поля важно для функций XDR, но не оказывает никакого воздействия на реализацию потока. Другими словами, реа- лизация потока не должна зависеть от этого значения. Поля x_private, x_base и x_handy принадлежат конкретной версии потока. Поле x_public используется клиентом XDR и никогда не должно быть задействовано потоком или функциями XDR. Макроопределения операций x_getpostn(), x_setpostn() и x_destroy() описаны в разделе "Безфильтровые элементы". Операция x_inline() имеет два параметра : XDR * и целое без знака (для счетчика байтов). Подпрограмма возвращает указатель на сегмент внутреннего бу- фера потока, который в дальнейшем может использоваться вызывающим процессом для любых нужд. Код возврата подпрограммы будет NULL, если она не сможет най- ти буфер требуемого размера. (Подпрограмма x_inline предназначена для циклических уплотнителей.) Операции x_getbytes() и x_putbytes() связаны с получением и выдачей наборов байт из потока и в поток. В случае успеха они возвращают TRUE, в случае неудачи - FALSE. Подпрограммы имеют идентичные параметры (замените только xxx): ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t │ │ xxxbytes(xdrs, buf, bytecount) │ │ XDR *xdrs; │ │ char *buf; │ │ uint bytecount; │ │ │ └────────────────────────────────────────────────────────────┘ Операции x_getlong() и x_putlong() связаны с получением и вы- дачей длинных чисел из потока и в поток. Эти подпрограммы отвеча- ют за преобразование чисел из машинного представления во внешнее (стандартное) и обратно. Функции XENIX/UNIX htonl() и ntohl() мо- гут им для этого пригодиться. Стандартное представление чисел рассматривается в разделе "Стандарт XDR". На верхнем уровне реа- лизации XDR предполагается, что длинные целые со знаком и без знака содержат одно и то же число бит, и неотрицательные целые имеют такое же двоичное представление, как и целые без знака. В случае успеха подпрограммы возвращают TRUE, в случае неуда- чи - FALSE. Они имеют идентичные параметры: ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t │ │ xxxlong(xdrs, lp) │ │ XDR *xdrs; │ │ long *lp; │ │ │ └────────────────────────────────────────────────────────────┘ Разработчики потоков XDR должны сделать создаваемую структуру доступной для клиентов, используя тот или иной тип процедуры соз- дания. Стандарт XDR ───────────────────────────────────────────────────────────── Раздел посвящен описанию стандарта XDR. Этот стандарт независим от языков программирования, операци- онных систем и аппаратной архитектуры. Поскольку данные распреде- ляются между машинами, не имеет никакого значения, получены ли данные на машине, которая работает в ОС XENIX/UNIX, а используют- ся на машине Sun, или же наоборот. Точно так же и выбор операци- онной системы не должен иметь никакого значения для внешнего представления данных. Что касается языков программирования, то данные, порожденные программой на Си, должны читаться как прог- раммой на Фортране, так и программой на Паскале. Стандарт XDR базируется на предположении о переносимости байт (или групп из восьми бит). Это означает, что аппаратные средства, выполняющие кодирование байт различными способами, обеспечивают сохранность закодированных значений при их пересылке через аппа- ратные границы. Стандарт XDR также предполагает наличие языка описания дан- ных. Этот язык представляет собой разновидность языка Си в части описания данных, но не программирования. (По аналогии стандарт Xerox Courier использует в качестве языка описания данных разно- видность языка Mesa.) Размер основного блока Для представления всех элементов данных требуется максимум 4 байта (или 32 бита). Байты нумеруются от 0 до n-1, где (n mod 4)= =0. Байты читаются из потока или записываются в поток так, что байт с номером m всегда предшествует байту с номером m+1. Целые Целое число со знаком в XDR представляет собой 32-битовый элемент, содержащий в закодированном (двоичном) представлении це- лое значение из диапазона [-2147483648, 2147483647]. Число предс- тавляется с дополнением до двух (в двоичной системе). Старший и младший байты имеют номера 0 и 3, соответственно. Такому предс- тавлению соответствует описание типа integer. Целые без знака Целое число без знака в XDR представляет собой 32-битовый элемент, содержащий в закодированном (двоичном) представлении не- отрицательное целое значение из диапазона [0, 4294967295]. Стар- ший и младший байты имеют номера 0 и 3, соответственно. Такому представлению соответствует описание типа unsigned. Перечисление типов Перечисление типов используется для описания подмножеств мно- жества целых чисел. Включаемые в них значения имеют такое же представление, как и целые числа. Описание перечисления имеет следующую форму: typedef enum { имя = значение, ... } имя_типа; Так, например, три цвета - красный, желтый и синий - можно описать следующим образом: typedef enum { RED = 2, YELLOW = 3, BLUE = 5 } colors; Булевы переменные Булевы переменные достаточно важны и встречаются достаточно часто для того, чтобы иметь свое собственное описание типа по стандарту. Такое описание представляет собой перечисление следую- щей формы: typedef enum { FALSE = 0, TRUE = 1 } boolean; Расширение целых чисел и чисел без знака Стандарт также поддерживает 64-разрядные (8-байтовые) числа, именуемые hyper integer и hyper unsigned. Их представление явля- ется очевидным расширением представления целых чисел и чисел без знака. Старший и младший байты имеют номера 0 и 7, соответствен- но. Плавающая запятая и двойная точность Стандарт поддерживает типы данных для чисел с плавающей запя- той: float (32 бита или 4 байта) и double (64 бита или 8 байт). При кодировании используется стандарт IEEE для представления нор- мализованных чисел с плавающей запятой одинарной и двойной точ- ности. Если вас интересуют подробности, обратитесь к указанному стандарту. Следующие три поля используются для описания числа с плавающей запятой: S Знак числа. Значения 0 и 1 обозначают положительное и отрицательное числа, соответственно. E Экспонента числа по основанию 2. Числа типа float от- водят на это поле 8 бит, double - 11 бит. Экспоненты для float и double смещены на 127 и 1023, соответс- твенно. F Дробная часть мантиссы числа по основанию 2. Числа типа float отводят на это поле 23 бита, double - 52 бита. Таким образом, число с плавающей запятой описывается форму- лой: ((-1)**S)x(2**(E-смещение))x1.F Поскольку старший и младший байты обыкновенного числа имеют номера 0 и 3, старший и младший значащие разряды числа с плаваю- щей запятой одинарной точности имеют номера 0 и 31. Смещение от начала до первого значащего разряда для полей S, E и F составляет 0, 1 и 9, соответственно. Числа с двойной точностью имеют аналогичные расширения. Сме- щение от начала до первого значащего разряда для полей S, E и F составляет 0, 1 и 12, соответственно. К спецификации IEEE следует обращаться, выполняя кодирование знакового нуля, знаковой бесконечности (переполнение) и денорма- лизованных чисел (потеря значимости). По спецификации IEEE обоз- начение "NaN" ("not a number" - не число) является системно-зави- симым и не должно использоваться. "Непрозрачные" данные Время от времени в межмашинном обмене принимают участие неин- терпретируемые данные фиксированного размера. Этот тип данных на- зывается opaque и описывается как: typedef opaque имя_типа[n]; opaque имя[n]; где n - определяемое заранее число байт, необходимое для размеще- ния данных типа opaque. Если n не кратно 4, за этими байтами следуют обнуленные байты количеством, достаточным для того, чтобы общее число байт объекта было кратно 4. Строки байтов заданного размера Стандарт поддерживает строки длиной n байт (с нумерацией от 0 до n-1), состоящие из числа n типа unsigned и следующих за ним n байт строковой информации. Если n не кратно 4, за этими байтами следуют обнуленные байты количеством, достаточным для того, чтобы общее число байт объекта было кратно 4. Строки описываются следу- ющим образом: typedef string имя_типа; typedef string имя_типа<>; string имя; string имя<>; В языке описания данных использование угловых скобок предус- мотрено для обозначения переменной длины в противоположность квадратным скобкам, обозначающим последовательности данных фикси- рованного размера. Константа N обозначает верхнюю границу количества байт, со- держащихся в строке. Если N не указано, подразумевается макси- мальная длина (2**32-1). Константа N обычно задается в специфика- ции протокола. Например, протокол записи в файл может констатиро- вать, что имя файла не должно быть длиннее 14 байт: string filename<14>; Спецификация XDR не говорит о том, что представляют собой отдельные байты строки; эта важная информация определяется в спецификациях верхнего уровня. Разумно было бы предположить, что в этих байтах закодированы символы ASCII. Массивы фиксированной размерности Описание данных, содержащихся в массивах фиксированной раз- мерности и однородной структуры, имеет вид: typedef elementtype имя_типа[n]; elementtype имя[n]; В массивах фиксированной размерности элементы нумеруются от 0 до n-1 и кодируются индивидуально в натуральном порядке. Массивы, содержащие счетчик элементов Массивы, включающие в себя счетчик элементов, представляют собой закодированные последовательности однородных элементов, имеющие переменную длину. Массив состоит из счетчика числа эле- ментов n (целое без знака), за которым следуют все элементы мас- сива с номерами от 0 до n-1. Описание таких массивов похоже на описание строк байт подсчитанной длины: typedef elementtype имя_типа; typedef elementtype имя_типа<>; elementtype имя; elementtype имя<>; И здесь константа N обозначает верхнюю границу количества элементов, содержащихся в массиве. Если N не указано, подразуме- вается максимальная длина (2**32-1). Структуры Описание структур очень похоже на принятое в языке Си: typedef struct { тип_компоненты имя_компоненты; ... } имя_типа; Компоненты структуры кодируются в порядке их объявления в структуре. Размеченные объединения Размеченное объединение представляет собой структуру, состоя- щую из дискриминанта, за которым следует тип, выбранный из набора перечисленных типов в соответствии со значением дискриминанта. Тип дискриминанта всегда enum. Типы компонент именуются ветвями (ответвлениями) объединения. Такое объединение кодируется как дискриминант, за которым следует подразумеваемая ветвь. Описание размеченного объединения имеет вид: typedef union switch (тип_дискриминанта) { значение_дискриминанта: тип_ветви; ... default: тип_ветви_по_умолчанию; } имя_типа; Указание ветви по умолчанию необязательно. Если оно не сдела- но, разрешается использовать только указанные значения дискрими- нанта. В большинстве спецификаций нет необходимости в использова- нии ветвей по умолчанию. Пропуски в спецификациях В стандарте отсутствуют представления для битовых полей и би- товых массивов, поскольку стандарт базируется на байтах. Перекрестные ссылки с использованием элементов библиотек Между элементами Си-библиотеки (см. раздел "Элементы библио- теки XDR") и стандартными типами данных, перечисленными в настоя- щем разделе, существует связь. Она описана в таблице. Маркирование записей Запись состоит из одного или нескольких фрагментов. Фрагмент записи представляет собой заголовок из 4 байт, за которым следует от 0 до (2**31-1) байт информации. Каждый байт содержит двоичное число без знака; как и в случае с целыми, байты располагаются от старших к младших. В числе кодируется два значения: булевое (ло- гическое), говорящее о том, является ли фрагмент последним фраг- ментом записи (значение 1) или нет, и 31-разрядное двоичное зна- чение без знака, соответствующее длине информационной части фрагмента (в байтах). Булевое значение занимает старший разряд заголовка; длина располагается в остальных 31 разряде. Такая спецификация отсутствует в стандарте XDR и не может быть реализована с помощью функций и подпрограмм XDR. Перспективные вопросы - списки с указателями ───────────────────────────────────────────────────────────── Раздел посвящен описанию порядка передачи структур данных с помощью связанных списков (списков с указателями) произвольной длины. В отличие от тех примеров, которые мы рассматривали ранее, приводимые ниже примеры иллюстрируют использование как библиотеч- ных подпрограмм XDR, так и языка описания данных XDR (см. раздел "Стандарт XDR"). Последний пример с xdr_numbers в разделе "Создание переноси- мых данных с помощью XDR" включает описание структуры данных на Си и связанных с ней XDR-процедур ведения активов и пассивов. Повторим его: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct gnumbers { │ │ long g_assets; │ │ long g_liabilities; │ │ }; │ │ │ │ bool_t │ │ xdr_gnumbers(xdrs, gp) │ │ XDR *xdrs; │ │ struct gnumbers *gp; │ │ { │ │ if (xdr_long(xdrs, &(gp->g_assets))) │ │ return (xdr_long(xdrs, &(gp->g_liabilities)));│ │ return(FALSE); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Теперь предположим, что вам нужно организовать эту информацию в виде связанного списка. Можно описать структуру данных следую- щим образом: typedef struct gnnode { struct gnumbers gn_numbers; struct gnnode *nxt; }; typedef struct gnnode *gnumbers_list; Заголовок связанного списка может мыслиться как информацион- ный объект; другими словами, он не является просто удобным описа- телем структуры. Точно так же поле nxt используется для того, чтобы обозначать конец объекта. Если объект имеет продолжение, поле nxt также содержит адрес продолжения. Однако, если объект преобразован в последовательную форму (сериализован), адреса свя- зи уже не несут полезной информации. Описание такого списка в XDR производится с помощью рекурсив- ного определения типа gnumbers_list: ┌────────────────────────────────────────────────────────────┐ │ │ │ struct gnumbers { │ │ unsigned g_assets; │ │ unsigned g_liabilities; │ │ }; │ │ │ │ typedef union switch (boolean) { │ │ case TRUE: struct { │ │ struct gnumbers current_element; │ │ gnumbers_list rest_of_list; │ │ }; │ │ case FALSE: struct (); │ │ } gnumbers_list; │ │ │ └────────────────────────────────────────────────────────────┘ В этом описании булевая переменная показывает, следует ли за ней какая-то информация или нет. Если ее значение FALSE, то она - последнее информационное поле в структуре. Если ее значение TRUE, то за ней следуют структура gnumbers и список gnumbers_list (ос- тавшаяся часть объекта). Обратите внимание на то, что в объявле- нии на Си булевая переменная не присутствует явно (хотя поле nxt неявно несет информацию), пока в описании данных XDR отсутствует явное указание на нее. Указания по написанию набора XDR-процедур для (де)сериализа- ции связанного списка записей можно найти в XDR-определении дан- ных, не включающем указатели. Набор состоит из взаимно рекурсив- ных подпрограмм xdr_gnumbers_list, xdr_wrap_list и xdr_gnnode: ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t │ │ xdr_gnnode(xdrs, gp) │ │ XDR *xdrs; │ │ struct gnnode *gp; │ │ { │ │ return (xdr_gnumbers(xdrs, &(gp->gn_numbers)) && │ │ xdr_gnumbers_list(xdrs, &(gp->nxt)) ); │ │ } │ │ │ │ bool_t │ │ xdr_wrap_list(xdrs, glp) │ │ XDR *xdrs; │ │ gnumbers_list *glp; │ │ { │ │ return (xdr_reference(xdrs, glp, │ │ sizeof(struct gnnode), xdr_gnnode)); │ │ } │ │ │ │ struct xdr_discrim choices[2] = { │ │ /* вызывается, если нужно выполнить (де)сериализацию│ │ * другого узла */ │ │ { TRUE, xdr_wrap_list }, │ │ /* вызывается, если больше нет узлов, подлежащих │ │ * (де)сериализации */ │ │ { FALSE, xdr_void } │ │ } │ │ │ │ bool_t │ │ xdr_gnumbers_list(xdrs, glp) │ │ XDR *xdrs; │ │ gnumbers_list *glp; │ │ { │ │ bool_t more_data; │ │ │ │ more_data = (*glp != (gnumbers_list)NULL); │ │ return (xdr_union(xdrs, &more_data, glp, choices, │ │ NULL)); │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Начальной (входной) подпрограммой является xdr_gnumbers_list(); ее функция заключается в установлении соот- ветствия между булевым значением more_data и значениями указате- лей в списке. Если больше нет данных, функция xdr_union() вызыва- ет xdr_void() и рекурсия завершается. В противном случае xdr_union() вызывает функцию xdr_wrap_list(), которая расшифровы- вает указатели из списка. Подпрограмма xdr_gnnode() фактически выполняет (де)сериализацию данных текущего узла связанного списка и рекурсивно запускает xdr_gnumbers_list() для обработки остав- шейся части списка. Читатели могут не сомневаться в том, что указанные подпрог- раммы работают правильно во всех трех направлениях (XDR_ENCODE, XDR_DECODE и XDR_FREE) для связанных списков любой длины (включая нулевую). Следует заметить, что булева переменная more_data всег- да инициализируется, но в случае выполнения XDR_DECODE ее значе- ние заменяется внешним. Также обратите внимание на то, что значе- ние bool_t остается в стеке. Сущность значения отражается на указателях списка. Нежелательный побочный эффект от (де)сериализации списка с помощью этих подпрограмм состоит в том, что стек увеличивается последовательно в соответствии с количеством узлов в списке. Кро- ме того, подпрограммы отличаются сложностью разработки (и понима- ния), являющейся следствием большого числа используемых разнород- ных функций (таких как xdr_reference, xdr_union и xdr_void). В следующей процедуре нарушается рекурсивность подпрограмм. В ней также используются другие усовершенствования, которые будут подробно обсуждаться ниже. ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t │ │ xdr_gnumbers_list(xdrs, glp) │ │ XDR *xdrs; │ │ gnumbers_list *glp; │ │ { │ │ bool_t more_data; │ │ │ │ while (TRUE) { │ │ more_data = (*glp != (gnumbers_list)NULL); │ │ if (!xdr_bool(xdrs, &more_data)) │ │ return (FALSE); │ │ if (!more_data) │ │ return (TRUE); │ │ if (!xdr_reference(xdrs, glp, │ │ sizeof(struct gnnode), xdr_gnumbers)) │ │ return (FALSE); │ │ glp = &((*glp)->nxt); │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ Преимущество состоит в том, что одну подпрограмму легче раз- работать и понять, чем три рекурсивные подпрограммы. Параметр glp трактуется как адрес указателя на заголовок оставшейся части списка, подлежащей (де)сериализации. Таким образом, по выходе из цикла while в glp заносится адрес поля nxt текущего узла. Разме- ченное объединение реализуется в строке; порядок использования переменной more_data в этой подпрограмме сохраняется. Ее значение заново вычисляется и (де)сериализуется на каждой итерации цикла. Поскольку *glp указывает на узел, указатель расшифровывается с помощью xdr_reference(). Обратите внимание на то, что третий па- раметр фактически является размером узла (информационные значения плюс указатель nxt), в то время как xdr_gnumbers() обрабатывает только информационные значения. При выполнении XDR_FREE в подпрограмме появляется неувязка, связанная с тем, что xdr_reference() освобождает узел *glp. По выходе из нее уже не гарантируется выполнение оператора glp = = &((*glp)->nxt), поскольку *glp больше не имеет отношения к уз- лу. Следующая программа работает во всех случаях. Наиболее слож- ная часть связана с избежанием отмены значения указателя, не про- шедшего инициализацию или освобожденного. ┌────────────────────────────────────────────────────────────┐ │ │ │ bool_t │ │ xdr_gnumbers_list(xdrs, glp) │ │ XDR *xdrs; │ │ gnumbers_list *glp; │ │ { │ │ bool_t more_data; │ │ bool_t freeing; │ │ gnumbers_list *next; /* следующее значение glp */ │ │ │ │ freeing = (xdrs->x_op == XDR_FREE); │ │ while (TRUE) { │ │ more_data = (*glp != (gnumbers_list)NULL); │ │ if (!xdr_bool(xdrs, &more_data)) │ │ return (FALSE); │ │ if (!more_data) │ │ return (TRUE); │ │ if (freeing) │ │ next = &((*glp)->nxt); │ │ if (!xdr_reference(xdrs, glp, │ │ sizeof(struct gnnode), xdr_gnumbers)) │ │ return (FALSE); │ │ glp = (freeing) ? next : &((*glp)->nxt); │ │ } │ │ } │ │ │ └────────────────────────────────────────────────────────────┘ В данном документе это первый пример, в котором на деле про- веряется выполнение операции (xdrs->x_op). Идея состоит в том, что корректную итерационную реализацию легче понять и разрабо- тать, чем рекурсивную. С учетом требований к стеку, принятых в Си, это конечно более эффективно. Краткий обзор подпрограмм XDR ───────────────────────────────────────────────────────────── В разделе приводятся синтаксис, описание и коды возврата каж- дой подпрограммы XDR. Подпрограммы следуют в алфавитном порядке. xdr_array() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_array(xdrs, arrp, sizep, maxsize, elsize, elproc) │ │ XDR *xdrs; │ │ char **arrp; │ │ uint *sizep, maxsize, elsize; │ │ xdrproc_t elproc; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между массивами и их внешним представлением. Параметр arrp является ад- ресом указателя на массив, sizep - адресом числа элементов в мас- сиве; размерность массива не должна превышать maxsize. Параметр elsize имеет смысл sizeof() для элементов каждого массива, elproc - XDR-фильтр, устанавливающий соответствие между Си-формой предс- тавления элементов в массиве и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_bool() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_bool(xdrs, bp) │ │ XDR *xdrs; │ │ bool_t *bp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между бу- левыми переменными и их внешним представлением. При выполнении кодирования фильтр генерирует значение, равное 1 или 0. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_bytes() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_bytes(xdrs, sp, sizep, maxsize) │ │ XDR *xdrs; │ │ char **sp; │ │ uint *sizep, maxsize; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между строками байт заданного размера и их внешним представлением. Па- раметр sp является адресом указателя строки. Длина строки разме- щается по адресу sizep; строки не могут быть длиннее maxsize. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_destroy() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ xdr_destroy(xdrs) │ │ XDR *xdrs; │ │ │ └────────────────────────────────────────────────────────────┘ Макроопределение, вызывающее подпрограмму удаления, связанную с потоком xdrs. Удаление обычно подразумевает освобождение част- ных структур данных, ассоциирующихся с потоком. Ссылка на xdrs после вызова xdr_destroy() считается неопределенной. xdr_double() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_double(xdrs, dp) │ │ XDR *xdrs; │ │ double *dp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между числами двойной точности и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_enum() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_enum(xdrs, ep) │ │ XDR *xdrs; │ │ enum_t *ep; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между данными перечислимого типа (enum) (фактически целого) и их внеш- ним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_float() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_float(xdrs, fp) │ │ XDR *xdrs; │ │ float *fp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между числами с плавающей точкой и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_getpos() ┌────────────────────────────────────────────────────────────┐ │ │ │ uint │ │ xdr_getpos(xdrs) │ │ XDR *xdrs; │ │ │ └────────────────────────────────────────────────────────────┘ Макроопределение, запускающее подпрограмму получения позиции, связанную с потоком xdrs. Возвращаемое подпрограммой значение является целым без знака, содержащим номер позиции в байтовом потоке XDR. xdr_inline() ┌────────────────────────────────────────────────────────────┐ │ │ │ long * │ │ xdr_inline(xdrs, len) │ │ XDR *xdrs; │ │ int len; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма инициирует выполнение встроенной процедуры, со- ответствующей XDR-потоку xdrs. Возвращает указатель на непрерыв- ный участок буферного пространства потока; len - длина буфера в байтах. Следует заметить, что указатель приводится к типу long *. ----------------------------------------------------------------- Замечание. Код возврата xdr_inline() может иметь значение 0 (NULL) в том случае, если подпрограмма не в состоянии выделить непрерывный участок буферного пространства. ----------------------------------------------------------------- xdr_int() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_int(xdrs, ip) │ │ XDR *xdrs; │ │ int *ip; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между це- лыми числами и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_long() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_long(xdrs, lp) │ │ XDR *xdrs; │ │ long *lp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между длинными (long) целыми числами и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_opaque() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_opaque(xdrs, cp, cnt) │ │ XDR *xdrs; │ │ char *cp; │ │ uint cnt; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между "непрозрачными" данными фиксированного размера и их внешним представлением. Параметр cp является адресом объекта, cnt - его размер в байтах. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_reference() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_reference(xdrs, pp, size, proc) │ │ XDR *xdrs; │ │ char **pp; │ │ uint size; │ │ xdrproc_t proc; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма обеспечивает представление указателей в структу- рах. Параметр pp является адресом указателя; size - размер струк- туры, на которую указывает *pp; proc - XDR-процедура, проверяющая соответствие между Си-формой структуры и ее внешним представлени- ем. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_setpos() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_setpos(xdrs, pos) │ │ XDR *xdrs; │ │ uint pos; │ │ │ └────────────────────────────────────────────────────────────┘ Макроопределение, запускающее подпрограмму задания позиции позиционирования), связанную с потоком xdrs. Параметр pos являет- ся значением, полученным от xdr_getpos(). В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. ----------------------------------------------------------------- Замечание. Позиционирование некоторых потоков XDR затруднено, поэтому с одним потоком операция может завершиться успешно, с другим - неу- дачно. ----------------------------------------------------------------- xdr_short() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_short(xdrs, sp) │ │ XDR *xdrs; │ │ short *sp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между ко- роткими (short) целыми числами и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_string() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_string(xdrs, sp, maxsize) │ │ XDR *xdrs; │ │ char **sp; │ │ uint maxsize; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между строками (массивами литер) и их внешним представлением. Размер строк не должен превышать maxsize. Обратите внимание на то, что sp является адресом указателя строки. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_u_int() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_u_int(xdrs, up) │ │ XDR *xdrs; │ │ unsigned *up; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между це- лыми без знака и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_u_long() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_u_long(xdrs, ulp) │ │ XDR *xdrs; │ │ unsigned long *ulp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между длинными целыми без знака и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_u_short() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_u_short(xdrs, usp) │ │ XDR *xdrs; │ │ unsigned short *usp; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между короткими целыми без знака и их внешним представлением. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_union() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_union(xdrs, dscmp, unp, choices, default) │ │ XDR *xdrs; │ │ int *dscmp; │ │ char *unp; │ │ struct xdr_discrim *choices; │ │ xdrproc_t default; │ │ │ └────────────────────────────────────────────────────────────┘ Фильтрующая процедура, устанавливающая соответствие между размеченным объединением и его внешним представлением. Параметр dscmp является адресом дискриминанта объединения, а unp - адресом самого объединения. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdr_void() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_void() │ │ │ └────────────────────────────────────────────────────────────┘ Код возврата подпрограммы всегда равен 1. Он может быть пере- дан тем подпрограммам RPC, которым требуется функциональный пара- метр, не связанный с каким-либо действием. xdr_wrapstring() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdr_wrapstring(xdrs, sp) │ │ XDR *xdrs; │ │ char **sp; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма обращается к xdr_string(xdrs,sp,MAXUNSIGNED); где MAXUNSIGNED - максимальное значение целого числа без знака. Может быть полезна, поскольку пакет XDR передает подпрограммам только два параметра, в то время как xdr_string(), одной из наи- более часто используемых процедур, требуется три параметра. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdrmem_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ xdrmem_create(xdrs, addr, size, op) │ │ XDR *xdrs; │ │ char *addr; │ │ uint size; │ │ enum xdr_op op; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма инициализирует потоковый объект XDR, на который указывает xdrs. Данные потока записываются в или читаются из участка памяти по адресу addr; размер участка в байтах не превы- шает значение size. Параметр op определяет направление потока XDR (XDR_ENCODE, XDR_DECODE или XDR_FREE). xdrrec_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ xdrrec_create(xdrs, sendsize, recvsize, handle, readit, │ │ writeit) │ │ XDR *xdrs; │ │ uint sendsize, recvsize; │ │ char *handle; │ │ int (*readit) (), (*writeit) (); │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма инициализирует потоковый объект XDR, на который указывает xdrs. Данные потока записываются в буфер размера sendsize; нулевое значение говорит системе о необходимости ис- пользовать подходящее умолчание. Считываются данные из буфера размера recvsize; передача нулевого значения также приводит к ис- пользованию подходящего умолчания. Когда буфер вывода заполняет- ся, вызывается writeit(). Аналогично, когда буфер ввода опустел, вызывается readit(). Механизм действия этих подпрограмм схож с механизмом действия системных функций XENIX/UNIX read и write, если не считать того, что handle передается им в качестве первого параметра. Обратите внимание на то, что значение поля op потока XDR устанавливается вызывающим процессом. ----------------------------------------------------------------- Замечание. В рамках потока XDR реализуется промежуточный поток на уров- не записей. Этим объясняется появление дополнительных байт, со- держащих информацию о границах записей. ----------------------------------------------------------------- xdrrec_endofrecord() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdrrec_endofrecord(xdrs, sendnow) │ │ XDR *xdrs; │ │ int sendnow; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма может работать только с потоками, созданными с помощью xdrrec_create(). Данные в буфере вывода помечаются как законченная запись и, если значение sendnow отлично от нуля, со- держимое буфера выводится. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdrrec_eof() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdrrec_eof(xdrs) │ │ XDR *xdrs; │ │ int empty; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма может работать только с потоками, созданными с помощью xdrrec_create(). В случае обнаружения конца текущей запи- си потока подпрограмма возвращает 1, если поток исчерпан, и 0 в противном случае. xdrrec_skiprecord() ┌────────────────────────────────────────────────────────────┐ │ │ │ xdrrec_skiprecord(xdrs) │ │ XDR *xdrs; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма может работать только с потоками, созданными с помощью xdrrec_create(). Она сообщает XDR о том, что оставшуюся часть текущей записи во входном буфере потока следует проигнори- ровать. В случае успешного выполнения код возврата подпрограммы равен 1, в противном случае - нулю. xdrstdio_create() ┌────────────────────────────────────────────────────────────┐ │ │ │ void │ │ xdrstdio_create(xdrs, file, op) │ │ XDR *xdrs; │ │ FILE *file; │ │ enum xdr_op op; │ │ │ └────────────────────────────────────────────────────────────┘ Подпрограмма инициализирует потоковый объект XDR, на который указывает xdrs. Данные потока записываются в или читаются из по- токового файла стандартного ввода-вывода. Параметр op определяет направление потока XDR (XDR_ENCODE, XDR_DECODE или XDR_FREE). ----------------------------------------------------------------- Замечание. Подпрограмма удаления, связанная с такими потоками XDR, по отношению к потоковому файлу использует fflush(), а не fclose(). -----------------------------------------------------------------