Удаленный вызов процедур rpc можно ли. Удаленные процедуры. Вызов удаленных процедур: подробная инструкция. Механизм работы RPC

Многие пользователи компьютерных систем слышали о таких понятиях, как удаленные процедуры, вызов удаленных процедур или RPC. Вот только далеко не все представляют себе, что это за технологии, как они работают и для чего нужны. А ведь многие из тех, кто отключил данную службу в Windows-системах, зачастую могут получать и ошибки, относящиеся к критическим сбоям. Об этом и многом другом далее пойдет речь.

Удаленный вызов процедур: что это?

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

Однако удаленные процедуры (вызов удаленных процедур), обозначаемые как RPC (сокращение от английского Remote Procedure Call), можно отнести не только к удаленным компьютерам. На локальном уровне такие технологии тоже применяются. В качестве простейшего примера можно привести вызов определенной функции одной программы из другого приложения посредством взаимодействия через специальные библиотеки.

Кроме того, абсолютно во всех версиях Windows имеется такая служба, а при ее отключении или отказе работоспособности модификация XP не работает вообще.

Принцип действия

Как правило, служба «Удаленный вызов процедур RPC» для работы в режиме клиент-сервер требует наличия как минимум двух основных компонентов: сетевого протокола для обмена данными и языка сериализации (перевода какого-то процесса или информационной структуры данных в битовую последовательность).

Архитектуры могут быть совершенно разными и отличающимися по своим возможностям. Но для обмена данными на так называемом транспортном уровне чаще всего применяются протоколы UDP и TCP, реже - HTTP.

Чтобы не вдаваться в технические аспекты, самым простым объяснением принципа работы таких технологий может стать следующие пример: клиентский процесс формирует запрос серверу с описанием выбранной процедуры с указанными параметрами и отправляет его, после чего сервер выполняет требуемую директиву и отправляет клиенту ответ, который отображается на клиентской машине. Однако сам серверный обработчик находится, так сказать, в режиме ожидания и активируется только в моменты получения клиентских запросов. При этом совсем не обязательно, чтобы выполнение схемы «запрос-ответ» производилось немедленно.

При этом максимальный эффект производительности достигается при обмене относительно малыми объемами данных и небольшим временем отклика компонентов, между которыми установлена интерактивная связь.

Удаленные процедуры (вызов удаленных процедур): характерные черты и реализации

Таким образом, можно выделить две главные особенности названных технологий:

  • асимметричность (инициация выполнения удаленной процедуры только одной из сторон);
  • синхронность (приостановка вызывающей процедуры с момента инициации запроса и возобновление после отправки ответа).

Что же касается реализаций, удаленные процедуры (вызов удаленных процедур) сегодня используют несколько базовых технологий, среди которых наиболее широко применяются следующие:

  • DCE/RPC - бинарный протокол на основе TCP/IP, SMB/SIFC и т. д.;
  • DCOM - объектно-ориентированное дополнение с возможностью передачи ссылок на объекты и вызовом методов их обработки;
  • JSON-RPC - текстовый протокол, основанный на HTTP;
  • .NET Remoting - бинарный протокол на основе UDP, TCP и HTTP;
  • JAVA RMI;
  • SOAP;
  • XML RPC;
  • SUN RPC;
  • ZeroC ICE;
  • Routix.RPC и др.

Проблемы и задачи

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

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

Наконец, одной из основных называется проблема совместимости ввиду неоднородности некоторых языков программирования, несмотря даже на установленные единые стандарты.

Основные типы подсистем

Удаленный вызов процедур Windows 10 или любой другой системы рангом ниже подразумевает использование специальных подсистем:

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

Какие типы программ требуют выполнения RPC?

Если же говорить о том, какие программные модули операционных систем требуют держать службу RPC включенной, все их перечислить просто невозможно.

Но среди всем известных компонентов Windows-систем можно отметить службу факсов, службы криптографии, регистрацию ошибок, справку и поддержку, доступ к устройствам HID, службу сообщений (Messenger), администрирование дисков и логических разделов, управление съемными накопителями, аудиосистему, установщик Windows и еще бог весть что.

Думается, этого списка достаточно, чтобы понять, насколько многие компоненты системы, да и сам пользователь, зависимы от этой службы.

На что влияет RPC

Вообще, исходя из предыдущего описания, можно оценить влияние RPC. Так, например, известно немало случаев, когда при отключении этой службы полностью пропадал звук, невозможным оказывалось восстановление системы после критических сбоев или инициированное пользователем, «слетали» настройки беспроводной сети.

Но самое печальное состоит в том, что, если отключить удаленный вызов процедур RPC, иногда бывает невозможно даже получить доступ к основным настройкам системы, будь пользователь хоть трижды администратором на собственном терминале.

Можно ли отключить эту службу

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

Но не все знают, что в разделе службы (services.msc) есть еще такая штука, как «Локатор удаленного вызова процедур RPC». Вот его-то как раз и можно отключить безболезненно для системы. Приложения, которые могут его использовать в своей работе, самостоятельно вызовут сервис при необходимости.

Устранение сбоев и ошибок

Наконец, посмотрим, что можно сделать, если выдается ошибка при удаленном вызове процедуры. В самом простом случае можно попытаться включить службу заново (если, конечно, получится).

Для этого в соответствующем разделе, где находится искомая служба, двойным кликом вызывается меню редактирования параметров, нажимается кнопка включения, а сам тип включения устанавливается на автоматический. Если проделать такую процедуру при стандартной загрузке системы не представляется возможным, можно попытаться произвести аналогичные действия в безопасном режиме. Некоторые специалисты советуют заодно на время проведения действий на всякий случай отключить антивирусное ПО.

Если это не поможет, но под рукой имеется установочный или восстановительный диск системы, можно запустить командную консоль с правами администратора (загружаться с диска не нужно) и прописать в ней такие команды:

  • cd z:\i386 (Z - литера оптического привода) ;
  • expand explorer.ex_ %TEMP%\explorer.exe;
  • expand svchost.ex_ %TEMP%\svchost.exe.

После этого запускаем «Диспетчер задач» (Ctrl + Del + Alt или taskmgr в меню «Выполнить») и завершаем процесс Explorer.exe.

В «Диспетчере» останавливаем все процессы svhost.exe, после чего в течение 60 секунд нужно успеть в командой строке ввести строку copy %TEMP%\svchost.exe %systemroot%\system32 /y.

Наконец, если есть доступ к редактору системного реестра (regedit) восстановлен, нужно пройти по ветке HKCC через разделы SYSTEM и CurrentControlSet и добраться до параметра CSConfigFlags, изменив его значение на ноль.

Это далеко не все методы исправления ошибок, связанных с RPC. Дело в том, что, если эта служба повлекла за собой нарущения в работе других сервисов, возможно, сначала придется устранять проблемы с их работоспособностью, а только потом предпринимать какие-то действия в отношении RPC. И не всегда к вышеописанным параметрам и настройкам можно получить полный доступ. Если уж совсем ничего не получится, как это ни плачевно звучит, придется полностью переустанавливать операционную систему, хотя хочется надеяться, что до этого дело не дойдет.

Заключение

Вот кратко и все, что касается технологии и службы RPC. На самом деле все это выглядит намного сложнее, нежели было представлено в данном описании, и для полного понимания вопроса необходимо обладать хотя бы начальными знаниями. Но для того, чтобы иметь об RPC общее представление, этого пока достаточно.

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

