Сервис для именования (Naming Service)
Принципиально устройство сервиса имен ничем не отличается от типичной файловой системы компьютера. Строится «дерево», каждая ветвь которого — часть общего имени. К примеру, один из файлов конфигурации операционной системы Windows мы можем найти среди ветвистого «дерева» FAT по его полному имени c:\windows\win.ini, а в Linux по имени, скажем, /etc/lilo.conf. Общим в данном случае будет то, что и первое и второе имена используют древовидную структуру для точного местонахождения конкретного файла внутри файловой системы дискового устройства. В сервисе же имен все та же древовидная структура помогает обнаружить ссылку на конкретный объект CORBA внутри пространства имен. Последнее служит для удобства организации всех объектных ссылок, имеющихся в доменах и в системе в целом.
Рассмотрим наш пример с сотовой связью. Все, с чем мы оперируем, есть не что иное, как масса ссылок. Внутри себя система как-нибудь разберется, какой объект должен быть вызван, благо бизнес-логика почти что реализована. А вот что делать оператору системы с сотней неорганизованных ссылок на соты, телефоны и прочие объекты (а их, смею вас заверить, будет очень много — стоит только фирме-провайдеру встать на ноги)? В этом случае единственным выходом будет создание пространства имен и установка ссылок на объекты системы в соответствие этим именам. Так, станция, расположенная в Ярославле в зоне № 5, может иметь имя cell#5 внутри полного имени cellular_system/Russia/Yaroslavl. Таким образом, если мы хотим отключить эту станцию, объекту консоли управления посылается сообщение:
setCellState(«cellular_system/Russia/Yaroslavl/cell#5», false);
Внутренние механизмы системы и технология CORBA сами найдут в общей сети ссылку на объект сотовой станции и прикажут остановить обслуживание абонентов. В общем-то не сложнее, чем работа с файлами. А если как следует организовать ссылки в пространстве имен, то даже самый непутевый оператор разберется здесь безо всякой документации.
Однако же необходимо заметить различие в терминологии, принятой в файловых системах и сервисе имен CORBA. То, что называется в файловой системе словом «каталог», в сервисе имен зовется «контекст именования» (для краткости просто «контекст»). Поэтому в вышеприведенном имени радиостанции слова cellular_system, Russia, Yaroslavl могут быть отнесены к контекстам, поскольку они содержат ссылки на контексты следующего уровня или ссылки на конкретные CORBA-объекты.
Но здесь скрыта маленькая ловушка. Контекст именования — это на самом деле не часть имени, а лишь программный интерфейс для работы с куском имени. На самом деле часть полного имени представлена структурой NameComponent, а программный интерфейс для работы с такими структурами, т. е. NameContext, и есть тот самый контекст именования. Вот такой казус. Похожее чувство смятения вызывает у начинающих программистов тема структур, когда им объясняют, что все данные хранятся в памяти компьютера в виде следующих друг за другом байтов, а структура — всего лишь удобный способ обращаться к этим байтам по имени. Так что считайте, что любое пространство имен состоит из этих самых имен (тип Name), которые в свою очередь состоят из компонентов имен (тип NameComponent).
Компоненты имен объединяются между собой в имена связками (тип Binding). Чтобы построить имя или найти ссылку на объект по имени, нужно использовать интерфейс контекста NameContext, а чтобы узнать логическую структуру пространства имен, следует обратиться к интерфейсу BindingIterator. В общем, не так уж сложно. Следует тем не менее помнить, что каждый контекст может адресовать не один, а несколько объектов или контекстов следующего уровня так же, как один каталог файловой системы умеет хранить сразу несколько файлов или каталогов.
Точкой отсчета, от которой «пляшет» программист, является компонент имени. Он состоит из идентификатора, определяющего его текстовое название (вспомните cellular_system, Russia, Yaroslavl), и вида, в котором программист может поместить произвольное слово (или текст), описывающее категорию, к которой относится компонент имени1:
:
struct NameComponent {
Istring id;
Istring kind;
};
Тип Istring предполагается изменить. Пока что это самая обычная строка типа string. В перспективе OMG обещает сделать Istring новым типом для реализации интернационализированных строк. В структуре идентификатор представлен полем id, а вид — полем kind. В логическом имени все компоненты связаны воедино:
typedef sequence<NameComponent> Name;
Назначение поля «вид» (kind) не всегда понимается правильно. Подобно тому как у каталогов файловой системы имеются атрибуты «только для чтения» или «скрытый», вы можете записать в поле вида любую текстовую информацию, какую вам заблагорассудится, скажем «region», чтобы пояснить, что идентификатор Yaroslavl обозначает область. Главное, чтобы текст соответствовал стандартной кодировке CORBA ISO 8859-1 (Latin-1) и в нем не было непечатных символов.
Теперь кратко об интерфейсе контекста именования. Вот его усеченное описание:
interface NamingContext {
. . .
void bind(in Name n, in Object obj)
raises(NotFound, CannotProceed, InvalidName,
AlreadyBound);
void rebind(in Name n, in Object obj)
raises(NotFound, CannotProceed, InvalidName);
void bind_context(in Name n, in NamingContext nc)
raises(NotFound, CannotProceed, InvalidName,
AlreadyBound);
void rebind_context(in Name n, in NamingContext nc)
raises(NotFound, CannotProceed, InvalidName);
Object resolve(in Name n)
raises(NotFound, CannotProceed, InvalidName);
void unbind(in Name n)
raises(NotFound, CannotProceed, InvalidName);
NamingContext new_context();
NamingContext bind_new_context(in Name n)
raises(NotFound, CannotProceed, InvalidName,
AlreadyBound);
void destroy()
raises(NotEmpty);
void list(in unsigned long how_many,
out BindingList bl,
out BindingIterator bi);
};
В нем не хватает только описаний исключительных ситуаций. Но вы можете представить их себе, взглянув на имена исключений, возбуждаемых операциями, — имена говорят сами за себя.
Так, bind и rebind связывают между собой имена и ссылки на конкретные объекты. Это самые важные операции, которыми и создается структура пространства имен. Принципиальное различие между ними лишь одно: если bind пытается связать объект с именем и такая связь уже существует, то будет возбуждена исключительная ситуация AlreadyBound. Напротив, rebind игнорирует существующую связь и создает ее заново. Для связывания имени с контекстом, а не с объектом имеются операции bind_context и rebind_context. Опять-таки различие между ними в том, что rebind_context не генерирует исключения AlreadyBound в том случае, если создаваемая связь уже имеется. Создать новый контекст именования можно операцией new_context, а создать и связать с именем за один вызов — операцией bind_new_context. В противоположность рассмотренным методам для разрыва связей в имени используется операция unbind, а для уничтожения контекста именования вызывается операция destroy.
Когда наступает время использовать имена для получения ссылок на объекты, применяется операция разрешения имени resolve. Если ей передается полное имя, то она возвращает ссылку на связанный с именем объект. Для неполного имени будет возвращена ссылка на контекст именования, который указан в имени самым последним.
Любопытные могут вызывать операцию list. Она возвратит список связей в имени и итератор для их перебора. Но об этом буквально через несколько абзацев.
Сервис имен значительно обновился в VisiBroker версии 4.0, что соответствует требованиям спецификации CORBA 2.3 и исправляет недоделки прошлых IDL-описаний этого сервиса. Появился расширенный контекст именований — тип NamingContextExt:
interface NamingContextExt: NamingContext {
. . .
StringName to_string(in Name n) raises(InvalidName);
Name to_name(in StringName sn) raises(InvalidName);
. . .
URLString to_url(in Address addr, in StringName sn)
raises(InvalidAddress, InvalidName);
Object resolve_str(in StringName n) raises(
NotFound, CannotProceed,
InvalidName, AlreadyBound
);
};
Так, операция to_string возвращает полное имя в виде строки текста, а операция to_name делает обратную работу: берет строку и строит по ней имя со всеми связями. Если нужно получить URL по имени, следует вызвать операцию to_url. Даже разрешение имени теперь можно сделать, не прибегая к операциям низкого уровня, просто передав текстовую строку вызову resolve_str. Это действительно большой шаг в сторону удобства использования сервиса имен.
А теперь об итераторе связей имени. Его описание приведено ниже:
interface BindingIterator {
boolean next_one(out Binding b);
boolean next_n(in unsigned long how_many,
out BindingList b);
void destroy();
};
Вызвав операцию next_one, вы получаете структуру Binding, описывающую связь в имени, полученном в результате работы операции list интерфейса NamingContext. Из этой структуры можно узнать имя связи и ее тип:
struct Binding {
Name binding_name;
BindingType binding_type;
};
Можно определить, соединена рассматриваемая связь с именем или с другим контекстом:
enum BindingType {
nobject,
ncontext
};
Исходя из наших теперешних знаний, можно создать спецификацию именования для сотовой системы. Первой идет имя корневого контекста «cellular_system». Без этого можно и обойтись, если с вашей консоли не будут осуществлять управление другие системы. Далее после символа «/» (слэш) идет страна (например, Russia). Скорее всего, пока это будет только Россия. Наконец, указывается область и имя самой соты (пример подобного описания приведен в начале статьи).
Пользоваться службой имен нетрудно. Сначала операцией ORB::resolve_initial_references получается ссылка на корневой контекст. Для этого вызывается следующая строка:
org.omg.CORBA.Object obj =
orb.resolve_initial_references(”NameService”);
Она возвращает ссылку на корневой контекст графа имен. Разумеется, нужно сделать приведение к типу контекста:
NamingContextExt rootCtx = NamingContextExtHelper.narrow(obj)
Далее для построения оставшегося графа можно пойти двумя путями. Первый — старый и более сложный. Он ясен из фрагмента исходного текста, показанного ниже:
. . .
NameComponent[] systemName =
{ new NameComponent(”cellular_system”, ”system”) };
NamingContext systemCtx =
<ссылка на корневой контекст>.bind_new_context
( systemName );
NameComponent[] countryName = { new NameComponent(”Russia”,
”country”) };
NamingContext countryCtx = systemCtx.bind_new_context
( countryName );
NameComponent[] regionName = { new NameComponent(”Yaroslavl”,
”region”) };
NamingContext regionCtx = countryCtx.bind_new_context
( regionName );
NameComponent[] objectName = { new NameComponent
(”cell#5”, ””) };
regionCtx.rebind( objectName, <ссылка на привязываемый
объект> );
. . .
На нем строится граф имени cellular_system.system/ Russia.country/Yaroslavl.region/cell#5. В конструкторах NameComponent первыми параметрами идут id имени, а вторыми — kind. Принцип работы очень простой. После получения ссылки на корневой контекст мы шаг за шагом создаем компоненты имени и связываем их, одновременно создавая новые контексты. Только сам объект не требует нового контекста и просто связывается с самым последним созданным контекстом посредством вызова rebind(). Помните: можно использовать и метод bind(), но тогда вы будете отвечать за обработку исключительной ситуации, если вдруг такое имя уже имеется.
С обновлением спецификации CORBA и ее составляющих, связывание объекта с именем можно выполнить еще проще, всего за один проход:
<ссылка на корневой контекст>.bind(
<ссылка на корневой контекст>.to_name(
«cellular_system.system/Russia.country/Yaroslavl.
region/cell#5»,
<ссылка на привязываемый объект>));
Всю рутинную работу по созданию контекстов и их связыванию берет на себя метод to_name().
С клиентской стороны разница в исходных текстах минимальна. Получается ссылка на корневой контекст, и создается имя. А когда наступает время получить по этому имени ссылку на объект, вызывается метод resolve(), как это схематично показано ниже:
org.omg.CORBA.Object obj = <ссылка на корневой контекст>.
resolve(
<ссылка на корневой контекст>.to_name(<искомое имя>));
Следует отметить, что многие службы имен от самых разных производителей позволяют объявить корневым любой контекст. Как правило, это делается при запуске клиентской программы. К примеру, для программы, запускаемой в среде Inprise VisiBroker, можно задать корневой контекст опцией -DSVCnameroot:
vbj -DSVCnameroot= cellular_system.system/Russia.country
<имя приложения>
После этого клиентская программа будет считать контекст Russia.country корнем графа имен. Это удобно, когда приложение не должно выходить за рамки своего уровня. Опять же не требуется строить и связывать лишние контексты.
В заключение несколько слов о написании имен. Разные производители ORB, к сожалению, могут придерживаться разных способов именования. Для Inprise VisiBroker характерно написание имени, когда его компоненты разделяются символами «/» (слэш), а значение поля id компонента отделяется от значения поля kind точкой:
cellular_system.system/Russia.country/Yaroslavl.region/cell#5
Это соответствует спецификации сервиса имен OMG. В варианте с более старыми брокерами объектов Orbix компании IONA это же имя выглядит так:
cellular_system-system.Russia-country.Yaroslavl-region.cell#5
И только в новой версии Orbix 2000 все приведено к стандартному виду со слэшами и точками, но тем не менее будьте готовы к некоторым казусам.