WebClub - Всероссийский Клуб Веб-разработчиков
WebClub.RU » Архив » Взаимодействие между объектами

Взаимодействие между объектами


Дата публикации: 17-03-2013

1. Введение

   В данной части, мы рассмотрим вопросы и проблемы, связанные с Объектными Адаптерами (OA - Object Adapter) CORBA. Мы сделаем акцент на том, что представляют из себя Объектные Адаптеры и опишем их роль в системах на основе технологии CORBA. В дополнение, мы начнем всестороннее обсуждение спецификации нового Переносимого Объектного Адаптера (POA - Portable Object Adapter), которая недавно была принята OMG. Последующие части продолжат данное обсуждение и в них будет приведено несколько примеров с использованием C++.
2. Терминология

   Перед тем как описывать то, что из себя представляют Объектные Адаптеры, необходимо определить несколько ключевых терминов. Даже если Вы хорошо знакомы с CORBA, все равно, Вы должны уделить внимание этим терминам и их определениям, так как некоторые из них были введены недавно, с принятием OMG спецификации POA.

    Объект CORBA (CORBA object): "Виртуальная" сущность, месторасположение которой может определять ORB, и к которой доставляются клиентские запросы. Объект CORBA идентифицируется (identified), определяется (located) и адресуется (addressed) с использованием его объектной ссылки (object reference). В контексте запроса (request invocation), объект CORBA, которому послан запрос, называется "целевым объектом" ("target object").

    Исполнитель (Servant): Элемент языка программирования, существующий в контексте сервера и реализующий объект CORBA. В не объектно-ориентированных языках, таких как С и COBOL, исполнитель реализован в виде набора функций, которые манипулируют данными (например, экземплярами структуры или записи), представляющими состояние объекта CORBA. В объектно-ориентированных языках, таких как С++ или Java, исполнители являются экземплярами определенного класса.
       Чрезвычайно важно понимать взаимосвязь между объектом CORBA и исполнителем. Объект CORBA считается виртуальным, так как он не существует сам по себе. Поэтому, для обработки клиентских запросов, он должен обладать материализующим его существование исполнителем, который и выполняет эти запросы. Однако заметьте, что объекты CORBA, могут иметь состояние, что, правда, отнюдь не значит то, что исполнителям, также обязательно этим состоянием необходимо обладать. Например, состояние объекта CORBA, может храниться в базе данных. Тогда, исполнитель просто используется для того, чтобы проверять и модифицировать это состояние, но поддерживать какое-либо собственное состояние исполнитель не должен.
       Взаимосвязь между объектом CORBA и исполнителем очень похожа на взаимосвязь между виртуальной и физической памятью в операционной системе. Как в реалии не существует виртуального адреса, также реально не существует и объекта CORBA. Осуществляемые компьютерной программой запись и чтение по месторасположению в виртуальной памяти, производятся за счет работы, выполняемой модулем управления памятью компьютера (MMU - Memory Management Unit). MMU производит отображение виртуальных адресов памяти в физические и гарантирует, что каждому правильному виртуальному адресу соответствует правильный адрес физической памяти. Подобным же образом, взаимодействуют друг с другом ORB и OA, предоставляя клиентским приложениям возможность выполнять запросы к объектам CORBA, при этом гарантируя, что каждому действительному объекту CORBA соответствует исполнитель. Дополнительно, ORB и OA, основываясь на информации хранимой в объектной ссылке, взаимодействуют друг с другом для прозрачного определения местоположения и вызова подходящего Исполнителя.

    Каркас, Скелет (Skeleton): Элемент языка программирования, присоединяющий исполнитель к OA, тем самым, позволяя последнему осуществлять перенаправление выполнения запроса исполнителю. В C, каркас представляет из себя набор указателей на конкретные функции исполнителя. В C++ же, это базовый класс, которому наследует класс исполнителя. Для статической обработки запроса, каркасы обычно генерируются автоматически с использованием IDL компилятора. В дополнение, CORBA поддерживает Интерфейс Динамических Каркасов (DSI - Dynamic Skeleton Interface), позволяющий серверу обрабатывать запросы, предназначенные объектам, для которых статические каркасы отсутствуют.

    Идентификатор Объекта (Object Id): Устанавливаемый пользователем или системой идентификатор, использующийся для "именования" объекта в рамках OA. При этом, не гарантируется глобальная уникальность идентификаторов объекта, также как не является обязательным их уникальность в рамках процесса сервера. Единственным условием является уникальность идентификатора объекта в рамках OA, в котором данный идентификатор объекта был создан и зарегистрирован.

    Активация (Activation): Действие запуска существующего объекта CORBA для того, чтобы он мог обслуживать запросы. Так как в конечном счете, обработкой запроса занимаются исполнители, активация требует, чтобы объект CORBA был ассоциирован с подходящим исполнителем. Заметьте, что активация не подразумевает создание объекта CORBA, так как в случае, если объект CORBA не был ранее создан, он не может быть активирован. Однако, активация может повлечь за собой создание исполнителя.
       Данное обсуждение активации может повлечь за собой вопрос о том, как создаются объекты CORBA. В отличие от классов C++, интерфейсы OMG IDL не имеют понятия конструкторов, либо каких-либо других специальных функций создания объектов. С точки зрения клиента, объекты CORBA часто создаются путем вызова стандартных операций объектов фабрик (factory objects) CORBA. С точки зрения сервера, исполнители для объектов фабрик реализованы таким образом, что они вызывают операции OA для связывания исполнителей с объектами CORBA, которые они создают, а также для создания объектных ссылок для этих новых объектов. В дополнение, фабричные операции как правило также производят активацию объекта CORBA.

    Деактивация (Deactivation): Действие по остановке активного объекта CORBA. Очевидно, что деактивация представляет из себя противоположность активации. Деактивация требует разрушения ассоциации между объектом CORBA и его исполнителем. Заметьте, что деактивация не подразумевает уничтожение объекта CORBA. После деактивации, объект CORBA может быть вновь активирован для обслуживания новых запросов. Однако, деактивация может повлечь за собой уничтожение исполнителя.

    Инкарнация, Воплощение (Incarnation): Толковый словарь определяет данное слово, как "действие по назначению чему-либо телесной формы или вещества". В основном контексте объектов CORBA и исполнителей, воплощение является действием по ассоциации исполнителя с объектом CORBA, для того, чтобы последний мог обрабатывать запросы. Другими словами, инкарнация предоставляет виртуальному объекту CORBA "тело", коим является исполнитель.

    Эфемеризация (Etherealization): Противоположность инкарнации, действие по уничтожению ассоциации между объектом CORBA и исполнителем. Эфемеризация отрывает от объекта CORBA "тело", которое было с ним ассоциировано в момент инкарнации. После эфемеризации, объект CORBA все еще существует как виртуальная сущность, но не имеет ассоциированного тела для обработки запросов.

    Таблица Активных Объектов (Active Object Map): Таблица, поддерживаемая объектным адаптером, и содержащая отображения между активными объектами CORBA и их исполнителями. Активные объекты CORBA именуются в таблице посредством идентификаторов объектов (Object Ids). Заметьте, что фраза "активный объект" ("active object") является не совсем адекватной по отношению к существующему термину, так как исполнителям в данной таблице не требуется реализовывать образец "Активный объект" (Active Object pattern) [1].
       Большая часть введенной здесь терминологии имеет отношение к циклу жизни объектов внутри CORBA системы. Возможно, наиболее простым способом запомнить различие между исполнителями и объектами CORBA, является то, что объект CORBA может быть представлен одним или несколькими Исполнителями на протяжении всего своего времени жизни. Аналогично, исполнитель может одновременно представлять один или несколько объектов CORBA. Активация и деактивация имеют отношение только к объектам CORBA, в то время, как термины инкарнация и эфемеризация имеют отношение только к исполнителям. По факту, два последних термина были введены для возможности обсуждения жизненных циклов исполнителей отдельно от жизненных циклов объектов CORBA (слухи по поводу того, что за два упомянутых термина было заплачено буддийской секте "Дзен" являются неправдой :).

   На рисунке 1 показан жизненный цикл объекта CORBA по отношению к жизненному циклу исполнителя(ей), который(ые) его воплощает(ют).

