İçindekilerGirişİndex
YukarıİlkÖncekiSonrakiSon
Geriİleri
Yazdır
Mustafa Hadi Dilek
hadi@ulakbim.gov.tr

Java'da Ağ (Network) Programlama

Genellikle network programlama denilince Uluslararası Standart Organizasyonu (ISO)'nun açık sistemler için ortaya koyduğu iletişim katmanlarından oturum katmanı (session layer) üzerinde duran uygulamalar akla gelir.

İki bilgisayarın haberleşebilmesi için protokol denilen ortak kurallar kullanmaları gerekir. Burada üzerinde durulacak olan TCP/IP modeline göre bilgisayarları birbirlerinden ayırdetmek için IP adreslemesi kullanılır. Burada 4 Byte'lık (IP V.4) adresler sözkonusudur. Günlük yaşamda ise rakamlardan çok kelimeler daha anlamlı olduğu için bu adresler alan adı (domain name) denilen isimlerle eşlenmişlerdir.

Bilgisayarlar arası haberleşme aslında bilgisayarlar üzrinde çalışan programların birbiriyle haberleşmesi olarak düşünülmelidir. Böyle olunca IP adreslemesine TCP/IP modelinin sunduğu ikinci bir adresleme düzeyi olan portları da eklememiz gerekir. Böylece bir bilgisayar üzerinde iletişim kurulacak noktayı IP adresi ve portunun birleşimi ile gösterebiliriz. İşte bu bağlantı noktasına soket diyoruz.

Soketler, alt tarafı işletim sistemi tarafından soyutlanmış bağlantı noktaları olarak üst seviye uygulamalara sunulur ve bunlar üzerinden iletişim kurmak için input ve output streamları kullanılacak şekilde tasarlanmışlardır.

Basit Bir İstemci/Sunucu Programı

İstemci/sunucu programları yazarken iletişimin her iki ucunda da programlama işlemi gerekir. Burada bir zaman sunucusu ve istemcisi örneklenmiştir. Burada zaman sunucusu işlev olarak hizmet veren taraf olduğu için sunucu olarak nitelenmiştir. Aslında soketler bağlamında düşünülünce soket açıp karşı taraftan istem bekleyen tarafı ifade eder ki aslında pekçok internet sunucusu (örneğin ftp) istemci tarafta bu türden bir soket açılıp beklenmesini isteyebilmektedir. Bu açıdan istemci sunucu terimlerini kendi bağlamı içinde düşünmek daha doğru olacaktır.

Zaman sunucusunda (DateServer.java) önce 'java.net' paketi içindeki 'ServerSocket' sınıfından bir sunucu soketi nesnesi yaratılır. Sunucu soketinin özelliği makinanın kendi adresi ve atanan portundan oluşan bir yerel soket (local socket) açıp karşı taraftan bu sokete bağlantı kurulmasını beklemesidir. Bu bekleme (dinleme) sürecini ise bu nesnenin 'accept' metodu başlatır. Bu metod çağrılınca program karşı taraftan bağlantı isteği gelene kadar bekler. Karşı taraf istemini bildirince IP adresi ve portunu içeren uzak soket (remote socket) elde edilir.

Göster Gizle Kopar Satır Gizle Satır Göster
  1 // Serialization example.
  2 // Derived by Hadi from previous DateServer.java
  3 import java.util.Date;
  4 import java.net.*;
  5 import java.io.*;
  6 public class DateServer {
  7   public static void main (String [] args) {
  8     try {
  9       DateServer world = new DateServer();
 10     } catch (IOException e) {
 11       System.out.println("IO exception " + e);
 12     }
 13   }
 14   static final public int portNumber = 4291;
 15   public DateServer () throws IOException {
 16     ServerSocket server = new ServerSocket(portNumber);
 17     while (true) {
 18       System.out.println("Waiting for a client");
 19       Socket sock = server.accept();
 20       System.out.println("Got a client, send a message");
 21       ObjectOutputStream out = new ObjectOutputStream(sock.getOutputStream());
 22       
 23       out.writeObject(new Date());
 24     }
 25   }
 26 }

