ОТДАЛЕЧЕНИ ОБЕКТИ

Въведение

Разпределени системи - необходимост от комуникация между изчислителни процеси работещи в различни адресни пространства, потенционално на различни машини.

Основната идея е създаването на фамилия от обекти които могат да се намират върху различни машини и които могат да комуникират помежду си чрез стандартни мрежови протоколи.

От гледна точка на езика за реализацията съществуват две възможности за фамилията обекти:

Втората възможност изисква обектите да могат да комуникират независимо от езика на създаването им. Стандартът CORBA (Common Object Request Broker Architecture) приет от OMG (Object Management Group, www.omg.org) дефинира общ механизъм позволяващ обмена на информация. Трябва да се спомене, че Microsoft  поддържа протокола  COM като конкурентен на CORBA, но акцента му е насочен към протокола .NET за комуникация между програмни компоненти.

В CORBA комуникационните услуги са делегирани на ORB (Object Request Broker), които са универсални преводачи.  ORB комуникират с мрежови протоколи и следват установените от OMG спецификации за протокола IIOP (Internet Inter - ORB protocol).

CORBA е напълно независима от използваните програмни езици. Обектите написани на  C++, Java, ... трябва да преминат през език на дефиниране на интерфейси  (IDL - Interface Definition Language) за да специфицира подписите на съобщенията и типовете на данните, които обектите могат да си разменят. Едно от предимствата на този модел е, че спецификации IDL могат да се създават и за стари обекти създадени много преди CORBA след което да се използват ORB.

Като недостатък на подхода трябва да се отбележи, че понастоящем подхода има проблеми със скоростта и сложността на създаваните приложения.  

Ако всички комуникиращи си обекти са създадени на Java сложността на и универсалността на CORBA не са необходими. Sun е разработил един по-прост и ефикасен механизъм наречен RMI (Remote Method Invocation) специално за поддържане на разпределени Java обекти.

RMI - Remote Method Invocation System

Дефиниции

 Отдалечен обект (Remote object) - обект, методите на който могат да бъдат извикани от друга Java машина - потенциално на друг компютър. Обект от този тип се задава чрез един или повече отдалечени интерфейси, които представляват Java интерфейси, деклариращи отдалечените методи.

Отдалечено извикване на методи (Remote method invocation - RMI) - извикването от друга Java машина на метод от отдалечения обект, дефиниран в отдалечения интерфейс. Извикването става по същия начин както в локалната Java машина.

Основни цели

 Реализираната на Java RMI система разчита на хомогенната среда на Java виртуалната машина (JVM) и ползва предимствата на платформата на Java обектите.  Основните цели на поддръжката на разпределени обекти в Java са:

Създаденият модел е естествен (следва идеологията на  Java ) и лесен за използване

Двата модела си приличат по:

Сравнение на разпределения и локалния Java модел

Двата модела си приличат по:

Двата модела се различават по:

 

Извикване на отдалечен метод

RMI приложенията представляват две отделни програми - сървър и клиент.

Сървър - създава отдалечени обекти, прави псевдонимите(reference) им достъпни и чака извикване от страна на клиента.

Клиент - взима псевдонимите на един или повече отдалечени обекти и след това извиква методи върху тях

RMI осигурява механизъм за комуникация между сървъра и клиента.

Разпределеното приложение трябва да може да:



RMI използва стандартен механизъм за комуникация между отдалечените обекти: stubs и skeletons. Обектът stub представлява локалния представител при клиента - заместник (proxy) на отдалечения обект. В RMI,  stub на отдалечен обект притежава същия интерфейс като този на отдалечения обект.

Когато се извиква отдалечен метод, извикването активира метод в обекта stub, който

Предназначението на stub е да скрие от потребителя сериализацията и десериализацията на обектите, и прехвърлянето на информацията на мрежово ниво, с оглед запазване простотата на извикването.

