inhoudstafel en auteursrecht
* STER *
Een computerprogramma heet gedistribueerd als bij de normale werking instructies worden uitgevoerd op minstens twee verschillende machines, en gegevens moeten worden uitgewisseld tussen verschillende machines.
Bij Java-programma's kunnen we dit begrip nog uitbreiden door de term "machine" te lezen als "virtuele machine". In dat geval kan je zelfs een gedistribueerd programma bouwen en gebruiken op één geïsoleerde computer! Vanuit het standpunt van de programmeur gaat het hier echter over twee verschillende machines, zodat een speciale inspanning nodig is voor de onderlinge communicatie.
Een client/serversysteem is een bijzonder soort gedistribueerd informatiesysteem waarbij aan één programma of object een dienstverlenende rol wordt toegekend, terwijl andere programma's of objecten van die diensten gebruikmaken. Bij een peer-to-peersysteem vervullen de verschillende componenten gelijke rollen.
Het bekendste voorbeeld van een client/servertoepassing is het World Wide Web. De belangrijkste programma-componenten zijn enerzijds de browser, anderzijds de web-serversoftware.
In een bedrijfscontext zal het servergedeelte heel vaak bestaan uit een relationeel gegevensbeheerssysteem.
Kort antwoord: NOOIT, of toch zo weinig mogelijk. Gedistribueerd programmeren schept veel meer uitdagingen dan mogelijkheden. Een gedistribueerd programma is moeilijker te ontwerpen, bouwen, gebruiken en onderhouden dan een alleenstaand programma.
Natuurlijk zijn er enkele situaties waarin gedistribueerd programmeren een noodzakelijk kwaad vormt, anders zou niemand eraan beginnen. De twee meest voorkomende redenen waarom een informatiesysteem over verschillende machines verdeeld wordt, zijn:
De belangrijkste beslissing bij het ontwerp van een gedistribueerd informatiesysteem is precies de verdeling van de taken en gegevens over de verschillende componenten. Daarbij kan rekening worden gehouden met de volgende criteria (min of meer in dalende volgorde van belang):
Een typisch informatiesysteem heeft intern globaal een drielagige architectuur: opslag van gegevens, business logic, presentatie van gegevens.
De eenvoudigste client/serversystemen bestaan uit twee componenten (jawel: de client en de server!) Traditioneel worden vijf types onderscheiden, naargelang van de plaats waar de scheiding tussen client en server wordt getrokken:
Momenteel is er veel te doen rond drielagige systemen ("three-tier client/server systems"). Daarbij zijn drie machines betrokken, en de scheiding tussen lagen komt netjes overeen met de scheiding tussen machines.
Infrastructuur is de technologie die communicatie tussen programma-componenten toelaat. De belangrijkste infrastructuur-beslissingen waar de ontwerper van een gedistribueerd systeem mee geconfronteerd wordt, zijn: enerzijds de keuze van de communicatietechnologie, anderzijds de keuze van de middleware.
Communicatietechnologie is een combinatie van systemen en protocollen. Traditioneel worden communicatie-technieken onderverdeeld in (zeven of minder) functionele lagen, zoals bijvoorbeeld in het OSI-model. De alomtegenwoordigheid van Internet maakt dat Java speciale aandacht besteedt aan de protocollen uit die context, vooral het netwerkprotocol IP (Internét Protocol) en de transportprotocollen TCP (Transfer Control Protocol) en UDP (Unreliable Datagram Protocol). Voor deze tekst volstaat het te weten dat TCP communicatie via een verbinding tot stand brengt, en dus geschikt is voor dialogen, terwijl UDP verbindingsloos werkt, en zich dus leent voor afzonderlijke boodschappen.
Middleware is de software die de communicatie-aspecten van het systeem voor zich neemt. Dankzij de middleware krijgen de verschillende programma-componenten van het systeem de illusie dat ze zelfstandig draaiende programma's zijn. Men onderscheidt vijf grote soorten middleware:
De rest van dit hoofdstuk gaat over twee bijzondere soorten middleware die de Java-programmeur standaard ter beschikking staan: Remote Method Invocation en Java IDL. De eerste soort implementeert een object-georiënteerde versie van verre procedure-aanroep die geschikt is voor situaties waarin alle componenten van het gedistribueerde systeem in Java geschreven worden. Java IDL daarentegen geeft toegang tot een taal- en platform-onafhankelijke object request broker zoals gedefinieerd in de CORBA-standaard van OMG.
De CORBA-standaard hanteert de begrippen "client" en "server" in een heel algemene zin, die ook buiten client/serversystemen van toepassing is. Voor de rest van deze paragraaf hanteren we de volgende definities.
Server: een object dat aan de buitenwereld diensten aanbiedt via methoden en/of attributen;
Client: eender welke reeks programma-instructies (al dan niet object-georiënteerd) waarin methoden en/of attributen van servers gebruikt worden.
Het mag duidelijk zijn dat eenzelfde Java-object nu eens de rol van client, dan weer de rol van server kan aannemen. Daarbij hoeven circulaire relaties geen probleem te zijn, bijvoorbeeld twee verschillende objecten kunnen elkaars methoden aanroepen.
Een CORBA-server formuleert zijn uitzicht voor de buitenwereld
aan de hand van een interface. Zo'n CORBA-interface
lijkt veel op een interface in de programmeertaal
Java, met enkele subtiele verschillen. De belangrijkste gelijkenis
is dat een interface methoden kan bevatten, elk met een reeks
van nul of meer parameters en een eventueel terugkeertype.
CORBA-interfaces worden
geformuleerd in een aparte, eenvoudig computertaal, de zogenaamde
Interface Definition Language (IDL).
IDL is géén complete programmeertaal. Ze bevat bijvoorbeeld
geen if-opdracht, geen lussen, enzovoort. IDL definieert
uitsluitend object- en gegevenstypes.
Elke afzonderlijke programmeertaal waarin CORBA-componenten geïmplementeerd worden, dient dan te definiëren hoe de IDL-datastructuren naar de eigen grammatica worden vertaald. Een dergelijke language mapping bestaat reeds voor ondermeer de programmeertalen C, C++, Smalltalk, Ada, COBOL en natuurlijk ook Java.
De belangrijkste elementen van een IDL-bestand zijn de eigenlijke
interfaces, ingeleid door het sleutelwoord interface.
Een interface bestaat uit een opsomming, tussen een paar accoladen
en telkens gevolgd door een kommapunt, van nul of meer van de volgende
elementen:
In deze tekst beperken we ons tot interfaces die uitsluitend uit operaties bestaan. Operaties zijn ongeveer hetzelfde als de (abstracte) Java-methoden in een Java-interfacedeclaratie, met de volgende verschillen:
in,
out of inoutthrows Exceptiontype wordt vervangen
door raises ( Exceptiontype )
interface Vertaler {
string vertaal(in string tekst);
};
De volgende tabel beschrijft de correspondentie tussen sommige elementaire IDL-types (links) en hun Java-tegenhanger (rechts).
| CORBA-type | minimale lengte volgens CORBA | Java-type | precieze lengte in de JVM |
|---|---|---|---|
short |
2 bytes | short |
2 bytes |
long |
4 bytes | int |
4 bytes |
long long |
8 bytes | long |
8 bytes |
char |
8 bits | char |
16 bits |
string |
- | String |
- |
boolean |
- | boolean |
- |
octet |
8 bits | byte |
8 bits |
float |
4 bytes | float |
4 bytes |
double |
8 bytes | double |
8 bytes |
Een CORBA-client kan de methoden van een server-object aanroepen, maar moet daartoe eerst wel een referentie naar het object bekomen, een soort identiteit van het object. Het object kan bijvoorbeeld zijn meegedeeld als terugkeerwaarde van een vroegere methode-aanroep.
Natuurlijk is er hier een kip-eiprobleem: vóór de eerste methode-aanroep moet het clientprogramma een allereerste objectreferentie kunnen bekomen van een object dat zich op de andere machine bevat. Hiertoe zullen we de volgende twee mechanismen bespreken:
org.omg.CosNaming
CORBA is erg beperkt in de mogelijke types van parameters en terugkeerwaarden. Ofwel behoren deze tot een vaste standaardset van types (voornamelijk tekst en getallen): in dat geval wordt een kopie van de gegeven waarde van de ene machine naar de andere overgebracht. Ofwel zijn het CORBA-objecttypes: in dat geval wordt een objectreferentie van de ene machine naar de andere overgebracht.
We beschrijven nu een elementair voorbeeld van een gedistribueerde toepassing. De server bestaat uit een object met één methode die een vaste tekst teruggeeft. De client roept die methode aan en toont de ontvangen tekst op het scherm.
CORBA is in principe taal-onafhankelijk, dus de verschillende componenten kunnen in verschillende talen geschreven worden. In deze tekst zullen echter alle voorbeelden Java-programma's zijn, zowel aan de kant van de server als aan de client-zijde.
// bestand HelloCorba.idl:
interface HelloCorba {
string getMessage();
};
De Java Development Kit bevat een eigen IDL-compiler,
idlj, die
definities van IDL-interfaces omzet naar corresponderende
Java-klassen en -interfaces. Voer de vertaling van bovenstaande
interface uit met het commando
idlj -fall HelloCorba.idl
De IDL-naar-Java-compiler genereert nu automatisch niet minder dan zes Java-bestanden:
_HelloCorbaImplBase.java _HelloCorbaStub.java HelloCorba.java HelloCorbaHelper.java HelloCorbaHolder.java HelloCorbaOperations.java
Het bestand HelloCorba.java definieert de interface HelloCorba
waarvan we een implementatie gaan definiëren. In feite is dit een lege doos:
public interface HelloCorba
extends HelloCorbaOperations,
org.omg.CORBA.Object,
org.omg.CORBA.portable.IDLEntity
{
}
De eigenlijke methode getMessage() wordt namelijk gedeclareerd
in de superinterface HelloCorbaOperations, ondergebracht in het
bestand HelloCorbaOperations.java.
public interface HelloCorbaOperations
{
String getMessage ();
}
Voor de implementatie van deze interface moeten we natuurlijk zelf
één of meer objectklassen definiëren. Dat wordt
ons makkelijk gemaakt door de abstracte
klasse _HelloCorbaImplBase
in het bestand _HelloCorbaImplBase.java, die reeds
alle technische verplichtingen van CORBA-objecten op zich neemt.
Wij moeten uitsluitend een subklassen daarvan definiëren,
die de abstracte methode getMessage() concreet
implementeert. Creëer dus een bestand HelloCorbaObject.java
met de volgende inhoud:
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
public class HelloCorbaObject extends _HelloCorbaImplBase {
public String getMessage() {
return "Hello, CORBA !";
}
public static void main(String[] args) {
/* 1. Construeer een server-object */
HelloCorbaObject o = new HelloCorbaObject();
/* 2. Verkrijg een referentie naar de
Object Request Broker */
ORB orb = ORB.init(new String[0], null);
/* 3. Registreer het server-object bij
de Object Request Broker */
orb.connect(o);
try {
/* 4. Verkrijg een referentie naar de
Naming Service */
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
/* 5. Registreer het server-object bij
de Naming Service onder de naam
"Hello" */
NameComponent nc = new NameComponent("Hello", "");
NameComponent[] path = { nc };
ncRef.rebind(path, o);
/* 6. Wacht eeuwig op binnenkomende verzoeken */
java.lang.Object sync = new java.lang.Object();
System.out.println("Klaar om verzoekjes te ontvangen...");
synchronized(sync) {
sync.wait();
}
}
catch (org.omg.CORBA.ORBPackage.InvalidName exc) {
System.err.println("HelloCorbaObject:"
+ " Name Service niet gevonden.");
}
catch (org.omg.CosNaming.NamingContextPackage.NotFound exc) {
System.err.println("HelloCorbaObject:"
+ " Kon object niet registreren bij Name Service.");
System.err.println(exc);
}
catch (org.omg.CosNaming.NamingContextPackage.CannotProceed exc) {
System.err.println("HelloCorbaObject:"
+ " Kon object niet registreren bij Name Service.");
System.err.println(exc);
}
catch (org.omg.CosNaming.NamingContextPackage.InvalidName exc) {
System.err.println("HelloCorbaObject:"
+ " Kon object niet registreren bij Name Service.");
System.err.println(exc);
}
catch (InterruptedException exc) {
System.err.println("HelloCorbaObject:"
+ " Onderbroken door extern signaal.");
}
}
}
In de methode main wordt het server-object
geconstrueerd en geregistreerd, achtereenvolgens bij
de ORB en bij de Naming Service (met de methode
rebind). Aan de hand van de
Naming Service kunnen clients achteraf een referentie
naar het server-object bekomen.
Het client-programma kan er dan bijvoorbeeld als volgt uitzien.
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
public class HelloCorbaClient {
public static void main (String[] args) throws Exception {
/* 1. Verkrijg een referentie naar de
Object Request Broker */
ORB orb = ORB.init(new String[0], null);
/* 2. Verkrijg een referentie naar de
Naming Service */
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
/* 3. Gebruik de Naming Service om een
referentie te verkrijgen naar een
HelloCorba-object dat geregistreerd
is onder de naam "Hello" */
NameComponent nc = new NameComponent("Hello", "");
NameComponent path[] = { nc };
HelloCorba helloRef = HelloCorbaHelper.narrow(ncRef.resolve(path));
/* 4. Laat het HelloCorba-object zijn ding doen */
String message = helloRef.getMessage();
System.out.println(message);
}
}
We zullen bovenstaande programma's op eenzelfde machine in twee verschillende virtuele machines laten draaien. Bovendien moet de Name Service afzonderlijk worden opgestart. Open dus bijvoorbeeld op een Windows-computer drie DOS-vensters.
Start eerst de Transient Name Service op met het commando
tnameserve
Start vervolgens de main-methode van het serverprogramma (als een afzonderlijk programma):
java HelloCorbaObject
Vervolgens kan je als derde programma de client een of meer keren starten:
java HelloCorbaClient
In het bovenstaande "Hello, CORBA !" voorbeeld bekwam de client een
referentie naar het verafgelegen server-object door bemiddeling van
de Naming Service. CORBA-objectreferenties kunnen ook
rechtstreeks worden bekomen door ze om te zetten naar het
tussenformaat van een tekststring. Dit proces heet verstrenging
(Eng. stringification). Het maakt gebruik van de volgende
twee methoden van de klasse ORB:
public abstract String object_to_string(org.omg.CORBA.Object obj) public abstract org.omg.CORBA.Object string_to_object(String str)
We geven hieronder de aangepaste versies van de server- en
client-code voor het "Hello, CORBA !" voorbeeld. De werking
van het programma is overigens hetzelfde, met dien verstande
dat beide programma's wel op een of andere manier toegang moeten
hebben tot de locatie van het tekstbestand myfile.txt.
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import java.io.*;
public class HelloCorbaObjectStringified extends _HelloCorbaImplBase {
public String getMessage() {
return "Hello, CORBA !";
}
public static void main(String[] args) {
/* 1. Construeer een server-object */
HelloCorbaObjectStringified o = new HelloCorbaObjectStringified();
/* 2. Verkrijg een referentie naar de
Object Request Broker */
ORB orb = ORB.init(new String[0], null);
/* 3. Registreer het server-object bij
de Object Request Broker */
orb.connect(o);
try {
/* 4. Schrijf een verstrengde versie
van de objectreferentie naar het
tekstbestand "myfile.txt" */
PrintWriter outFile = new PrintWriter(
new FileWriter("myfile.txt"), true);
outFile.println(orb.object_to_string(o));
/* 5. Wacht eeuwig op binnenkomende verzoeken */
java.lang.Object sync = new java.lang.Object();
System.out.println("Klaar om verzoekjes te ontvangen...");
synchronized(sync) {
sync.wait();
}
}
catch (IOException exc) {
System.err.println("HelloCorbaObjectStringified: "
+ " Kon geen verstrengde objectreferentie schrijven"
+ " naar bestand myfile.txt");
}
catch (InterruptedException exc) {
System.err.println("HelloCorbaObject:"
+ " Onderbroken door extern signaal.");
}
}
}
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;
public class HelloCorbaClientStringified {
public static void main (String[] args) throws Exception {
/* 1. Verkrijg een referentie naar de
Object Request Broker */
ORB orb = ORB.init(new String[0], null);
try {
/* 2. Gebruik "ontstrenging"
referentie te verkrijgen naar een
HelloCorba-object */
BufferedReader inFile = new BufferedReader(
new FileReader("myfile.txt"));
HelloCorba helloRef = HelloCorbaHelper.narrow(
orb.string_to_object(inFile.readLine()));
/* 3. Laat het HelloCorba-object zijn ding doen */
String message = helloRef.getMessage();
System.out.println(message);
}
catch (IOException exc) {
System.err.println("HelloCorbaObjectStringified: "
+ " Kon geen verstrengde objectreferentie lezen"
+ " uit bestand myfile.txt");
}
}
}
Tijdens en na het server-programma bevat het bestand myfile.txt
de volgende tekstregel. We splitsen hem over verschillende regels omwille
van de zichtbaarheid is de browser, maar in werkelijkheid betreft het één
enkele regel.
IOR:000000000000001349444c3a48656c6c6f43 6f7262613a312e30000000000001000000000000 0054000101000000000e3231332e3232342e3132 2e323600049800000018afabcafe00000002f1de e766000000080000000000000000000000010000 0001000000140000000000010020000000000001 010000000000
In de meeste gedistribueerde toepassingen zal informatie worden verstuurd tussen clients en servers in beide richtingen. In IDL kan dit zowel door de parameters en terugkeerwaarde van een operatie, als door een object-attribuut. Zowel operaties als attributen worden echter in Java weergegeven door methoden.
Beschouwen we als voorbeeld een server-object dat in staat is de rekenkundige bewerkingen optellen en aftrekken van gehele getallen uit te voeren. Dit is een karikatuur van de meer realistische situatie waarbij intensief rekenwerk door één computer aan een andere wordt gedelegeerd.
De IDL-interface is als volgt gedefinieerd.
module rekenen {
interface EenvoudigeRekenaar {
long telOp(in long term1, in long term2);
long trekAf(in long term1, in long term2);
};
};
Modules zijn het middel om interfaces in een hiërarchische structuur te ordenen, ongeveer zoals pakketten in Java. Het belangrijkste verschil is dat een module ook andere modules kan bevatten, terwijl een Java-pakket nooit een ander Java-pakket bevat.
Bij de vertaling van IDL-modules in Java-pakketten
wordt de hiërarchische structuur weergegeven
door de puntjesnotatie. Zo zal een module datums
die deel uitmaakt van een grotere module astronomie
door de IDL-compiler in een pakket met de samengestelde naam
datums.astronomie vertaald worden.
De interface EenvoudigeRekenaar beschrijft het
uiterlijke gedrag van objecten die twee diensten
aanbieden: het optellen resp. aftrekken van twee
gehele getallen, met als terugkeerwaarde het resultaat
van de berekening. We herinneren eraan dat het
CORBA-type long overeenstemt met het
Java-type int, terwijl het CORBA-type
long long overeenstemt met het
Java-type long.
De volgende code definieert een klasse
EenvoudigeRekenServer die bovenstaande
IDL-interface realiseert. Ze bevat bovendien een
main-methode om één
dergelijk object te construeren en bij de ORB en
de Naming Service de registreren.
import rekenen.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
public class EenvoudigeRekenServer
extends _EenvoudigeRekenaarImplBase {
public int telOp(int term1, int term2) {
return term1 + term2;
}
public int trekAf(int term1, int term2) {
return term1 - term2;
}
public static void main(String[] args) throws Exception {
EenvoudigeRekenaar rek = new EenvoudigeRekenServer();
ORB orb = ORB.init(new String[0], null);
orb.connect(rek);
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
NameComponent nc = new NameComponent("DeRekenaar", "");
NameComponent[] path = { nc };
ncRef.rebind(path, rek);
java.lang.Object sync = new java.lang.Object();
synchronized(sync) {
sync.wait();
}
}
}
Aan de client-kant schrijven we twee korte Javaprogramma's, TelOp en TrekAf, die telkens het volgende doen:
DeRekenaar;We geven hier slechts de broncode voor de aftrekking, aangezien die van de optelling nagenoeg identiek is.
import rekenen.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
public class TrekAf {
public static void main(String[] args) {
try {
int getal1 = Integer.parseInt(args[0]);
int getal2 = Integer.parseInt(args[1]);
ORB orb = ORB.init(new String[0], null);
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
NameComponent nc = new NameComponent("DeRekenaar", "");
NameComponent[] path = { nc };
EenvoudigeRekenaar rek =
EenvoudigeRekenaarHelper.narrow(ncRef.resolve(path));
int resultaat = rek.trekAf(getal1, getal2);
System.out.println(getal1 + " - " + getal2
+ " = " + resultaat);
}
catch (ArrayIndexOutOfBoundsException e) {
System.err.println("Gelieve twee argumenten mee te geven");
}
catch (NumberFormatException e) {
System.err.println("De argumenten moeten gehele getallen zijn");
}
catch (Exception e) {
System.err.println(
"Algemene fout bij het contacteren van de rekenserver");
}
}
}
IDL laat toe gestructureerde gegevenstypes op te bouwen die
zijn samengesteld uit bestaande types. Daarbij kan de programmeur
gebruik maken van elementaire types, zoals char
en long, maar ook van
andere gestructureerde types. We kunnen dus een complete
hiërarchie van gegevenstypes opbouwen.
Gestructureerde gegevenstypes worden opgebouwd met het
sleutelwoord struct. Stel dat we bijvoorbeeld
een gegevenstype Klant willen opbouwen aan
de hand van een identificatienummer, een naam en een
woonplaats. In IDL ziet dat er als volgt uit.
struct Klant {
long nummer;
string naam;
string woonplaats;
};
Deze nieuwe, gestructureerde gegevenstypes kunnen vervolgens in verdere definities (bijvoorbeeld interfaces) gebruikt worden als type van attributen, parameters en terugkeerwaarden.
Beschouw de volgende IDL-module. Ze definieert de uitwendige interface van een printerwachtlijn: toevoegen van nieuwe printerjobs, of uitvoeren van de printerjob die zich het langst in de wachtlijn bevindt (first-in-first-out). Een printerjob wordt gekenmerkt door een naam, een hoofding en een tekst (die moet worden afgedrukt).
module afdrukken {
struct PrinterJob {
string naam;
string hoofding;
string tekst;
};
interface Wachtlijn {
void voegToe(in PrinterJob p);
void drukVolgende();
};
};
De parameter van de operatie voegToe in van het
gestructureerde type PrinterJob. Als een client
de methode voegToe van een server-object wil
aanroepen, moet ze de vereiste datastructuur als parameter
meegeven. Er zullen dan effectief drie tekststrings langs
het netwerk worden getransporteerd.
We zouden bovenstaande situatie ook kunnen uitwerken
door een PrinterJob als een volwaardig
CORBA-object te beschouwen:
module afdrukken {
interface PrinterJob {
string naam;
string hoofding;
string tekst;
};
interface Wachtlijn {
void voegToe(in PrinterJob p);
void drukVolgende();
};
};
Maar deze aanpak heeft twee belangrijke gevolgen:
voegToe zal in de tweede aanpak slechts een
referentie naar een object worden getransporteerd:
de drie strings bevinden zich alleen in het geheugen van
de machine waar het PrintJob-object gecreëerd is.
struct is volledig
gedefinieerd in de interface, en kan dus door alle
deelnemers (client en server) worden gecreëerd
en integraal over het netwerk getransporteerd worden.
We werken nu een voorbeeld uit van (een lichte variatie op)
de tweede aanpak. Bij het gebruiken van het voorbeeld zal
aanschouwelijk bewezen worden dat de
PrinterJob uitsluitend leeft in de virtuele
machine waarin ze gecreëerd wordt.
Het IDL-bestand ziet er als volgt uit.
module afdrukken {
interface PrintJob {
void wissen();
void toevoegen(in string tekst);
void afdrukken();
};
interface Printer {
void plaatsInWachtlijn(in PrintJob p);
void voerVolgendeJobUit();
PrintJob maakNieuweJob();
};
};
We implementeren de PrintJob-interface met de
klasse PrintJobServer:
import afdrukken.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
public class PrintJobServer
extends _PrintJobImplBase {
private StringBuffer sb = new StringBuffer("");
public void wissen() {
sb = new StringBuffer("");
}
public void toevoegen(String tekst) {
sb.append(tekst);
}
public void afdrukken() {
System.out.println(sb);
}
}
De klasse Lijndrukker implementeert de
CORBA-interface Printer. Haar main-methode
creëert tevens een serverwachtlijn waarvan de naam
gegeven wordt door het eerste argument van de opdrachtregel.
import afdrukken.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import java.util.*;
public class Lijndrukker
extends _PrinterImplBase {
public final static int CAPACITEIT = 10;
private LinkedList wachtlijn = new LinkedList();
public void plaatsInWachtlijn(PrintJob p) {
wachtlijn.addLast(p);
}
public void voerVolgendeJobUit() {
java.lang.Object o = wachtlijn.removeFirst();
if (o instanceof PrintJob) {
PrintJob p = (PrintJob) o;
p.afdrukken();
}
}
public PrintJob maakNieuweJob() {
return new PrintJobServer();
}
public static void main(String[] args) {
try {
Lijndrukker ld = new Lijndrukker();
ORB orb = ORB.init(new String[0], null);
orb.connect(ld);
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
NameComponent nc = new NameComponent(args[0], "Printer");
NameComponent[] path = { nc };
ncRef.rebind(path, ld);
java.lang.Object sync = new java.lang.Object();
synchronized(sync) {
sync.wait();
}
}
catch (org.omg.CORBA.ORBPackage.InvalidName e) {
System.err.println("Kan de Naming Service niet contacteren.");
}
catch (InvalidName e) {
System.err.println("Ongeldige naam.");
}
catch (NotFound e) {
System.err.println("Kan de Lijndrukker niet registreren"
+ " bij de Naming Service.");
}
catch (CannotProceed e) {
System.err.println("Kan de Lijndrukker niet registreren"
+ " bij de Naming Service.");
}
catch (InterruptedException e) {
System.err.println("Lijndrukkerservice onderbroken.");
}
}
}
Dankzij de methode maakNieuweJob fungeert een object
van de klasse Lijndrukker als een object factory.
We hoeven dus niet iedere PrintJob te registreren bij
de Naming Service.
Tenslotte hebben we een kort testprogramma nodig aan de kant van de client. Het volgende programma communiceert met evenveel servers als er opdrachtregel-parameters gegeven worden. Elke server krijgt de opdracht twee jobs uit te voeren. Er is evenwel een klein verschil tussen de twee job-objecten voor wat betreft hun creatie: het eerste object wordt gecreëerd op dezelfde server als waarop het wordt uitgevoerd; het tweede object wordt steeds op de eerste server gecreëerd.
import afdrukken.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
public class AfdrukTester {
public static void main(String[] args) throws Exception {
ORB orb = ORB.init(new String[0], null);
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
int aantal = args.length;
Printer[] p = new Printer[aantal];
for (int i = 0; i < aantal; i++) {
NameComponent nc = new NameComponent(args[i], "Printer");
NameComponent[] path = { nc };
p[i] = PrinterHelper.narrow(ncRef.resolve(path));
PrintJob pj = p[i].maakNieuweJob();
pj.toevoegen("Ik ben de " + (i+1) + "e printer"
+ " en mijn naam is " + args[i]);
p[i].plaatsInWachtlijn(pj);
pj = p[0].maakNieuweJob();
pj.toevoegen("Deze job is door de 1e printer"
+ " aangemaakt, en vervolgens naar de " + (i+1) + "e printer"
+ " ter uitvoering doorgestuurd.");
p[i].plaatsInWachtlijn(pj);
}
for (int i = 0; i < aantal; i++) {
p[i].voerVolgendeJobUit();
p[i].voerVolgendeJobUit();
}
}
}
We zullen dit testprogramma nu uitvoeren met twee printservers, nummer1 en nummer2 genaamd. Open dus vier DOS-vensters:
tnameserv);java Lijndrukker nummer1
java Lijndrukker nummer2
java AfdrukTester nummer1 nummer2
Kijk nu naar het tweede en het derde venster. Het blijkt dat de
printjobs niet worden uitgevoerd op de machine die de methode
afdrukken() aanroept, maar wel op de machine waar
de objecten gecreëerd zijn! De terugkeerwaarde van
maakNieuweJob() en de parameter van
plaatsInWachtlijn zijn slechts verwijzingen
naar jobs. De eigenlijke string-data worden niet tussen
virtuele machines onderling doorgegeven.
De CORBA Interface Definition Language, in al haar beperktheid als declaratieve computertaal, is toch in hoge mate object-georiënteerd. In het bijzonder kent zij ook de mogelijkheid, interfaces van elkaar te laten erven. En net zoals in Java kan één interface erven van verschillende andere interfaces (multiple inheritance).
Syntactisch worden de moederinterfaces van een interface opgesomd, gescheiden door komma's, na een dubbelepunt die volgt op de naam van de kind-interface.
Stel dat we de interface EenvoudigeRekenaar
(zie paragraaf "doorgeven van parameters" hierboven) willen
uitbreiden door de rekenmachine twee extra bewerkingen te
geven: vermenigvuldigen en delen. We doen dit best niet
door aan EenvoudigeRekenaar twee nieuwe abstracte
methoden toe te voegen. Interfaces can't grow.
Dan zouden namelijk alle bestaande servers ongeldige
implementaties vormen: ze plegen contractbreuk doordat
ze de nieuwe methoden niet implementeren.
In plaats daarvan definiëren we een nieuwe interface die een uitbreiding is van de bestaande.
module rekenen {
interface EenvoudigeRekenaar {
long telOp(in long term1, in long term2);
long trekAf(in long term1, in long term2);
};
interface ComplexeRekenaar : EenvoudigeRekenaar {
long long vermenigvuldig(in long term1, in long term2);
long deel(in long term1, in long term2);
};
};
De interface ComplexeRekenaar telt vier
abstracte methoden. Naast de twee uitdrukkelijk vermelde,
erft hij ook de bestaande methoden van EenvoudigeRekenaar
Tot dusver is er niet veel aan de hand. We kunnen gewoon een
serverimplementatie maken, ComplexeRekenServer,
die allevier de methoden van
ComplexeRekenaar realiseert.
Maar wat als we de implementatie van ComplexeRekenServer
willen laten erven van de bestaande serverimplementatie
EenvoudigeRekenServer ? We zijn tenslotte
object-georiënteerd aan het programmeren, dus we
willen codeduplicatie vermijden!
Het probleem is: de klasse ComplexeRekenServer
kan niet afgeleid worden van de klasse EenvoudigeRekenServer,
omdat ComplexeRekenServer reeds moet afgeleid
worden van de (automatisch gegenereerde) klasse
_ComplexeRekenaarImplBase. En Java-klassen
kennen geen meervoudige overerving.
public class ComplexeRekenServer
extends _ComplexeRekenaarImplBase { // en de andere klasse ???
...
}
We zullen dit oplossen door delegatie. Dat betekent
dat een ComplexeRekenServer geen
EenvoudigeRekenServer is,
maar er wél een bevat.
Hier is de volledige implementatie van de klasse
ComplexeRekenServer, inclusief een
main-methode om het serverobject
de construeren en te registeren. De client-code
laten we als een oefening voor de lezer.
import rekenen.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
public class ComplexeRekenServer
extends _ComplexeRekenaarImplBase {
/** Inwendige referentie naar een object waaraan we
het rekenwerk voor de bewerkingen telOp en trekAf
kunnen toevertrouwen. Dit hoeft geen CORBA-object
te zijn: een Java-object volstaat.
*/
private EenvoudigeRekenServer gedelegeerde = new EenvoudigeRekenServer();
public int telOp(int term1, int term2) {
// niet uitrekenen, maar delegeren
return gedelegeerde.telOp(term1, term2);
}
public int trekAf(int term1, int term2) {
// niet uitrekenen, maar delegeren
return gedelegeerde.trekAf(term1, term2);
}
public long vermenigvuldig(int factor1, int factor2) {
// dit moeten we zelf doen
return ((long) factor1) * ((long) factor2);
}
public int deel(int deeltal, int deler) {
// dit moeten we zelf doen
return deeltal / deler;
}
public static void main(String[] args) throws Exception {
ComplexeRekenaar rek = new ComplexeRekenServer();
ORB orb = ORB.init(new String[0], null);
orb.connect(rek);
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
NameComponent nc = new NameComponent("NogEenRekenaar", "");
NameComponent[] path = { nc };
ncRef.rebind(path, rek);
java.lang.Object sync = new java.lang.Object();
synchronized(sync) {
sync.wait();
}
}
}
In voorbereiding.