Skip to content

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:

  1. İstemci/sunucu uygulamaları, iletişimin her iki ucunda da Java uygulamalarıdır
  2. İstemci, sunucuda bulunan nesneleri sanki yerelmiş gibi kullanabilir
  3. 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:

  1. 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.
  2. 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:

  1. yankı yapan yöntem
  2. 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:

  1. İ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.
  2. 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:

Naming.rebind(String nom, Remote obj)

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

  1. klavyeden girilen bir satırı okur
  2. bunu yankı sunucusuna gönderir
  3. sunucunun gönderdiği yanıtı görüntüler
  4. 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:

            serveur=(interEcho) Naming.lookup(urlService);

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:

    rmi://makine:bağlantı noktası/nom_service

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:

E:\data\java\RMI\echo\client>dir *.java

CLTECH~1 JAV         1 427  09/03/99  16:08 cltEcho.java

ayrıca sunucunun derlenmesi sırasında oluşturulan interEcho.class dosyası da bulunmaktadır:

E:\data\java\RMI\echo\client>dir *.class

INTERE~1 CLA           256  09/03/99  15:59 interEcho.class

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:

            serveur=(interEcho) Naming.lookup(urlService);                

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:

E:\data\java\RMI\echo\serveur>j:\jdk12\bin\rmic srvEcho

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:

start j:\jdk12\bin\rmiregistry

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:

E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho

Echo sunucusu yeni bir DOS penceresinde çalışır ve istenildiği gibi şu çıktıyı gösterir:

Serveur d’écho prêt

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:

grant {
    // Şimdilik her şeye izin ver
    permission java.security.AllPermission;
};

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.

Image

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:

    200 - Connexion réussie
    500 - Echec de la connexion

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:

    100 n

veritabanı güncelleme isteği için; burada n, güncellenen satır sayısını ifade eder

    500 msg d’erreur

istek bir hata oluşturduysa

    501 Pas de résultats

istek herhangi bir sonuç üretmediyse

    101 ligne1
    101 ligne2
    101 ...

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:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

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 pilote urlBase id mdp separateur

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:

    srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

burada:

urlserviceAnnuaire    srvSQL

SQL sunucusunun rmi adı

pilote     sun.jdbc.odbc.JdbcOdbcDriver

ODBC arayüzlü veritabanları için genel sürücü

urlBase    jdbc:odbc:articles

Windows makinesindeki ODBC genel veritabanları listesinde bildirilen bir ürün veritabanını kullanmak için

id    null

kimlik yok

mdp    null

şifre yok

separateur    , 

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
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • kullanıcıdan klavyeden bir SQL sorgusu girmesini ister
  • bunu SQL sunucusuna gönderiyor
executeSQL(requete, ’’,’’);
  • 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
F:\>start j:\jdk12\bin\rmiregistry
  • Aşağıdaki mypolicy dosyası, istemci ve sunucunun dizinlerine yerleştirilir
grant {
    // Şimdilik her şeye izin ver
    permission java.security.AllPermission;
};
  • 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
F:\>start j:\jdk12\bin\rmiregistry
  • 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:
client : connect machine port pilote urlBase id mdp

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:

200 - Connexion réussie
500 - Echec de la connexion
client : executeSQL requete, separateur

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

    100 n

veritabanını güncelleme isteği için; burada n, güncellenen satırların sayısıdır

    500 msg d’erreur

istek bir hata oluşturduysa

    501 Pas de résultats

eğer sorgu herhangi bir sonuç vermediyse

    101 ligne1
    101 ligne2
    101 ...

sorgu sonuç verdiğinde. Sunucu tarafından bu şekilde döndürülen satırlar, sorgunun sonuç satırlarıdır.

client : close

uzaktaki veritabanıyla olan bağlantıyı kapatmak için. Sunucu, bu kapatma işleminin sonucunu belirten bir dize gönderebilir:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

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:

    commande param1 param2 ... paramq

RMI sunucusunda ise şu tür bir yöntem bulunabilir:

    String commande(param1,param2,..., paramq)

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.