В отдалечената JVM, всеки отдалечен обект има съответстващ skeleton (ако всички виртуални машини работят в Java 2  среда, skeletons не са необходими). Обектът skeleton прехвърля заявката към актуалният отдалечен метод. При получаването на отдалечена заявка той:

Кодирането на параметри и резултати се нарича marshalling  и представлява представянето на обекти във формат подходящ за предаване между различни програми или различни Java виртуални машини. Механизмът в Java много прилича на serialization, но за разлика от него включва и codebase на обекта.

Да се "marshal" един обект означава да се съхрани неговото състояние и codebase(s) по такъв начин, че когато той се декодира ( "unmarshall"), се получава копие на оригиналния обект, с възможно автоматично зареждане нa дефиницията на класа му. В
Java под codebase се разбира списък от URLs от където може да се свали дефиницията на класа.

В JVM на Java 2 са интегрирани допълнителни stub протоколи за да се избегне нуждата от  skeleton.

stub - при клиента

автоматично зареждане - използва се стандартен WEB сървър за зареждането на  stub.

Етапи при създаването на RMI система:

1. Дефиниране на отдалечените интерфейси (интерфейсите на отдалечените обекти).
2. Разработване на отдалечените класове (класове - сървъри), които наследяват отдалечените интерфейси.
3. Разработка на програмата сървър генерираща отдалечените обекти.
4. Разработка на програмата - клиент.

5. Компилиране на класовете.
6. Генериране на stubs и евентуално skeletons.
7. Стартиране на RMI registry.
8. Стартиране на отдалечените обекти.
9. Стартиране на клиента.

Пример:

Нека отдалечения обект да предлага функции за:

1. Сумиране на две цели числа
2. Намиране на датата на сървъра

I.Локална разработка на клиента и сървъра

1. Отдалечен интерфейс

Клиентът трябва да има информация за услугите предлагани от от отдалечения обект. Това се реализира чрез интерфейс, който трябва да се намира при сървъра и при клиента:

/* SampleServer.java */
import java.rmi.*;

public interface SampleServer extends Remote { public int sum(int a,int b) throws RemoteException;
public java.util.Date getDate() throws RemoteException;
}

 

Клиентът достига до обекта на сървъра чрез локален stub, който наследява отдалечения интерфейс:

SampleServer remoteObject = ... //достигането до локалния stub е обяснено по-късно
System.out.println(" 1 + 2 = " + remoteObject.sum(1,2) );
Date d = remoteObject.getDate();

2.Отдалечен обект

Това е обектът на ниво сървър, който наследява отдалечения интерфейс и реализира отдалечените методи:

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class SampleServerImpl extends UnicastRemoteObject implements SampleServer{
private static final long serialVersionUID = 1L;
public SampleServerImpl(String name) throws RemoteException {
super();
try {
Naming.rebind(name, this);
}
catch(Exception e){System.out.println("Exception occurred: " + e);}
}
public java.util.Date getDate() throws RemoteException{
return new java.util.Date();
}
public int sum(int a,int b) throws RemoteException{
return a+b;
}
}

    Всички класове на сървъра трябва да наследяват класа RemoteServer от пакета java.rmi.server, но той е абстрактен клас, който дефинира само най-важните механизми за комуникация между обекта и съответният му отдалечен stub. Класът UnicastRemoteObject наследява абстрактният клас RemoteServer  и може да бъде използван без писането на допълнителен код. На фигурата е показана диаграмата на  връзките между тези класове.

Всеки обект UnicastRemoteObject е локализиран върху сървъра и трябва да бъде активен и на разположение през протокола TCP/IP, когато услугата е поискана. Това е единствения клас в текущата версия на rmi.

За в бъдеще се говори за други класове като например MulticastRemoteObject за обекти дублирани в повече сървъри.

Конструкторът на класа UnicastRemoteObject (извикан в случая с метода  super() ) не само създава обект, но и го "изнася" (export) - прави го достъпен за отдалечено извикване. За достъп до обекта се използва порт, който се определя при изпълнението (anonymous port).  Това позволява обектът да бъде поддържан  и да не бъде събран от боклукосъбирача.