После перезагрузки компа не запустилась служба "Удаленный вызов процедур (RPC) ". Очень многое зависит от этой службы. В итоге не работает восстановление системы, сетевое окружение, звук, Windows Installer, почти не работает консоль управления (MMC), на панели задач не показываются открытые окна и т.д. и т.п. Попытка ручного запуска приводит к ошибке "Неудается запустить...(RPC) на xxxComp. Ошибка 5: Отказано в доступе ". Антивирус ничего не нашел. Два дня копаний и комп удалось вернуть к жизни.

По рекомендации Microsoft, первое, что пробовал, найти и удалить ветку реестра . Ее у меня не оказалось, возможно в результате каких-то установленных обновлений.

Далее, попытка восстановить параметры службы в реестре. Поскольку regedit.exe работал только на чтение/удаление (еще один побочный эффект), не получилось внести изменения. Да они и не нужны были, т.к. все было верно. Должно выглядеть вот так:

Windows Registry Editor Version 5.00 "Description"="Обеспечивает сопоставление конечных точек и иных служб RPC." "DisplayName"="Удаленный вызов процедур (RPC)" "ErrorControl"=dword:00000001 "Group"="COM Infrastructure" "ImagePath"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,\ 74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,73,\ 00,76,00,63,00,68,00,6f,00,73,00,74,00,20,00,2d,00,6b,00,20,00,72,00,70,00,\ 63,00,73,00,73,00,00,00 "ObjectName"="NT AUTHORITY\\NetworkService" "Start"=dword:00000002 "Type"=dword:00000010 "FailureActions"=hex:00,00,00,00,00,00,00,00,00,00,00,00,01,00,00,00,00,00,00,\ 00,02,00,00,00,60,ea,00,00 "ServiceSidType"=dword:00000001 "ServiceDll"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,\ 00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,\ 72,00,70,00,63,00,73,00,73,00,2e,00,64,00,6c,00,6c,00,00,00 "Security"=hex:01,00,14,80,a8,00,00,00,b4,00,00,00,14,00,00,00,30,00,00,00,02,\ 00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,\ 00,00,02,00,78,00,05,00,00,00,00,00,14,00,8d,00,02,00,01,01,00,00,00,00,00,\ 05,0b,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,\ 20,02,00,00,00,00,18,00,8d,00,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,\ 02,00,00,00,00,14,00,9d,00,00,00,01,01,00,00,00,00,00,05,04,00,00,00,00,00,\ 18,00,9d,00,00,00,01,02,00,00,00,00,00,05,20,00,00,00,21,02,00,00,01,01,00,\ 00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00 "0"="Root\\LEGACY_RPCSS\\0000" "Count"=dword:00000001 "NextInstance"=dword:00000001

Значение параметра start может отличаться. Изменить реестр все же можно, но при этом нужно загрузиться с MS ERD commander .

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

  • Запустить консоль (Пуск > Выполнить: cmd )
  • cd z:\i386 (там дистрибутив Windows)
  • expand explorer.ex_ %TEMP%\explorer.exe
  • expand svchost.ex_ %TEMP%\svchost.exe
  • Запустить Диспетчер задач (Ctrl+Shift+Esc)
  • Остановить процесс exlporer.exe
  • copy %TEMP%\explorer.exe %SYSTEMROOT% /y
  • Остановить все процессы svchost.exe. Внимание! После этого у вас будет 60 секунд до перезагрузки машины.
  • copy %TEMP%\svchost.exe %systemroot%\system32 /y

Этот финт тоже не дал результатов. Еще вариант: запустить проверку всех защищенных системных файлов с заменой неправильных версий правильными. В консоли выполнить:

sfc /PURGECACHE - Очистка файлового кэша и немедленная проверка файлов
sfc /SCANONCE - Разовая проверка при следующей загрузке

Не помогло.. Тогда совсем брутальный ход - восстановление параметров безопасности. Опять же в консоли:

secedit /configure /cfg %windir%\repair\secsetup.inf /db secsetup.sdb /verbose

После перезагрузки комп заработал, базовые сервисы стартовали. Появился новый косяк (а может он был с самого начала): под моей учеткой не запускался, как минимум, менеджер управления дисками и Windows Installer. Отказано в доступе. Можно через консоль восстановить права доступа к системному диску "по умолчанию":

secedit /configure /db %TEMP%\temp.mdb /Cfg %WINDIR%\inf\defltwk.inf /areas filestore

После чего нужно в ручную определить права для каждой учетки к или пересоздать их, смотря что проще.

В моем случае я просто назначил одинаковые права на весь системный диск, взяв за эталон доступ к каталогу . К эталону добавил свою учетку в домене с полными правами к диску. Может это неправильно с точки зрения безопасности, но копаться с каждым каталогом отдельно у меня времени нет.

Что еще можно было предпринять

Пока комп "болел" вот этого не было в реестре:

"ActiveService"="RpcSs"

Возможно ручное добавление как-то бы изменило ситуацию.

Попытки ручного запуска сервиса, например через команду "net start rcpss " заканчивались ошибкой "Error 5: access denied ". Я предполагаю, отказано в доступе потому, что сервис должен запускаться под учеткой системы - "NT AUTHORITY". В реестре есть такой параметр:

"ObjectName"="NT AUTHORITY\\NetworkService"

Я бы попытался вписать сюда админскую учетку и опять запустить сервис. Но это только идея, не дожившая до реализации.

Еще вариант: использование эксплоита KiTrap0D для получения консоли с правами системы. Об этом эксплоите писали в . Собственно бинарник . Вот только у меня стоят виндовские обновки, так что похоже данный эксплоит уже не работает.

Похожие материалы:

Лекция 4

4.1 Концепция удаленного вызова процедур

Идея вызова удаленных процедур (Remote Procedure Call - RPC) состоит в расширении хорошо известного и понятного механизма передачи управления и данных внутри программы, выполняющейся на одной машине, на передачу управления и данных через сеть. Средства удаленного вызова процедур предназначены для облегчения организации распределенных вычислений. Наибольшая эффективность использования RPC достигается в тех приложениях, в которых существует интерактивная связь между удаленными компонентами с небольшим временем ответов и относительно малым количеством передаваемых данных. Такие приложения называются RPC-ориентированными.

Характерными чертами вызова локальных процедур являются: асимметричность, то есть одна из взаимодействующих сторон является инициатором; синхронность, то есть выполнение вызывающей процедуры при останавливается с момента выдачи запроса и возобновляется только после возврата из вызываемой процедуры.

Реализация удаленных вызовов существенно сложнее реализации вызовов локальных процедур. Начнем с того, что поскольку вызывающая и вызываемая процедуры выполняются на разных машинах, то они имеют разные адресные пространства, и это создает проблемы при передаче параметров и результатов, особенно если машины не идентичны. Так как RPC не может рассчитывать на разделяемую память, то это означает, что параметры RPC не должны содержать указателей на ячейки нестековой памяти и что значения параметров должны копироваться с одного компьютера на другой. Следующим отличием RPC от локального вызова является то, что он обязательно использует нижележащую систему связи, однако это не должно быть явно видно ни в определении процедур, ни в самих процедурах. Удаленность вносит дополнительные проблемы. Выполнение вызывающей программы и вызываемой локальной процедуры в одной машине реализуется в рамках единого процесса. Но в реализации RPC участвуют как минимум два процесса - по одному в каждой машине. В случае, если один из них аварийно завершится, могут возникнуть следующие ситуации: при аварии вызывающей процедуры удаленно вызванные процедуры станут "осиротевшими", а при аварийном завершении удаленных процедур станут "обездоленными родителями" вызывающие процедуры, которые будут безрезультатно ожидать ответа от удаленных процедур.

