sonyps4.ru

Вызов удаленных процедур (RPC). Удаленные процедуры: вызов удаленных процедур, определение и особенности Вызов удаленных процедур rpc ультразвуковой сканер

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

Вступление

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

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

Терминология

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

Я разделил терминологию на три области: RPC (Remote Procedure Call), Messaging и REST. Эти области имеют под собою исторические корни.

RPC

RPC технологии - наиболее старые технологии. Наиболее яркие представители RPC, это - CORBA и DCOM .

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

Поэтому вы не заметите, не должны заметить, никакой разницы между вызовом локальной функции и вызовом удаленной функции.
Сейчас наблюдается своеобразный ренесанс RPC, наиболее яркие представители которого: Google ProtoBuf, Thrift, Avro.

Messaging

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

Появились технологии веб-сервисов . Мы стали говорить ABC: Address, Binding, Contract . Не совсем понятно, почему появились контракты, которые по сути являются Envelope (конвертами) для входных аргументов. Контракты чаще усложняют всю модель, чем упрощают ее. Но… неважно.

Теперь программист явным образом создавал сервис (Service ) или клиента (Client ), вызывающего сервис. Сервис представлял из себя набор операций (Operation ), каждая из которых на входе принимала запрос (Request ) и выдавала ответ (Response ). Клиент явным образом посылал (Sent ) запрос, сервис явным образом получал (Receive ) его и отвечал (Sent), высылая ответ. Клиент получал (Receive) ответ и на этом вызов завершался.

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

Запросы и ответы явным образом преобразуются к формату, предназначенному для передачи по проводам. Чаще всего это массив байт. Преобразование называется Serialization и Deserialization и иногда прячется в коде Proxy.
Кульминация messaging проявилась в появлении парадигмы ESB (Enterprise Service Bus) . Никто толком не может сформулировать, что это такое, но все сходятся на том, что данные по ESB движутся в виде сообщений.

REST

В постоянной борьбе со сложностью кода, программисты сделали очередной шаг и создали REST .

Основной принцип REST в том, что операции-функции резко ограничили и оставили только набор операций CRUD: Create - Read - Update - Delete . В этой модели все операции всегда применяются к некоторым данным. Имеющихся в CRUD операций достаточно для большей части приложений. Так как REST технологии в большинстве случаев подразумевают использование протокола HTTP, то команды CRUD отразились на команды HTTP (Post - Get - Put - Delete ) . Постоянно утверждается, что REST не обязательно привязан к HTTP. Но на практике повсеместно используется отражение сигнатур операций на синтаксис HTTP команд. К примеру, вызов функции

EntityAddress ReadEntityAddress(string param1, string param2)

Выразится в таком виде:

GET: entityAddress?param1=value1¶m2=value2

Заключение

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

Лекция 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

После перезагрузки компа не запустилась служба "Удаленный вызов процедур (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 для получения консоли с правами системы. Об этом эксплоите писали в . Собственно бинарник . Вот только у меня стоят виндовские обновки, так что похоже данный эксплоит уже не работает.

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

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

Характерными чертами RPC являются:

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

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

Существует несколько реализаций процедур удаленного вызова в различных операционных системах. В операционной системе UNIX используется процедура под одноименным названием (Remote Procedure Call - RPC). Данная процедура внедрена в ядро системы. Ее выполнение обеспечивается протоколом RPC. В операционных системах Windows удаленный вызов процедур начал развиваться на базе механизмов OLE, которые постепенно развились в технологию DCOM (Distributed Component Object Model). Данная технология позволяет создавать достаточно мощные распределенные сетевые вычислительные среды. В технологии используются фирменные протоколы Microsoft.

Механизм работы RPC

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

При удаленном вызове процедуры в распределенной системе происходят следующие действия:

1. Процедура клиента вызывает стаб как обычную процедуру. Стаб упаковывает параметры (маршализация, marshaling).

2. Стаб обращается к ядру ОС.

3. Ядро посылает сообщение на удаленную машину (ядру удаленного ПК).

4. Передача полученного сообщения скелетону серверного процесса.

5. Распаковка параметров (демаршализация, unmarshaling). Вызов требуемой процедуры.

6. Процедура на сервере выполняется. Возвращает результаты скелетону.

7. Скелетон упаковывает результат.

8. Передача результата ядру.

9. Ядро сервера передает сообщение по сети ядру клиента.

10. Ядро клиента обращается к стабу. Стаб распаковывает полученный результат.

11. Передача от стаба клиентскому процессу.