Ако класът вече наследява друг клас всеки обект и не може да бъде производен на UnicastRemoteObject, след създаването си трябва да бъде "изнесен" чрез статичния метод exportObject():

Remote server = new MyServer();
UnicastRemoteObject.exportObject(server,port);              //  при създаването на обекта 
server             

или:    UnicastRemoteObject.exportObject(this,port);        // в конструктора на server


3. Генериране на stub - тази стъпка не е необходима, ако версията при клиента е 1.5 или по-висока

За отдалечения клас SampleServerImpl трябва да бъде генериран stub. За целта има средство (сред компилиране на класа):

rmic -v1.2 SampleServerImpl

или

rmic -v1.1 SampleServerImpl

Във версия 1.5 опцията v1.2 е подразбираща се - тя генерира само stub без skeleton.

====================================== 

    Класът Naming

Класът Naming наследява java.lang.Object и осигурява методи за съхраняване и получаване на псевдоними на обекти  в регистъра на отдалечените обекти. Всеки метод от класа има като аргумент  "name" от тип java.lang.String в URL формат във вида 

    //host:port/name

Където host е машината, където се намира регистърът, port е номерът на порта, на който регистърът слуша мрежата, и name е обикновен обект от java.lang.String, който не се интерпретира от регистъра. Той задава името на обекта с който той се закачва.   Частта host:port може да се пропусне. Ако се пропусне host, се подразбира локалната машина, а ако се пропусне port, се използва подразбиращият се 1099.

Регистрирането (Binding) на името на отдалечен обект позволява под това име да бъде намерен обекта. След регистрацията клиентските програми могат да намират отдалечения обект, да получат ппсевдонима му и да извикват отдалечените му методи (наследяващите интерфейса Remote). Един регистър може да бъде споделен от всички сървъри на машината. Възможно е също така всеки сървър да създава и използва собствен регистър ( за повече подробности виж java.rmi.registry.LocateRegistry.createRegistry() ).