Кроме того, существует ряд проблем, связанных с неоднородностью языков программирования и операционных сред: структуры данных и структуры вызова процедур, поддерживаемые в каком-либо одном языке программирования, не поддерживаются точно так же во всех других языках.


Эти и некоторые другие проблемы решает широко распространенная технология RPC, лежащая в основе многих распределенных операционных систем.

Базовые операции RPC

Чтобы понять работу RPC, рассмотрим вначале выполнение вызова локальной процедуры в обычной машине, работающей автономно. Пусть это, например, будет системный вызов

count=read (fd,buf,nbytes);

где fd – целое число;

buf – массив символов;

nbytes – целое число.

Чтобы осуществить вызов, вызывающая процедура заталкивает параметры в стек в обратном порядке. После того, как вызов read выполнен, он помещает возвращаемое значение в регистр, перемещает адрес возврата и возвращает управление вызывающей процедуре, которая выбирает параметры из стека, возвращая его в исходное состояние. Заметим, что в языке С параметры могут вызываться или по ссылке (by name), или по значению (by value). По отношению к вызываемой процедуре параметры-значения являются инициализируемыми локальными переменными. Вызываемая процедура может изменить их, и это не повлияет на значение оригиналов этих переменных в вызывающей процедуре.

Если в вызываемую процедуру передается указатель на переменную, то изменение значения этой переменной вызываемой процедурой влечет изменение значения этой переменной и для вызывающей процедуры. Этот факт весьма существенен для RPC.

Существует также другой механизм передачи параметров, который не используется в языке С. Он называется call-by-copy/restore и состоит в необходимости копирования вызывающей программой переменных в стек в виде значений, а затем копирования назад после выполнения вызова поверх оригинальных значений вызывающей процедуры.

Решение о том, какой механизм передачи параметров использовать, принимается разработчиками языка. Иногда это зависит от типа передаваемых данных. В языке С, например, целые и другие скалярные данные всегда передаются по значению, а массивы - по ссылке.

Идея, положенная в основу RPC, состоит в том, чтобы сделать вызов удаленной процедуры выглядящим по возможности также, как и вызов локальной процедуры. Другими словами - сделать RPC прозрачным: вызывающей процедуре не требуется знать, что вызываемая процедура находится на другой машине, и наоборот.

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

Этапы выполнения RPC

Взаимодействие программных компонентов при выполнении удаленного вызова процедуры иллюстрируется рисунком 2.

Рисунок 2. Remote Procedure Call

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

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

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

На стороне сервера поступающие биты помещаются принимающей аппаратурой либо во встроенный буфер, либо в оперативную память. Когда вся информация будет получена, генерируется прерывание. Обработчик прерывания проверяет правильность данных пакета и определяет, какому стабу следует их передать. Если ни один из стабов не ожидает этот пакет, обработчик должен, либо поместить его в буфер, либо вообще отказаться от него. Если имеется ожидающий стаб, то сообщение копируется ему. Наконец, выполняется переключение контекстов, в результате чего восстанавливаются регистры и карта памяти, принимая те значения, которые они имели в момент, когда стаб сделал вызов receive.

Теперь начинает работу серверный стаб. Он распаковывает параметры и помещает их соответствующим образом в стек. Когда все готово, выполняется вызов сервера. После выполнения процедуры сервер передает результаты клиенту. Для этого выполняются все описанные выше этапы, только в обратном порядке.

Рисунок 3 показывает последовательность команд, которую необходимо выполнить для каждого RPC-вызова.

Рисунок 3. Этапы выполнения процедуры RPC

Механизм сокетов

Механизм сокетов (sockets) впервые появился в версии 4.3 BSD UNIX (Berkeley Software Distribution UNIX - ветвь UNIX, начавшая развиваться в калифорнийском университете Беркли). Позже он превратился в одну из самых популярных систем сетевого обмена сообщениями. Сегодня этот механизм реализован во многих операционных системах, иногда его по-прежнему называют Berkeley Sockets, отдавая дань уважения его создателям, хотя существует большое количество его реализаций как для различных ОС семейства UNIX, так и для других ОС, например для ОС семейства Windows, где он носит название Windows Sockets (WinSock).

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

Независимость от нижележащих сетевых протоколов и технологий. Для этого используется понятие коммуникационный домен (communication domain). Коммуникационный домен обладает некоторым набором коммуникационных свойств, определяющих способ именования сетевых узлов и ресурсов, характеристики сетевых соединений (надежные, дейтаграммные, упорядоченные), способы синхронизации процессов и т. п. Одним из наиболее популярных доменов является домен Интернета с протоколами стека TCP/IP.

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

Сокет может иметь как высокоуровневое символьное имя (адрес), так и низкоуровневое, отражающее специфику адресации определенного коммуникационного домена. Например, в домене Интернета низкоуровневое имя представлено парой (IP-адрес, порт).

Для каждого коммуникационного домена могут существовать сокеты различных типов. С помощью типа сокета можно задавать определенный вид взаимодействия, имеющий смысл для домена. Так, во многих доменах существуют дейтаграммные соединения (datagram) и соединения потоковые (stream), гарантирующие надежную упорядоченную доставку.

Еще одним удобным механизмом, облегчающим взаимодействие операционных систем и приложений по сети, является механизм вызова удаленных процедур (Remote Procedure Call, RPC). Этот механизм представляет собой надстройку над системой обмена сообщениями ОС, поэтому в ряде случаев он позволяет более удобно и прозрачно организовать взаимодействие программ по сети, однако его полезность не универсальна.

Идея вызова удаленных процедур состоит в расширении хорошо известного и понятного механизма передачи управления и данных внутри программы, выполняющейся на одной машине, на передачу управления и данных через сеть. Средства удаленного вызова процедур предназначены для облегчения организации распределенных вычислений. Впервые механизм RPC реализовала компания Sun Microsystems, и он хорошо соответствует девизу «Сеть - это компьютер», взятому этой компанией на вооружение, так как приближает сетевое программирование к локальному. Наибольшая эффективность RPC достигается в тех приложениях, в которых существует интерактивная связь между удаленными компонентами с небольшим временем ответов и относительно малым количеством передаваемых данных. Такие приложения называются RPC-ориентированными.

Характерными чертами вызова локальных процедур являются:

Асимметричность - одна из взаимодействующих сторон является инициатором взаимодействия;

Синхронность - выполнение вызывающей процедуры блокируется с момента выдачи запроса и возобновляется только после возврата из вызываемой процедуры.

Реализация удаленных вызовов существенно сложнее реализации вызовов локальных процедур. Начнем с того, что поскольку вызывающая и вызываемая процедуры выполняются на разных машинах, то они имеют разные адресные пространства и это создает проблемы при передаче параметров и результатов, особенно если машины и их операционные системы не идентичны. Так как RPC не может рассчитывать на разделяемую память, это означает, что параметры RPC не должны содержать указателей на ячейки памяти и что значения параметров должны как-то копироваться с одного компьютера на другой.

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

