9. JAVA RMI
9.1. Giriş
sockets adlı iletişim araçlarını kullanarak ağ uygulamalarının nasıl oluşturulacağını gördük. Bu araçlar üzerine kurulu bir istemci/sunucu uygulamasında, istemci ile sunucu arasındaki bağlantı, iletişim kurmak için benimsedikleri iletişim protokolüdür. Her iki uygulama da farklı dillerle yazılabilir: örneğin istemci için Java, sunucu için Perl veya başka herhangi bir kombinasyon. İkisi de, her ikisinin de bildiği bir iletişim protokolüyle birbirine bağlı iki ayrı uygulamadır. Ayrıca, soketler aracılığıyla ağa erişim bir Java uygulaması için şeffaf değildir: uygulama, sockets adlı iletişim araçlarını yönetmek için özel olarak oluşturulmuş Socket sınıfını kullanmalıdır.
JAVA ve RMI (Remote Method Invocation), aşağıdaki özelliklere sahip ağ uygulamaları oluşturulmasına olanak tanır:
- İstemci/sunucu uygulamaları, iletişimin her iki ucunda da Java uygulamalarıdır
- İstemci, sunucuda bulunan nesneleri sanki yerelmiş gibi kullanabilir
- Ağ katmanı şeffaf hale gelir: uygulamalar, bilgilerin bir noktadan diğerine nasıl aktarıldığı konusunda endişelenmek zorunda kalmaz.
Son nokta, taşınabilirlik açısından önemli bir faktördür: Bir RMI uygulamasının ağ katmanı değişse bile, uygulamanın kendisi yeniden yazılmak zorunda kalmaz. Yeni ağ katmanına uyarlanması gerekenler, Java dilindeki RMI sınıflarıdır.
RMI iletişiminin çalışma prensibi şöyledir:
- Geleneksel bir Java uygulaması A makinesinde yazılır. Bu uygulama sunucu rolünü üstlenecektir. Bunun için, uygulamanın çalıştığı A makinesinde bazı nesneleri “yayınlanacak” ve böylece hizmetlere dönüşecektir.
- B makinesinde klasik bir Java uygulaması yazılır. Bu uygulama istemci rolünü üstlenecektir. A makinesinde yayınlanan nesnelere/hizmetlere erişebilecek, yani uzaktan bir referans aracılığıyla bunları sanki yerelmiş gibi kullanabilecektir. Bunun için, erişmek istediği uzak nesnenin yapısını (yöntemler ve özellikler) bilmesi gerekecektir.
9.2. Örnekle öğrenelim
RMI arayüzünün temelindeki teori basit değildir. Konuyu daha net bir şekilde anlamak için, Java’nın RMI paketini kullanan bir istemci/sunucu uygulamasının yazımını adım adım takip edeceğiz. RMI ile ilgili birçok kitapta yer alan bir uygulamayı ele alıyoruz: istemci, uzak bir nesnenin tek bir yöntemini çağırır ve nesne de ona bir karakter dizisi döndürür. Burada, buna hafif bir varyasyon sunuyoruz: sunucu, istemcinin gönderdiği veriyi aynen geri yansıtır. Bu kitapta daha önce, soketlere dayanan benzer bir uygulamayı ele almıştık.
9.2.1. Sunucu uygulaması
9.2.1.1. Adım 1: Nesne/sunucu arayüzü
Uzak bir nesne, java.rmi paketinde tanımlanan Remote arayüzünü uygulaması gereken bir sınıf örneğidir. Nesnenin uzaktan erişilebilir yöntemleri, Remote arayüzünden türetilen bir arayüzde bildirilen yöntemlerdir:
import java.rmi.*;
// uzak arayüz
public interface interEcho extends Remote{
public String echo(String msg) throws java.rmi.RemoteException;
}
Burada, interEcho arayüzü tanımlanarak, echo yöntemi uzaktan erişilebilir olarak belirtilmiştir. Bu yöntem, ağla ilgili tüm hataları içeren RemoteException sınıfından bir istisna oluşturabilir.
9.2.1.2. 2. Adım: Sunucu nesnesinin yazılması
Bir sonraki adımda, önceki uzaktan erişim arayüzünü uygulayan sınıf tanımlanır. Bu sınıf, uzaktan yöntem çağrılarına izin veren yöntemlere sahip olan UnicastRemoteObject sınıfından türetilmelidir.
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// uzaktan yankı işlevini uygulayan sınıf
public class srvEcho extends UnicastRemoteObject implements interEcho{
// yapıcı
public srvEcho() throws RemoteException{
super();
}// yapıcı sonu
// yankı işlevini gerçekleştiren yöntem
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// yankı sonu
}// sınıf sonu
Önceki sınıfta şunu buluyoruz:
- yankı yapan yöntem
- ana sınıfın oluşturucusunu çağırmaktan başka bir şey yapmayan bir oluşturucu. Bu, RemoteException türünde bir istisna oluşturabileceğini belirtmek için vardır.
Bu sınıfın bir örneğini main yöntemiyle oluşturacağız. Bir nesneye/hizmete dışarıdan erişilebilmesi için, bu nesnenin oluşturulması ve dışarıdan erişilebilir nesneler dizinine kaydedilmesi gerekir. Uzak bir nesneye erişmek isteyen bir istemci aslında şu şekilde işlem yapar:
- İstediği nesnenin bulunduğu makinedeki dizin hizmetine başvurur. Bu dizin hizmeti, müşterinin bilmesi gereken bir bağlantı noktasında çalışır (varsayılan olarak 1099). İstemci, dizin hizmetinden adını belirttiği bir nesne/hizmetin referansını ister. Eğer bu ad, dizindeki bir nesne/hizmetin adıysa, dizin hizmeti istemciye bir referans gönderir; istemci bu referans aracılığıyla uzak nesne/hizmetle iletişim kurabilir.
- Bu andan itibaren, istemci bu uzak nesneyi sanki yerelmiş gibi kullanabilir
Sunucumuza geri dönersek, srvEcho türünde bir nesne oluşturmalı ve bunu dışarıdan erişilebilir nesneler dizinine kaydetmeliyiz. Bu kayıt, Naming sınıfının rebind sınıf yöntemi ile yapılır:
ile
ad: uzak nesneye atanacak ad
obj: uzak nesne
Böylece srvEcho sınıfımız şu şekilde olur:
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// uzaktan yankıyı uygulayan sınıf
public class srvEcho extends UnicastRemoteObject implements interEcho{
// yapıcı
public srvEcho() throws RemoteException{
super();
}// yapıcı sonu
// yankı işlevini gerçekleştiren yöntem
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// yankı sonu
// hizmetin oluşturulması
public static void main (String arg[]){
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// ana işlev
}// sınıf sonu
Önceki programı okuduğumuzda, yankı hizmetini oluşturup kaydettikten hemen sonra programın duracağı izlenimi ediniriz. Durum böyle değildir. srvEcho sınıfı, UnicastRemoteObject sınıfından türetildiğinden, oluşturulan nesne sonsuza kadar çalışır: istemcilerin isteklerini, durumlara göre sistem tarafından seçilen anonim bir bağlantı noktasında dinler. Hizmetin oluşturulması asenkron gerçekleşir: örnekte, main yöntemi hizmeti oluşturur ve çalışmaya devam eder; "Echo sunucusu hazır" mesajını görüntüler.
9.2.1.3. 3. Adım: Sunucu uygulamasının derlenmesi
Şu aşamada sunucumuzu derleyebiliriz. interEcho arayüzüne ait interEcho.java dosyasını ve srvEcho sınıfına ait srvEcho.java dosyasını derliyoruz. Böylece, ilgili .class dosyalarını elde ediyoruz: interEcho.class ve srvEcho.class.
9.2.1.4. 4. Adım: İstemciyi yazma
Echo sunucusunun URL dosyasını parametre olarak alan ve
- klavyeden girilen bir satırı okur
- bunu yankı sunucusuna gönderir
- sunucunun gönderdiği yanıtı görüntüler
- 1. adıma geri döner ve girilen satır "son" olduğunda durur.
Böylece şu istemci elde edilir:
import java.rmi.*;
import java.io.*;
public class cltEcho {
public static void main(String arg[]){
// sözdizimi: cltEcho URLService
// argümanların doğrulanması
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// istemci-sunucu iletişimi
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
// klavye akışının açılması
in=new BufferedReader(new InputStreamReader(System.in));
// hizmetin konumlandırılması
serveur=(interEcho) Naming.lookup(urlService);
// yankı sunucusuna gönderilecek mesajların okunma döngüsü
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// sunucuya mesaj gönderme ve yanıt alma
reponse=serveur.echo(msg);
// izleme
System.out.println("Réponse serveur : " + reponse);
// sonraki mesaj
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// işlem tamamlandı
System.exit(0);
// hata yönetimi
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// main
}// sınıf
Bu istemcide, sunucudan bir referans isteyen komut dışında özel bir şey yoktur:
Hatırlayacağınız üzere, yankı hizmetimiz bulunduğu makinenin hizmet dizinine şu komutla kaydedilmişti:
Naming.rebind("srvEcho",serveurEcho);
Dolayısıyla istemci de kullanmak istediği sunucunun referansını almak için Naming sınıfının bir yöntemini kullanıyor. Kullanılan lookup yöntemi, parametre olarak istenen hizmetin URL’sini kabul eder. Bu URL, klasik bir URL biçimine sahiptir:
burada
rmi: isteğe bağlı - rmi protokolü
machine: yankı sunucusunun çalıştığı makinenin adı veya IP adresi - isteğe bağlı, varsayılan değer localhost.
port: bu makinedeki dizin hizmetinin dinleme bağlantı noktası - isteğe bağlı, varsayılan olarak 1099
nom_service: İstenen hizmetin kaydedildiği ad (örneğimizde srvEcho)
Alınan sonuç, interEcho uzaktan arayüzünün bir örneğidir. İstemci ile sunucunun aynı makinede olmadığını varsayarsak, cltEcho.java istemcisini derlerken, aynı dizinde, uzak arayüz interEcho'in derlenmesi sonucu ortaya çıkan interEcho.class dosyasının bulunması gerekir; aksi takdirde, bu arayüze atıfta bulunan satırlarda derleme hatası oluşur.
9.2.1.5. Adım 5: İstemci-sunucu uygulaması için gerekli .class dosyalarının oluşturulması
Sunucu tarafında ve istemci tarafında nelerin bulunduğunu daha iyi anlayabilmek için, sunucuyu echo\serveur dizinine, istemciyi ise echo\client dizinine yerleştireceğiz.
Sunucu dizini aşağıdaki kaynak dosyaları içerir:
E:\data\java\RMI\echo\serveur>dir *.java
INTERE~1 JAV 158 09/03/99 15:06 interEcho.java
SRVECH~1 JAV 759 09/03/99 15:07 srvEcho.java
Bu iki kaynak dosyanın derlenmesinden sonra, aşağıdaki .class dosyaları elde edilir:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
Müşterinin dizininde şu kaynak dosya bulunur:
ayrıca sunucunun derlenmesi sırasında oluşturulan interEcho.class dosyası da bulunmaktadır:
Kaynak dosyanın derlenmesinden sonra aşağıdaki .class dosyaları elde edilir:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
cltEcho istemcisini çalıştırmaya çalıştığımızda şu hata mesajı alınır:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Erreur : java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
srvEcho sunucusunu çalıştırmaya çalışıldığında şu hata mesajı görüntülenir:
E:\data\java\RMI\echo\serveur>j:\jdk12\bin\java srvEcho
Erreur java.rmi.StubNotFoundException: Stub class not found: srvEcho_Stub; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
Her iki durumda da Java sanal makinesi, srvEcho_stub sınıfını bulamadığını bildiriyor. Aslında, bu sınıftan daha önce hiç haberimiz yoktu. İstemcide, sunucunun konumu şu komutla belirlenmiştir:
Burada, urlservice, rmi://localhost/srvEcho dizesidir ve
Rmi: RMI protokolü
Localhost: sunucunun çalıştığı makine – burada istemcinin de bulunduğu makine. Sözdizimi normalde makine:bağlantı noktasıdır. Bağlantı noktası belirtilmezse, varsayılan olarak 1099 numaralı bağlantı noktası kullanılır. Bu bağlantı noktasında dinleme durumunda olan, sunucunun dizin hizmetidir.
srvEcho: İstenen belirli hizmetin adıdır
Derleme sırasında herhangi bir hata bildirilmedi. Sadece uzak arayüzdeki interEcho.class dosyasının mevcut olması gerekiyordu.
Çalıştırma sırasında, istenen hizmet srvEcho ise sanal makine srvEcho_stub.class dosyasının varlığını talep eder; genel olarak X hizmeti için X_stub.class dosyası gerekir. Bu dosya, istemcinin derlenmesi sırasında değil, yalnızca çalıştırılması sırasında gereklidir. Aynısı sunucu için de geçerlidir. Peki bu dosya nedir?
Sunucuda, uzaktaki nesnemiz/hizmetimiz olan srvEcho.class sınıfı bulunur. İstemci, bu sınıfa ihtiyaç duymasa da, onunla iletişim kurabilmek için bir tür görüntüsüne ihtiyaç duyar. Aslında istemci, isteklerini doğrudan uzak nesneye yöneltmez: isteklerini, kendisiyle aynı makinede bulunan yerel görüntüsü srvEcho_stub.class’e yöneltir. Bu yerel görüntü srvEcho_stub.class, bu kez sunucuda bulunan aynı türden bir görüntü (srvEcho_stub.class) ile iletişim kurar. Bu görüntü, sunucudaki .class dosyasından rmic adlı bir Java aracı kullanılarak oluşturulur. Windows'ta şu komut kullanılır:
komutu, srvEcho.class dosyasından iki adet .class dosyası oluşturacaktır:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~2 CLA 3 264 09/03/99 16:57 srvEcho_Stub.class
SRVECH~3 CLA 1 736 09/03/99 16:57 srvEcho_Skel.class
Burada, istemci ve sunucunun çalıştırma sırasında ihtiyaç duyduğu srvEcho_stub.class dosyası bulunmaktadır. Ayrıca, şu an için işlevi bilinmeyen bir srvEcho_Skel.class dosyası da bulunmaktadır. srvEcho_stub.class dosyasının bir kopyasını istemci ve sunucu dizinlerine kopyalıyoruz ve srvEcho_Skel.class dosyasını siliyoruz. Böylece elimizde şu dosyalar kalıyor:
sunucu tarafında:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
istemci tarafında:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
9.2.1.6. 6. Adım: Echo istemci-sunucu uygulamasını çalıştırma
İstemci-sunucu uygulamamızı çalıştırmaya hazırız. İlk etapta, istemci ve sunucu aynı makinede çalışacaktır. Öncelikle sunucu uygulamamızı başlatmalıyız. Hatırlayacağınız üzere bu uygulama:
- hizmeti oluşturur
- hizmeti, yankı sunucusunun çalıştığı makinenin hizmet dizinine kaydeder
Bu son adım için bir dizin hizmetinin mevcut olması gerekir. Bu hizmet şu komutla başlatılır:
rmiregistry, dizin hizmetidir. Burada, start komutuyla bir Windows DOS penceresinde arka planda başlatılır. Dizin etkin hale geldiğinde, yankı hizmetini oluşturabilir ve hizmet dizinine kaydedebiliriz. Burada da, start komutuyla arka planda başlatılır:
Echo sunucusu yeni bir DOS penceresinde çalışır ve istenildiği gibi şu çıktıyı gösterir:
Artık istemcimizi çalıştırıp test etmemiz gerekiyor:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
9.2.1.7. İki farklı makinede bulunan istemci ve sunucu
Önceki örnekte, istemci ve sunucu aynı makinedeydi. Şimdi bunları farklı makinelere yerleştiriyoruz:
- sunucu bir Windows makinesinde
- istemci bir Linux makinesinde
Sunucu, daha önce olduğu gibi Windows makinesinde başlatılır. Linux makinesine ise istemcinin .class dosyaları aktarılmıştır:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 9
drwxr-xr-x 2 serge admin 1024 Mar 10 10:02 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
İstemci başlatıldı:
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Erreur : java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling call header; nested exception is:
java.rmi.UnmarshalException: skeleton class not found but required for client version
Dolayısıyla bir hata var: Java sanal makinesi, görünüşe göre rmic yardımcı programı tarafından oluşturulmuş ancak şu ana kadar kullanılmamış olan srvEcho_skel.class dosyasını istiyor. Bu dosyayı yeniden oluşturup Linux makinesine de aktarıyoruz:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 10:17 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 10:17 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
Daha önce karşılaştığımız hatanın aynısı ortaya çıkıyor... Bunun üzerine biraz kafa yorup RMI ile ilgili belgeleri tekrar okuyoruz. Sonunda, belki de sunucunun kendisinin meşhur srvEcho_Skel.class dosyasına ihtiyacı olduğu sonucuna varıyoruz. Bunun üzerine Windows makinesinde, srvEcho_Stub.class ve srvEcho_Skel.class dosyalarının yanı sıra : dosyası da mevcutken sunucuyu yeniden başlattık
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~2 CLA 3 264 10/03/99 9:05 srvEcho_Stub.class
SRVECH~3 CLA 1 736 10/03/99 9:05 srvEcho_Skel.class
E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho
ardından Linux makinesinde istemciyi tekrar test ediyoruz ve bu sefer çalışıyor:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Bundan, sunucu tarafında srvEcho_Stub.class ve srvEcho_Skel.class adlı iki dosyanın mevcut olması gerektiği sonucuna varılır. İstemci tarafında ise şu ana kadar yalnızca srvEcho_Stub.class dosyası gerekli olmuştur. Bu dosya, istemci ve sunucu aynı Windows makinesindeyken vazgeçilmez olduğu ortaya çıkmıştı. Linux'ta ise denemek amacıyla bu dosyayı kaldırıyoruz...
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:28)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
İlginç bir hata ile karşı karşıyayız; bu hata, Java sanal makinesinin meşhur stub sınıfını yüklemeye çalıştığını, ancak bir “güvenlik yöneticisi” (security manager) bulunmadığı için başarısız olduğunu gösteriyor gibi görünüyor. Bu konuyla ilgili belgelerde bir şeyler gördüğümüzü hatırlıyoruz. Tekrar inceledik... ve sunucunun, sınıf yüklemesini talep eden istemcilere bu sınıfların güvenli olduğunu garanti eden bir güvenlik yöneticisi (security manager) oluşturup kurması gerektiğini öğrendik. Bu güvenlik yöneticisi olmadığında, sınıfların yüklenmesi imkansızdır. Bu mantıklı görünüyor: Linux istemcimiz, ihtiyaç duyduğu srvEcho_stub.class sınıfını sunucuya talep etti ve sunucu, herhangi bir güvenlik yöneticisinin kurulmadığını belirterek talebi reddetti. Bu nedenle, sunucudaki main işlevinin kodunu şu şekilde değiştiriyoruz:
// hizmetin oluşturulması
public static void main (String arg[]){
// güvenlik yöneticisinin kurulumu
System.setSecurityManager(new RMISecurityManager());
// hizmetin başlatılması ve kaydedilmesi
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// ana sayfa
Derleme işlemi gerçekleştirilir ve srvEcho_stub.class ile srvEcho_Skel.class dosyaları, rmic aracı kullanılarak oluşturulur. Dizin hizmetini (rmiregistry) ve ardından sunucuyu başlatıyoruz ve daha önce karşılaşmadığımız bir hata alıyoruz!
Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve) lors du lancement du serveur d’écho
Güvenlik yöneticisi fazla etkili olmuş gibi görünüyor. Belgeleri tekrar okuyoruz... Bir güvenlik yöneticisi etkin olduğunda, bir programı başlatırken onun izinlerini belirtmek gerektiğini fark ediyoruz. Bu, aşağıdaki seçenekle yapılır:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
burada
java.security.policy bir anahtar kelimedir
mypolicy, programın izinlerini tanımlayan bir metin dosyasıdır. Burada şu şekildedir:
Programın burada tüm hakları vardır.
Tekrar başlıyoruz. Sunucu dizinine girip sırasıyla şunları yapıyoruz:
- dizin hizmetini başlatın: start j:\jdk12\bin\rmiregistry
- sunucuyu başlatın: start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
Ve bu sefer, yankı sunucusu (istemci henüz değil) düzgün bir şekilde başlatılır. Şimdi şu deneyi yapabilirsiniz:
- yankı sunucusunu, ardından dizin hizmetini durdurun
- sunucunun bulunduğu dizinden farklı bir dizindeyken dizin hizmetini yeniden başlatın
- sunucunun bulunduğu dizine geri dönün yankı sunucusunu başlatın – şu hatayı alırsınız:
Erreur java.rmi.ServerException: RemoteException occurred in server thread; nes
ted exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
Buradan, dizin hizmetinin başlatıldığı dizinin önemli olduğu sonucuna varılabilir. Burada Java, srvEcho_stub.class sınıfını bulamadı çünkü dizin hizmeti sunucu dizininden başlatılmamıştı. Sunucuyu başlatırken, sunucu için gerekli sınıfların hangi dizinde bulunduğunu belirtebilirsiniz:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Komut tek bir satırda yazılır. java.rmi.server.codebase anahtar sözcüğü, sunucuya gerekli sınıfları içeren dizinin yolunu belirtmek için kullanılır. Burada, bu URL, yerel dosyalara erişim protokolü olan file protokolünü ve sunucudaki .class dosyalarını içeren dizini belirtir. Dolayısıyla şu şekilde ilerlersek:
- dizin hizmetini durdurma
- sunucunun dizininden farklı bir dizinden dizin hizmetini yeniden başlatın
- sunucunun dizininde, şu komutla (tek satırda) sunucuyu başlatın:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Sunucu bu şekilde başarıyla başlatılmıştır. Artık istemciye geçebiliriz. İstemciyi test edelim:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
Aynı hata mesajı, bir güvenlik yöneticisinin bulunmadığını bildiriyor. Belki de bir hata yaptığımızı ve güvenlik yöneticisini müşterinin kendisinin oluşturması gerektiğini düşünüyoruz. Sunucuyu kendi güvenlik yöneticisiyle bırakıyoruz, ancak müşteri için de bir tane oluşturuyoruz. cltEcho.java müşterisinin main işlevi şu şekilde oluyor:
public static void main(String arg[]){
// sözdizimi: cltEcho makine bağlantı noktası
// makine: yankı sunucusunun çalıştığı makine
// bağlantı noktası: yankı hizmeti makinesinde hizmet dizininin çalıştığı bağlantı noktası
// argümanların doğrulanması
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// bir güvenlik yöneticisinin kurulumu
System.setSecurityManager(new RMISecurityManager());
// istemci-sunucu iletişimi
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
....
} catch (Exception e){
....
}// dene
}// ana
Ardından şu şekilde devam edilir:
- cltEcho.java dosyasını yeniden derleriz
- .class dosyalarını Linux makinesine aktarırız
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
- istemciyi başlatıyoruz
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
java.io.FileNotFoundException: /e:/data/java/rmi/echo/serveur/srvEcho_Stub.class
at java.io.FileInputStream.<init>(FileInputStream.java)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:150)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:170)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:119)
at sun.applet.AppletClassLoader.findClass(AppletClassLoader.java:496)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:199)
at sun.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:159)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:97)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
File not found when looking for: srvEcho_Stub
Erreur : java.rmi.UnmarshalException: Return value class not found; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Görünüşe rağmen ilerleme kaydediyoruz: hata artık aynı değil. İstemcinin sunucudan srvEcho_Stub.class sınıfını talep edebildiği, ancak sunucunun bunu bulamadığı görülüyor. Dolayısıyla, istemcinin sunucudan sınıf talep edebilmesi için bir güvenlik yöneticisine sahip olması gerekiyor.
Önceki hataya bakarsak, srvEcho_Stub.class dosyasının e:/data/java/rmi/echo/sunucu/ dizininde arandığını ve bulunamadığını görüyoruz. Oysa dosya tam da orada bulunuyor. Hatada yer alan yöntemlerin listesine daha yakından bakarsak, şunu görürüz: sun.net.www.protocol.file.FileURLConnection.getInputStream. İstemci, FileURLConnection türünde bir nesneyle bir akış açmış gibi görünüyor. Bütün bunların, sunucumuzu başlatma şeklimizle bir ilgisi olduğunu düşünebiliriz:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Hata mesajı, java.rmi.server.codebase anahtar sözcüğünün değerine atıfta bulunuyor gibi görünüyor. Belgelere tekrar bakıldığında, verilen örneklerde bu anahtar kelimenin değerinin her zaman http://... ve c.a.d olduğu ve kullanılan protokolün http olduğu görülüyor. İstemcinin sunucudan sınıflarını nasıl talep ettiği ve aldığı net olarak anlaşılmıyor. Belki de sunucunun başlatılması sırasında belirtilen java.rmi.server.codebase, URL anahtar kelimelerinin URL değerini kullanarak bunları talep ediyordur. Bu nedenle, sunucuyu aşağıdaki yeni komutla başlatmaya karar veriyoruz:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Artık protokol http'dir. .class dosyalarını, sınıfların depolanacağı makinedeki http sunucusunun erişebileceği bir konuma taşımak zorundayız. Örneğimizde, sunucu Microsoft'un PWS HTTP sunucusuna sahip bir Windows makinesinde çalışıyor. Bu sunucunun kök dizini d:\Inetpub\wwwroot'tir. Bu nedenle şu şekilde ilerliyoruz:
- d:\Inetpub\wwwroot\rmi\echo dizinini oluşturuyoruz
- bu dizine sunucudaki .class dosyalarını ve mypolicy dosyasını yerleştirin
- Henüz yapılmadıysa web sunucusunu başlatın
- dizin hizmetini (rmiregistry) yeniden başlatın
- aşağıdaki komutla sunucuyu yeniden başlatın
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
- Linux makinesinde istemciyi başlatın:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1622 Mar 10 14:37 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Uff! Çalışıyor. İstemci, söz konusu srvEcho_Stub.class dosyasını başarıyla aldı.
Tüm bunlar bize bazı fikirler verdi ve sunucunun Windows makinesinde bulunan istemcinin de srvEcho_Stub.class dosyası olmadan çalışıp çalışmayacağını merak ediyoruz. İstemcinin dizinine geçip, varsa srvEcho_Stub.class dosyasını siliyoruz ve istemciyi Linux'ta olduğu gibi başlatıyoruz:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 622 10/03/99 14:12 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : nouveau message
Réponse serveur : [nouveau message]
Message : fin
9.2.1.8. Résumé
Windows sunucu tarafında:
- sunucuda bir güvenlik yöneticisi bulunmaktadır
- sunucu şu seçeneklerle başlatılmıştır: start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Linux veya Windows istemci tarafında
- istemcide bir güvenlik yöneticisi bulunur
- Linux'ta, java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho komutuyla başlatılmıştır
- Windows'ta ise j:\jdk12\bin\java -Djava.security.policy=mypolicy komutuyla başlatılmıştır. cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
9.2.1.9. Linux'ta yankı sunucusu, Windows ve Linux'ta istemciler
Şimdi sunucuyu bir Linux makinesine taşıyıp Linux ve Windows istemcilerini test ediyoruz. İzlenecek adımlar şunlardır:
- .class dosyalarını sunucudan Linux makinesine aktarıyoruz
shiva[serge]:/home/admin/serge/WWW/rmi/echo/serveur#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 16:15 .
drwxr-xr-x 3 serge admin 1024 Mar 10 16:09 ..
-rw-r--r-- 1 serge admin 256 Mar 10 16:09 interEcho.class
-rw-r--r-- 1 serge admin 1245 Mar 10 16:09 srvEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 16:09 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 16:09 srvEcho_Stub.class
- Müşteriler tarafından srvEcho_Stub.class sınıfı talep edileceğinden, sunucu sınıfları için seçilen dizin, Linux makinesindeki HTTP sunucusundan erişilebilir bir dizindir. Bu dizindeki URL, http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur adresindedir.
- dizin hizmeti arka planda başlatılır: /usr/local/bin/jdk/rmiregistry &
- sunucu arka planda başlatılır: /usr/local/bin/jdk/bin/java
-Djava.rmi.server.codebase=http://shiva.istia.univ-angers.fr/~serge/rmi/echo/sunucu/
srvEcho &
İstemcileri test edebiliriz. Önce Windows istemcisi.
- Windows makinesinde istemcinin bulunduğu dizine geçin
- Aşağıdaki komutla istemciyi başlatıyoruz:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://shiva.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : fin
Linux istemcisini test ediyoruz:
shiva[serge]:/home/admin/serge/java/rmi/echo/client#
$ java cltEcho srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Echo sunucusuyla aynı makinede çalışan Linux istemcisi için, istenen hizmetin URL'inde makine belirtilmesine gerek olmadığına dikkat edilmelidir.
9.3. İkinci örnek: Windows makinesinde çalışan SQL sunucusu
9.3.1. Sorun
JDBC bölümünde ilişkisel veritabanlarını nasıl yöneteceğimizi görmüştük. Sunulan örneklerde, uygulamalar ve kullanılan veritabanı aynı Windows makinesinde bulunuyordu. Burada, uzak istemcilerin sunucunun bulunduğu makinedeki ODBC genel veritabanlarını kullanabilmelerini sağlayacak bir RMI sunucusunu bir Windows makinesinde kurmayı hedefliyoruz.