Служба "Удаленный вызов процедур (RPC)" в ОС Windows

Для того чтобы понять важность механизма удаленного вызова процедур, можно рассмотреть хотя бы список утилит и служб, которые не работают без RPC в ОС Windows 2000. Фактически, отключение службы RPC в указанной среде приводит к краху всей системы. Итак, от службы "Удаленный вызов процедур (RPC)" зависят:

1. Telnet - позволяет удаленному пользователю войти в систему и запустить программы консоли с помощью командной строки.

2. Windows Installer - устанавливает, удаляет или восстанавливает программное обеспечение в соответствии с инструкциями файлов MSI.

3. Агент политики IPSEC - управляет политикой IP-безопасности и запускает ISAKMP/Oakley (IKE) и драйвер IP-безопасности.

4. Диспетчер очереди печати - загружает в память файлы для последующей печати.

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

6. Инструментарий управления Windows - предоставляет информацию об управлении системой.

7. Клиент отслеживания изменившихся связей - посылает оповещения о файлах, перемещенных между томами NTFS в сетевом домене.

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

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

10. Планировщик заданий - позволяет выполнять программы в назначенное время.

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

12. Система событий COM+ - автоматическое распространение событий подписавшимся компонентам COM.

13. Служба индексирования - индексирование для быстрого поиска.

14. Служба сообщений - посылает и получает сообщения, переданные администраторами или службой оповещений.

15. Служба факсов - помогает отправлять и принимать факсимильные сообщения.

16. Съемные ЗУ - управляет съемными носителями, дисками и библиотеками.

17. Телефония - обеспечивает поддержку Telephony API (TAPI) для программ, управляющих телефонным оборудованием и голосовыми IP-подключениями на этом компьютере, а также через ЛВС - на серверах, где запущена соответствующая служба.

RMI-приложения

Вызов Удаленных Методов Remote Method Invocation (RMI) является реализацией идей RPC для языка программирования Java.

RMI - продукт компании JavaSoft, разработанный для Java и интегрированный в JDK 1.1 и выше. RMI реализует распределенную модель вычислений и обеспечивает средства коммуникации между Java-программами (виртуальными Java-машинами), выполняющимися на одном или нескольких удаленных компьютерах. RMI позволяет клиентским и серверным приложениям через сеть вызывать методы клиентов/серверов, выполняющихся на виртуальных Java-машинах. Основное преимущество RMI заключается в том, что он предоставляет программисту программируемый интерфейс более высокого уровня, который позволяет передавать ссылку на удаленный объект в качестве аргумента или возвращать ее в качестве результата. RMI требует, чтобы на обоих концах соединения выполнялись Java-программы. Сетевое соединение достигается с использованием TCP/IP-протокола. Архитектура RMI приведена на рис. "Архитектура RMI".

Client Stub (переходник для клиента - некая сущность на клиенте, которая обеспечивает функции приема/передачи), и Server Skeleton (переходник для сервера - некая сущность на сервере, которая обрабатывает удаленные вызовы) порождены от общего интерфейса, но различаются тем, что Client Stub служит просто для подсоединения к RMI Registry, а Server Stub используется для связи непосредственно с функциями сервера.

RMI является в действительности новым видом брокера объектных запросов, который строится на объектной модели Java. Как и ORB, RMI вводит пять ключевых моментов:

1. Позволяет перемещать код в дополнение к данным.

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

3. Позволяет передавать объекты по значению.

4. Использует Java как язык определения интерфейса и как язык реализации.

5. Использует именующую схему на базе унифицированного указателя ресурсов Uniform Resource Locator (URL).

При этом производится преобразование объектов в последовательную форму - в поток байтов, передаваемых как параметр в сообщении с помощью протокола TCP/IP.

Интерфейсы RMI можно разделить на 4 категории:

Ядро RMI - определяет интерфейсы, необходимые для выполнения вызовов удаленных методов;

Служба именования RMI - определяет интерфейсы и классы, позволяющие получить ссылки на серверные объекты по имени;

Безопасность RMI - определяет новый менеджер безопасности RMI и интерфейсы загрузчика классов (RMI расширяет механизм загрузки классов Java по требованию на загрузку стаба);

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

JavaSoft и OMG работают над сближением объектных моделей RMI и CORBA. Это сближение происходит в двух областях:

RMI через IIOP. JavaSoft разрабатывает версию RMI, которая работает поверх транспорта IIOP. IIOP предоставляет следующие преимущества для RMI:

1. Встроенную поддержку для распространения транзакций.