При аварии вызывающей процедуры удаленно вызванные процедуры становятся «осиротевшими»;

При аварийном завершении удаленных процедур становятся «обездоленными родителями» вызывающие процедуры, которые будут безрезультатно ожидать ответа от удаленных процедур.

Кроме того, существует ряд проблем, связанных с неоднородностью языков программирования и операционных сред: структуры данных и структуры вызова процедур, поддерживаемые в каком-либо одном языке программирования, не поддерживаются точно таким же способом в других языках.

Рассмотрим, каким образом технология RPC, лежащая в основе многих распределенных операционных систем, решает эти проблемы.

Чтобы понять работу RPC, рассмотрим сначала выполнение вызова локальной процедуры в автономном компьютере. Пусть это, например, будет процедура записи данных в файл:

m.= my_write(fd,buf.length);

Здесь fd - дескриптор файла, целое число, buf - указатель на массив символов, length - длина массива, целое число.

Чтобы осуществить вызов, вызывающая процедура помещает указанные параметры в стек в обратном порядке и передает управление вызываемой процедуре my_write. Эта пользовательская процедура после некоторых манипуляций с данными символьного массива buf выполняет системный вызов write для записи данных в файл, передавая ему параметры тем же способом, то есть помещая их в стек (при реализации системного вызова они копируются в стек системы, а при возврате из него результат помещается в пользовательский стек). После того как процедура my_write выполнена, она помещает возвращаемое значение m в регистр, перемещает адрес возврата и возвращает управление вызывающей процедуре, которая выбирает параметры из стека, возвращая его в исходное состояние. Заметим, что в языке С параметры могут вызываться по ссылке (by name), представляющей собой адрес глобальной области памяти, в которой хранится параметр, или по значению (by value), в этом случае параметр копируется из исходной области памяти в локальную память процедуры, располагаемую обычно в стековом сегменте. В первом случае вызываемая процедура работает с оригинальными значениями параметров и их изменения сразу же видны вызывающей процедуре. Во втором случае вызываемая процедура работает с копиями значений параметров, и их изменения никак не влияют на значение оригиналов этих переменных в вызывающей процедуре. Эти обстоятельства весьма существенны для RPC.

Решение о том, какой механизм передачи параметров использовать, принимается разработчиками языка. Иногда это зависит от типа передаваемых данных. В языке С, например, целые и другие скалярные данные всегда передаются по значению, а массивы - по ссылке.

Рисунок 69 иллюстрирует передачу параметров вызываемой процедуре: стек до выполнения вызова write (а), стек во время выполнения процедуры (б), стек после возврата в вызывающую программу (в).

Рис. 69. Передача параметров вызываемой процедуре

Идея, положенная в основу RPC, состоит в том, чтобы вызов удаленной процедуры по возможности выглядел так же, как и вызов локальной процедуры. Другими словами, необходимо сделать механизм RPC прозрачным для программиста: вызывающей процедуре не требуется знать, что вызываемая процедура находится на другой машине, и наоборот.

Механизм RPC достигает прозрачности следующим образом. Когда вызываемая процедура действительно является удаленной, в библиотеку процедур вместо локальной реализации оригинального кода процедуры помещается другая версия процедуры, называемая клиентским стабом (stub - заглушка). На удаленный компьютер, который выполняет роль сервера процедур, помещается оригинальный код вызываемой процедуры, а также еще один стаб, называемый серверным стабом . Назначение клиентского и серверного стабов - организовать передачу параметров вызываемой процедуры и возврат значения процедуры через сеть, при этом код оригинальной процедуры, помещенной на сервер, должен быть полностью сохранен. Стабы используют для передачи данных через сеть средства подсистемы обмена сообщениями, то есть существующие в ОС примитивы send и receive. Иногда в подсистеме обмена сообщениями выделяется программный модуль, организующий связь стабов с примитивами передачи сообщений, называемый модулем RPCRimtime.

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

Рис. 70. Выполнение удаленного вызова процедуры

Эта операция называется операцией упаковки параметров. После этого клиентский стаб обращается к примитиву send для передачи этого сообщения удаленному компьютеру, на который помещена реализация оригинальной процедуры. Получив из сети сообщение, ядро ОС удаленного компьютера вызывает серверный стаб, который извлекает из сообщения параметры и вызывает обычным образом оригинальную процедуру. Для получения сообщения серверный стаб должен предварительно вызвать примитив receive, чтобы ядро знало, для кого пришло сообщение. Серверный стаб распаковывает параметры вызова, имеющиеся в сообщении, и обычным образом вызывает оригинальную процедуру, передавая ей параметры через стек. После окончания работы процедуры серверный стаб упаковывает результат ее работы в новое сообщение и с помощью примитива send передает сообщение по сети клиентскому стабу, а тот возвращает обычным образом результат и управление вызывающей процедуре. Ни вызывающая процедура, ни оригинальная вызываемая процедура не изменились оттого, что они стали работать на разных компьютерах.

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

Автоматический способ основан на применении специального языка определения интерфейса (Interface Definition Language, IDL). С помощью этого языка программист описывает интерфейс между клиентом и сервером RPC. Описание включает список имен процедур, выполнение которых клиент может запросить у сервера, а также список типов аргументов и результатов этих процедур. Информация, содержащаяся в описании интерфейса, достаточна для выполнения стабами проверки типов аргументов и генерации вызывающей последовательности. Кроме того, описание интерфейса содержит некоторую дополнительную информацию, полезную для оптимизации взаимодействия стабов, например каждый аргумент помечается как входной, выходной или играющий и ту, и другую роли (входной аргумент передается от клиента серверу, а выходной - в обратном направлении). Интерфейс может включать также описание общих для клиента и сервера констант. Необходимо подчеркнуть, что обычно интерфейс RPC включает не одну, а некоторый набор процедур, выполняющих взаимосвязанные функции, например функции доступа к файлам, функции удаленной печати и т. п. Поэтому при вызове удаленной процедуры обычно необходимо каким-то образом задать нужный интерфейс, а также конкретную процедуру, поддерживаемую этим интерфейсом. Часто интерфейс также называют сервером RPC, например файловый сервер, сервер печати.

После того как описание интерфейса составлено программистом, оно компилируется специальным IDL-компилятором, который вырабатывает исходные модули клиентских и серверных стабов для указанных в описании процедур, а также генерирует специальные файлы-заголовки с описанием типов процедур и их аргументов. Генерации исходных модулей и файлов-заголовков стабов выполняются для конкретного языка, программирования, например для языка С. После этого исходные модули интерфейса могут включаться в любое приложение наряду с любыми другими модулями как написанными программистом, так и библиотечными, компилироваться и связываться в исполняемую программу стандартными средствами инструментальной системы программирования.

Механизм RPC оперирует двумя типами сообщений: сообщениями-вызовами, с помощью которых клиент запрашивает у сервера выполнение определенной удаленной процедуры и передает ее аргументы; сообщениями-ответами, с помощью которых сервер возвращает результат работы удаленной процедуры клиенту.