Bu programda sadece sunucu tarafın istemciye zaman bilgisi göndermesi istendiği için iletişimin tek yönlülüğü söz konusudur. Bu nedenle sunucu, sadece uzak soketin 'OutputStream' türü üyesini kullanmaktadır. Uzak sokete mesaj göndermekte kullanılan bu nesneye, bu streama yazmayı sağlayan fonksiyonlar içeren bir 'OutputStreamWriter' türü nesne aracılığı ile zaman bilgisi gönderilir.

Burada streamı açık olarak (explicitly) kapatarak hem yeni bağlanacak istemciler için bilgisayar kaynaklarını boşaltmış oluruz hem de streamın yastık (buffer) alanındaki veri karşı tarafa derhal gönderilir (flush). Sunucu program uzak soketi açma ve çıkış streamına veri basma işlemlerini döngü içinde yapmaktadır.

İstemci programda (DateClient.java) ise bu sefer 'socket' sınıfından bir istemci soketi nesnesi yaratılıyor. Bu soket nesnesi yaratılırken parametre olarak bağlanacağı hedef makina adresi ve port numarası (uzak soket parametreleri) verilir. Soketin inşa edicisi (constructor) çalışırken arka tarafta (imlicitly) işletim sistemindeki TCP/IP yönetim kesiminden istemci için yerel bir soket yaratılır (port numarası işletim sistemi tarafından atanır) ve sonra sunucu ile bağlantı kurularak sunucu soketi (istemciye göre uzak soket) elde edilir. Bu sırada sunucu tarafın 'accept' metoduna istem gönderilmiş olur ve sunucu programı da istemci tarafına ait soketi (sunucuya göre uzak soket) elde eder. İstemci taraftaki istemci soketi de karşı tarafın soketini göstermektedir ve bu soketten de sadece okuma yapılacağı için sadece giriş streamı kullanılmıştır. Okumaları satır sonu karakterine ("\n" ile gösterilir) kadar blok halinde yapabilmek için giriş stream sınıfı 'BufferredReader' sınıfına özelleştirilerek okumalarda bu sınıftan üretilen stream kullanılmıştır.

Göster Gizle Kopar Satır Gizle Satır Göster
  1 import java.net.*;
  2 import java.io.*;
  3 public class DateClient {
  4   public static void main (String [] args) {
  5     try {
  6       DateClient world = new DateClient();
  7     } catch (IOException e) {
  8       System.out.println("Received an IO exception " + e);
  9     }
 10   }
 11   static final public int portNumber = 4291;
 12   public DateClient () throws IOException {
 13     Socket sock = new Socket(InetAddress.getLocalHost(), portNumber);
 14     Reader isread = new InputStreamReader(sock.getInputStream());
 15     BufferedReader input = new BufferedReader(isread);
 16     System.out.println("message is " + input.readLine());
 17   }
 18 }

Programda dikkati çeken bir husus da her iki programdaki ana metodların (burada ana sınıfların inşa edicileri), IOException üretebilecek şekilde tasarlanmış olmasıdır. Böylece hem 'ServerSocket' (inşa edici), 'accept', 'getInputStream', 'getOutputStream' gibi soket fonksiyonları, hem de 'write', 'readLine' gibi stream fonksiyonları işletilirken oluşacak giriş-çıkış hataları ele alınmış oluyor.

Çoklu İstemci Bağlantıları

Pratikte çoğu zaman sunucular bir yerine birden çok istemciyle aynı anda ilgilenmek zorundadırlar. Burada bu durum 'Therapist' programı ile örneklenmiştir.

Göster Gizle Kopar Satır Gizle Satır Göster
  1 import java.net.*;
  2 import java.io.*;
  3 class Therapist {
  4   static public void main (String [ ] args) {
  5     try {
  6       Therapist world = new Therapist();
  7     } catch (IOException e) {
  8       System.out.println("Received an IO Exception" + e);
  9     }
 10   }
 11   static final public int portNumber = 5321; 
 12   public Therapist () throws IOException {
 13     ServerSocket server = new ServerSocket(portNumber);
 14     while (true) {
 15       Socket sock = server.accept();
 16       Thread session = new TherapySession
 17         (sock.getInputStream(), sock.getOutputStream());
 18       session.start();
 19     }
 20   }
 21 }