2. Поддержку брандмауэра на основе ORB с помощью заместителя IIOP (без HTTP-туннелирования).

3. Взаимодействие с объектами, написанными на других языках через подмножество RMI/IDL.

4. Открытый стандарт распределенных объектов.

RMI/IDL. Стандарт CORBA Java в IDL является стандартом сближения CORBA/RMI. Он позволяет программистам Java определять интерфейсы CORBA c помощью семантики Java RMI вместо CORBA IDL. Компилятор использует эту семантику для автоматического создания CORBA IDL, стабов и скелетонов. Подмножество RMI/IDL позволяет программам RMI вызываться многоязычными клиентами CORBA с помощью IIOP; он также позволяет RMI-программам вызывать объекты CORBA, написанные на других языках.

RMI через IIOP кажется хорошим решением для системы CORBA/Java, поскольку объединяет две мощные технологии. Основным достоинством RMI является то, что с его помощью можно наиболее быстро и просто создать небольшую распределенную систему в чисто Java-среде. Основным недостатком RMI является невозможность интегрирования этого механизма с существующими приложениями.

Сравнение распределенных и нераспределенных Java-программ

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

Интерфейсы в RMI

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

Это соответствует требованиям распределенных систем, в которых клиенты знают об определениях служб, а серверы предоставляют эти службы. Конкретно в RMI определение удаленной службы кодируется при помощи интерфейса Java. Реализация удаленной службы кодируется в классе. Таким образом, ключ к пониманию RMI - помнить, что интерфейсы определяют поведение, а классы определяют реализацию.

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

Клиентская программа вызывает методы прокси-объекта, RMI передает запрос на удаленную JVM и направляет его в реализацию объекта. Любые возвращаемые из реализации значения передаются назад в прокси-объект и затем в клиентскую программу.

Уровни архитектуры RMI

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

Следующий уровень - уровень удаленной ссылки. Этот уровень понимает, как интерпретировать и управлять ссылками на удаленные объекты служб. В JDK 1.1 этот уровень соединяет клиентов с удаленными объектами служб, которые исполняются на сервере. Это соединение является связью типа один к одному (однонаправленное соединение). В Java 2 SDK этот уровень был расширен поддержкой активации пассивных удаленных объектов при помощи технологии Remote Object Activation.

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

Поиск удаленных объектов

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

RMI может использовать много различных служб каталогов, включая Java Naming and Directory Interface (JNDI). RMI и сама включает в себя простую службу, называемую реестром RMI, rmiregistry. Реестр RMI работает на каждой машине, содержащей объекты удаленных служб и принимающей запросы на обслуживание, по умолчанию используя порт 1099. На хосте программа сервера создает удаленную службу, предварительно создавая локальный объект, реализующий эту службу. Затем она экспортирует этот объект в RMI. Как только объект экспортирован, RMI создает службу прослушивания, ожидающую соединения с клиентом и запроса службы. После экспорта, сервер регистрирует объект в реестре RMI, используя общедоступное имя.

На стороне клиента к реестру RMI доступ обеспечивается через статический класс Naming. Он предоставляет метод lookup(), который клиент использует для запросов к реестру. Метод lookup() принимает URL, указывающий на имя хоста и имя требуемой службы. Метод возвращает удаленную ссылку на обслуживающий объект. URL принимает следующий вид:

rmi:// [:] /
где host_name - это имя, распознаваемое в локальной сети (LAN), или DNS-имя в сети Internet. Необходимо только указать name_service_port, если служба имен исполняется на порте, отличном от принимаемого по умолчанию 1099.

Использование RMI

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

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

1. Написать и откомпилировать Java-код для интерфейсов.

2. Написать и откомпилировать Java-код для классов реализации.

3. Создать файлы классов заглушки и скелета из классов реализации.

4. Написать Java-код программы хоста для удаленного обслуживания.

5. Разработать Java-код для клиентской программы RMI.

6. Установить и запустить RMI-систему.

Пример RMI - приложения

Первым шагом является написание и компилирование Java-кода для интерфейсов служб. Интерфейс Calculator определяет все удаленные возможности, предлагаемые службой:

public interface Calculator extends java.rmi.Remote {
public long add(long a, long b) throws java.rmi.RemoteException;
public long sub(long a, long b) throws java.rmi.RemoteException;
public long mul(long a, long b) throws java.rmi.RemoteException;
public long div(long a, long b) throws java.rmi.RemoteException;
}