Жизненный цикл запроса в Переносимом Объектном Адаптере

Рис. 1. Жизненный цикл запроса в Переносимом Объектном Адаптере.

   В начале объект CORBA создается и затем активируется, либо незамедлительно либо позже, по получении запроса на обработку. После активации, он может быть незамедлительно воплощен исполнителем, или же в течении периода, когда объект активен, для обработки каждого запроса, получаемого объектом, может быть осуществлена инкарнация и эфемеризация множества исполнителей. Затем объект CORBA деактивируется. В последствии, он может быть опять активирован, и тем самым опять начнутся циклы инкарнации и эфемеризации исполнителя. По достижению какого-либо события, объект CORBA уничтожается и его жизненный цикл заканчивается.
   Неспособность определить различие между жизненными циклами объектов CORBA и исполнителей часто вызывает у разработчиков CORBA неразбериху. Происходит это потому, что очень легко думать, что цикл жизни исполнителя на С++ одинаков с циклом жизни объекта CORBA, который он представляет. А это не всегда так. В качестве последствий непонимания данных различий выступают приложения, которые без нужды создают новые объекты CORBA вместо повторной активации существующих объектов и инкарнации их с новыми исполнителями.
3. Обзор Объектных Адаптеров CORBA
3.1 Причина появления Объектных Адаптеров
   Как и подразумевает его имя, Объектный Адаптер CORBA представляет из себя образец Адаптер [2], так как он адаптирует понятие исполнителя языка программирования к понятию объектов CORBA. Роль данной адаптации ясно показана на рисунке 2, где OA занимает место непосредственно между основной частью ORB (ORB Core) и статическими (IDL Skeleton) и динамическими (DSI) Каркасами.

Компоненты Эталонной Модели CORBA