Sunucu sınıfın bir önceki örnekten en önemli farkı çoklu kanallar (multiple threads) kullanmasıdır. Ayrıca iletişim iki yönlü düşünüldüğü için uzak soketin output streamı yanında input streamı da yaratılmıştır.

Sunucu taraf (Therapist.java) önce, kendi sunucu soketini yaratıp, bir döngü içinde bu soket üzerinden dinlemeye başlıyor. Bir istemci bağlanınca elde ettiği uzak soketin giriş ve çıkış streamlarını yeni yarattığı bir kanala (TherapySession.java) aktarıp kanalı çalıştırıyor. Kanal çalışmaya başlarken, sunucu tekrar aynı port üzerinden yeni bağlanacak istemcileri dinlemeye başlıyor. Burada görüldüğü gibi aynı port üzerine eş zamanlı bağlantı kurmak mümkün.

Göster Gizle Kopar Satır Gizle Satır Göster
  1 import java.io.*;
  2 import java.util.Vector;
  3 import java.util.StringTokenizer;
  4 class TherapySession extends Thread {
  5   public TherapySession (InputStream ins, OutputStream outs) {
  6     Reader isread = new InputStreamReader(ins);
  7     in = new BufferedReader(isread);
  8     out = new OutputStreamWriter(outs);
  9   }
 10   private String name = "";
 11   private BufferedReader in;
 12   private Writer out;
 13   private String response(String text) {
 14       // answer a question with a question
 15     if (text.endsWith("?"))
 16       return "Why do you want to know?";
 17       // break up line
 18     Vector words = new Vector();
 19     StringTokenizer breaker = new StringTokenizer(text, " .,?!");
 20     while (breaker.hasMoreElements())
 21       words.addElement(breaker.nextElement());
 22       // look for ``I feel''
 23     if ((words.size() > 1) && 
 24       words.elementAt(0).equals("i") &&
 25       words.elementAt(1).equals("feel"))
 26       return "Why do you feel that way?";
 27       // look for relatives
 28     for (int i = 0; i < words.size(); i++) {
 29       String relative = (String) words.elementAt(i);
 30       if (isRelative(relative))
 31         return "Tell me more about your " + relative;
 32       }
 33       // nothing else, generic response
 34     return "Tell me more";
 35   }
 36   private boolean isRelative(String name) {
 37     return name.equals("mother") || name.equals("father")
 38       || name.equals("brother") || name.equals("sister")
 39       || name.equals("uncle");
 40   }
 41    
 42   public void run() {
 43     try {
 44         // get name
 45       out.write("Hello.  Welcome to therapy. What is your name?\n");
 46       out.flush();
 47       name = in.readLine();
 48       out.write("Well " + name + " what can we do for you today?\n");
 49       out.flush();
 50         // now read and respond
 51       while (true) {
 52         String text = in.readLine();
 53         out.write(response(text) + "\n");
 54         out.flush();
 55       }
 56     } catch (IOException e) { stop(); }
 57   }
 58 }

Streamlar üzerinden gelen mesajları (request) alma ve cevap olarak mesaj gönderme (response) işlemleri kanallar tarafından yürütülür.

İstemci tarafa (TherapyClient.java) baktığımızda ilk örneğe göre çok fazla fark yok. Sadece iletişimin iki yönlü olması öngörüldüğü için giriş streamı yanında çıkış streamı da kullanılıyor.