Обратите внимание, что этот интерфейс расширяет интерфейс Remote, и в сигнатуре каждого метода определяется, что он может генерировать объект RemoteException. Вообще, объект называется удаленным, если он реализует интерфейс Remote. "Реализует" в смысле заголовка (public interface Calculator extends java.rmi.Remote), никаких методов в этом интерфейсе нет. Это - метка. Теперь необходимо написать реализацию удаленной службы. Ниже приведен класс CalculatorImpl:

public class CalculatorImpl extends java.rmi.server.UnicastRemoteObject
implements Calculator {
// Реализации должны иметь явный конструктор для того, чтобы объявить
// исключительную ситуацию RemoteException
public CalculatorImpl()
throws java.rmi.RemoteException {
super();
}
public long add(long a, long b) throws java.rmi.RemoteException {
return a + b;
}
public long sub(long a, long b) throws java.rmi.RemoteException {
return a - b;
}
public long mul(long a, long b) throws java.rmi.RemoteException {
return a * b;
}
public long div(long a, long b) throws java.rmi.RemoteException {
return a / b;
}
}

Класс реализации использует Unicast RemoteObject для присоединения к системе RMI. В данном примере класс реализации непосредственно расширяет UnicastRemoteObject. Это не является обязательным требованием. Класс, не расширяющий UnicastRemoteObject, может использовать свой метод exportObject() для присоединения к RMI. Если класс расширяет UnicastRemoteObject, он должен обеспечить конструктор, объявляющий, что он может сгенерировать объект RemoteException. Если этот конструктор вызывает метод super(), он активизирует код в UnicastRemoteObject, который выполняет RMI-соединение и инициализацию удаленного объекта.

Удаленные службы RMI должны быть помещены в процесс сервера. Класс CalculatorServer является очень простым сервером, предоставляющим простые элементы для размещения.

import java.rmi.Naming;

public class CalculatorServer {
public CalculatorServer() {
try {
Calculator c = new CalculatorImpl();
Naming.rebind("
rmi://localhost:1099/
CalculatorService", c);
} catch (Exception e) {
System.out.println("Trouble: " + e);
}
}
new CalculatorServer();
}
}

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

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
public class CalculatorClient {
public static void main(String args) {
try {
Calculator c = (Calculator)
Naming.lookup(
"rmi://remotehost
/CalculatorService");
System.out.println(c.sub(4, 3));
System.out.println(c.add(4, 5));
System.out.println(c.mul(3, 6));
System.out.println(c.div(9, 3));
}
catch (MalformedURLException murle) {
System.out.println();
System.out.println(
"MalformedURLException");
System.out.println(murle);
}
catch (RemoteException re) {
System.out.println();
System.out.println(
"RemoteException");
System.out.println(re);
}
catch (NotBoundException nbe) {
System.out.println();
System.out.println(
"NotBoundException");
System.out.println(nbe);
}
catch (
java.lang.ArithmeticException
ae) {
System.out.println();
System.out.println(
"java.lang.ArithmeticException");
System.out.println(ae);
}
}
}

Теперь можно запускать систему. Сделать это можно (после получения соответствующих class-файлов и размещения их на одном или разных ПК) так:

1. Запустить реестр RMI ("rmiregistry").

2. Запустить сервер ("java CalculatorServer").

3. Запустить клиент ("java CalculatorClient").

Если все пройдет хорошо, вы увидите следующую информацию:

1
9
18
3

Вот и все - работающая система RMI готова. Даже если вы запустили три консоли на одном и том же компьютере, RMI использует стек протоколов TCP/IP вашей сети для взаимодействия между тремя отдельными JVM. Это вполне законченная RMI-система.

Распространение классов RMI

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

Для сервера должны быть доступны (для загрузчика классов) классы:

Реализации удаленных служб

Скелеты для классов реализации (только для серверов, основанных на JDK 1.1)

Заглушки для классов реализации

Все остальные классы сервера

Для клиента должны быть доступны (для загрузчика классов) классы:

Определения интерфейса удаленной службы

Заглушки для классов, реализующих удаленную службу

Классы сервера для объектов, используемых клиентом (таких, как возвращаемое значение)

Все остальные классы клиента

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

Распределенная сборка мусора

Одним из преимуществ программирования для платформы Java является отсутствие беспокойства о распределении памяти. JVM имеет автоматический сборщик мусора, который освобождает память, занимаемую любым объектом, который больше не используется исполняющейся программой. Одним из требований к разработке RMI была ее бесшовная интеграция в язык программирования Java, включая и сборку мусора. Разработка эффективного сборщика мусора для одной машины является тяжелой задачей; разработка распределенного сборщика мусора является очень тяжелой задачей. RMI-система обеспечивает подсчитывающий ссылки алгоритм распределенной сборки мусора, основанный на сетевых объектах, используемых в Modula-3. Эта система при работе следит за тем, какие клиенты запросили доступ к удаленным объектам, выполняющимся на сервере. Когда появляется ссылка, сервер помечает объект как "грязный", а когда клиент удаляет ссылку, объект помечается как "чистый".

Интерфейс к DGC (распределенный сборщик мусора) скрыт на уровне заглушек и скелетов. Однако удаленный объект может реализовать интерфейс java.rmi.server.Unreferenced и получить уведомление через метод unreferenced, когда нет больше ни одного клиента, содержащего живую ссылку. В дополнение к механизму подсчета ссылок живая ссылка в клиенте имеет срок аренды с указанным временем. Если клиент не обновляет соединение к удаленному объекту до истечения срока аренды, ссылка считается мертвой и удаленный объект может быть утилизирован сборщиком мусора. Время аренды управляется системным свойством java.rmi.dgc.leaseValue. Его значение указывается в миллисекундах и по умолчанию равно 10 минутам. Из-за такой семантики сборки мусора, клиент должен быть подготовлен для работы с объектами, которые могут "исчезать".

Заключение

Технология Remote Method Invocation (RMI), впервые представленная в JDK 1.1, продвинула сетевое программирование на более высокий уровень. Хотя RMI относительно проста в использовании и не лишена недостатков, она является необыкновенно мощной технологией и раскрывает перед обычным Java-программистом полностью новую парадигму - мир распределенных объектных вычислений.


Вызов удаленных процедур (RPC) Концепция удаленного вызова процедур

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

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

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

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

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

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

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

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

Count=read (fd,buf,nbytes);

где fd - целое число,
buf - массив символов,
nbytes - целое число.

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

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

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

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

Рис. 3.1. а) Стек до выполнения вызова read;
б) Стек во время выполнения процедуры;
в) Стек после возврата в вызывающую программу

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

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

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

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

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