С помощью этих сообщений реализуется протокол RPC, определяющий способ взаимодействия клиента с сервером. Протокол RPC обычно не зависит от транспортных протоколов, с помощью которых сообщения RPC доставляются по сети от клиента к серверу и обратно. При использования в сети стека протоколов TCP/IP это могут быть протоколы TCP или UDP, в локальных сетях часто используется также NetBEUI/NetBIOS или IPX/SPX.

Типичный формат двух типов сообщений, используемых RPC, показан на рисунке 71.

Рис. 71. Формат сообщений RPC

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

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

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

Рассмотрим вопрос о том, как клиент узнает место расположения сервера, которому необходимо послать сообщение-вызов. Процедура, устанавливающая соответствие между клиентом и сервером RPC, носит название связывание (binding). Методы связывания, применяемые в различных реализациях RPC, отличаются:

Способом задания сервера, с которым хотел бы быть связанным клиент;

Способом обнаружения сетевого адреса (места расположения) требуемого сервера процессом связывания;

Стадией, на которой происходит связывание.

Метод связывания тесно связан с принятым методом именования сервера. В наиболее простом случае имя или адрес сервера RPC задается в явной форме, в качестве аргумента клиентского стаба или программы-сервера, реализующей интерфейс определенного типа. Например, можно использовать в качестве такого аргумента IP-адрес компьютера, на котором работает некоторый RPC-сервер, и номер TCP/UDP порта, через который он принимает сообщения-вызовы своих процедур. Основной недостаток такого подхода - отсутствие гибкости и прозрачности. При перемещении сервера или при существовании нескольких серверов клиентская программа не может автоматически выбрать новый сервер или тот сервер, который в данный момент наименее загружен. Тем не менее во многих случаях такой способ вполне приемлем и ввиду своей простоты часто используется на практике. Необходимый сервер часто выбирает пользователь, например, путем просмотра списка или графического представления имеющихся в сети разделяемых файловых систем (набор этих файловых систем может быть собран операционной системой клиентского компьютера за счет прослушивания широковещательных объявлений, которые периодически делают серверы). Кроме того, пользователь может задать имя требуемого сервера на основании заранее известной ему информации об адресе или имени сервера.

Подобный метод связывания можно назвать полностью статическим. Существуют и другие методы, которые являются в той или иной степени динамическими, так как не требуют от клиента точного задания адреса RPC-сервера, вплоть до указания номера порта, а динамически находят нужный клиенту сервер.

Динамическое связывание требует изменения способа именования сервера. Наиболее гибким является использование для этой цели имени RPC-интерфейса, состоящего из двух частей:

Типа интерфейса;

Экземпляра интерфейса.

Тип интерфейса определяет все характеристики интерфейса, кроме его месторасположения. Это те же характеристики, который имеются в описании для IDL-компилятора, например файловая служба определенной версии, включающая процедуры open, close, read, write, и т. п. Часть, описывающая экземпляр интерфейса, должна точно задавать сетевой адрес сервера, который поддерживает данный интерфейс. Если клиенту безразлично, какой сервер его будет обслуживать, то вторая часть имени интерфейса опускается.

Динамическое связывание иногда называют импортом/экспортом интерфейса: клиент импортирует интерфейс, а сервер его экспортирует.

В том случае, когда для клиента важен только тип интерфейса, процесс обнаружения требуемого сервера в сети с экземпляром интерфейса определенного типа может быть построен двумя способами:

С использованием широковещания;

С использованием централизованного агента связывания.

Эти два способа характерны для поиска сетевого ресурса любого типа по его имени, они уже рассматривались в общем виде в подразделе «Способы адресации» раздела «Механизм передачи сообщений в распределенных системах». Первый способ основан на широковещательном распространении по сети серверами RPC имени своего интерфейса, включая и адрес экземпляра. Применение этого способа позволяет автоматически балансировать нагрузку на несколько серверов, поддерживающий один и тот же интерфейс, - клиент просто выбирает первое из подходящих ему объявлений.

Схема с централизованным агентом связывания предполагает наличие в сети сервера имен, который связывает тип интерфейса с адресом сервера, поддерживающего такой интерфейс. Для реализации этой схемы каждый сервер RPC должен предварительно зарегистрировать тип своего интерфейса и сетевой адрес у агента связывания, работающего на сервере имен. Сетевой адрес агента связывания (в формате, принятом в данной сети) должен быть известным адресом как для серверов RPC, так и для клиентов. Если сервер по каким-то причинам не может больше поддерживать определенный RFC-интерфейс, то он должен обратиться к агенту и отменить свою регистрацию. Агент связывания на основании запросов регистрации ведет таблицу текущего наличия в сети серверов и поддерживаемых ими RFC-интерфейсов.

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

Агент связывания может работать в составе общей централизованной справочной службы сети, такой как NDS, X.500 или LDAP (справочные службы более подробно рассматриваются в следующей главе).

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

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

Необходимо отметить, что и в тех случаях, когда используется статическое связывание, такая часть адреса, как порт сервера интерфейса (то есть идентификатор процесса, обслуживающего данный интерфейс), определяется клиентом динамически. Эту процедуру поддерживает специальный модуль RPCRuntime, называемый в ОС UNIX модулем отображения портов (portmapper), а в ОС семейства Windows - локатором RFC (RPC Locator). Этот модуль работает на каждом сетевом узле, поддерживающем механизм RPC, и доступен по хорошо известному порту TCP/UDP. Каждый сервер RPC, обслуживающий определенный интерфейс, при старте обращается к такому модулю с запросом о выделении ему для работы номера порта из динамически распределяемой области (то есть с номером, большим 1023). Модуль отображения портов выделяет серверу некоторый свободный номер порта и запоминает это отображение в своей таблице, связывая порт с типом интерфейса, поддерживаемым сервером. Клиент RPC, выяснив каким-либо образом сетевой адрес узла, на котором имеется сервер RPC с нужным интерфейсом, предварительно соединяется с модулем отображения портов по хорошо известному порту и запрашивает номер порта искомого сервера. Получив ответ, клиент использует данный номер для отправки сообщений - вызовов удаленных процедур. Механизм очень похож на механизм, лежащий в основе работы агента связывания, но только область его действия ограничивается портом одного компьютера.

Механизм вызова удаленных процедур - RPC

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

Интерфейс RPC можно представить состоящим из трех уровней:

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

Средний уровень предназначен для наиболее общих приложений. RPC-вызовами на этом уровне занимаются подпрограммы registerrpc() и callrpc(): registerrpc() получает общесис темный код, а callrpc() исполняет вызов удаленной процедуры. Вызов rnusers() реализуется с помощью этих двух подпрограмм.

Нижний уровень используется для более сложных задач, изменяющих умолчания на значения параметров процедур. На этом уровне вы можете явно манипулировать гнездами, используемыми для передачи RPC-сообщений.

Как правило, вам следует пользоваться верхним уровнем и избегать использования нижних уровней без особой необходимости.

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

RPC (Remote Procedure Call, Сервис вызова удаленных процедур) представляет собой интерфейс между удаленными пользователями и определенными программами хоста, которые запускаются по запросам этих пользователей. Сервис RPC какого-либо хоста, как правило, предоставляет клиентам комплекс программ. Каждая из таких программ состоит, в свою очередь, из одной или нескольких удаленных процедур. Например, сервис удаленной файловой системы NFS, который построен на вызовах RPC, может состоять только из двух программ: например, одна программа взаимодействует с высокоуровневыми пользовательскими интерфейсами, а другая - с низкоуровневыми функциями ввода-вывода.