Göster Gizle Kopar Satır Gizle Satır Göster
  1 import java.io.*;
  2 import java.net.*;
  3 class TherapyClient {
  4   public static void main (String [ ] args) {
  5     try {
  6       TherapyClient world = new TherapyClient();
  7     } catch (IOException e) {
  8       System.out.println("got IO exception " + e);
  9     }
 10   }
 11   static final public int portNumber = 5321; 
 12   private BufferedReader input, term;
 13   private Writer output;
 14   public TherapyClient () throws IOException {
 15       // open standard input as buffered reader
 16     term = new BufferedReader(new InputStreamReader(System.in));
 17       // open socket as a reader and a writer
 18     Socket sock = new Socket(InetAddress.getLocalHost(), portNumber);
 19     Reader isread = new InputStreamReader(sock.getInputStream());
 20     input = new BufferedReader(isread);
 21     output = new OutputStreamWriter(sock.getOutputStream());
 22       // now read and print
 23     while (true) {
 24         // read and print something from therapist
 25       String line = input.readLine();
 26       System.out.println(line);
 27         // get our response
 28       line = term.readLine();
 29       if (line.equals("Quit"))
 30         break;
 31       output.write(line + "\n");
 32       output.flush();
 33     }
 34   }
 35 }

Ağ Üzerinden Nesne Aktarımı

Nesnelerin ağ üzerinden aktarılması nesne dizileştirme (object serialization) yötemiyle mümkündür. Nesne dizileştirme metodu çalışma esnasında üretilen herhangibir nesnenin o anki durumunu saklamak ve bir dahaki işletimde kalınan yeerden devam etmek gibi amaçlarla da kullanılmaktadır. Burada yapılan sadece nesneyi, diske veya ağ üzerinden karşı tarafa aktarabilecek şekilde karakter dizilerine çevirmektir. Sonra aynı işlemin tersi (unserialization) yapılarak diskten veya ağ üzerinden okunan karakter dizileri programın kullanabileceği nesneler haline getirilir.

Örnek olarak daha önce verilen 'DateServer.java' ve 'DateClient.java' uygulaması biraz değiştirilerek sunucunun verdiği zamanın istemcinin çalıştığı makinanın yerel zaman formatında gösterilmesini sağlandı.

Sunucu tarafında (DateServer.java), çıkış akımı (stream), 'ObjectOutputStream' türü bir nesne olarak açılarak sistem zamanını gösteren 'Date' türü nesnemiz 'writeObject' metoduyla bu akıma verildi. Bu fonksiyon içinde nesne dizileştirilerek ağ üzerinden karşı makineye gönderilir.

İstemci tarafında (DateClient.java) ise giriş akımı (stream), 'ObjectInputStream' türü bir nesne olarak açılır ve 'readObject' metodu kullanılarak soketin giriş akımı üzerinden, gönderilen dizi alınır ve tekrar nesne haline getirilir. Sonra da nesnenin yerel makinadaki karşılığı 'String' olarak alınarak ekrana basılır.

Daha Karmaşık Altyapı Desteği

Javanın RMI (Uzak Metod Çağırma - Remote Method Invocation) paketi (java.rmi.*) tam anlamıyla dağıtık uygulamalar geliştirmek için altyapı sunmaktadır. Bu yapı sayesinde bir nesnenin durduğu yer programcıya saydam olmakta ve hangi makinada olursa olsun program içinde aynı arabirimle kullanılabilmektedir. Bu yapı, istemci sunucu yerine çok katmanlı programlama yaklaşımına ulaşmakta yardımcı olmuştur.

Genelde veritabanı uygulamaları geliştirmek için veritabanı sunucusu ile aynı dili konuşabilecek bir arabirime ihtiyacınız olur ve bu ihtiyacı uygulamayı geliştirdiğiniz dil yerel (native) destekle sağlar. Java ile gelen Java Database Connectivity (JDBC) kütüphanesi, ilgili veritabanıyla yerel (native) konuşabilecek JDBC sürücüleri olmak kaydıyla yazdığınız programda her tür veri tabanı için aynı arabirimi kullanabilmenizi sağlamaktadır.

Servletler de appletlere alternatif olarak geliştirilmişlerdir. Appletler istemci mainada çalışırken servletler sunucu tarafta çalışırlar.

Dosya Listesi

İçindekilerGirişİndex
YukarıİlkÖncekiSonrakiSon
Geriİleri
Yazdır