Рис. 3.2. Remote Procedure Call

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

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

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

Рисунок 3.3 показывает последовательность команд, которую необходимо выполнить для каждого RPC-вызова, а рисунок 3.4 - какая доля общего времени выполнения RPC тратится на выполнение каждого их описанных 14 этапов. Исследования были проведены на мультипроцессорной рабочей станции DEC Firefly, и, хотя наличие пяти процессоров обязательно повлияло на результаты измерений, приведенная на рисунке гистограмма дает общее представление о процессе выполнения RPC.

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

Рис. 3.4. Распределение времени между 14 этапами выполнения RPC

1. Вызов стаба

2. Подготовить буфер

3. Упаковать параметры

4. Заполнить поле заголовка

5. Вычислить контрольную сумму в сообщении

6. Прерывание к ядру

7. Очередь пакета на выполнение

8. Передача сообщения контроллеру по шине QBUS

9. Время передачи по сети Ethernet

10. Получить пакет от контроллера

11. Процедура обработки прерывания

12. Вычисление контрольной суммы

13. Переключение контекста в пространство пользователя

14. Выполнение серверного стаба

Динамическое связывание

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

Начальным моментом для динамического связывания является формальное определение (спецификация) сервера. Спецификация содержит имя файл-сервера, номер версии и список процедур-услуг, предоставляемых данным сервером для клиентов (рисунок 3.5). Для каждой процедуры дается описание ее параметров с указанием того, является ли данный параметр входным или выходным относительно сервера. Некоторые параметры могут быть одновременно входными и выходными - например, некоторый массив, который посылается клиентом на сервер, модифицируется там, а затем возвращается обратно клиенту (операция copy/ restore).

Рис. 3.5. Спецификация сервера RPC

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

При запуске сервера самым первым его действием является передача своего серверного интерфейса специальной программе, называемой binder"ом. Этот процесс, известный как процесс регистрации сервера, включает передачу сервером своего имени, номера версии, уникального идентификатора и описателя местонахождения сервера. Описатель системно независим и может представлять собой IP, Ethernet, X.500 или еще какой-либо адрес. Кроме того, он может содержать и другую информацию, например, относящуюся к аутентификации.

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

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

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

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

Семантика RPC в случае отказов

В идеале RPC должен функционировать правильно и в случае отказов. Рассмотрим следующие классы отказов:

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

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

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

Как поступать с сиротами? Рассмотрим 4 возможных решения.

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

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



Загрузка...