Рис. 2. Компоненты Эталонной Модели CORBA (CORBA Reference Model).

   В соответствии с ролью адаптера, различные OA могут поддерживать различные стили реализации исполнителей. Например, Каркас Адаптера Объектной Базы Данных (Object Database Adapter Framework) [3] Orbix допускает использование в качестве Исполнителей для объектов CORBA объекты, реализованные с помощью объектной базы данных (такой как ODI's ObjectStore). Другим примером может служить предоставляемый TAO [4] Объектный Адаптер реального времени. Конечно, возможно определить архитектуру промежуточного программного обеспечения распределенных объектных вычислений (distributed object computing middleware architecture), в которой исполнители напрямую регистрируются в ORB, а не в OA. Однако, такая архитектура будет заведомо обладать одним из следующих ограничений:

    Большая ширина касания (Large footprint): Один из подходов мог бы потребовать от основной части ORB (ORB Core) поддержку сразу множества интерфейсов и услуг для обеспечения возможности наличия множества стилей реализации исполнителей. Однако, данный подход сделал бы основную часть ORB большой, медленной и намного более запутанной для большинства приложений. Следовательно, данный подход нарушил бы "принцип экономичности" ("principle of parsimony"), заставляя приложения обращать внимание даже на те возможности, которые они могут и не использовать.
    Потеря гибкости (Lack of flexibility): Альтернативой могла бы стать поддержка только одной формы реализации Исполнителей. Например, обязать всех Исполнителей наследовать одному базовому классу. Однако, данный подход ограничил бы пригодность ORB как интеграционного механизма, способного иметь дело с широким разнообразием языков программирования, наследуемыми системами (legacy systems), и стилями проектирования программного обеспечения (software design styles). Следовательно, использование Объектных Адаптеров означает, что поддержка различных стилей реализации исполнителей может быть выделена в различные OA.. А это ведет к уменьшению и упрощению основной части ORB.

3.2 Функциональность Объектного Адаптера

   Объектные Адаптеры CORBA предоставляют следующие функциональные возможности:

    Демультиплексирование запроса (Request demultiplexing): Объектные Адаптеры демультиплексируют каждый CORBA запрос определенному исполнителю. Когда ORB получает запрос, то для того, чтобы убедиться в том, что запрос достигает соответствующего исполнителя, он взаимодействует с OA через закрытый (т.е. не стандартный) интерфейс.
    Передача выполнения операции (Operation dispatching): Как только OA находит целевой исполнитель, он передает ему выполнение запрошенной операции. При этом, для преобразования параметров запроса в аргументы вызываемой операции исполнителя используется каркас.
    Активация и Деактивация (Activation and Deactivation): Объектные Адаптеры способны активировать объекты CORBA. При этом в процессе активации, для обслуживания поступающих запросов, они также способны производить инкарнацию исполнителей. Подобным же образом, Объектные Адаптеры могут деактивировать объекты CORBA и эфемеризовать соответствующие им исполнители, если необходимости в последних больше не существует.
    Создание Объектных Ссылок (Generating object references): Объектный Адаптер ответственен за генерацию объектных ссылок для зарегистрированных в нем объектов CORBA. Объектные ссылки идентифицируют объект CORBA стандартным способом и содержат информацию о том, как достичь данный объект в распределенной системе. Это означает, что Объектные Адаптеры должны в конечном счете взаимодействовать со средствами коммуникации (communication facilities), встроенными в ORB и нижележащие операционные системы, для гарантии того, что информация необходимая для достижения объекта представлена в объектной ссылке. Например, Переносимая (интероперабельная) Объектная Ссылка (IOR - Interoperable Object Reference) поддерживающая передачу по IIOP (Internet Inter-ORB Protocol) будет содержать Интернет адрес серверного узла (server host), так же как и номер порта, который "слушает" серверный процесс.
       Объектные Адаптеры глубоко вовлечены в процесс передачи выполнения запросов операциям объекта. По этой причине, чтобы не стать узким местом на пути обработки запроса, они должны быть аккуратно спроектированы. Обычные Брокеры Объектных Запросов демультиплексируют запросы клиента подходящей операции исполнителя, используя при этом шаги, показанные на рисунке 3.

    Поуровневое демультиплексирование CORBA запросов
    Рис. 3: Поуровневое демультиплексирование CORBA запросов.
    Шаги 1 и 2: Стек протоколов ОС несколько раз демультиплексирует запрос клиента, например, через канал передачи данных (data link), сеть, и транспортные уровни, вплоть до границы между ядром ОС (OS Kernel) и пользовательской средой, которой в данном случае является основная часть ORB (ORB Core);
    Шаги 3, 4 и 5: Основная часть ORB (ORB Core) использует адресную информацию в объектном ключе (object key) клиента для нахождения соответствующего Объектного Адаптера, исполнителя, и каркаса целевой операции IDL (target IDL operation);
    Шаг 6: Каркас IDL определяет соответствующую операцию, выполняет демаршалинг буфера запроса в параметры операции, и выполняет ее вызов.
       Демультиплексирование запросов клиента через все вышеуказанные уровни является дорогостоящей операцией, особенно когда в IDL интерфейсе присутствует большое количество операций и/или когда Объектный Адаптер управляет большим количеством исполнителей. [5] оценивает производительность некоторых стратегий демультиплексирования и передачи выполнения запросов (dispatching) Объектных Адаптеров. В основном же, техники демультиплексирования, основанные на безупречном хешировании (perfect hashing) и активном демультиплексировании, существенно быстрее тех, которые основаны на линейном поиске (linear search) или динамическом хешировании (dynamic hashing).

4. Стандартные Объектные Адаптеры

   На протяжении всего времени жизни спецификации CORBA, только два Объектных Адаптера были официально утверждены OMG. Первым из них был Базовый Объектный Адаптер (BOA - Basic Object Adapter), приведенный в оригинальной спецификации CORBA. Достаточно недавно, OMG одобрило новый Переносимый Объектный Адаптер (POA - Portable Object Adapter). Для понимания и использования POA не требуется понимание BOA, однако изучение возможностей и истории BOA может помочь нам понять то, как спроектирован и на что способен POA.

4.1 Базовый Объектный Адаптер CORBA (BOA)
4.1.1 Основные возможности
   Первая версия спецификации CORBA содержала описание Базового Объектного Адаптера (BOA). Объектный Адаптер BOA был cпозиционирован как многоцелевой Объектный Адаптер, который мог поддерживать различные стили исполнителей. По причинам заявленной гибкости архитектуры BOA (Because of the purported flexibility), изначальные проектировщики CORBA думали, что для большинства приложений, использующих BOA, будет достаточно лишь несколько Объектных Адаптеров (например, library OA, database OA и т.д.).
   Архитектура BOA поддерживала четыре различные модели активации (activation models), описанные ниже. Заметьте однако, что данные модели имеют отношение только к активации процессов сервера, а не объектов CORBA.

    Неразделяемый сервер (Unshared server): Это сервер, которой поддерживает только один объект CORBA. При этом, неразделяемый сервер обычно имеет дело с объектом только одного типа. И всякий раз при создании нового объекта CORBA этого типа, ORB автоматически запускает новый процесс сервера, содержащий этот объект.
    Разделяемый (общий) сервер (Shared server): Это сервер, поддерживающий множество объектов CORBA, часто различного типа. Например, такой сервер может поддерживать объекты CORBA, выполняющие роль фабрик (таких как Quoter_Factory, определенной нами в [6]), а также типы объектов CORBA, созданных такими фабриками. На практике, наиболее распространенным видом серверов CORBA являются разделяемые сервера.
    Устойчивый (постоянный) сервер (Persistent server): Это сервер, который ORB автоматически не запускается. Эти типы серверов должны быть, например, запущены скриптом, который выполняется каждый раз при запуске системы. Заметьте, что использование слова "устойчивый (persistent)" является неудачным использованием этого термина, так как он означает, что состояние исполнителей, запущенных в рамках данного сервера, будет устойчиво, т.е. автоматически поддерживаться сервером даже в период сбоев и завершения работы. Так как это не так, то наиболее подходящим термином могло бы послужить "вручную запускаемый (manually launched)" сервер. В дополнение, заметьте, что механизмы используемые для запуска серверного процесса полностью ортогональны тому, является ли сервер разделяемым или нет, таким образом эта особая модель активации не совсем взаимосвязана с другими моделями.
    Сервер на операцию (Server-per-operation): Такой тип "сервера" - это не просто одиночный процесс. На самом деле, он представляет из себя набор процессов, каждый из которых реализует заданную операцию определенного типа объекта CORBA. Теоретически, этот тип сервера можно считать полезным для исполнителей, реализованных в виде скриптов командного процессора ОС (shell scripts). Например, различные операции объекта CORBA реализуются различными скриптами. По причинам сложности поддержки и малой полезности, лишь небольшое количество коммерческих ORB поддерживают этот типа сервера.

4.1.2 Оценка BOA
   К сожалению, описание BOA в оригинальной спецификаций CORBA явно неполно. Далее представлены некоторые ключевые проблемы связанные с этой спецификацией:

    Не определяет переносимого (portable) способа ассоциации каркасов с исполнителями: Спецификация BOA не описывает то, как должны выглядеть каркасы, и как исполнители должны с ними ассоциироваться. В результате, спецификация отображения OMG IDL на C++ в оригинальном варианте определяла только то, как должны выглядеть тела и сигнатуры методов исполнителей. Она не могла определить имена базовых классов, от которых исполнители должны были наследовать. Естественно, что такое упущение сильно затруднило написание переносимого CORBA кода на языке C++.
    Неспособность описать то, как регистрируются Исполнители: Реализации BOA обычно позволяют регистрировать исполнители неявным образом (например, во время инкарнации, в конструкторе исполнителя) и/или явно (например, путем вызова метода BOA). Но, оригинальная спецификация BOA не определяет API для выполнения этих операций. Отсюда, каждый ORB стремиться реализовать данную функциональность отличным от других способом.
    Полное игнорирование вопросов, связанных с многопоточностью серверного процесса: Многопоточные ORB важны, так как они позволяют одновременное выполнение длительных задач без помехи другим задачам. Однако, спецификация BOA в CORBA 2.0 не касается вопросов многопоточности, оставляя право принятия решения разработчикам ORB. Как результат, различные ORB стремятся реализовать многопоточность по своему.
    Неспособность точно определить функции, требуемые для того, чтобы сервер начал ожидать запросы (to make a server listen for requests): Базовый Объектный Адаптер предоставляет две операции, предназначенные для того, чтобы сервер начал обслуживать запросы: impl_is_ready() и obj_is_ready(). Спецификация BOA ассоциирует данные операции с принятыми в ней моделями активации, утверждая что то, какой из двух методов вызывать, зависит от выбранной для серверного приложения модели активации. К сожалению, данная часть спецификации является чрезвычайно смутной и неясной, приводя к тому, что в различных ORB продуктах, эти две операции используются совершенно по разному.
       В следствии ограничений связанных со спецификацией BOA, каждый поставщик ORB, реализовавший BOA, сделал это по своему. Это, в свою очередь, привело к фактическому отсутствию переносимости серверного кода между различными ORB продуктами. В итоге, OMG выпустило RFP (Request For Proposals - запрос на предложения) документ по расширению переносимости [7], который запрашивал либо решение проблем с BOA (за полным списком проблем, связанных с BOA, обращайтесь, пожалуйста, к указанному RFP документу. Семь его страниц посвящены перечню и описанию проблем BOA.), либо замену его новым Объектным Адаптером.

4.2 Переносимый Объектный Адаптер (POA - Portable Object Adapter)
   В Марте 1997-го, в качестве замены BOA на рассмотрение в OMG был представлен Переносимый Объектный Адаптер (POA) [8]. Спецификация POA окончательно обеспечивает переносимость серверных приложений CORBA. Также она предоставляет новые и очень полезные функциональные возможности.
   Одним из нововведений, приведенных в документе "Portability Joint Submission", является отказ от BOA. Таким образом, как только данное предложение будет полностью интегрировано со спецификацией CORBA (что должно произойти в начале 1998 года), OMG полностью уберет BOA из CORBA. Конечно, производители ORB, которые на текущий момент поддерживают BOA, смогут и далее делать это ради уже существующих у них покупателей.

4.3 Обзор возможностей POA
   Недавно определенная спецификация POA поддерживает широкое разнообразие возможностей, включая: идентификаторы объектов, предоставляемые пользователем или системой (user- or system-supplied identifiers); постоянные (persistent) и временные (transient) объекты, явная активация и активация по требованию (on-demand activation); возможность сопоставления объекту CORBA множества исполнителей; полный контроль приложения над поведением и существованием объекта, а также поддержка статических и DSI исполнителей. Каждая из этих возможностей описана ниже. Последующие части детально, с использованием С++, проиллюстрируют эти и другие возможности POA.

4.3.1 Идентификатор Объекта (Object Identifier)
   Идентификатор Объекта (ObjectId) представляет из себя последовательность октетов, используемую для идентификации объекта CORBA в рамках контекста POA. ObjectId является "ключом демультиплексирования" ("demultiplexing key"), используемым для ассоциации запросов клиента с объектами CORBA.
   Важно отдавать себе отчет в том, что идентификаторы ObjectId необязательно будут идентифицировать объект за пределами его POA. Таким образом, не гарантируется, что они будут уникальными глобальными идентификаторами. Объекты CORBA идентифицируются только своими объектными ссылками, частью которых являются идентификаторы ObjectId (заметьте, что спецификация POA не требует присутствия в объектных ссылках идентификаторов ObjectId, но большинство реализаций POA с большой долей вероятности разместят их именно там). Также, из-за непрозрачности объектных ссылок для приложений, клиенты не могут, и на самом деле не нуждаются в том, чтобы "получать доступ внутрь этих ссылок" и исследовать части ObjectId. Клиенты просто используют объектные ссылки для обозначения (именования) получателей исходящих от них запросов; ORB же заботиться обо все остальном.
   В ссылках IOR, идентификаторы ObjectId составляют только часть всего объектного ключа (object key), используемого ORB для определения месторасположения объекта CORBA. Основной протокол взаимодействия ORB (GIOP - General Inter-ORB Interoperability Protocol (Интернет-протокол взаимодействия ORB (IIOP - Internet Inter-ORB Protocol) представляет из себя спецификацию того, каким образом GIOP используется для доставки запросов и ответов через TCP/IP.) определяет объектный ключ как часть IOR, используемую для идентификации объекта CORBA на концах канала связи (communication link endpoints) указанных в IOR. Например, если запрос к объекту CORBA послан по IIOP, к хосту и порту, указанному в IOR объекта, принимающая на этом порту сторона может использовать объектный ключ для того чтобы уникальным образом идентифицировать целевой объект. Это означает, что объектный ключ является уникальным только по отношению к конечной точке связи (communication endpoint).
   Тем не менее, объектный ключ представляет из себя нечто большее, чем просто идентификатор ObjectId. Например, он может также содержать указание о том, какой POA предполагается использовать для передачи выполнения запроса целевому объекту. Вообще, GIOP не определяет содержимого объектного ключа, и поэтому, для определения целевого объекта в указанной конечной точке, реализации ORB помещают в объектный ключ любую необходимую им информацию. Важно заметить, что такая гибкость в спецификации GIOP не приводит к проблемам переносимости между различными ORB, так как предполагается, что создавший IOR ссылку ORB аналогичен ORB, слушающему на указанных концах линии связи, и, тем самым, способен осуществить декодирование объектного ключа, так как он же его и создал. Однако, это является причиной того, что объект, созданный одним ORB не может быть реактивирован в ORB другого поставщика, так как новый ORB совершенно не обязательно будет способен декодировать объектный ключ, созданный оригинальным ORB.

4.3.2 Постоянные (persistent) Объекты против Временных (transient)
   Постоянными объектами являются объекты CORBA, чье время жизни независимо от каких-либо серверных процессов, внутри которых они были активированы. В противоположность им, временные объекты представляют из себя объекты CORBA, чье время жизни ограничено временем жизни серверных процессов, в рамках которых они были созданы. Когда серверные процессы умирают, их временные объекты умирают вместе с ними.
   Постоянные объекты - это "нормальные" объекты CORBA, которые могут быть, по потребности, активированы для выполнения клиентского запроса. С другой стороны, временные объекты, в отличие от постоянных, обычно влекут за собой меньшие накладные расходы, так как ORB и POA не требуется хранить для них записей об активировании. Они полезны для "временных" (temporary) объектов, вроде некоторых типов объектов обратного вызова [9].
   Использование термина "постоянный" для объектов CORBA, которые переживают любой одиночный серверный процесс, является потенциально запутывающим, так как этот термин используется по разному в нескольких других разделах CORBA и у специалистов по базам данных. В контексте POA, термин "постоянный объект" в основном имеет отношение к тому факту, что объект этого типа "продолжает существовать" независимо от активаций серверного процесса ("persists" across server process activations). Кое-кто, однако, может ожидать, что использование данного термина также подразумевает, что между активациями состояние данного объекта сохраняется в постоянном хранилище данных. Но, несмотря на то, что постоянные объекты CORBA как правило обладают постоянным состоянием, POA, сам по себе, постоянного состояния не имеет, так же как и не предоставляет каких-либо функций непосредственной помощи объектам в сохранении и загрузке их постоянного состояния. В дополнение, заметьте, что постоянные объекты не подразумевают использование некорректно названной "постоянной" модели активации BOA, хотя конечно представляется возможным иметь постоянные объекты в рамках постоянно (т.е. вручную) запущенного сервера (within a persistently (i.e. manually) launched server).

4.3.3 Активация (Activation)
   Режимы активации, поддерживаемые BOA, были сильно сконцентрированы вокруг процессов сервера. В противоположность, предоставленные POA средства активации (activation facilities) полностью сфокусированы на активации объекта CORBA и инкарнации исполнителя. POA поддерживает следующие стили активации:

    Явная активация (Explicit activation): программист серверного приложения регистрирует исполнители для объектов CORBA путем непосредственного вызова соответствующих методов POA. Такое подход полезен для серверных приложений с небольшим количеством объектов CORBA.
    Активация по требованию (On-demand activation): Программист серверного приложения регистрирует менеджер исполнителей (servant manager), обратный вызовов метода которого при получении запроса, и в случае если Объект CORBA еще не активирован, произведет POA. В случае обратного вызова, менеджер исполнителей обычно выполняет одну из следующих операций:
        В случае необходимости производит инкарнацию исполнителя, и регистрирует его в POA, который затем передает выполнение запроса этому исполнителю.
        Поднимает исключение ForwardRequest (определено в модуле PortableServer) с целью посылки запроса другому объекту. Данное исключение содержит в объектной ссылке информацию указывающую на объект, которому должен быть перенаправлен данный запрос. Данная возможность может быть использована при разработке сервера, например, выполняющего специфичное для приложения выравнивание нагрузки.
        Поднимает исключение CORBA::OBJECT_NOT_EXIST для индикации того, что объект CORBA был уничтожен.
    Неявная активация (Implicit activation): любое действие над исполнителем приводит к активации без каких-либо явных обращений к POA. В отображении IDL на C++, неявная активация исполнителя в POA, созданным с использованием соответствующих политик, может быть достигнута путем вызова метода исполнителя _this().
    Исполнитель по умолчанию (Default servant): приложение регистрирует исполнитель по умолчанию, используемый при поступлении запроса к еще не активированному объекту CORBA, в случае отсутствия зарегистрированных менеджеров исполнителей. Данная возможность очень полезна для серверных приложений на основе DSI, так как она позволяет одному DSI исполнителю производить инкарнацию всех объектов CORBA не требуя вмешательства менеджера исполнителя.

4.3.4 Уникальность идентификатора объекта (Object ID Uniqueness)
   Адаптер POA может требовать, чтобы каждый объект CORBA был активирован уникальным Исполнителем. Это очень прямолинейная техника реализации, так как между объектом CORBA и исполнителем обеспечивается лишь связь "один к одному". Примером того, где такая техника оказывается полезной, являются временные CORBA объекты чье состояние хранится непосредственно в элементах данных исполнителя (servant data members). Несмотря на концептуальную простоту данной техники, она плохо масштабируется в случае большого количества объектов CORBA внутри сервера, так как последний должен поддерживать отдельные исполнители для каждого исполняемого объекта CORBA.
   В качестве альтернативы, адаптер POA может позволять одному объекту исполнителя осуществлять инкарнацию нескольких объектов CORBA. Разрешение на инкарнацию множества объектов CORBA одиночным исполнителем, очень полезно для минимизации использования ресурсов сервера. К примеру, представьте базу данных типа "ключ-значение", в которой каждая запись трактуется в качестве отдельного объекта CORBA, чьим идентификатором является "ключ" соответствующей записи базы данных. Полезным подходом реализации сервера для базы данных, является использование одного C++ исполнителя, определяющего на основе переданного вместе с запросом ObjectId то, какая запись базы данных необходима. Такой подход убирает потребность в наличии отдельного исполнителя для каждой записи базы данных. И поэтому, является хорошо масштабируемым в независимости от количества записей в базе данных.
   Для предотвращения регистрации всех записей базы данных в качестве объектов CORBA при каждом запуске сервера, вышеописанное приложение может также использовать активизацию по требованию. Если в качестве результатов CORBA запросов постоянно задействуется лишь небольшое, определенное количество записей, то такой подход уменьшает ненужные накладные расходы сервера, связанные с регистрацией. В дополнение, данный подход уменьшает количество ресурсов требующихся POA для обслуживания регистраций исполнителей.

4.3.5 Поведение и Существование Объекта (Object Behavior and Existence)
   Приложения полностью контролируют состояние и поведение объекта. Адаптер POA не обладает постоянным состоянием (persistent state); приложения сами ответственны за любую, требуемую постоянность состояния объекта. Приложения определяют существование объекта и могут поднимать исключение CORBA::OBJECT_NOT_EXISTS для индикации того, что объект CORBA больше не существует. Подобным же образом, они могут бросать исключения PortableServer::ForwardRequest для указания ORB осуществить поиск объекта где-то в другом месте. Такое перенаправление полностью прозрачно для осуществляющего запрос приложения.
   Так как существует большое количество возможных путей реализации концепции постоянства, адаптеры POA не предпринимают никаких действий по отслеживанию постоянных состояний объектов CORBA. Если, предположим, спецификация POA определит один или даже несколько возможных подходов для реализации постоянства, то это может ограничить другие классы приложений для которых уже доказана неприменимость данных подходов. Более того, постоянство объектов лучше обеспечивается сервисом более высокого уровня. Существующий сервис Постоянства Объектов (Object Persistence service) большим количеством специалистов считается очень запутанным[10], и поэтому, на текущий момент OMG работает над его заменой.
   Разрешение приложениям поднимать исключение CORBA::OBJECT_NOT_EXISTS, дает им полный контроль над существованием объектов, если они того, конечно, желают. Давайте рассмотрим описанный ранее пример с базой данный еще раз. Если запись в базе данных удалена, то при получении запроса к этой записи, исполнитель приложения может просто поднять исключение CORBA::OBJECT_NOT_EXIST. Учитывая тот факт, что адаптер POA не хранит постоянного состояния, такой подход относительно прост. Если бы POA отслеживал объекты, которые уже были уничтожены, то даже для них (например, "кладбище объектов"), POA требовал бы постоянного состояния на объект, из-за чего, в итоге могла бы пострадать масштабируемость приложения.

4.3.6 Статические и DSI Исполнители (Static and DSI Servants)    Поддержка исполнителей, основанных на статических каркасах совершенно не удивительна. На статическом подходе, традиционно, основано большинство серверных приложений CORBA с использованием языка C++. Популярность статических каркасов в основном происходит от их эффективности, знания их программистами на C++, а также потому, что DSI отсутствовал в CORBA до версии 2.0.
   В новой спецификации POA поддерживаются два типа статических Исполнителей:

    Исполнители, основанные на наследовании (Inheritance-based servants): исполнители, основанные на наследовании представляют из себя пример "класс" формы образца проектирования Адаптер и, практически не изменились со времен оригинальной спецификации отображения OMG IDL на C++. В этом подходе, класс исполнителя наследует абстрактному базовому классу - каркасу.
       Одним важным отличием от оригинальной спецификации IDL C++ отображения является изменение метода именования классов каркасов. Например, для интерфейса B, определенного в модуле A, полностью определенным (fully-scoped) именем каркаса будет являться POA_A::B. Для интерфейса C, определенного в глобальной области видимости, соответствующий класс каркаса будет называться POA_C.
       Мотивацией для изменения схемы именования, является создание, для основанных на POA каркасов, серверного пространства имен, отделенного от пространства имен на стороне клиента. Это также позволяет поставщикам ORB поддерживать в рамках одной и той же программы как POA приложения, так и наследуемые BOA приложения, при этом не беспокоясь о конфликтах в именовании каркасов BOA и POA.
    Исполнители, основанные на связи (модели делегирования (Tie-based servants): Исполнители, основанные на связи, представляют из себя пример "объектной" формы образца проектирования Адаптер, и должны на текущий момент поддерживаться всеми, соответствующими спецификации, Брокерами Объектных Запросов. На самом деле этот тип исполнителей очень похож на нормальные исполнители, которые наследуют абстрактному базовому классу - каркасу. Каждый исполнитель, основанный на связи, представляет из себя инстанцию специального, автоматически сгенерированного, класса-шаблона (прим: здесь подразумевается C++ template class) исполнителя. Этот класс-шаблон принимает один тип в качестве аргумента (например, специфичный для приложения класс реализации) и делегирует все поступающие запросы к инстанции типа аргумента шаблона.
       Интересно заметить, что при использовании связей в рамках отображения IDL на С++, инстанция связи (класса-шаблона) является исполнителем , а инстанция C++ класса, которая "завязана в связи" зовется связанным объектом. Обычно связанный объект не имеет отношения наследования к каким-либо сгенерированным из IDL классам-каркасам, что позволяет легко использовать данный подход в качестве средства по интеграции с наследуемым кодом. Tie-классы именуются таким же образом как и каркасы, за исключением того, что они также обладают _tie суффиксом. Например, для IDL интерфейса A, соответствующий класс будет назван как POA_A_TIE. Для серверных приложений, которые требуют использования DSI, как часть спецификации POA, представлен абстрактный базовый класс DynamicImplementation (определен в модуле PortableServer). Использование этого базового класса очень похоже на использование каркасов построенных по принципу исполнителей, основанных на наследовании, так как классы исполнителей DSI также должны наследовать базовому классу.

4.4 Политики POA (POA Policies)
   То, что серверное приложение может иметь множество вложенных друг в друга адаптеров POA, является интересным различием между BOA и POA. Для поддержки различных видов объектов CORBA и/или различных стилей реализации исполнителей, серверное приложение может создать несколько адаптеров POA. Например, приложение может иметь два POA адаптера, один из которых поддерживает временные объекты CORBA, а другой - постоянные.
   Вложенный адаптер POA может быть создан путем вызова фабричного метода другого POA. Все сервера облают по крайне мере одним адаптером POA, который называется корневым (Root POA). Для создания POA, вложенного в корневой POA, приложение вызывает операцию create_POA() корневого адаптера. При этом объектная ссылка на корневой POA доступна через API ORB. Заметьте, что это подразумевает отсутствие у приложений возможности предоставления их собственных реализаций POA, что навряд ли повлекло бы какие-то либо проблемы на практике.
   Характеристики каждого адаптера POA, отличного от корневого, контролируются на этапе создания POA с использованием различных политик. Спецификация POA определяет следующие политики, описанные ниже:
4.4.1 Политика организации поточной обработки (Threading Policy)
   Адаптер POA может использовать либо только один поток (single-threaded), либо обязанности по контролю за его потоками возлагаются на ORB. В случае использования одного потока, все запросы, выполнение которых POA направляет исполнителям, будут выполняться последовательно, либо путем запуска их в одном потоке, либо используя множество синхронизированных потоков с целью выполнения за раз только одного запроса. В противоположность, если указана контролируемая ORB политика организации поточной обработки (ORB-controlled threading policy), установкой потока или потоков, используемых POA для перенаправления выполнения запросов, занимается ORB.
   Учитывая, что спецификация BOA не делала никакого упоминания о потоках, спецификация POA, явно поддерживая политики организации поточной обработки, выглядит в этом отношении намного лучше. К сожалению, определенные в POA политики организации поточной обработки, в реалии все равно не являются достаточными. Например, приложение не в состоянии указать POA использовать Пул Потоков (Thread Pool) [11] или отдельный поток на Объект CORBA (Thread-per-CORBA-object) [6], так же как отсутствуют какие-либо, подобные фильтрам потоков Orbix[11], способы, позволяющие приложениям определять собственные модели реализации параллелизма. Что плохо, так это то, что спецификация не говорит определенно даже о том, всегда ли или нет, использование контролируемой ORB модели (ORB-controlled model) означает наличие нескольких потоков! Этот факт, оставляет поставщикам ORB возможность определения контролируемой ORB политики организации поточной обработки, как однопоточной, что потенциально может повлечь серьезные проблемы с переносимостью.
   В конечном счете, несмотря на то, что спецификация действительно уделяет внимание организации многопоточной обработки (multi-threading), предоставляемой POA гибкости в организации поточной обработки явно недостаточно для всех типов приложений. Поэтому, контролируемая ORB политика организации поточной обработки - это новая проблема переносимости, ждущая своего часа. Надо надеяться, что группа специалистов OMG, осуществляющая ревизию усовершенствований, связанных с переносимостью (OMG Portability Enhancement Revision Task Force), в следующей ревизии спецификации POA, которая должна быть готова в начале 1998 года, исправит контролируемую ORB модель и добавит расширения, которые позволят приложениям управлять их собственными моделями организации поточной обработки.
4.4.2 Политика сохранения (удержания) Исполнителей (Servant Retention Policy)
   Адаптер POA либо сохраняет ассоциацию между исполнителями и объектами CORBA, либо каждый раз при поступлении запроса создает новую связь. На текущий момент, большинство С++ приложений используют нечто похожее на работу данной политики, созданной со значением RETAIN (RETAIN value), так как они регистрируют исполнители в OA и ожидают от него перенаправления выполнения запросов к этим исполнителям.
   Если политика сохранения используется в значении NON-RETAIN, то она, тем не менее, позволяет приложению контролировать назначение исполнителей объектам CORBA. Происходит это потому, что каждый приходящий к NON-RETAIN POA запрос требует от него обращения к приложению для получения исполнителя. Политика NON-RETAIN, к примеру, позволяет приложению предоставить собственный объект менеджера исполнителя, который будет участвовать в каждом перенаправлении выполнения запроса для того, чтобы убеждаться в том, что целевой объект CORBA все еще существует.
4.4.3 Политика Обработки Запроса (Request Processing Policy)
    При поступлении запроса к конкретному объекту CORBA, адаптер POA может либо:

    Обратиться только к его таблице активных объектов: он может проверить наличие в ней исполнителя, ассоциированного с ObjectId целевого объекта. Если искомая ассоциация найдена, то выполнение перенаправления запроса может быть выполнено. В противном случае, адаптер POA бросает исключение CORBA::OBJECT_NOT_EXISTS.
    Использовать исполнитель по умолчанию: приложение может зарегистрировать исполнитель по умолчанию, извлекаемый всякий раз когда адаптер POA не может найти в своей Таблице Активных Объектов исполнитель, ассоциированный с ObjectId целевого объекта.
    Вызывать менеджер исполнителя: если в POA был зарегистрирован менеджер исполнителя, то вызов последнего осуществляется всякий раз, когда POA не может обнаружить исполнитель, ассоциированный с ObjectId целевого объекта. Менеджер исполнителя - это предоставляемый приложением объект CORBA, который может осуществить инкарнакцию или активизацию исполнителя и вернуть его адаптеру POA для продолжения обработки запроса. Поддерживаются две формы менеджера исполнителя: ServantActivator, используемый при RETAIN политике POA, и ServantLocator, применяемый с NON-RETAIN политикой.

Комбинирование вышеуказанных политик с политикой сохранения (retention policy) предоставляет очень гибкие возможности контроля регистрации и назначения исполнителя в рамках процесса сервера.
4.4.4 Политика неявной активации (Implicit Activation Policy)
   При наличии поддержки данной политики, исполнитель может быть активирован в POA неявным образом. Это очень полезно при регистрации исполнителей для временных (transient) объектов CORBA. К примеру, С++ сервер может создать исполнитель, и затем, путем установки его POA и вызова метода _this(), он регистрирует исполнитель и создает объектную ссылку на объект CORBA.
4.4.5 Политика уникальности ObjectId
   Данная политика позволяет приложению контролировать то, может ли исполнитель быть ассоциирован только с одним объектом CORBA, или он может обрабатывать запросы поступающие к множеству объектов CORBA. На сегодняшний день, уникальные исполнители применяются наиболее часто, так как большинство серверных CORBA приложений на C++ используют отдельный C++ объект для каждого объекта CORBA. Однако, и как это описано выше, в примере с базой данных, исполнители, представляющие несколько объектов CORBA также могут быть очень полезны, по причине того, что они уменьшают использование сервером оперативной памяти.
4.4.6 Политика долговечности (Lifespan Policy)
   Данная политика позволяет приложению указывать то, являются ли созданные внутри POA объекты CORBA временными (transient) или же они являются постоянными (persistent). Заметьте, что так как политики устанавливаются в момент создания POA, это решение относиться к типу "все или ничего" - адаптер POA либо поддерживает только временные объекты либо только постоянные, но ни как ни смесь этих двух типов.
4.4.7 Политика назначения ObjectId
Данная политика контролирует то, будут ли идентификаторы ObjectId назначаться POA либо их будет предоставлять приложение.
4.5 Оценка POA
   Как Вы можете видеть, новая спецификация POA определяет широкий диапазон политик, которые позволяют разработчикам "шить" поведение ORB таким образом, чтобы удовлетворить большому количеству разных вариантов использования (use-cases) приложения. Однако, тот факт, что поставщики ORB могут расширять доступные политики своими собственными - есть потенциальная проблема переносимости. Будем надеяться, что разработчики ORB продолжат поставлять информацию о новых, полезных политиках POA в группу специалистов OMG, осуществляющих ревизию усовершенствований, связанных с переносимостью (Portability Enhancement Revision Task Force), и таким образом свойство переносимости не исчезнет.
   На первый взгляд, гибкость, предоставляемая всеми этими политиками POA может показаться непомерной и сложной. К счастью, адаптер POA может быть также достаточно простым для использования в простых приложениях. В качестве примера, рассмотрим простое, доступное для понимания, и переносимое серверное приложение:

int main (int argc, char *argv[])      
{      
using namespace CORBA;      
using namespace PortableServer;      
// Initialize the ORB.      
ORB_var orb = ORB_init (argc, argv);      
// Obtain an object reference for      
// the Root POA.      
Object_var obj =      
orb->resolve_initial_references ("RootPOA");      
POA_var poa = POA::_narrow(obj);      
// Incarnate a servant.      
My_Servant_Impl servant;      
// Implicitly register the servant      
// with the RootPOA.      
obj = servant._this ();      
// Start the POA listening for requests.      
poa->the_POAManager ()->activate ();      
// Run the ORB's event loop.      
orb->run ();      
// ...      
}      

   В данном примере сначала осуществляется инициализация ORB, затем из него извлекается объектная ссылка на Корневой POA (Root POA). После этого приложение создает объект класса исполнителя, названный My_Servant_Impl и вызывает его метод _this(). Это неявным образом регистрирует исполнитель в корневом POA и генерирует для только что созданного объекта CORBA объектную ссылку. Новый объект CORBA является временным, так как корневому POA назначены умалчиваемые политики, установку которых требует спецификация по расширению переносимости (Portability Enhancement specification). Затем, для обеспечения возможности выполнять обработку запросов, активируется менеджер адаптера POA. Окончательно же, для того чтобы запустить основной цикл событий ORB, принимающий все запросы и выполняющий их перенаправление к My_Servant_Impl, осуществляется вызов операции ORB::run().

5. Заключительные Замечания

   В данной части, мы предоставили детальный обзор нового Переносимого Объектного Адаптера CORBA (POA). Привели и пояснили термины, использованные в спецификации POA, а также обсудили некоторые возможности POA. Вообще, POA является намного более переносимым и мощным чем BOA. Тем не менее, как это и показано чуть ранее в коротком примере, он может быть достаточно просто использован.
   В следующей части, мы возвратимся к нашей традиционной практике предоставления реальных примеров кода на С++, которые показывают как применять все вышеуказанные возможности. Например, мы детализируем наш простой POA пример, а также, для иллюстрации мощи и гибкости политик POA, покажем и другие примеры. Также мы обсудим ситуации в которых возможности и политики POA могут быть использованы с наибольшей эффективностью. В будущих частях, мы обсудим проблемы, связанные с реализацией POA, такие как минимизация накладных расходов при демультиплексировании запроса и добавление поддержки планирования (распределения) реального времени (real-time scheduling support).
   Благодарим Thorsten Albrecht, Jonathan Biggar, Laurent Chardonnens, Anton van Straaten, и Irfan Pyarali за их полезные комментарии к этой статье.

Список использованных источников
[1] R. G. Lavender and D. C. Schmidt, "Active Object: an Object Behavioral Pattern for Concurrent Programming," in Pattern Languages of Program Design (J. O. Coplien, J. Vlissides, and N. Kerth, eds.), Reading, MA: Addison-Wesley, 1996.
[2] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Pat-terns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
[3] IONA, "IONA's Object Database Framework Adapter (ODAF)." Available from http://www.usa.iona.com/Press/PR/odaf.html, 1997.
[4] T. H. Harrison, D. L. Levine, and D. C. Schmidt, "The Design and Performance of a Real-time CORBA Event Service," in Proceedings of OOPSLA '97, (Atlanta, GA), ACM, October 1997.
[5] A. Gokhale and D. C. Schmidt, "Evaluating the Performance of Demultiplexing Strategies for Real-time CORBA," in Pro-ceedings of GLOBECOM '97, (Phoenix, AZ), IEEE, November 1997.
[6] D. Schmidt and S. Vinoski, "Comparing Alternative Programming Techniques for Multi-threaded CORBA Servers: Thread-per-Object," C++ Report, vol. 8, July 1996.
[7] Object Management Group, ORB Portability Enhancement RFP, OMG Document 1995/95-06-26 ed., June 1995.
[8] Object Management Group, Specification of the Portable Object Adapter (POA), OMG Document orbos/97-05-15 ed., June 1997.
[9] D. Schmidt and S. Vinoski, "Distributed Callbacks and Decoupled Communication in CORBA," C++ Report, vol.8, October 1996.
[10] J. Klefndfenst, F. Plasfl, and P. Tuma, "Lessons Learned from Implementing the CORBA Persistence Object Servicesj," in Proceedings of OOPSLA '96, (San Jose, CA), ACM, October 1996.
[11] D. Schmidt and S. Vinoski, "Comparing Alternative Programming Techniques for Multi-threaded CORBA Servers: Thread Pool," C++ Report, vol. 8, April 1996.
[12] D. Schmidt and S. Vinoski, "Comparing Alternative Programming Techniques for Multi-threaded CORBA Servers: Thread-per-Request," C++ Report, vol. 8, February 1996.

Популярное

Не так давно в сети появился новый сервис, под названием Dead Man Zero. Этот сервис сделал...
Рынок социальных площадок уже давно стал стабильным. Несмотря на то, что время от времени...
Artisteer 4 – единственный в своем роде продукт, позволяющий автоматизировать работу над созданием...
Март 2017 (1)
Февраль 2017 (3)
Январь 2017 (1)
Август 2016 (1)
Май 2016 (2)
Ноябрь 2015 (1)

Карта сайта: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41

Друзья сайта

Хотите продать свой сайт?
- Мы быстро и удобно для Вас сможем его купить:
  • Заявка на продажу сайта
  • Раcсматриваем цены на каждый сайт в индивидуальном порядке.

    Случайная цитата

    Роберт Кийосаки:

    "Когда люди спрашивают: «С чего мне начинать?» или «Как быстро разбогатеть?», мой ответ их часто разочаровывает. Я просто говорю им то, что когда-то говорил мне богатый папа: «Если хочешь быть богатым, нужно быть финансово грамотным»."

    Опрос

    Как Вам новый дизайн сайта?

    Отлично
    Неплохо
    Нормальный
    Ужасно