Класът притежава 5 метода (където name може да бъде зададено директно за локаланат машина или във формата  //host:port/name ): 

bind(String name, Remote obj)     // Регистрира обекта с зададеното име; само на локалната машина;
                                                 //  name - test, //:1099/test

list(String name)                          // Връща масив от регистрираните имена
                                                 //  list("") - всички регистрирани на локалната машина
                                                 //  list("rmi://192.168.43.68/") - всички регистрирани на адрес
192.168.43.68, подразбиращ се порт
                                                 //  list("rmi://192.168.43.68:1099/") -
всички регистрирани на адрес 192.168.43.68, порт 1099
                                                 //  list("rmi://192.168.43.68/test") - обект с име test , регистриран на адрес 192.168.43.68 ;
                                                 // 
 list("rmi://192.168.43.68:1099/test") - обект с име test , регистриран на адрес 192.168.43.68, порт 1099 ;

lookup(String name)                    // Връща stub на отдалечения обект асоцииран с името
                                                 //  
lookup("rmi://192.168.43.68/name") - за обект с име name, регистриран на адрес 192.168.43.68

rebind(String name, Remote obj)  // Пререгистрира името на друг обект; само на локалната машина;

unbind(String name)                    // Премахва името от регистъра;  само на локалната машина;

 ================================

4. Получаване на stub от клиента

За локализиране на отдалечения обект върху сървъра библиотеката предлага средство, което се нарича bootstrap registry service. Всеки клас - сървър може да регистрира обекти с тази услуга:

Naming.rebind(name, this);

 Името трябва да бъде уникално за да могат да се различават.

Клиентът може да провери дали в регистрите на даден сървър има закачени обект с

String[] bindings = Naming.list(url);  // ако url е "" се работи с локалната машина на подразбиращия се порт

и да получи stub за  чрез

SampleServer remoteObject = (SampleServer)Naming.lookup(url);

URL rmi започват с rmi:// следвани от сървъра и евентуално номер на порта и името на обекта:

rmi://host_server:1099/object_name;

Подразбиращият се порт за rmi е 1099.

От съображения за сигурност едно приложение може да свързва, отвързва и променя обекти в регистрите само на локалната машина. Отдалечените клиенти обаче могат да търсят в регистрите.


5. Генериране на отдалечените обекти

import java.rmi.*;
class SampleServerStart{
public static void main(String args[]) {
try{

//create a local instance of the object
new SampleServerImpl("SAMPLE-SERVER");

System.out.println("Server waiting.....");
}
catch (RemoteException re) {
System.out.println("Remote exception: " + re.toString()); }
}
}

Програмата генерира един обект и го "записва" в регистрите под името"SAMPLE-SERVER".

В документацията на rmi  се препоръчва инсталирането на  RMISecurityManager, който да задава правата на сървъра. В случая, когато сървъра няма да се използва и като клиент (или ако не се налагат специални ограничения) това не е необходимо и излишно усложнява пускането на системата.

6. Стартиране на сървъра

Преди стартирането на сървъра трябва да се стартира услугата bootstrap registry service:

rmiregistry &                                         под Unix

start rmiregistry                                     под Windows

След което може да се стартира сървъра:

java SampleServerStart &                    Unix

start java SampleServerStart                Windows

Забележка: не е добра идея да се използва javaw под Windows вместо start, защото Windows  не управлява добре един процес javaw  - не го показва в списъка на процесите, не извежда съобщенията за грешки.

7. Проверка дали обекта е регистриран

import java.rmi.*;

public class ShowBindings{
public static void main (String args[]){
try{
String[] bindings = Naming.list("");
for (int i=0; i< bindings.length; i++)
System.out.println(bindings[i]);
}
catch(Exception e){
e.printStackTrace();
}
}
}

резултат:

//:1099/SAMPLE-SERVER

8. Програмата клиент

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

 System.setSecurityManager(new RMISecurityManager());

Ако всички класове включително класовете stub са локални, няма нужда от управление на сигурността, но когато stub  трябва да се получи по мрежата е необходимо да се създаде файл за управление на сигурността - в нашия случай - client.policy и да се свърже с java.security.policy:

 System.setProperty("java.security.policy","client.policy");

Политиката на управление може да се установи и при стартирането на клиента:

java -Djava.security.policy=client.policy SampleClient
 

файлът client.policy трябва да разрешава ползването на сокети на портовете над 1023:

grant
{
permission java.net.SocketPermission "*:1024-65535", "connect";
};

клиентът:

import java.rmi.*;
import java.util.Date;
public class SampleClient
{
public static void main(String[] args)
{
// set the security manager for the client
System.setProperty("java.security.policy","client.policy");
System.setSecurityManager(new SecurityManager());
if (args.length != 1){
System.out.println("usage: java SampleClient <IP address of host running RMI server>");
System.exit(0);
}
//get the remote object from the registry
try {
System.out.println("Security Manager loaded");
String url = "rmi://"+args[0] +"/SAMPLE-SERVER";
System.out.println("url:"+url);
SampleServer remoteObject = (SampleServer)Naming.lookup(url);
System.out.println("Got remote object");
System.out.println(" 1 + 2 = " + remoteObject.sum(1,2) );
Date d = remoteObject.getDate();
System.out.println("Date on server is " + d);
}
catch (RemoteException exc) {
System.out.println("Error in lookup: " + exc.toString()); }
catch (java.net.MalformedURLException exc) {
System.out.println("Malformed URL: " + exc.toString()); }
catch (java.rmi.NotBoundException exc) {
System.out.println("NotBound: " + exc.toString()); }
}
}

При стартирането на клиента трябва да се получи:

java SampleClient 127.0.0.1


Security Manager loaded
url:rmi://127.0.0.1/SAMPLE-SERVER
Got remote object
1 + 2 = 3
Date on server is Fri Dec 16 09:54:45 EET 2005