RMI istemcisi 3 işlem gerçekleştirebilir:
- seçtiği veritabanına bağlanmak
- SQL istekleri gönderebilir
- bağlantıyı kapatmak
Sunucu, istemcinin SQL isteklerini yürütür ve sonuçları istemciye gönderir. Bu, sunucunun temel görevidir ve bu nedenle ona SQL sunucusu diyeceğiz.
Daha önce yankı sunucusu ile gördüğümüz farklı adımları uyguluyoruz.
9.3.2. Adım 1: Uzak arayüz
Uzak arayüz, RMI sunucusunun RMI istemcileri tarafından erişilebilecek yöntemlerini listeleyen arayüzdür. Aşağıdaki arayüzü kullanacağız:
import java.rmi.*;
// uzak arayüz
public interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp)
throws java.rmi.RemoteException;
public String[] executeSQL(String requete, String separateur)
throws java.rmi.RemoteException;
public String close()
throws java.rmi.RemoteException;
}
Farklı yöntemlerin rolleri şu şekildedir:
Connect: İstemci, pilote, url, JDBC, kimliği id ve şifresi mdp'i girer. Sunucu, bağlantının sonucunu belirten bir karakter dizisini geri döndürür:
executeSQL: İstemci, bağlı olduğu veritabanında SQL sorgusunun yürütülmesini talep eder. Geri gönderilen sonuçlarda alanları ayıracak karakteri belirtir. Sunucu bir dizi karakter dizisi gönderir:
veritabanı güncelleme isteği için; burada n, güncellenen satır sayısını ifade eder
istek bir hata oluşturduysa
istek herhangi bir sonuç üretmediyse
eğer sorgu sonuç vermişse. Sunucu tarafından bu şekilde döndürülen satırlar, sorgunun sonuç satırlarıdır.
close: İstemci, uzak veritabanıyla olan bağlantısını kapatır. Sunucu, bu kapatma işleminin sonucunu belirten bir dize döndürür:
9.3.3. Adım 2: Sunucu kodu
SQL sunucusunun Java kaynak kodu aşağıdadır. Bu kodu anlamak için, JDBC veritabanı yönetimi ve RMI sunucu yapısını kavramış olmanız gerekir. Programdaki açıklamalar, kodu anlamanızı kolaylaştıracaktır.
// ithal edilen paketler
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;
// srvSQL sınıfı
public class srvSQL extends UnicastRemoteObject implements interSQL{
// sınıfın genel verileri
private Connection DB;
// ------------- oluşturucu
public srvSQL() throws RemoteException{
super();
}
// --------------- bağlantı
public String connect(String pilote, String url, String id,
String mdp) throws RemoteException{
// sürücü aracılığıyla url tabanına bağlantı
// id ve mdp şifresi ile kimlik doğrulama
String resultat=null; // yöntemin sonucu
try{
// sürücünün yüklenmesi
Class.forName(pilote);
// bağlantı isteği
DB=DriverManager.getConnection(url,id,mdp);
// tamam
resultat="200 Connexion réussie";
} catch (Exception e){
// hata
resultat="500 Echec de la connexion (" + e + ")";
}
// son
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur)
throws RemoteException{
// SQL sorgusunu DB veritabanında yürütür
// ve sonuçları bir dize dizisine yerleştirir
// sorgunun yürütülmesi için gerekli veriler
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// sorgu konteynerinin oluşturulması
S=DB.createStatement();
// sorgunun yürütülmesi
if (! S.execute(requete)){
// güncelleme sorgusu
// güncellenen satır sayısı döndürülüyor
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// bu bir sorgu idi
// sonuçlar alınır
RS=S.getResultSet();
// Sonuç kümesindeki alan sayısı
int nbChamps=RS.getMetaData().getColumnCount();
// bunlar işlenir
while(RS.next()){
// sonuç satırının oluşturulması
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// sonuç vektörüne eklenir
resultats.addElement(ligne);
}// while
// sonuçların işlenmesi sona erdi
// kaynaklar serbest bırakılıyor
RS.close();
S.close();
// sonuçların döndürülmesi
int nbLignes=resultats.size();
if (nbLignes==0){
lignes=new String[1];
lignes[0]="501 Pas de résultats";
} else {
lignes=new String[resultats.size()];
for(int i=0;i<lignes.length;i++)
lignes[i]=(String) resultats.elementAt(i);
}//if
return lignes;
} catch (Exception e){
// hata
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- kapat
public String close() throws RemoteException {
// veritabanı bağlantısını kapatır
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// sonucu döndürür
return resultat;
}
// ----------- ana
public static void main (String[] args){
// güvenlik yöneticisi
System.setSecurityManager(new RMISecurityManager());
// hizmeti başlatır
srvSQL serveurSQL=null;
try{
// oluşturma
serveurSQL=new srvSQL();
// kayıt
Naming.rebind("srvSQL",serveurSQL);
// izleme
System.out.println("Serveur SQL prêt");
} catch (Exception e){
// hata
System.err.println("Erreur lors du lancement du serveur SQL ("+ e +")");
}// try-catch
}// main
}// sınıf
9.3.4. RMI istemcisinin yazılması
RMI sunucusunun istemcisi aşağıdaki parametrelerle çağrılır:
urlserviceAnnuaire: RMI sunucusunu kaydeden dizin hizmetinin URL'si
sürücü: SQL sunucusunun veritabanını yönetmek için kullanması gereken sürücü
urlBase: yönetilecek veritabanının JDBC URL'si
id: müşterinin kimliği veya kimlik yoksa null
mdp: müşterinin şifresi; şifre yoksa null
ayırıcı: SQL sunucusunun, bir sorgunun sonuç satırlarındaki alanları ayırmak için kullanması gereken karakter
İşte olası parametrelerin bir örneği:
burada:
SQL sunucusunun rmi adı
ODBC arayüzlü veritabanları için genel sürücü
Windows makinesindeki ODBC genel veritabanları listesinde bildirilen bir ürün veritabanını kullanmak için
kimlik yok
şifre yok
sonuç alanları virgülle ayrılacaktır
Yukarıdaki parametrelerle başlatıldıktan sonra, istemci aşağıdaki adımları izler:
- RMI srvSQL sunucusuna bağlanır; yani, istemciyle aynı makinede bulunan RMI sunucusuna
- ürün veritabanına bağlanmayı talep eder
- kullanıcıdan klavyeden bir SQL sorgusu girmesini ister
- bunu SQL sunucusuna gönderiyor
- sunucu tarafından gönderilen sonuçları ekrana görüntüler
- kullanıcıdan klavyeden bir sorgu SQL girmesini tekrar ister. Sorgu bittiğinde durur.
İstemcinin Java kodu aşağıdadır. Açıklamalar, kodu anlamak için yeterli olmalıdır.
import java.rmi.*;
import java.io.*;
public class cltSQL {
// sınıfın genel verileri
private static String syntaxe =
"syntaxe : cltSQL urlServiceAnnuaire pilote urlBase id mdp separateur";
private static BufferedReader in=null;
private static interSQL serveurSQL=null;
public static void main(String arg[]){
// sözdizimi: cltSQL urlServiceAnnuaire ayırıcı sürücü url kimlik şifre
// urlServiceAnnuaire: İletişim kurulacak hizmet dizininin URL'si RMI
// sürücü: kullanılacak veritabanı için sürücü
// urlBase : kullanılacak veritabanının JDBC URL'si
// id: kullanıcı kimliği
// mdp: şifresi
// ayırıcı: sorgu sonuçlarındaki alanları ayıran dize
// argüman sayısının doğrulanması
if(arg.length!=6)
erreur(syntaxe,1);
// veritabanı bağlantı parametrelerinin başlatılması
String urlService=arg[0];
String pilote=arg[1];
String urlBase=arg[2];
String id, mdp, separateur;
if(arg[3].equals("null")) id=""; else id=arg[3];
if(arg[4].equals("null")) mdp=""; else mdp=arg[4];
if(arg[5].equals("null")) separateur=" "; else separateur=arg[5];
// güvenlik yöneticisinin kurulumu
System.setSecurityManager(new RMISecurityManager());
// istemci-sunucu iletişimi
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// klavye akışının açılması
in=new BufferedReader(new InputStreamReader(System.in));
// izleme
System.out.println("--> Connexion au serveur RMI en cours...");
// hizmetin yerelleştirilmesi
serveurSQL=(interSQL) Naming.lookup(urlService);
// izleme
System.out.println("--> Connexion à la base de données en cours");
// veritabanına ilk bağlantı isteği
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// takip
System.out.println("<-- "+reponse);
// yanıt analizi
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// sunucuya gönderilecek isteklerin okuma döngüsü SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// sunucuya isteğin gönderilmesi ve yanıtın alınması
lignes=serveurSQL.executeSQL(requete,separateur);
// izleme
afficheLignes(lignes);
// sonraki istek
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// while
// takip
System.out.println("--> Fermeture de la connexion à la base de données distante");
// bağlantı sonlandırılıyor
reponse=serveurSQL.close();
// takip
System.out.println("<-- " + reponse);
// son
System.exit(0);
// hata yönetimi
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// deneme
}// ana
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ hata
private static void erreur(String msg, int exitCode){
// hata mesajı görüntüleniyor
System.err.println(msg);
// kaynakların serbest bırakılması (gerekirse)
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// çıkılıyor
System.exit(exitCode);
}// hata
}// sınıf
9.3.5. 3. Adım: .class dosyalarının oluşturulması
- sunucu derlenir
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac interSQL.java
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac srvSQL.java
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
- Stub ve Skel dosyaları oluşturulur
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\rmic srvSQL
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
SRVSQL~2 CLA 4 491 12/03/99 17:56 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 12/03/99 17:56 srvSQL_Skel.class
- interSQL.class, srvSQL_Stub.class, srvSQL_Skel.class dosyaları müşterinin dizinine aktarılır
E:\data\java\RMI\sql\client>dir
CLTSQL~1 JAV 3 486 11/03/99 11:39 cltSQL.java
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
- istemci derleniyor
E:\data\java\RMI\sql\client>j:\jdk12\bin\javac cltSQL.java
E:\data\java\RMI\sql\client>dir *.class
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
CLTSQL~1 CLA 2 839 12/03/99 18:00 cltSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
9.3.6. 4. Adım: Aynı Windows makinesinde sunucu ve istemci ile testler
- Dizin hizmeti, sunucu ve istemcinin bulunduğu dizin dışında bir dizinde başlatılır
- Aşağıdaki mypolicy dosyası, istemci ve sunucunun dizinlerine yerleştirilir
- sunucuyu başlatın
E:\data\java\RMI\sql\serveur>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/sql/serveur/ srvSQL
- istemci başlatılıyor
E:\data\java\RMI\sql\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltSQL srvSQL
sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur RMI en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Sorgu: select isim, stock_actu from articles order by stock_actu desc
<-- 101 vélo,31
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 panthère,11
<-- 101 léopard,11
<-- 101 cachalot,10
<-- 101 fusil,10
<-- 101 arc,10
--> Sorgu: update articles set stock_actu=stock_actu-1 where stock_actu<=11
<-- 100 5
--> Sorgu: select isim, stock_actu from articles order by stock_actu asc
<-- 101 cachalot,9
<-- 101 fusil,9
<-- 101 arc,9
<-- 101 panthère,10
<-- 101 léopard,10
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 vélo,31
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
9.3.7. Adım 5: Windows makinesinde sunucu ve Linux makinesinde istemci ile testler
- Gerekirse sunucu ve dizin hizmetini durdururuz
- İstemcinin .class dosyalarını bir Linux makinesine aktarırız
shiva[serge]:/home/admin/serge/java/rmi/sql/client#
$ dir *.class
-rw-r--r-- 1 serge admin 2839 Mar 11 14:37 cltSQL.class
-rw-r--r-- 1 serge admin 451 Mar 11 14:37 interSQL.class
- Sunucudaki dosyalar, Windows makinesindeki HTTP sunucusunun erişebileceği bir dizine yerleştirilir
D:\Inetpub\wwwroot\rmi\sql>dir
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 3 238 11/03/99 13:19 srvSQL.class
SRVSQL~2 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
MYPOLICY 81 08/06/98 15:01 mypolicy
- rehber hizmeti yeniden başlatılıyor
- sunucu, önceki testte kullanılanlardan farklı ayarlarla yeniden başlatılıyor
D:\Inetpub\wwwroot\rmi\sql>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/sql/ srvSQL
- Linux makinesinde istemci başlatılıyor
/usr/local/bin/jdk/bin/java cltSQL rmi://tahe.istia.univ-angers.fr/srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Sorgu: select isim,stock_actu,stock_mini from articles order by isim
<-- 101 arc,9,8
<-- 101 cachalot,9,6
<-- 101 canoé,13,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,10,7
<-- 101 panthère,10,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Sorgu: update articles set stock_actu=stock_mini where stock_mini<=7
<-- 100 4
--> Sorgu: select isim, stock_actu, stock_mini from articles, isim'e göre sırala
<-- 101 arc,9,8
<-- 101 cachalot,6,6
<-- 101 canoé,7,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,7,7
<-- 101 panthère,7,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
9.3.8. Sonuç
Bu uygulama, ağdaki herhangi bir bilgisayardan veritabanına erişim sağladığı için ilginçtir. Bu uygulamayı, soketler kullanarak geleneksel bir şekilde de yazabilirdik; nitekim veritabanları ile ilgili bölümdeki bir alıştırmada da bu istenmektedir. Bu uygulamayı geleneksel bir şekilde yazsaydık:
- farklı dillerle yazılabilecek bir istemci ve bir sunucu olurdu
- istemci ve sunucu, metin satırları alışverişi yoluyla iletişim kurar ve diyalogları şu şekilde olurdu:
burada ilk iki parametre sunucunun nerede bulunacağını, sonraki dört parametre ise kullanılacak veritabanına bağlanma ayarlarını belirtir
Sunucu ise şunun gibi bir yanıt verebilirdi:
Bu yanıt, sunucudan müşteriye bağlı veritabanında SQL sorgusunu çalıştırmasını ister. "separateur", yanıt satırlarındaki alanları ayıran karakterdir.
Sunucu, aşağıdaki gibi bir yanıt verebilir
veritabanını güncelleme isteği için; burada n, güncellenen satırların sayısıdır
istek bir hata oluşturduysa
eğer sorgu herhangi bir sonuç vermediyse
sorgu sonuç verdiğinde. Sunucu tarafından bu şekilde döndürülen satırlar, sorgunun sonuç satırlarıdır.
uzaktaki veritabanıyla olan bağlantıyı kapatmak için. Sunucu, bu kapatma işleminin sonucunu belirten bir dize gönderebilir:
Burada görüldüğü gibi, yukarıdaki türden bir protokol kullanarak geleneksel bir uygulama geliştirebiliyorsak, RMI sunucusunun olası bir yapısını da çıkarabiliriz. Protokolde, istemciden sunucuya şu tür bir ifade yer alıyorsa:
RMI sunucusunda ise şu tür bir yöntem bulunabilir:
adlı bir yöntem bulunabilir ve bu yöntem, istemci tarafından erişilebilir olması nedeniyle sunucunun yayınlanan arayüzünün bir parçası olmalıdır.
Son olarak şunu belirtelim: Sunucumuz şu anda yalnızca bir istemciyi yönetebiliyor; mevcut haliyle birden fazla istemciyi yönetemez. Nitekim, bir istemci B1 veritabanına bağlandığında, sunucu tarafından Connection DB=DB1 nesnesi oluşturulur. İkinci bir istemci B2 veritabanına bağlanmak isterse, sunucu bunu Connection DB=DB2 şeklinde kaydederek, ilk müşterinin B1 veritabanına olan bağlantısını keser.
9.4. Alıştırmalar
9.4.1. Alıştırma 1
Yukarıdaki SQL sunucusunu, birden fazla istemciyi yönetebilecek şekilde genişletin.
9.4.2. Alıştırma 2
JDBC bölümündeki alıştırmalarda sunulan e-ticaret Java uygulamasını, önceki alıştırmadaki RMI sunucusuyla çalışacak şekilde yazın.