В каждом вызове удаленной процедуры участвуют две стороны: активный клиент, который отправляет запрос вызова процедуры на сервер, и сервер, который отправляет клиенту ответ.

Примечание. Следует иметь в виду, что термины "клиент" и "сервер" в данном случае относятся к определенной транзакции Конкретный хост или программное обеспечение (процесс или программа) могут работать как в роли клиента, так и в роли сервера. Например, программа, которая обеспечивает работу сервиса удаленных процедур, в то же время может быть клиентом в работе с сетевой файловой системой.

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

В случае работы с удаленной процедурой, основное отличие состоит в том, что вызов удаленной функции обслуживают два процесса: клиентский процесс и серверный процесс.

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

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

Однако между вызовами локальных и удаленных процедур есть несколько важных отличий:

1. Обработка ошибок. Клиент в любом случае должен получать уведомление об ошибках, возникающих при вызовах удаленных процедур на сервере или в сети.

2. Глобальные переменные. Поскольку сервер не имеет доступа к адресному пространству клиента, при вызовах удаленных процедур нельзя использовать скрытые параметры в виде глобальных переменных.

3. Производительность. Скорость выполнения удаленных процедур, как правило на один или два порядка ниже скорости выполнения аналогичных локальных процедур.

4. Аутентификация. Поскольку вызовы удаленных процедур происходят по сети, необходимо использовать механизмы аутентификации клиента.

Принципы построения протокола.

Протокол RPC может использовать несколько различных транспортных протоколов. В обязанности RPC-протокола входит только обеспечение стандартов и интерпретация передачи сообщений. Достоверность и надежность передачи сообщений целиком обеспечивается транспортным уровнем.

Однако RPC может контролировать выбор и некоторые функции транспортного протокола. В качестве примера взаимодействия между RPC и транспортным протоколом рассмотрим процедуру назначения RPC-порта работы прикладного процесса через RPC - Portmapper .

Эта функция динамически (по запросу) назначает соединению RPC определенный порт. Функция Portmapper используется довольно часто, поскольку набор зарезервированных для RPC транспортных портов ограничен, а количество процессов, которые потенциально могут одновременно работать очень высоко. Portmapper , например, вызывается при выборе портов взаимодействия клиента и сервера системы NFS.

Сервис Portmapper использует механизм широковещательных сообщений RPC на определенный порт - III. На этот порт клиент отправляет широковещательное сообщение запроса порта определенного сервиса RPC. Сервис Portmapper обрабатывает таксе сообщение, определяет адрес локального сервиса RPC и отправляет клиенту ответ. Сервис RPC Portmapper может работать как с TCP, так и с UDP-протоколами.

RPC может работать с различными транспортными протоколами, но никогда не дублирует их функции, т. е. если RPC работает поверх TCP, все заботы о надежности и достоверности соединения RPC возлагает на TCP. Однако, если протокол RPC установлен поверх UDP, он может обеспечивать дополнительные собственные функции обеспечения гарантированной доставки сообщений.

Примечание. Прикладные задачи могут рассматривать RPC-протокол как определенную процедуру вызова функции по сети JSR (Jump Subroutine Instruction).

Для работы RPC-протокола необходимо выполнение следующих условий:

1. Уникальная идентификации всех удаленно вызываемых процедур на данном хосте. RPC-запросы содержат три поля идентификаторов - номер удаленной программы (сервиса), номер версии удаленной программы и номер удаленной процедуры указанной программы. Номер программы назначается производителем сервиса, номер процедуры указывает на конкретную функцию данного сервиса

2. Идентификация версии RPC-протокола. RPC-сообщения содержат поле версии RPC-протокола. Она используется для согласования форматов передаваемых параметров при работе клиента с различными версиями RPC.

3. Предоставление механизмов аутентификации клиента на сервере. RPC-протокол обеспечивает процедуру аутентификации клиента в сервисе, и, в случае необходимости, при каждом запросе или отправке ответа клиенту. Кроме того, RPC позволяет использовать различные дополнительные механизмы безопасности.

RPC может использовать четыре типа механизмов аутентификации:

AUTH_NULL - без использования аутентификации

AUTH_UNIX - аутентификация по стандарту UNIX

AUTH_SHORT - аутентификация по стандарту UNIX с собственной структурой кодирования

AUTH_DES - аутентификация по стандарту DES

4. Идентификация сообщений ответа на соответствующие запросы. Ответные сообщения RPC содержат идентификатор запроса, на основании которого они были построены. Этот идентификатор можно назвать идентификатором транзакции вызова RPC. Данный механизм особенно необходим при работе в асинхронном режиме и при выполнении последовательности из нескольких RPC-вызовов.

5. Идентификация ошибок работы протокола. Все сетевые или серверные ошибки имеют уникальные идентификаторы, по которым каждый из участников соединения может определить причину сбоя в работе.

Структуры сообщений протокола

При передаче RPC-сообщений поверх транспортного протокола, несколько RPC-сообщений могут располагаться внутри одного транспортного пакета. Для того чтобы отделять одно сообщение от другого, используется маркер записи (RM - Record Marker). Каждое RPC-сообщение "маркируется" ровно одним RM.

RPC-сообщение может состоять из нескольких фрагментов. Каждый фрагмент состоит из четырех байт заголовка и (от 0 до 2**31-1) данных. Первый бит заголовка указывает, является ли данный фрагмент последним, а остальные 31 бит указывают длину пакета данных.

Структура RPC формально описана на языке описания и представления форматов данных - XDR с дополнениями, касающимися описания процедур. Можно даже сказать, что язык описания RPC является расширением XDR, дополненным работой с процедурами.

Структура RPC-пакета выглядит следующим образом:

struct rpc_msg {

unsigned int xid;

union switch (msg_type mtype) {

call_body cbody;

reply body rbody;

где xid - идентификатор текущей транзакции, call_body - пакет запроса, reply_body - пакет ответа. Структура запроса выглядит примерно так:

struct call body {

unsigned int rpcvers;

unsigned int prog;

unsigned int vers;

unsigned int proc;

opaque_auth cred;

opaque_auth verf;

/* procedure parameters */

Структура ответа (reply_body) может содержать либо структуру, передаваемую в случае ошибки (тогда она содержит код ошибки), либо структуру успешной обработки запроса (тогда она содержит возвращаемые данные).

Программный интерфейс высокого уровня.

Использование подпрограмм в программе - традиционный способ структурировать задачу, сделать ее более ясной. Наиболее часто используемые подпрограммы собираются в библиотеки, где могут использоваться различными программами. В данном случае речь идет о локальном (местном) вызове, т. е. и вызывающий, и вызываемый объекты работают в рамках одной программы на одном компьютере.

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

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

Данный подход является возможной основой создания распределенных приложений, и хотя многие современные системы не используют этот механизм, основные концепции и термины во многих случаях сохраняются. При описании механизма RPC мы будем традиционно называть вызывающий процесс - клиентом, а удаленный процесс, реализующий процедуру, - сервером.

Удаленный вызов процедуры включает следующие шаги:

1. Программа-клиент производит локальный вызов процедуры, называемой заглушкой (stub). При этом клиенту "кажется", что, вызывая заглушку, он производит собственно вызов процедуры-сервера. И действительно, клиент передает заглушке необходимые параметры, а она возвращает результат. Однако дело обстоит не совсем так, как это себе представляет клиент. Задача заглушки - принять аргументы, предназначаемые удаленной процедуре, возможно, преобразовать их в некий стандартный формат и сформировать сетевой запрос. Упаковка аргументов и создание сетевого запроса называется сборкой (marshalling).

2. Сетевой запрос пересылается по сети на удаленную систему. Для этого в заглушке используются соответствующие вызовы, например, рассмотренные в предыдущих разделах. Заметим, что при этом могут быть использованы различные транспортные протоколы, причем не только семейства TCP/IP.

3. На удаленном хосте все происходит в обратном порядке. Заглушка сервера ожидает запрос и при получении извлекает параметры - аргументы вызова процедуры. Извлечение (unmarshalling) может включать необходимые преобразования (например, изменения порядка расположения байтов).

4. Заглушка выполняет вызов настоящей процедуры-сервера, которой адресован запрос клиента, передавая ей полученные по сети аргументы.

5. После выполнения процедуры управление возвращается в заглушку сервера, передавая ей требуемые параметры. Как и заглушка клиента; заглушка сервера преобразует возвращенные процедурой значения, формируя сетевое сообщение-отклик, который передается по сети системе, от которой пришел запрос.

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

Таким образом, с точки зрения клиента, он производит вызов удаленной процедуры, как он это сделал бы для локальной. То же самое можно сказать и о сервере: вызов процедуры происходит стандартным образом, некий объект (заглушка сервера) производит вызов локальной процедуры и получает возвращенные ею значения. Клиент воспринимает заглушку как вызываемую процедуру-сервер, а сервер принимает собственную заглушку за клиента.

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

Передача параметров

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

Связывание (binding)

Прежде чем клиент сможет вызвать удаленную процедуру, необходимо связать его с удаленной системой, располагающей требуемым сервером. Таким образом, задача связывания распадается на две:

Нахождение удаленного хоста с требуемым сервером

Нахождение требуемого серверного процесса на данном хосте

Для нахождения хоста могут использоваться различные подходы. Возможный вариант - создание некоего централизованного справочника, в котором хосты анонсируют свои серверы, и где клиент при желании может выбрать подходящие для него хост и адрес процедуры.

Каждая процедура RPC однозначно определяется номером программы и процедуры. Номер программы определяет группу удаленных процедур, каждая из которых имеет собственный номер. Каждой программе также присваивается номер версии, так что при внесении в программу незначительных изменений (например, при добавлении процедуры) отсутствует необходимость менять ее номер. Обычно несколько функционально сход-ных процедур реализуются в одном программном модуле, который при запуске становится сервером этих процедур, и который идентифицируется номером программы.

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

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

Обработка особых ситуаций (exception)

Обработка особых ситуаций при вызове локальных процедур не представляет особой проблемы. UNIX обеспечивает обработку ошибок процессов таких как деление на ноль, обращение к недопустимой области памяти и т. д. В случае вызова удаленной процедуры вероятность возникновения ошибочных ситуаций увеличивается. К ошибкам сервера и заглушек добавляются ошибки, связанные, например, с получением ошибочного сетевого сообщения.

Например, при использовании UDP в качестве транспортного протокола производится повторная передача сообщений после определенного тайм-аута. Клиенту возвращается ошибка, если, спустя определенное число попыток, отклик от сервера так и не был получен. В случае, когда используется протокол TCP, клиенту возвращается ошибка, если сервер оборвал TCP-соединение.

Семантика вызова

Вызов локальной процедуры однозначно приводит к ее выполнению после чего управление возвращается в головную программу. Иначе дело обстоит при вызове удаленной процедуры. Невозможно установить, когда конкретно будет выполняться процедура, будет ли она выполнена вообще, а если будет, то какое число раз? Например, если запрос будет получен удаленной системой после аварийного завершения программы сервера, процедура не будет выполнена вообще. Если клиент при неполучении отклика после определенного промежутка времени (тайм-аута) повторно посылает запрос, то может создаться ситуация, когда отклик уже передается по сети, а повторный запрос вновь принимается на обработку удаленной процедурой. В этом случае процедура будет выполнена несколько раз.

Таким образом, выполнение удаленной процедуры можно характеризовать следующей семантикой:

- Один и только один раз. Данного поведения (в некоторых случаях наиболее желательного) трудно требовать ввиду возможных аварий сервера.

- Максимум раз. Это означает, что процедура либо вообще не была выполнена, либо была выполнена только один раз. Подобное утверждение можно сделать при получении ошибки вместо нормального отклика.

- Хотя бы раз. Процедура наверняка была выполнена один раз, но возможно и больше. Для нормальной работы в такой ситуации удаленная процедура должна обладать свойством идемпотентности (от англ. idemponent). Этим свойством обладает процедура, многократное выполнение которой не вызывает кумулятивных изменений. Например, чтение файла идемпотентно, а добавление текста в файл - нет.

Представление данных

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

Большинство реализаций системы RPC определяют некоторые стандартные виды представления данных, к которым должны быть преобразованы все значения, передаваемые в запросах и откликах.

Например, формат представления данных в RPC фирмы Sun Microsystems следующий:

Порядок следования байтов - Старший - последний

Представление значений с плавающей точкой - IEEE

Представление символа - ASCII

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

Программные реализации системы, как правило, поддерживают один или два протокола. Например, система RPC разработки фирмы Sun Microsystems поддерживает передачу сообщений с использованием протоколов TCP и UDP. Выбор того или иного протокола зависит от требований приложения. Выбор протокола UDP оправдан для приложений, обладающих следующими характеристиками:

Вызываемые процедуры идемпотентны

Размер передаваемых аргументов и возвращаемого результата меньше размера пакета UDP - 8 Кбайт.

Сервер обеспечивает работу с несколькими сотнями клиентов. Поскольку при работе с протоколами TCP сервер вынужден поддерживать соединение с каждым из активных клиентов, это занимает значительную часть его ресурсов. Протокол UDP в этом отношении является менее ресурсоемким

С другой стороны, TCP обеспечивает эффективную работу приложений со следующими характеристиками:

Приложению требуется надежный протокол передачи

Вызываемые процедуры неидемпонентны

Размер аргументов или возвращаемого результата превышает 8 Кбайт

Выбор протокола обычно остается за клиентом, и система по-разному организует формирование и передачу сообщений. Так, при использовании протокола TCP, для которого передаваемые данные представляют собой поток байтов, необходимо отделить сообщения друг от друга. Для этого например, применяется протокол маркировки записей, описанный в RFC1057 "RPC: Remote Procedure Call Protocol specification version 2", при котором в начале каждого сообщения помещается 32-разрядное целое число, определяющее размер сообщения в байтах.

По-разному обстоит дело и с семантикой вызова. Например, если RPC выполняется с использованием ненадежного транспортного протокола (UDP), система выполняет повторную передачу сообщения через короткие промежутки времени (тайм-ауты). Если приложение-клиент не получает отклик, то с уверенностью можно сказать, что процедура была выполнена ноль или большее число раз. Если отклик был получен, приложение может сделать вывод, что процедура была выполнена хотя бы однажды. При использовании надежного транспортного протокола (TCP) в случае получения отклика можно сказать, что процедура была выполнена один раз. Если же отклик не получен, определенно сказать, что процедура выполнена не была, нельзя3.

Как это работает?

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

В качестве примера рассмотрим RPC фирмы Sun Microsystems. Система состоит из трех основных частей:

Rpcgen(1) - RPC-компилятор, который на основании описания интерфейса удаленной процедуры генерирует заглушки клиента и сервера в виде программ на языке С.

Библиотека XDR (eXternal Data Representation), которая содержит функции для преобразования различных типов данных в машинно-независимый вид, позволяющий производить обмен информацией между разнородными системами.

Библиотека модулей, обеспечивающих работу системы в целом.

Рассмотрим пример простейшего распределенного приложения для ведения журнала событий. Клиент при запуске вызывает удаленную процедуру записи сообщения в файл журнала удаленного компьютера.

Для этого придется создать как минимум три файла: спецификацию интерфейсов удаленных процедур log.x (на языке описания интерфейса), собственно текст удаленных процедур log.c и текст головной программы клиента main () - client.c (на языке С) .

Компилятор rpcgen(l) на основании спецификации log.x создает три файла: текст заглушек клиента и сервера на языке С (log clnt.c и log svc.c) и файл описаний log.h, используемый обеими заглушками.

Итак, рассмотрим исходные тексты программ.

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

program LOG_PROG {

version LOG_VER {

int RLOG (string) = 1;

} = 0х31234567;

Компилятор rpcgen(l) создает файл заголовков log.h, где, в частности, определены процедуры:

log.h

* Please do not edit this file.

* It was generated using rpcgen.

#ifndef _LOG_H_RPCGEN

#define _LOG_H_RPCGEN

#include

/* Номер программы*/

#define LOG_PROG ((unsigned long) (0х31234567))

#define LOG_VER ((unsigned long) (1)) /*Номер версии*/

#define RLOG ((unsigned long) (1)) /*Номер процедуры*/

extern int *rlog_l () ;

/*Внутренняя процедура - нам ее использовать не придется*/ extern int log_prog_l_freeresult();

#endif /* !_LOG_H_RPCGEN */

Рассмотрим этот файл внимательно. Компилятор транслирует имя RLOG определенное в файле описания интерфейса, в rlog_1, заменяя прописные символы на строчные и добавляя номер версии программы с подчеркиванием. Тип возвращаемого значения изменился с int на int *. Таково правило - RPC позволяет передавать и получать только адреса объявленных при описании интерфейса параметров. Это же правило касается и передаваемой в качестве аргумента строки. Хотя из файла print.h это не следует, на самом деле в качестве аргумента функции rlog_l () также передается адрес строки.

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

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

log.c

#include

#include

#include

#include "log.h"

int *rlog_1 (char **arg)

/*Возвращаемое значение должно определяться как static*/

static int result;

int fd; /*Файловый дескриптор журнала*/

/*0ткроем файл журнала (создадим, если он не существует), в случае неудачи вернем код ошибки result == 1.*/

if ((fd=open("./server .log",

O_CREAT | O_RDWR | O_APPEND)) < 0) return (&result);

len = strlen(*arg);

if (write(fd, *arg, strlen(*arg)) != len)

return(&result); /*Возвращаем результат - адрес result*/

Заглушка клиента принимает аргумент, передаваемый удаленной процедуре, делает необходимые преобразования, формирует запрос на сервер portmap(1M), обменивается данными с сервером удаленной процедуры и, наконец, передает возвращаемое значение клиенту. Для клиента вызов удаленной процедуры сводится к вызову заглушки и ничем не отличается от обычного локального вызова.

client.c

#include

#include "log.h"

main(int argc, char *argv)

char *server, *mystring, *clnttime;

if (argc != 2) {

fprintf(stderr, "Формат вызова: %s Адрес_хоста\n",

/*Получим дескриптор клиента. В случае неудачи - сообщим о

невозможности установления связи с сервером*/

if ((с1 = clnt_create (server,

LOG_PROG, LOG_VER, "udp")) == NULL) {

clnt_pcreateerror (server);

/*Выделим буфер для строки*/

mystring = (char *)malloc (100);

/*Определим время события*/

bintime = time ((time_t *) NULL);

clnttime = ctime(&bintime);

sprintf (mystring, "%s - Клиент запущен", clnttime);

/*Передадим сообщение для журнала - время начала работы клиента. В случае неудачи - сообщим об ошибке*/

if ((result = rlog_l(&mystring, cl)) == NULL) {

fprintf(stderr, "error2\n");

clnt_perror(cl, server);

/*B случае неудачи на удаленном компьютере сообщим об ошибке*/

if (*result !=0)

fprintf(stderr, "Ошибка записи в журнал\n");

/*0свободим дескриптор*/

cint destroy(cl);

Заглушка клиента log_clnt.c компилируется с модулем client.c для получения исполняемой программы клиента.

cc -о rlog client.c log_clnt.c -Insl

Заглушка сервера log_svc.c и процедура log.c компилируются для получения исполняемой программы сервера.

cc -о logger log_svc.c log.c -Insl

Теперь на некотором хосте server.nowhere.ru необходимо запустить серверный процесс:

После чего при запуске клиента rlog на другой машине сервер добавит соответствующую запись в файл журнала.

Схема работы RPC в этом случае приведена на рис. 1. Модули взаимодействуют следующим образом:

1. Когда запускается серверный процесс, он создает сокет UDP и связывает любой локальный порт с этим сокетом. Далее сервер вызывает библиотечную функцию svc_register(3N) для регистрации номеров программы и ее версии. Для этого функция обращается к процессу portmap(IM) и передает требуемые значения. Сервер portmap(IM) обычно запускается при инициализации системы и связывается с некоторым общеизвестным портом. Теперь portmap(3N) знает номер порта для нашей программы и версии. Сервер же ожидает получения запроса. Заметим, что все описанные действия производятся заглушкой сервера, созданной компилятором rpcgen(IM).

2. Когда запускается программа rlog, первое, что она делает, - вызывает библиотечную функцию clnt_create(3N), указывая ей адрес удаленной системы, номера программы и версии, а также транспортный протокол. Функция направляет запрос к серверу portmap(IM) удаленной системы server.nowhere.m и получает номер удаленного порта для сервера журнала.

3. Клиент вызывает процедуру rlog_1 () , определенную в заглушке клиента, и передает управление заглушке. Та, в свою очередь, формирует запрос (преобразуя аргументы в формат XDR) в виде пакета UDP и направляет его на удаленный порт, полученный от сервера portmap(IM). Затем она некоторое время ожидает отклика и в случае неполучения повторно отправляет запрос. При благоприятных обстоятельствах запрос принимается сервером logger (модулем заглушки сервера). Заглушка определяет, какая именно функция была вызвана (по номеру процедуры), и вызывает функцию rlog_1 () модуля log.c. После возврата управления обратно в заглушку последняя преобразует возвращенное функцией rlog_1 () значение в формат XDR, и формирует отклик также в виде пакета UDP. После получения отклика заглушка клиента извлекает возвращенное значение, преобразует его и возвращает в головную программу клиента.