Skip to content

8. TCP-IP Programlama

8.1. Genel Bilgiler

8.1.1. İnternet Protokolleri

Burada, iki ana protokolün adından esinlenerek TCP/IP (Transfer Kontrol Protokolü / İnternet Protokolü) olarak da adlandırılan İnternet iletişim protokollerine bir giriş sunuyoruz. Dağıtık uygulamaların geliştirilmesine geçmeden önce, okuyucunun ağların işleyişi ve özellikle TCP/IP protokollerine ilişkin genel bir kavrayışa sahip olması yararlı olacaktır.

Aşağıdaki metin, 90'lı yılların başında yayınlanan NOVELL'in "Lan Workplace for Dos - Administrator's Guide" adlı belgesinde yer alan bir metnin kısmi çevirisidir.


Farklı türdeki bilgisayarların bir ağını oluşturma konusundaki genel kavram, ABD'deki DARPA (Savunma İleri Araştırma Projeleri Ajansı) tarafından yürütülen araştırmalardan kaynaklanmaktadır. DARPA, heterojen makinelerin birbirleriyle iletişim kurmasını sağlayan ve TCP/IP adıyla bilinen protokol paketini geliştirmiştir. Bu protokoller, daha sonra INTERNET ağı haline gelen ARPAnet adlı bir ağ üzerinde test edilmiştir. TCP/IP protokolleri, kullanılan ağların ve donanımların yapısından bağımsız olarak iletim ve alım formatlarını ve kurallarını tanımlar.

DARPA tarafından tasarlanan ve TCP/IP protokolleri tarafından yönetilen ağ, bir paket anahtarlamalı ağdır. Bu tür bir ağ, bilgileri ağ üzerinde "paket" adı verilen küçük parçalar halinde iletir. Dolayısıyla, bir bilgisayar büyük bir dosya gönderirse, bu dosya küçük parçalara bölünür ve ağ üzerinden gönderilir; hedef noktaya ulaştığında ise yeniden birleştirilir. TCP/IP, bu paketlerin formatını şu şekilde tanımlar:

  • paketin kaynağı
  • hedef
  • uzunluk
  • tür

8.1.2. OSI modeli

TCP/IP protokolleri, ISO (Uluslararası Standartlar Örgütü) tarafından tanımlanan OSI (Açık Sistemler Arabağlantı Referans Modeli) adlı açık ağ modelini büyük ölçüde takip eder. Bu model, makineler arasındaki iletişimin yedi katmanlı bir modelle temsil edilebildiği ideal bir ağı tanımlamaktadır:

Image

Her katman, alt katmandan hizmet alır ve üst katmana kendi hizmetlerini sunar. A ve B adlı farklı makinelerde bulunan iki uygulamanın iletişim kurmak istediğini varsayalım: bu iletişim, Application katmanında gerçekleşir. Ağın işleyişinin tüm ayrıntılarını bilmelerine gerek yoktur: her uygulama, iletmek istediği bilgiyi bir alt katmana, yani Présentation katmanına aktarır. Dolayısıyla uygulamanın, yalnızca Présentation katmanıyla arayüz oluşturma kurallarını bilmesi yeterlidir.

Bilgi, Présentation katmanına ulaştığında, diğer kurallara göre Session katmanına aktarılır ve bu süreç, bilgi fiziksel ortama ulaşana ve hedef makineye fiziksel olarak iletilene kadar devam eder. Orada, gönderen makinede uygulanan işlemin tam tersi işlemden geçecektir.

Her katmanda, bilgiyi göndermekle görevli gönderen işlem, bilgiyi kendisiyle aynı katmana ait diğer makinedeki bir alıcı işleme gönderir. Bunu, katman protokolü olarak adlandırılan belirli kurallara göre yapar. Dolayısıyla, nihai iletişim şeması şu şekildedir:

Image

Farklı katmanların rolleri şöyledir:

Physique
Fiziksel bir ortam üzerinden bitlerin iletilmesini sağlar. Bu katmanda, terminal veya bilgisayar gibi veri işleme uç ekipmanları (E.T.T.D.) ile modülatör/demodülatör, çoklayıcı, yoğunlaştırıcı gibi veri devre sonlandırma ekipmanları (E.T.C.D.) bulunur. Bu seviyede dikkat edilmesi gereken noktalar şunlardır:
. bilginin kodlama şeklinin seçimi (analog veya dijital)
. iletim modunun seçimi (senkron veya asenkron).
Liaison de données
Fiziksel katmanın fiziksel özelliklerini gizler. İletim hatalarını algılar ve düzeltir.
Réseau
Ağa gönderilen bilgilerin izlemesi gereken yolu yönetir. Buna routage denir: bir bilginin alıcısına ulaşması için izlemesi gereken yolu belirlemek.
Transport
Önceki katmanlar yalnızca makineler arası iletişime izin verirken, bu katman iki uygulama arasındaki iletişimi mümkün kılar. Bu katman tarafından sağlanan bir hizmet, çoklama olabilir: taşıma katmanı, birden fazla uygulamaya ait bilgileri iletmek için aynı ağ bağlantısını (makineden makineye) kullanabilir.
Session
Bu katmanda, bir uygulamanın uzak bir makinede bir çalışma oturumu açmasına ve sürdürmesine olanak tanıyan hizmetler bulunur.
Présentation
Bu katman, farklı makinelerdeki verilerin temsilini standartlaştırmayı amaçlar. Böylece, A makinesinden gelen veriler, ağa gönderilmeden önce A makinesinin Présentation katmanı tarafından standart bir biçime göre "düzenlenir". Veriler, alıcı makine B’nin Présentation katmanına ulaştığında, bu katman verileri standart formatları sayesinde tanıyacak ve makine B’deki uygulamanın tanıyabilmesi için veriler farklı bir şekilde biçimlendirilecektir.
Application
Bu aşamada, genellikle e-posta veya dosya aktarımı gibi kullanıcıya yakın uygulamalar bulunur.

8.1.3. TCP/IP modeli

OSI modeli, henüz hayata geçirilmemiş ideal bir modeldir. TCP/IP protokol dizisi, aşağıdaki şekilde bu modele yaklaşmaktadır:

Image

Fiziksel Katman

Yerel ağlarda genellikle Ethernet veya Token-Ring teknolojisi kullanılır. Burada sadece Ethernet teknolojisini ele alacağız.

Ethernet

Bu isim, 1970'lerin başında PARC Xerox tarafından icat edilen ve 1978'de Xerox, Intel ve Digital Equipment tarafından standartlaştırılan paket anahtarlamalı yerel ağ teknolojisine verilen addır. Ağ fiziksel olarak, çapı yaklaşık 1,27 cm ve uzunluğu en fazla 500 m olan bir koaksiyel kablodan oluşur. répéteurs aracılığıyla genişletilebilir; iki makine arasında en fazla iki tekrarlayıcı bulunabilir. Kablo pasiftir: tüm aktif bileşenler kabloya bağlı cihazlarda bulunur. Her cihaz, aşağıdakileri içeren bir ağ erişim kartı aracılığıyla kabloya bağlanır:

  • Kablodaki sinyallerin varlığını algılayan ve analog sinyalleri dijital sinyallere, dijital sinyalleri de analog sinyallere dönüştüren bir verici (transceiver).
  • vericiden gelen dijital sinyalleri alan ve bunları işlenmek üzere bilgisayara ileten veya tersi yönde çalışan bir kuplör.

Ethernet teknolojisinin başlıca özellikleri şunlardır:

  • 10 Megabit/saniye kapasite.
  • Bus topolojisi: tüm cihazlar aynı kabloya bağlanır

Image

  • Yayılım ağı - Verici bir makine, alıcı makinenin adresini içeren bilgileri kablo üzerinden iletir. Bağlı tüm makineler bu bilgileri alır, ancak yalnızca bilgilerin adreslendiği makine bu bilgileri saklar.
  • Erişim yöntemi şöyledir: İletim yapmak isteyen verici kabloyu dinler; böylece bir taşıyıcı dalganın varlığını veya yokluğunu algılar; bu dalganın varlığı, bir iletimin devam ettiği anlamına gelir. Bu, CSMA (Carrier Sense Multiple Access) tekniğidir. Taşıyıcı dalga yoksa, bir verici sırayla iletim yapmaya karar verebilir. Bu kararı veren birden fazla verici olabilir. Yayınlanan sinyaller birbirine karışır: buna çarpışma denir. Verici bu durumu algılar: kablo üzerinden sinyal gönderirken, aynı anda kabloda gerçekte neyin geçtiğini de dinler. Kabloda geçen bilginin kendi gönderdiği bilgi olmadığını algılarsa, bir çarpışma olduğu sonucuna varır ve sinyal göndermeyi durdurur. Sinyal gönderen diğer vericiler de aynısını yapar. Her verici, kendisine özgü rastgele bir süre geçtikten sonra vermeye yeniden başlayacaktır. Bu tekniğe CD (Çarpışma Algılama) adı verilir. Erişim yöntemi ise CSMA/CD olarak adlandırılır.
  • 48 bitlik adresleme. Her makinenin, burada fiziksel adres olarak adlandırılan ve onu kabloya bağlayan kart üzerinde yazılı bir adresi vardır. Bu adrese, makinenin Ethernet adresi denir.

Ağ Katmanı

Bu katmanda IP, ICMP, ARP ve RARP protokollerini buluruz.

IP (İnternet Protokolü)
Ağın iki düğümü arasında paketleri iletir
ICMP
(İnternet Kontrol Mesajı Protokolü)
ICMP, bir makinedeki IP protokol programı ile başka bir makinedeki program arasında iletişimi sağlar. Dolayısıyla, bu protokol, IP protokolünün kendi içinde mesaj alışverişi için kullanılan bir protokoldür.
ARP
(Adres Çözümleme Protokolü)
makinenin İnternet adresi ile makinenin fiziksel adresi arasında eşleştirme yapar
RARP
(Ters Adres Çözümleme Protokolü)
makinenin fiziksel adresi ile makinenin İnternet adresi arasında eşleştirme yapar

Taşıma/Oturum Katmanları

Bu katmanda aşağıdaki protokoller bulunur:

TCP (İletim Kontrol Protokolü)
İki istemci arasında bilgilerin güvenilir bir şekilde iletilmesini sağlar
UDP (Kullanıcı Datagram Protokolü)
İki istemci arasında bilgilerin güvenilir olmayan bir şekilde iletilmesini sağlar

Uygulama/Sunum/Oturum Katmanları

Burada çeşitli protokoller bulunur:

TELNET
A makinesinin B makinesine terminal olarak bağlanmasını sağlayan terminal emülatörü
FTP (File Transfer Protocol)
dosya aktarımına olanak tanır
TFTP (Trivial File Transfer Protocol)
dosya aktarımına olanak tanır
SMTP (Simple Mail Transfer protocol)
ağ kullanıcıları arasında mesaj alışverişine izin verir
DNS (Domain Name System)
bir makine adını o makinenin İnternet adresine dönüştürür
XDR (eXternal Data Representation)
sun tarafından oluşturulan MicroSystems, makinelerden bağımsız standart bir veri temsilini belirtir
RPC(Remote Procedures Call)
Yine Sun tarafından tanımlanmıştır; bu, taşıma katmanından bağımsız, uzak uygulamalar arasındaki bir iletişim protokolüdür. Bu protokol önemlidir: programcının taşıma katmanının ayrıntılarını bilmesi gerekliliğini ortadan kaldırır ve uygulamaları taşınabilir hale getirir. Bu protokol, XDR protokolüne dayanır
NFS (Network File System)
Yine Sun tarafından tanımlanan bu protokol, bir makinenin başka bir makinenin dosya sistemini "görmesini" sağlar. Bu protokol, yukarıdaki RPC protokolüne dayanır

8.1.4. İnternet protokollerinin işleyişi

TCP/IP ortamında geliştirilen uygulamalar genellikle bu ortamdaki protokollerin birçoğunu kullanır. Bir uygulama programı, protokollerin en üst katmanıyla iletişim kurar. Bu katman, bilgiyi bir alt katmana aktarır ve bu şekilde fiziksel ortama ulaşana kadar devam eder. Burada bilgi, fiziksel olarak alıcı makineye aktarılır; burada da aynı katmanları, bu kez ters yönde geçerek, gönderilen bilgilerin alıcı uygulamasına ulaşana kadar ilerler. Aşağıdaki şema, bilginin izlediği yolu göstermektedir:

Image

Bir örnek verelim: Application katmanında tanımlanan ve makineler arasında dosya aktarımına olanak tanıyan FTP uygulaması.

  • Uygulama, transport. katmanına iletilecek bir bayt dizisi üretir
  • transport katmanı, bu bayt dizisini segments ve TCP olarak böler ve her segmentin başına kendi numarasını ekler. Segmentler, IP protokolü tarafından yönetilen Ağ katmanına aktarılır.
  • IP katmanı, alınan TCP segmentini kapsülleyen bir paket oluşturur. Bu paketin başına, kaynak ve hedef makinelerin İnternet adreslerini yerleştirir. Ayrıca, alıcı makinenin fiziksel adresini de belirler. Tüm bunlar, Veri Bağlantısı ve Fiziksel Bağlantı katmanına, yani makineyi fiziksel ağa bağlayan ağ kartına aktarılır.
  • Burada, IP paketi de bir fiziksel çerçeve içine kapsüllenir ve kablo üzerinden alıcısına gönderilir.
  • Hedef makinede, Veri Bağlantısı ve Fiziksel Bağlantı katmanı tersi işlemi gerçekleştirir: IP paketini fiziksel çerçeveden çıkarır ve IP katmanına iletir.
  • IP katmanı, paketin doğru olup olmadığını kontrol eder: alınan bitlere göre bir kontrol toplamı hesaplar (checksum) ve bu toplamın paket başlığında bulunması gerektiğini doğrular. Aksi takdirde paket reddedilir.
  • Paket doğru olarak onaylanırsa, IP katmanı, paketin içindeki TCP segmentini kapsülden çıkarır ve bir üst katman olan transport katmanına iletir.
  • transport katmanı — örneğimizde TCP katmanı — segmentlerin doğru sırasını sağlamak için segment numarasını inceler.
  • Ayrıca TCP segmenti için bir doğrulama toplamı hesaplar. Doğruluğu teyit edilirse, TCP katmanı kaynak makineye bir onay mesajı gönderir; aksi takdirde TCP segmenti reddedilir.
  • TCP katmanına geriye kalan tek şey, segmentin veri kısmını bir üst katmandaki alıcı uygulamaya iletmektir.

8.1.5. İnternetteki adresleme sorunları

Bir ağdaki noeud, bir bilgisayar, akıllı yazıcı, dosya sunucusu veya aslında TCP/IP protokollerini kullanarak iletişim kurabilen herhangi bir şey olabilir. Her düğümün, ağ türüne bağlı bir biçime sahip bir fiziksel adresi vardır. Bir Ethernet ağında fiziksel adres 6 baytlık bir kodla ifade edilir. Bir X25 ağındaki adres ise 14 basamaklı bir sayıdır.

Bir düğümün İnternet adresi mantıksal bir adrestir: kullanılan donanımdan ve ağdan bağımsızdır. Bu, hem bir yerel ağı hem de bu ağdaki bir düğümü tanımlayan 4 baytlık bir adrestir. İnternet adresi genellikle, 4 baytın değerlerini temsil eden ve nokta ile ayrılmış 4 sayı şeklinde gösterilir. Örneğin, Angers Fen Fakültesi'ndeki Lagaffe adlı bilgisayarın adresi 193.49.144.1, Liny adlı bilgisayarın adresi ise 193.49.144.9 olarak yazılır. Buradan, yerel ağın İnternet adresinin 193.49.144.0 olduğu anlaşılır. Bu ağda en fazla 254 düğüm bulunabilir.

İnternet adresleri veya IP adresleri ağdan bağımsız olduğundan, A ağındaki bir makine, hangi ağda bulunduğuna bakılmaksızın B ağındaki bir makineyle iletişim kurabilir: bunun için sadece IP adresini bilmesi yeterlidir. Her ağın IP protokolü, IP adresi ile fiziksel adres arasında iki yönlü dönüştürme işlemini gerçekleştirir.

IP adreslerinin hepsi birbirinden farklı olmalıdır. Bu adreslerin dağıtımından resmi kurumlar sorumludur. Aslında bu kurumlar, yerel ağlar için bir adres tahsis eder; örneğin, Angers Fen Fakültesi ağı için 193.49.144.0 adresi verilir. Bu ağın yöneticisi daha sonra IP 193.49.144.1 ile 193.49.144.254 arasındaki adresleri istediği gibi atayabilir. Bu adres genellikle ağa bağlı her makinenin özel bir dosyasına kaydedilir.

8.1.5.1. IP adres sınıfları

Bir IP adresi, genellikle I1.I2.I3.I4 olarak gösterilen 4 baytlık bir dizidir ve aslında iki adres içerir:

  • ağ adresi
  • bu ağdaki bir düğümün adresi

Bu iki alanın boyutuna göre, IP adresleri 3 sınıfa ayrılır: A, B ve C sınıfları.

A sınıfı

IP: I1.I2.I3.I4 adresi, R1.N1.N2.N3 biçimindedir; burada

R1 ağ adresidir

N1.N2.N3 bu ağdaki bir makinenin adresidir

Daha kesin olarak, A sınıfı bir adres olan IP'in biçimi şöyledir:

Image

Ağ adresi 7 bit, düğüm adresi ise 24 bit uzunluğundadır. Dolayısıyla, her biri en fazla 224 düğüm içeren 127 adet A sınıfı ağ olabilir.

B Sınıfı

Burada, IP: I1.I2.I3.I4 adresi, R1.R2.N1.N2 biçimindedir; burada

R1.R2 ağ adresidir

N1.N2 bu ağdaki bir makinenin adresidir

Daha kesin olarak, B sınıfı bir adres olan IP'in biçimi şöyledir:

Image

Ağ adresi ve düğüm adresi 2 bayt (tam olarak 14 bit) uzunluğundadır. Dolayısıyla, her biri en fazla 2¹⁶ düğüm içeren 2¹⁴ adet B sınıfı ağ olabilir.

C Sınıfı

Bu sınıfta, IP: I1.I2.I3.I4 adresi, R1.R2.R3.N1 biçimindedir; burada

R1.R2.R3 ağ adresidir

N1 bu ağdaki bir makinenin adresidir

Daha kesin olarak, C sınıfı bir adres olan IP'in biçimi şöyledir:

Image

Ağ adresi 3 bayt (3 bit hariç) ve düğüm adresi 1 bayttır. Dolayısıyla, en fazla 256 düğüm içeren 221 adet C sınıfı ağ olabilir.

Angers Fen Fakültesi'ne ait Lagaffe makinesinin adresi 193.49.144.1 olduğundan, en üst baytın değeri 193, yani ikili sistemde 11000001 olduğu görülür. Bundan, ağın C sınıfı olduğu sonucuna varılır.

Ayrılmış adresler

. Bazı IP adresleri, ağdaki düğüm adresleri değil, ağ adresleridir. Bunlar, düğüm adresinin 0 olarak ayarlandığı adreslerdir. Dolayısıyla, 193.49.144.0 adresi, Angers Fen Fakültesi ağının IP adresidir. Sonuç olarak, bir ağdaki hiçbir düğümün adresi sıfır olamaz.

. IP adresinde düğüm adresi yalnızca 1'lerden oluşuyorsa, bu bir yayın adresidir: bu adres ağdaki tüm düğümleri belirtir.

. Teorik olarak 28=256 düğüme izin veren bir C sınıfı ağda, yasaklanmış iki adresi çıkarırsak, geriye sadece 254 izin verilen adres kalır.

8.1.5.2. İnternet Adresi <--> Fiziksel Adres dönüştürme protokolleri

Bir makineden diğerine bilgi aktarımı sırasında, bu bilgilerin IP katmanından geçerken paketler halinde kapsüllendiğini görmüştük. Bu paketler şu şekildedir:

Image

Dolayısıyla, IP paketi, kaynak ve hedef makinelerin İnternet adreslerini içerir. Bu paket, fiziksel ağa gönderilmek üzere sorumlu katmana iletildiğinde, nihayetinde ağa gönderilecek fiziksel çerçeveyi oluşturmak üzere başka bilgiler de eklenir. Örneğin, bir Ethernet ağındaki çerçevenin biçimi şöyledir:

Image

Nihai çerçevede, kaynak ve hedef makinelerin fiziksel adresleri bulunur. Bunlar nasıl elde edilir?

Gönderen makine, iletişim kurmak istediği makinenin IP adresini bildiğinden, ARP (Adres Çözümleme Protokolü) adı verilen özel bir protokolü kullanarak bu makinenin fiziksel adresini elde eder.

  • Fiziksel adresini bulmak istediği makinenin IP adresini içeren, ARP adı verilen özel bir paket gönderir. Ayrıca, bu pakete kendi IP adresini ve fiziksel adresini de eklemeyi ihmal etmemiştir.
  • Bu paket, ağdaki tüm düğümlere gönderilir.
  • Düğümler, paketin özel niteliğini fark eder. Pakette kendi adresi olan IP'i tanıyan düğüm, paketin gönderenine fiziksel adresini göndererek yanıt verir. Bunu nasıl yapabilir? Pakette, gönderenin IP adresini ve fiziksel adresini bulmuştur.
  • Böylece gönderen, aradığı fiziksel adresi alır. Aynı alıcıya başka paketler gönderilmesi gerektiğinde daha sonra kullanabilmek için bu adresi bellekte saklar.

Bir makinenin IP adresi normalde, makinenin bu adresi öğrenmek için başvurabileceği dosyalarından birinde kayıtlıdır. Bu adres değiştirilebilir: dosyayı düzenlemek yeterlidir. Fiziksel adres ise ağ kartının belleğinde kayıtlıdır ve değiştirilemez.

Bir yönetici ağını farklı bir şekilde düzenlemek istediğinde, tüm düğümlerin IP adreslerini değiştirmek ve dolayısıyla farklı düğümlerin farklı yapılandırma dosyalarını düzenlemek zorunda kalabilir. Bu işlem, çok sayıda makine varsa zahmetli olabilir ve hatalara yol açabilir. Bir yöntem, makinelere IP adresi atamamaktır: bu durumda, makinenin IP adresini bulması gereken dosyaya özel bir kod yazılır. IP adresine sahip olmadığını fark eden makine, RARP (Ters Adres Çözümleme Protokolü) adı verilen bir protokol aracılığıyla bu adresi talep eder. Ardından, önceki ARP paketine benzer şekilde, fiziksel adresini içeren RARP adlı özel bir paketi ağa gönderir. Bu paket, RARP paketini tanıyan tüm düğümlere gönderilir. Bunlardan biri, RARP sunucusu olarak adlandırılan sunucu, tüm düğümlerin fiziksel adres <--> IP adresi eşleşmelerini içeren bir dosyaya sahiptir. Ardından, RARP paketinin gönderenine yanıt vererek, ona IP adresini geri gönderir. Ağını yeniden yapılandırmak isteyen bir yönetici, bu nedenle RARP sunucusunun eşleme dosyasını düzenlemesi yeterlidir. Bu sunucunun normalde sabit bir IP adresi olmalıdır ve kendisi RARP protokolünü kullanmaya gerek kalmadan bu adresi bilmelidir.

8.1.6. İnternetin IP katmanı olarak adlandırılan ağ katmanı

IP protokolü (İnternet Protokolü), paketlerin alması gereken biçimi ve gönderilme veya alınma sırasında nasıl yönetilmeleri gerektiğini tanımlar. Bu özel paket türüne IP datagramı denir. Bunu daha önce tanıtmıştık:

Image

Önemli olan nokta, IP datagramının iletilecek verilerin yanı sıra, kaynak ve hedef makinelerin İnternet adreslerini de içermesidir. Böylece alıcı makine, mesajı kimin gönderdiğini bilir.

Geçiş yaptığı ağın fiziksel özelliklerine göre uzunluğu belirlenen bir ağ çerçevesinden farklı olarak, IP datagramının uzunluğu yazılım tarafından belirlenir ve bu nedenle farklı fiziksel ağlarda aynı olacaktır. Ağ katmanından fiziksel katmana doğru inerken, IP datagramının bir fiziksel çerçeve içine kapsüle edildiğini görmüştük. Ethernet ağındaki fiziksel çerçeve örneğini vermiştik:

Image

Fiziksel çerçeveler, gönderen makinenin bulunduğu fiziksel ağda olmayabilecek hedeflerine doğru düğümden düğüme ilerler. Dolayısıyla, IP paketi, farklı türdeki iki ağı birbirine bağlayan düğümlerde art arda farklı fiziksel çerçevelere kapsüllenebilir. Ayrıca, IP paketinin bir fiziksel çerçeveye kapsüllenemeyecek kadar büyük olması da mümkündür. Bu sorunun yaşandığı düğümdeki IP yazılımı, IP paketini belirli kurallara göre fragments'e böler ve her biri daha sonra fiziksel ağ üzerinden gönderilir. Bu paketler ancak nihai varış noktalarında yeniden birleştirilir.

8.1.6.1. Yönlendirme

Yönlendirme, IP paketlerinin hedeflerine ulaştırılması yöntemidir. İki yöntem vardır: doğrudan yönlendirme ve dolaylı yönlendirme.

Doğrudan yönlendirme

Doğrudan yönlendirme, bir IP paketinin aynı ağ içinde göndereninden alıcısına doğrudan iletilmesini ifade eder:

  • Bir IP datagramını gönderen makine, alıcının IP adresine sahiptir.
  • Bu makine, alıcının fiziksel adresini ARP protokolü aracılığıyla veya bu adres daha önce elde edilmişse kendi tablolarından alır.
  • Paketi, ağ üzerinden bu fiziksel adrese gönderir.

Dolaylı yönlendirme

Dolaylı yönlendirme, IP paketinin, gönderenin ait olduğu ağdan farklı bir ağda bulunan bir hedefe iletilmesini ifade eder. Bu durumda, kaynak ve hedef makinelerin IP adreslerinin ağ adresi kısımları birbirinden farklıdır. Kaynak makine bu durumu fark eder. Ardından paketi, yönlendirici (router) adı verilen özel bir düğüme gönderir; bu düğüm, bir yerel ağı diğer ağlara bağlar ve kaynak makine, bu düğümün IP adresini kendi tablolarında bulur. Bu adres, başlangıçta bir dosyadan, kalıcı bir bellekten veya ağ üzerinde dolaşan bilgiler yoluyla elde edilmiştir.

Bir yönlendirici iki ağa bağlıdır ve bu iki ağın içinde IP adresine sahiptir.

Image

Yukarıdaki örneğimizde:

  • 1 numaralı ağın İnternet adresi 193.49.144.0, 2 numaralı ağın adresi ise 193.49.145.0'dır.
  • 1 numaralı ağın içinde yönlendiricinin adresi 193.49.144.6, 2 numaralı ağın içinde ise 193.49.145.3'tür.

Yönlendiricinin görevi, aldığı ve 1 numaralı ağa özgü bir fiziksel çerçeve içinde bulunan IP paketini, 2 numaralı ağda iletilebilecek bir fiziksel çerçeveye dönüştürmektir. Paketin alıcısının IP adresi 2 numaralı ağda bulunuyorsa, yönlendirici paketi doğrudan alıcıya gönderir; aksi takdirde paketi, 2 numaralı ağı 3 numaralı ağa bağlayan başka bir yönlendiriciye gönderir ve bu süreç böyle devam eder.

8.1.6.2. Hata ve kontrol mesajları

Yine ağ katmanında, yani IP protokolüyle aynı seviyede, ICMP (İnternet Kontrol Mesajı Protokolü) protokolü bulunur. Bu protokol, ağın iç işleyişiyle ilgili mesajlar (arıza veren düğümler, bir yönlendiricide tıkanma vb.) göndermek için kullanılır. ICMP mesajları, IP paketleri içine kapsüllenir ve ağ üzerinden gönderilir. Farklı düğümlerin IP katmanları, aldıkları ICMP mesajlarına göre uygun eylemleri gerçekleştirir. Böylece, bir uygulama, ağa özgü bu sorunları hiçbir zaman görmez.

Bir düğüm, ICMP bilgilerini kullanarak yönlendirme tablolarını günceller.

8.1.7. Taşıma katmanı: UDP ve TCP protokolleri

8.1.7.1. UDP protokolü: Kullanıcı Datagram Protokolü

UDP protokolü, iki nokta arasında güvenilir olmayan veri alışverişine olanak tanır; yani bir paketin hedefine doğru bir şekilde ulaştırılması garanti edilmez. Uygulama, isterse bunu kendisi yönetebilir; örneğin, bir mesajı gönderdikten sonra bir onay mesajı bekleyerek bir sonraki mesajı gönderebilir.

Şu an için ağ düzeyinde, makinelerin IP adreslerinden bahsettik. Oysa bir makinede, birbirleriyle iletişim kurabilen farklı işlemler aynı anda bir arada bulunabilir. Bu nedenle, bir mesaj gönderilirken sadece alıcı makinenin IP adresini değil, aynı zamanda alıcı işlemin "adını" da belirtmek gerekir. Bu ad aslında bir sayıdır ve buna bağlantı noktası numarası denir. Bazı numaralar standart uygulamalar için ayrılmıştır: örneğin, tftp (trivial file transfer protocol) uygulaması için 69 numaralı bağlantı noktası.

UDP protokolü tarafından yönetilen paketlere datagram da denir. Bu paketler şu biçimdedir:

Image

Bu datagramlar, IP paketleri içine, ardından da fiziksel çerçeveler içine kapsüllenir.

8.1.7.2. TCP Protokolü: Aktarım Kontrol Protokolü

Güvenli iletişim için UDP protokolü yetersizdir: uygulama geliştiricisi, paketlerin doğru şekilde iletildiğini tespit etmesini sağlayan bir protokolü kendisi geliştirmelidir.

TCP protokolü (Aktarım Kontrol Protokolü) bu sorunları ortadan kaldırır. Özellikleri şunlardır:

  • Gönderim yapmak isteyen işlem, öncelikle göndereceği bilgilerin alıcı işlemiyle bir bağlantı kurar. Bu bağlantı, gönderen makinenin bir bağlantı noktası ile alıcı makinenin bir bağlantı noktası arasında kurulur. İki bağlantı noktası arasında böylece sanal bir yol oluşturulur ve bu yol, bağlantıyı kuran sadece bu iki işlem için ayrılır.
  • Kaynak işlem tarafından gönderilen tüm paketler bu sanal yolu izler ve gönderildikleri sırayla ulaşır; bu durum, paketlerin farklı yollar izleyebilmesi nedeniyle UDP protokolünde garanti edilemiyordu.
  • Gönderilen bilgi sürekli bir yapıya sahiptir. Gönderen işlem, kendi ritminde bilgi gönderir. Bu bilgiler mutlaka hemen gönderilmez: TCP protokolü, gönderilmek üzere yeterli miktarda bilgi birikene kadar bekler. Bilgiler, TCP segmenti adı verilen bir yapıda depolanır. Bu segment dolduğunda, IP katmanına iletilir ve burada bir IP paketine kapsüllenir.
  • TCP protokolüyle gönderilen her segment numaralandırılır. Alıcı TCP protokolü, segmentleri sırayla aldığını doğrular. Düzgün bir şekilde alınan her segment için, gönderene bir alım onayı gönderir.
  • Gönderici bu onayı aldığında, bunu gönderen işleme bildirir. Böylece gönderen, bir segmentin başarıyla ulaştığını öğrenebilir; bu, UDP protokolüyle mümkün değildi.
  • Bir süre geçtikten sonra, bir segmenti gönderen TCP protokolü bir onay mesajı almazsa, söz konusu segmenti yeniden gönderir ve böylece bilgi aktarım hizmetinin kalitesini garanti eder.
  • İletişim kuran iki işlem arasında kurulan sanal devre full-duplex'tir: bu, bilginin her iki yönde de aktarılabileceği anlamına gelir. Böylece, kaynak işlem bilgi göndermeye devam ederken hedef işlem onay mesajları gönderebilir. Bu, örneğin kaynak TCP protokolünün, onay mesajını beklemeden birden fazla segment göndermesine olanak tanır. Belirli bir süre sonra n numaralı bir segmentin onay mesajını almadığını fark ederse, segmentlerin gönderimine bu noktadan itibaren devam eder.

8.1.8. Uygulamalar Katmanı

UDP ve TCP protokollerinin üzerinde çeşitli standart protokoller bulunmaktadır:

TELNET

Bu protokol, ağdaki A makinesindeki bir kullanıcının B makinesine (genellikle ana makine olarak adlandırılır) bağlanmasına olanak tanır. TELNET, A makinesinde evrensel olarak adlandırılan bir terminali taklit eder. Böylece kullanıcı, B makinesine bağlı bir terminale sahipmiş gibi davranır. Telnet, TCP protokolünü kullanır.

FTP: (Dosya Aktarım Protokolü)

Bu protokol, iki uzak makine arasında dosya alışverişine ve örneğin dizin oluşturma gibi dosya işlemlerine olanak tanır. TCP protokolünü kullanır.

TFTP: (Basit Dosya Aktarım Kontrolü)

Bu protokol, FTP protokolünün bir varyantıdır. UDP protokolüne dayanır ve FTP protokolünden daha az gelişmiştir.

DNS: (Etki Alanı Adı Sistemi)

Bir kullanıcı, örneğin FTP aracılığıyla uzak bir makineyle dosya alışverişi yapmak istediğinde, bu makinenin İnternet adresini bilmesi gerekir. Örneğin, Angers Üniversitesi'ndeki Lagaffe adlı makinede FTP yapmak için, FTP'i şu şekilde çalıştırmak gerekir: FTP 193.49.144.1

Bu durum, makine <--> IP adresi eşleşmesini içeren bir dizin bulunmasını gerektirir. Muhtemelen bu dizinde makineler şu tür sembolik adlarla belirtilecektir:

Angers Üniversitesi'ne ait DPX2/320 makinesi

Angers'daki ISERPA Sun makinesi

Bir makineyi IP adresiyle değil de bir adla tanımlamanın daha kolay olacağı açıktır. Bu durumda adların benzersizliği sorunu ortaya çıkar: birbirine bağlı milyonlarca makine vardır. Adların bir merkezi kurum tarafından tahsis edildiğini düşünebiliriz. Bu şüphesiz oldukça zahmetli olurdu. Aslında adların yönetimi alanlara dağıtılmıştır. Her alan adı, genellikle çok küçük çaplı bir kurum tarafından yönetilir ve bu kurum, makine isimlerinin seçiminde tam bir serbestliğe sahiptir. Dolayısıyla Fransa’daki makineler, Paris’teki Inria tarafından yönetilen fr alan adına aittir. İşleri daha da basitleştirmek için kontrol yetkisi bir kez daha dağıtılır: fr alan adı içinde alt alan adları oluşturulur. Böylece Angers Üniversitesi, univ-Angers alan adına aittir. Bu etki alanını yöneten birim, Angers Üniversitesi ağındaki makinelere isim verme konusunda tam bir serbestliğe sahiptir. Şu an için bu etki alanı alt bölümlere ayrılmamıştır. Ancak, ağda çok sayıda makine barındıran büyük bir üniversitede, bu etki alanı alt bölümlere ayrılabilir.

Angers Üniversitesi’ne ait DPX2/320, Lagaffe olarak adlandırılırken, PC ve 486DX50 ise liny olarak adlandırılmıştır. Bu makinelere dışarıdan nasıl referans verilir? Ait oldukları etki alanı hiyerarşisini belirterek. Böylece Lagaffe adlı makinenin tam adı şöyle olacaktır:

        Lagaffe.univ-Angers.fr

Etki alanlarının içinde göreceli adlar kullanılabilir. Dolayısıyla, fr etki alanı içinde ve univ-Angers etki alanı dışında, Lagaffe makinesine şu şekilde atıfta bulunulabilir:

        Lagaffe.univ-Angers

Son olarak, univ-Angers etki alanı içinde, bu makineye basitçe şu şekilde atıfta bulunulabilir:

        Lagaffe

Dolayısıyla bir uygulama, bir makineye adıyla atıfta bulunabilir. Sonuçta yine de bu makinenin İnternet adresini elde etmek gerekir. Bu nasıl gerçekleştirilir? A makinesinden B makinesiyle iletişim kurmak istediğimizi varsayalım.

  • B makinesi, A makinesiyle aynı etki alanına aitse, muhtemelen A makinesindeki bir dosyada B makinesinin IP adresi bulunacaktır.
  • Aksi takdirde, A makinesi başka bir dosyada ya da daha önce bahsedilen dosyada, adresleri IP olan birkaç ad sunucusunun listesini bulacaktır. Bir ad sunucusu, bir makine adı ile adresi IP arasındaki eşleşmeyi sağlamakla görevlidir. A makinesi, listesindeki ilk ad sunucusuna, aranan makinenin adını içeren ve DNS olarak adlandırılan özel bir sorgu gönderecektir. Sorgulanan sunucunun kayıtlarında bu ad varsa, A makinesine karşılık gelen IP adresini gönderecektir. Aksi takdirde, sunucu da kendi dosyalarında sorgulayabileceği bir ad sunucuları listesi bulacaktır. Ardından bu sunucuları sorgulayacaktır. Böylece, bir dizi ad sunucusu sorgulanacaktır; bu işlem rastgele değil, istek sayısını en aza indirecek şekilde gerçekleştirilecektir. Makine nihayetinde bulunursa, yanıt Makine A’ya geri gönderilecektir.

XDR: (eXternal Veri Temsili)

Sun tarafından oluşturulan MicroSystems protokolü, makinelerden bağımsız standart bir veri temsilini tanımlar.

RPC: (Uzak Yordam Çağrısı)

Yine sun tarafından tanımlanan bu protokol, aktarım katmanından bağımsız olarak uzak uygulamalar arasında iletişim sağlayan bir protokoldür. Bu protokol önemlidir: programcının aktarım katmanının ayrıntılarını bilmesini gerektirmez ve uygulamaları taşınabilir hale getirir. Bu protokol, XDR protokolüne dayanır

NFS: Ağ Dosya Sistemi

Yine Sun tarafından tanımlanan bu protokol, bir makinenin başka bir makinenin dosya sistemini "görmesini" sağlar. Bu protokol, yukarıdaki RPC protokolüne dayanmaktadır.

8.1.9. Sonuç

Bu giriş bölümünde İnternet protokollerinin temel hatlarını ele aldık. Bu konuyu daha derinlemesine incelemek isteyenler, Douglas Comer'ın mükemmel kitabını okuyabilirler:

Başlık: TCP/IP: Mimari, Protokoller, Uygulamalar.

Yazar: Douglas COMER

Yayınevi: InterEditions

8.2. Java'da ağ adreslerinin yönetimi

8.2.1. Tanım

İnternetteki her makine, benzersiz bir adres veya adla tanımlanır. Bu iki öğe, Java'da InetAddress sınıfı tarafından yönetilir; bu sınıfın bazı yöntemleri şunlardır:

byte [] getAddress()
IP adresinin 4 baytını, InetAddress örneğinin geçerli adresi olarak verir
String getHostAddress()
InetAddress örneğinin IP adresini verir
String getHostName()
geçerli InetAddress örneğinin İnternet adını verir
String toString()
IP adres kimliğini/InetAddress örneğinin internet adını verir
InetAddress getByName(String Host)
Host ile belirtilen makinenin InetAddress örneğini oluşturur. Host bilinmiyorsa bir istisna oluşturur. Host, bir makinenin internet adı veya IP biçimindeki I1.I2.I3.I4 adresi olabilir
InetAddress getLocalHost()
bu komutu içeren programın çalıştığı makinenin InetAddress örneğini oluşturur.

8.2.2. Bazı örnekler

8.2.2.1. Yerel makineyi tanımlama


import java.net.*;

public class localhost{
  public static void main (String arg[]){
    try{
      InetAddress adresse=InetAddress.getLocalHost();
    byte[] IP=adresse.getAddress();
    System.out.print("IP=");
    int i;
    for(i=0;i<IP.length-1;i++) System.out.print(IP[i]+".");
    System.out.println(IP[i]);
      System.out.println("adresse="+adresse.getHostAddress());
    System.out.println("nom="+adresse.getHostName());
    System.out.println("identité="+adresse);
    } catch (UnknownHostException e){
      System.out.println ("Erreur getLocalHost : "+e);
    }// try bloğu sonu
  }// main sonu
}// class sonu

Çalıştırma sonuçları şöyledir:

IP=127.0.0.1
adresse=127.0.0.1
nom=tahe
identité=tahe/127.0.0.1

Her makinenin 127.0.0.1 olan bir dahili IP adresi vardır. Bir program bu ağ adresini kullandığında, üzerinde çalıştığı makineyi kullanır. Bu adresin avantajı, ağ kartı gerektirmemesidir. Böylece, bir ağa bağlı olmadan ağ programlarını test etmek mümkündür. Yerel bilgisayarı belirtmenin bir başka yolu da localhost adını kullanmaktır.

8.2.2.2. Herhangi bir bilgisayarı tanımlama


import java.net.*;

public class getbyname{
  public static void main (String arg[]){
    String nomMachine;
    // argümanı alınır
    if(arg.length==0) 
      nomMachine="localhost";
    else nomMachine=arg[0];
    // makinenin adresini almaya çalışıyoruz
    try{
      InetAddress adresse=InetAddress.getByName(nomMachine);
      System.out.println("IP : "+  adresse.getHostAddress());
      System.out.println("nom : "+ adresse.getHostName());
      System.out.println("identité : "+ adresse);
    } catch (UnknownHostException e){
      System.out.println ("Erreur getByName : "+e);
    }// try sonu
  }// main sonu
}// sınıf sonu

java getbyname çağrısıyla şu sonuçlar elde edilir:

IP : 127.0.0.1
nom : localhost
identité : localhost/127.0.0.1

java getbyname shiva.istia.univ-angers.fr çağrısı ile şu sonuçlar elde edilir:

IP : 193.52.43.5
nom : shiva.istia.univ-angers.fr
identité : shiva.istia.univ-angers.fr/193.52.43.5

java getbyname www.ibm.com çağrısı ile şu sonuç elde edilir:

IP : 204.146.18.33
nom : www.ibm.com
identité : www.ibm.com/204.146.18.33

8.3. İletişim TCP-IP

8.3.1. Genel Bilgiler

Image

A makinesindeki bir AppA uygulaması, İnternet'teki B makinesindeki bir AppB uygulamasıyla iletişim kurmak istediğinde, birkaç bilgiye sahip olması gerekir:

  • B makinesinin IP adresi veya adı
  • AppB uygulamasının çalıştığı bağlantı noktası numarası. Zira B makinesi, İnternet üzerinde çalışan birçok uygulamayı destekleyebilir. Ağdan gelen bilgileri aldığında, bu bilgilerin hangi uygulamaya yönelik olduğunu bilmesi gerekir. B makinesindeki uygulamalar, iletişim portları olarak da adlandırılan arayüzler aracılığıyla ağa erişir. Bu bilgi, B makinesi tarafından alınan pakette bulunur ve böylece paket doğru uygulamaya iletilebilir.
  • B makinesinin anladığı iletişim protokolleri. Çalışmamızda yalnızca TCP-IP protokollerini kullanacağız.
  • AppB uygulaması tarafından kabul edilen iletişim protokolü. Nitekim, A ve B makineleri birbirleriyle “iletişim kuracak”. Birbirlerine söyleyecekleri şeyler, TCP-IP protokollerinde kapsüllenecektir. Bununla birlikte, zincirin sonunda AppB uygulaması, AppA uygulaması tarafından gönderilen bilgiyi aldığında, bu bilgiyi yorumlayabilmesi gerekir. Bu durum, A ve B adlı iki kişinin telefonla iletişim kurmasına benzemektedir: diyalogları telefon aracılığıyla aktarılır. Konuşma, A’nın telefonunda sinyal biçiminde kodlanır, telefon hatları üzerinden taşınır ve B’nin telefonuna ulaştığında burada kod çözülür. B kişisi bu şekilde konuşmaları duyar. İşte burada “iletişim protokolü” kavramı devreye girer: Eğer A Fransızca konuşuyorsa ve B bu dili anlamıyorsa, A ve B arasında anlamlı bir iletişim kurulamaz.

Bu nedenle, iletişim kuran iki uygulama, hangi diyalog türünü benimseyecekleri konusunda anlaşmalıdır. Örneğin, ftp hizmetiyle yapılan diyalog, pop hizmetiyle yapılan diyalogla aynı değildir: bu iki hizmet aynı komutları kabul etmez. Her ikisinin de farklı bir iletişim protokolü vardır.

8.3.2. TCP protokolünün özellikleri

Burada yalnızca TCP aktarım protokolünü kullanan ağ iletişimlerini inceleyeceğiz. Bu protokolün özelliklerini hatırlayalım:

  • Veriyi göndermek isteyen işlem, öncelikle göndereceği bilgilerin alıcı işlemiyle bir bağlantı kurar. Bu bağlantı, gönderen makinenin bir bağlantı noktası ile alıcı makinenin bir bağlantı noktası arasında kurulur. İki bağlantı noktası arasında böylece sanal bir yol oluşturulur ve bu yol, bağlantıyı kuran sadece bu iki işleme ayrılır.
  • Kaynak işlem tarafından gönderilen tüm paketler bu sanal yolu takip eder ve gönderildikleri sırayla ulaşır
  • Gönderilen bilgi sürekli bir yapıya sahiptir. Gönderen süreç, kendi hızında bilgi gönderir. Bu bilgiler mutlaka hemen gönderilmez: TCP protokolü, gönderilmek üzere yeterli miktarda bilgi birikene kadar bekler. Bilgiler, TCP segmenti adı verilen bir yapıda depolanır. Bu segment dolduğunda, IP katmanına iletilir ve burada bir IP paketine kapsüllenir.
  • TCP protokolü tarafından gönderilen her segment numaralandırılır. Alıcı TCP protokolü, segmentleri sırayla aldığını doğrular. Düzgün bir şekilde alınan her segment için, gönderene bir alım onayı gönderir.
  • Gönderici, bu onayı aldığında bunu gönderen işleme bildirir. Böylece gönderen, bir segmentin başarıyla ulaştığını öğrenebilir.
  • Belirli bir süre geçtikten sonra, bir segmenti gönderen TCP protokolü bir onay mesajı almazsa, söz konusu segmenti yeniden gönderir ve böylece bilgi aktarım hizmetinin kalitesini garanti eder.
  • İletişim kuran iki işlem arasında kurulan sanal devre full-duplex'tir: bu, bilginin her iki yönde de aktarılabileceği anlamına gelir. Böylece, kaynak işlem bilgi göndermeye devam ederken hedef işlem de onay mesajları gönderebilir. Bu, örneğin kaynak TCP protokolünün, onay mesajını beklemeden birden fazla segment göndermesine olanak tanır. Belirli bir süre sonra n numaralı bir segmentin onay mesajını almadığını fark ederse, segmentlerin gönderimine bu noktadan itibaren devam eder.

8.3.3. İstemci-sunucu ilişkisi

İnternet üzerindeki iletişim genellikle asimetriktir: A makinesi, B makinesinden bir hizmet talep etmek üzere bir bağlantı başlatır; B makinesinin SB1 hizmetiyle bağlantı kurmak istediğini belirtir. B makinesi bunu kabul eder veya reddeder. Kabul ederse, A makinesi isteklerini SB1 hizmetine gönderebilir. Bu istekler, SB1 hizmeti tarafından anlaşılan iletişim protokolüne uymak zorundadır. Böylece, istemci makine olarak adlandırılan A makinesi ile sunucu makine olarak adlandırılan B makinesi arasında bir istek-yanıt diyaloğu kurulur. İki tarafın biri bağlantıyı sonlandıracaktır.

8.3.4. Bir istemcinin mimarisi

Bir sunucu uygulamasının hizmetlerini talep eden bir ağ programının mimarisi şu şekilde olacaktır:

ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
        tant que ce n'est pas fini
            préparer une demande
            l'émettre vers la machine B
            attendre et récupérer la réponse
            la traiter
        fin tant que
finsi

8.3.5. Bir sunucunun mimarisi

Hizmet sunan bir programın mimarisi şu şekilde olacaktır:

ouvrir le service sur la machine locale
tant que le service est ouvert
        se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
        lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que

Sunucu programı, bir istemcinin ilk bağlantı talebini, hizmet almak amacıyla yaptığı sonraki taleplerinden farklı şekilde işler. Program, hizmeti kendisi sağlamaz. Eğer bunu yapsaydı, hizmet süresince bağlantı taleplerini dinleyemezdi ve istemcilere hizmet verilemezdi. Bu nedenle farklı bir yol izler: Dinleme bağlantı noktasında bir bağlantı isteği alındığında ve kabul edildiğinde, sunucu, istemcinin talep ettiği hizmeti sunmakla görevli bir görev oluşturur. Bu hizmet, sunucu makinesinin hizmet bağlantı noktası olarak adlandırılan başka bir bağlantı noktası üzerinden sunulur. Böylelikle aynı anda birden fazla istemciye hizmet verilebilir.

Bir hizmet görevi aşağıdaki yapıya sahip olacaktır:

tant que le service n'a pas été rendu totalement
        attendre une demande sur le port de service
        lorsqu'il y en a une, élaborer la réponse
        transmettre la réponse via le port de service
fin tant que
libérer le port de service

8.3.6. Socket sınıfı

8.3.6.1. Définition

İnternet üzerinden iletişim kuran programlar tarafından kullanılan temel araç socket'tir. Bu İngilizce kelime "elektrik prizi" anlamına gelir. Burada anlamı genişletilerek "ağ prizi" olarak kullanılmaktadır. Bir uygulamanın İnternet ağı üzerinden bilgi gönderebilmesi ve alabilmesi için bir ağ prizine, yani bir socket'e ihtiyacı vardır. Bu araç ilk olarak Berkeley Üniversitesi'nin Unix sürümlerinde geliştirilmiştir. Daha sonra tüm Unix sistemlerine ve Windows ortamına da uyarlanmıştır. Ayrıca Java sanal makinelerinde iki farklı biçimde mevcuttur: istemci uygulamaları için Socket sınıfı ve sunucu uygulamaları için ServerSocket sınıfı. Burada, Socket sınıfının bazı oluşturucularını ve yöntemlerini açıklıyoruz:

public Socket(String host, int port)
host makinesinin port bağlantı noktası üzerinden bir uzaktan bağlantı kurar
public int getLocalPort()
soket tarafından kullanılan yerel bağlantı noktası numarasını döndürür
  
public int getPort()
soketin bağlı olduğu uzak bağlantı noktası numarasını döndürür
  
public InetAddress getLocalAdress()
soketin bağlı olduğu yerel adresi döndürür
  
public InetAddress getInetAdress()
soketin bağlı olduğu uzak adresi döndürür
  
public InputStream getInputStream()
uzaktaki ortağın gönderdiği verileri okumaya olanak tanıyan bir giriş akışı oluşturur
  
public OutputStream getOutputStream()
uzaktaki ortağa veri göndermek için bir çıkış akışı döndürür
  
public void shutdownInput()
soketin giriş akışını kapatır
  
public void shutdownOutput()
soketin çıkış akışını kapatır
  
public void close()
soketi ve G/Ç akışlarını kapatır
  
public String toString()
soketi "temsil eden" bir karakter dizisi döndürür
 

8.3.6.2. Bir sunucu makinesiyle bağlantı açma

Gördüğümüz gibi, A makinesinin B makinesindeki bir hizmete bağlantı açabilmesi için iki bilgiye ihtiyacı vardır:

  • B makinesinin adresi IP veya adı
  • istenen hizmetin çalıştığı bağlantı noktası numarası

Yapıcı

    public Socket(String  host, int  port);

bir soket oluşturur ve bunu host makinesine port bağlantı noktası üzerinden bağlar. Bu yapıcı, çeşitli durumlarda bir istisna oluşturur:

  • yanlış adres
  • yanlış bağlantı noktası
  • istek reddedildi

Bu istisnayı yönetmemiz gerekiyor:


    Socket  sClient=null;
    try{
        sClient=new Socket(host,port);
    } catch(Exception e){
        // bağlantı başarısız oldu - hata işleniyor
        ….
    }

Bağlantı isteği başarılı olursa, istemciye B makinesiyle iletişim kurması için yerel olarak bir bağlantı noktası atanır. Bağlantı kurulduktan sonra, bu bağlantı noktası şu yöntemle öğrenilebilir:

public int getLocalPort();

Bağlantı başarılı olursa, sunucunun kendi tarafında, hizmet olarak adlandırılan bir bağlantı noktasında çalışan başka bir görev aracılığıyla hizmeti sağladığını görmüştük. Bu bağlantı noktası numarası şu yöntemle öğrenilebilir:

public int getPort();

8.3.6.3. Ağa bilgi gönderme

Aşağıdaki yöntemle soket üzerinden ve dolayısıyla ağ üzerinden bir yazma akışı elde edilebilir:

public OutputStream getOutputStream();

Bu akışa gönderilen her şey, sunucu makinesinin hizmet bağlantı noktasında alınacaktır. Birçok uygulama, satır sonu ile biten metin satırları şeklinde bir diyaloga sahiptir. Bu nedenle, println yöntemi bu durumlarda oldukça kullanışlıdır. Böylece OutputStream çıkış akışını, println yöntemine sahip olan PrintWriter akışına dönüştürürüz. Yazma işlemi bir istisna oluşturabilir.

8.3.6.4. Ağdan gelen bilgileri okuma

Sokete gelen bilgileri okumak için şu yöntem kullanılabilir:

public InputStream getInputStream();

Bu akışta okunan her şey, sunucu makinesinin hizmet bağlantı noktasından gelir. Satır sonu ile biten metin satırları şeklinde diyalog içeren uygulamalar için readLine yöntemini kullanmak tercih edilir. Bunun için InputStream giriş akışını, readLine() yöntemine sahip olan BufferedReader akışına dönüştürürüz. Okuma işlemi bir istisna oluşturabilir.

8.3.6.5. Bağlantının kapatılması

Bu işlem şu yöntemle gerçekleştirilir:

public void close();

Bu yöntem bir istisna oluşturabilir. Kullanılan kaynaklar, özellikle ağ bağlantı noktası, serbest bırakılır.

8.3.6.6. İstemci mimarisi

Artık bir internet istemcisinin temel mimarisini tanımlamak için gerekli unsurlara sahibiz:


    Socket  sClient=null;
    try{
            // M makinesinin P bağlantı noktasında çalışan servise bağlanılıyor
        sClient=new Socket(M,P);

        // istemci soketinin giriş-çıkış akışları oluşturuluyor
        BufferedReader in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
        PrintWriter out=new PrintWriter(sClient.getOutputStream(),true);

        // istek-yanıt döngüsü
        boolean  fini=false;
        String demande;
        String réponse;
        while (! fini){
            // istek hazırlanıyor
            demande=…
            // istek gönderilir
            out.println(demande);
            // yanıt okunur
            réponse=in.readLine();
            // yanıt işleniyor

        }
        // işlem tamamlandı
        sClient.close();
    } catch(Exception e){
        // istisna işleniyor
        ….
    }

Örneği karmaşıklaştırmamak için, Socket oluşturucusu veya readline, getInputStream, getOutputStream ve close yöntemleri tarafından üretilen farklı istisna türlerini yönetmeye çalışmadık. Her şey tek bir istisna altında birleştirildi.

8.3.7. ServerSocket sınıfı

8.3.7.1. Définition

Bu sınıf, sunucu tarafında soketlerin yönetimi için tasarlanmıştır. Burada, bu sınıfın bazı oluşturucularını ve yöntemlerini açıklayacağız:

public ServerSocket(int port)
port bağlantı noktasında dinleme soketi oluşturur
public ServerSocket(int port, int count)
aynı işlevi yerine getirir, ancak count ile kuyruk boyutunu, c.a.d ile ise istemci bağlantısı geldiğinde sunucu meşgulse beklemeye alınacak maksimum istemci bağlantı sayısını belirler.
public int getLocalPort()
soket tarafından kullanılan dinleme bağlantı noktası numarasını döndürür
public InetAddress getInetAdress()
soketin bağlı olduğu yerel adresi döndürür
public Socket accept()
sunucuyu bağlantı bekleme moduna geçirir (engelleyici işlem). Bir istemci bağlantısı geldiğinde, istemciye hizmetin sunulacağı bir soket döndürür.
public void close()
soketi ve G/Ç akışlarını kapatır
public String toString()
soketi "temsil eden" bir karakter dizisi döndürür
public void close()
hizmet soketini kapatır ve ona bağlı kaynakları serbest bırakır

8.3.7.2. Hizmetin açılması

Aşağıdaki iki yapıcı ile gerçekleştirilir:

public ServerSocket(int  port);    
public ServerSocket(int  port, int  count);

port, hizmetin dinleme bağlantı noktasıdır: istemcilerin bağlantı isteklerini gönderdikleri bağlantı noktasıdır. count, hizmetin kuyruk kapasitesinin maksimum değeridir (varsayılan olarak 50); bu kuyruk, sunucunun henüz yanıtlamadığı istemcilerin bağlantı isteklerini depolar. Kuyruk dolduğunda, gelen bağlantı istekleri reddedilir. Her iki yapı da bir istisna oluşturur.

8.3.7.3. Bağlantı isteğinin kabul edilmesi

Bir istemci, hizmetin dinleme bağlantı noktası üzerinden bir bağlantı isteği gönderdiğinde, hizmet bu isteği şu yöntemle kabul eder:

    public Socket accept();

Bu yöntem, Socket nesnesini döndürür: bu, hizmetin sunulacağı sokettir; hizmet genellikle başka bir görev tarafından sunulur. Yöntem bir istisna oluşturabilir.

8.3.7.4. Hizmet soketi üzerinden okuma/yazma

Hizmet soketi, Socket sınıfının bir örneği olduğundan, bu konunun ele alındığı önceki bölümlere başvurulmalıdır.

8.3.7.5. Müşteriyi tanımlama

Hizmet soketi elde edildikten sonra, müşteri şu yöntemle tanımlanabilir:

    public InetAddress getInetAddress()

yöntemiyle tanımlanabilir. Böylece IP adresine ve müşterinin adına erişilebilir.

8.3.7.6. Hizmeti kapatma

Bu işlem,

    public void close();

yöntemiyle gerçekleştirilir. Bu işlem, kullanılan kaynakları, özellikle de dinleme bağlantı noktasını serbest bırakır. Yöntem bir istisna oluşturabilir.

8.3.7.7. Bir sunucunun temel mimarisi

Yukarıda anlatılanlardan yola çıkarak, bir sunucunun temel yapısını şu şekilde yazabiliriz:


SocketServer sEcoute=null;
try{
    // hizmet açılıyor
    int portEcoute=…
    int maxConnexions=…
    sEcoute=new ServerSocket(portEcoute,maxConnexions);

    // bağlantı istekleri işleniyor
    boolean fini=false;
    Socket sService=null;
    while( ! fini){
        // bir talebin bekletilmesi ve kabulü
        sService=sEcoute.accept();

        // hizmet, hizmet soketinin aktarıldığı başka bir görev tarafından sunulur
        new Service(sService).start();

        // bağlantı taleplerini bekleme durumuna geri dönülür
    }
    // işlem tamamlandı - hizmet sonlandırılıyor
    sEcoute.close();
} catch (Exception e){
    // istisna işlenir

}

Service sınıfı, aşağıdaki gibi görünebilecek bir thread'tir:


public class Service extends Thread{

    Socket sService;        // hizmet soketi

    // yapıcı
    public Service(Socket S){
        sService=S;
    }

// çalıştırma
public void run(){
    try{
        // giriş-çıkış akışları oluşturuluyor
    BufferedReader in=new BufferedReader(new InputStreamReader(sService.getInputStream()));
    PrinttWriter out=new PrintWriter(sService.getOutputStream(),true);

    // istek-yanıt döngüsü
    boolean  fini=false;
    String demande;
    String réponse;
    while (! fini){
        // isteği okunur
        demande=in.readLine();

        // işleniyor 


        // yanıt hazırlanıyor
        réponse=…

        // yanıt gönderiliyor
        out.println(réponse);
    }
    // işlem tamamlandı
    sService.close();
    } catch(Exception e){
    // istisna işleniyor
    ….
    }// try
} // çalıştır

8.4. Uygulamalar

8.4.1. Yankı Sunucusu

DOS penceresinden şu komutla başlatılacak bir yankı sunucusu yazmayı planlıyoruz:

    java serveurEcho port

Sunucu, parametre olarak geçirilen bağlantı noktasında çalışır. Sunucu, istemcinin kendisine gönderdiği isteği, istemcinin kimliği (IP+ad) ile birlikte istemciye geri gönderir. Bekleme kuyruğunda 2 bağlantı kabul eder. Burada bir TCP sunucusunun tüm bileşenleri mevcuttur. Program şu şekildedir:

// çağrı: serveurEcho bağlantı noktası
// yankı sunucusu
// istemciye, istemcinin kendisine gönderdiği satırı geri gönderir


import java.net.*;
import java.io.*;

public class serveurEcho{
    public final static String syntaxe="Syntaxe : serveurEcho port";
    public final static int nbConnexions=2;

    // ana program
    public static void main (String arg[]){

     // bir argüman var mı
     if(arg.length != 1)
        erreur(syntaxe,1);

     // bu argüman >0 bir tamsayı olmalıdır
     int port=0;
     boolean erreurPort=false;
     Exception E=null;
     try{
        port=Integer.parseInt(arg[0]);
     }catch(Exception e){
            E=e;
            erreurPort=true;
     }
     erreurPort=erreurPort || port <=0;
     if(erreurPort)
        erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

     // dinleme soketi oluşturulur
     ServerSocket ecoute=null;
     try{
        ecoute=new ServerSocket(port,nbConnexions);
     } catch (Exception e){
        erreur("Erreur lors de la création de la socket d'écoute ("+e+")",3);
     }

     // izleme
     System.out.println("Serveur d'écho lancé sur le port " + port);

     // hizmet döngüsü
     boolean serviceFini=false;
     Socket service=null;
     while (! serviceFini){
         // bir istemci bekleniyor
        try{
            service=ecoute.accept();
        } catch (IOException e){
                erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
        }

         // bağlantı tanımlanıyor
        try{
            System.out.println("Client ["+identifie(service.getInetAddress())+","+
            service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
            + "," + service.getLocalPort() + "]");
        } catch (Exception e) {
            erreur("identification liaison",1);
        }


         // hizmet başka bir görev tarafından sağlanıyor
        new traiteClientEcho(service).start();
     }// while döngüsünün sonu
    }// main sonu

// hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }

    // tanımlama
    private static String identifie(InetAddress Host){
        // ana bilgisayar tanımlama
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// class sonu


// yankı sunucusunun bir müşterisine hizmet sağlar

class traiteClientEcho extends Thread{

    private Socket service;            // hizmet soketi
    private BufferedReader in;        // giriş akışı
    private PrintWriter out;            // çıkış akışı

     // yapıcı
    public traiteClientEcho(Socket service){
        this.service=service;
    }

     // run yöntemi
    public void run(){

         // giriş ve çıkış akışlarının oluşturulması
        try{
            in=new BufferedReader(new InputStreamReader(service.getInputStream()));
        } catch (IOException e){
                erreur("Erreur lors de la création du flux déentrée de la socket de service ("+e+")",1);
        }// try sonu
        try{
            out=new PrintWriter(service.getOutputStream(),true);
        } catch (IOException e){
                erreur("Erreur lors de la création du flux de sortie de la socket de service ("+e+")",1);
        }// try sonu

         // bağlantı kimliği istemciye gönderilir
        try{
            out.println("Client ["+identifie(service.getInetAddress())+","+
            service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
            + "," + service.getLocalPort() + "]");
        } catch (Exception e) {
            erreur("identification liaison",1);
        }

         // istek/yazma-yanıt okuma döngüsü
        String demande,reponse;
        try{
             // istemci dosya sonu işaretini gönderdiğinde hizmet durur
            while ((demande=in.readLine())!=null){
                // isteğin yankısı
                reponse="["+demande+"]";
                out.println(reponse);
                 // istemci "son" gönderdiğinde hizmet durur
                if(demande.trim().toLowerCase().equals("fin")) break;
            }// while döngüsünün sonu
        } catch (IOException e){
                erreur("Erreur lors des échanges client/serveur ("+e+")",3);
        }// try sonu

         // soket kapatılıyor
        try{
            service.close();
        } catch (IOException e){
            erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
        }// try sonu
    }// çalıştırma sonu

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }// hata sonu

     // kimlik belirleme
    private String identifie(InetAddress Host){
         // ana bilgisayar tanımlama
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// sınıf sonu

Hizmet için gerekli olan iki sınıf, tek bir kaynak dosyada birleştirilmiştir. Bunlardan yalnızca biri, yani main işlevine sahip olan sınıf, public özniteliğine sahiptir. Sunucunun yapısı, genel TCP sunucu mimarisine uygundur. Sunucu ile bir istemci arasındaki bağlantıyı tanımlamaya yarayan bir yöntem (identifie) eklenmiştir. İşte bazı sonuçlar:

Sunucu şu komutla başlatılır:

    java serveurEcho 187

Ardından kontrol penceresinde şu mesaj görüntülenir:

Serveur d'écho lancé sur le port 187

Bu sunucuyu test etmek için, hem Unix hem de Windows’ta bulunan telnet programı kullanılır. Telnet, iletişimlerinde satır sonu işaretiyle biten metin satırlarını kabul eden tüm sunuculara uygun, evrensel bir TCP istemcisidir. Echo sunucumuz da buna bir örnektir. Windows (bu örnekte Windows 2000) üzerinde, bir DOS penceresine telnet yazarak ilk telnet istemcisini başlatıyoruz:


DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numéro 2195)
Client Telnet Microsoft
Client Telnet numéro 5.00.99203.1

Le caractère d'échappement est 'CTRL+$'

Microsoft Telnet> help

Les commandes peuvent être abrégées. Les commandes prises en charge sont :

close           ferme la connexion en cours
display         affiche les paramètres d'opération
open            ouvre une connexion à un site
quit            quitte telnet
set             définit les options (entrez 'set ?' pour afficher la liste)
status          affiche les informations d'état
unset           annule les options (entrez 'unset ?' pour afficher la liste)
? ou help       affiche des informations d'aide

Microsoft Telnet> set ?
NTLM            Active l'authentification NTLM.
LOCAL_ECHO      Active l'écho local.
TERM x          (où x est ANSI, VT100, VT52 ou VTNT))
CRLF            Envoi de CR et de LF

Microsoft Telnet> set local_echo

Microsoft Telnet> open localhost 187

telnet programı, varsayılan olarak klavyeden girilen komutları ekrana yansıtmaz. Bu yansıma özelliğini etkinleştirmek için şu komutu girin:

Microsoft Telnet> set local_echo

Sunucuyla bağlantı kurmak için, yankı hizmetinin bağlantı noktasını (187) ve sunucunun bulunduğu makinenin adresini (localhost) belirterek şu komutu verin:

Microsoft Telnet> open localhost 187

İstemcinin DOS penceresinde şu mesaj görüntülenir:

Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]

Sunucu penceresinde şu mesaj görüntülenir:

Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]

Burada tahe ve localhost aynı makineyi ifade eder. telnet istemci penceresinde metin satırları girebiliriz. Sunucu bunları yankı olarak geri gönderir:

Client [127.0.0.1,tahe,1059] connectÚ au serveur [127.0.0.1,tahe,187]
je suis là
[je suis là]
au revoir
[au revoir]

İstemcinin bağlantı noktası (1059) doğru bir şekilde algılanmış olsa da, hizmet bağlantı noktası (187) dinleme bağlantı noktasıyla (187) aynıdır; bu beklenmedik bir durumdur. Aslında, dinleme bağlantı noktası yerine hizmet soketinin bağlantı noktasını elde etmemiz beklenirdi. Unix altında da aynı sonuçların elde edilip edilmediğini kontrol etmek gerekir. Şimdi ikinci bir istemciyi çalıştıralım: telnet. Sunucu penceresi şu şekilde görünür:

Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]

İkinci istemcinin penceresine de metin satırları girebiliriz:

Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]

Böylece, yankı sunucusunun aynı anda birden fazla istemciye hizmet verebileceği görülmektedir. telnet istemcileri, çalıştıkları DOS penceresi kapatılarak sonlandırılabilir.

8.4.2. Yankı sunucusu için bir Java istemcisi

Önceki bölümde, yankı hizmetini test etmek için bir telnet istemcisi kullandık. Şimdi kendi istemcimizi yazıyoruz:

// çağrı: clientEcho makine bağlantı noktası
// yankı sunucusunun istemcisi
// satırları sunucuya gönderir, sunucu da bunları yankılar

import java.net.*;
import java.io.*;

public class clientEcho{
    public final static String syntaxe="Syntaxe : clientEcho machine port";

    // ana program    
    public static void main (String arg[]){

     // iki argüman var 
     if(arg.length != 2)
        erreur(syntaxe,1);

     // ilk argüman mevcut bir makinenin adı olmalıdır
    String machine=arg[0];
    InetAddress serveurAddress=null;
    try{
        serveurAddress=InetAddress.getByName(machine);
    } catch (Exception e){
        erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
    }

     // bağlantı noktası >0 olan bir tamsayı olmalıdır
     int port=0;
     boolean erreurPort=false;
     Exception E=null;
     try{
        port=Integer.parseInt(arg[1]);
     }catch(Exception e){
            E=e;
            erreurPort=true;
     }
     erreurPort=erreurPort || port <=0;
     if(erreurPort)
        erreur(syntaxe+"\nPort incorrect ("+E+")",3);

     // sunucuya bağlanılıyor
     Socket sClient=null;
     try{
        sClient=new Socket(machine,port);
     } catch (Exception e){
        erreur("Erreur lors de la création de la socket de communication ("+e+")",4);
     }

     // bağlantı tanımlanıyor
    try{
        System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
        sClient.getLocalPort()+"] connecté au serveur [" + identifie (sClient.getInetAddress())
        + "," + sClient.getPort() + "]");
    } catch (Exception e) {
        erreur("identification liaison ("+e+")",5);
    }

     // klavyeden girilen satırların okuma akışı oluşturuluyor
    BufferedReader IN=null;
    try{
        IN=new BufferedReader(new InputStreamReader(System.in));
    } catch (Exception e){
        erreur("Création du flux d'entrée clavier ("+e+")",6);
    }
     // istemci soketiyle ilişkili giriş akışının oluşturulması
    BufferedReader in=null;
    try{
        in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
    } catch (Exception e){
        erreur("Création du flux d'entrée de la socket client("+e+")",7);
    }
     // istemci soketiyle ilişkili çıkış akışının oluşturulması
    PrintWriter out=null;
    try{
        out=new PrintWriter(sClient.getOutputStream(),true);
    } catch (Exception e){
        erreur("Création du flux de sortie de la socket ("+e+")",8);
    }

     // istek-yanıt döngüsü
    boolean serviceFini=false;
    String demande=null;
    String reponse=null;

    // bağlantı kurulduktan hemen sonra sunucu tarafından gönderilen mesajın okunması 
    try{
        reponse=in.readLine();
    } catch (IOException e){
            erreur("Lecture réponse ("+e+")",4);
    }        

     // yanıtın görüntülenmesi
    System.out.println("Serveur : " +reponse);

    while (! serviceFini){
         // klavyeden girilen bir satırın okunması
        System.out.print("Client : ");
        try{
            demande=IN.readLine();
        } catch (Exception e){
            erreur("Lecture ligne ("+e+")",9);
        }
         // ağa istek gönderme
        try{
            out.println(demande);
        } catch (Exception e){
            erreur("Envoi demande ("+e+")",10);
        }
         // yanıtı bekleme/okuma
        try{
            reponse=in.readLine();
        } catch (IOException e){
                erreur("Lecture réponse ("+e+")",4);
        }
         // yanıtın görüntülenmesi
        System.out.println("Serveur : " +reponse);
         // bitti mi?
        if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
    }
     // bitti
    try{
        sClient.close();
    } catch(Exception e){
        erreur("Fermeture socket ("+e+")",11);
    }
}// ana sayfa

// hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }

    // kimlik
    private static String identifie(InetAddress Host){
        // Ana Bilgisayar Kimliği
        String ipHost=Host.getHostAddress();
        String nomHost=Host.getHostName();
        String idHost;
        if (nomHost == null) idHost=ipHost;
            else idHost=ipHost+","+nomHost;
        return idHost;
    }

}// sınıf sonu

Bu istemcinin yapısı, tcp istemcilerinin genel mimarisine uygundur. Burada, olası farklı istisnalar tek tek ele alınmıştır, bu da programı ağırlaştırmaktadır. Bu istemciyi test ettiğimizde elde edilen sonuçlar şunlardır:

Client : Client [127.0.0.1,tahe,1045] connecté au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connectÚ au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis là
Serveur : [je suis là]
Client : fin
Serveur : [fin]

Client ile başlayan satırlar istemci tarafından gönderilen satırlardır; Serveur ile başlayan satırlar ise sunucu tarafından yankılanan satırlardır.

8.4.3. Genel bir TCP istemcisi

İnternetin ilk dönemlerinde oluşturulan birçok hizmet, daha önce incelediğimiz yankı sunucusu modeline göre çalışır: istemci-sunucu iletişimi, metin satırlarının karşılıklı olarak gönderilmesi yoluyla gerçekleşir. Aşağıdaki şekilde başlatılacak genel bir TCP istemcisi yazacağız: java cltTCPgenerique sunucu bağlantı noktası

Bu TCP istemcisi, serveur sunucusunun port bağlantı noktasına bağlanacaktır. Bağlantı kurulduktan sonra, iki iş parçacığı oluşturacaktır:

  1. klavyeden girilen komutları okumak ve sunucuya göndermekle görevli bir iş parçacığı
  2. sunucudan gelen yanıtları okumak ve bunları ekranda görüntülemekle görevli bir iş parçacığı

Önceki uygulamada böyle bir ihtiyaç duyulmamışken neden iki iş parçacığı gerekiyor? Önceki uygulamada iletişim protokolü belliydi: istemci tek bir satır gönderir, sunucu da tek bir satırla yanıt verirdi. Her hizmetin kendine özgü bir protokolü vardır ve aşağıdaki durumlarla da karşılaşılabilir:

  • istemcinin yanıt alabilmesi için birkaç satır metin göndermesi gerekir
  • sunucunun yanıtı birden fazla satır metin içerebilir

Bu nedenle, sunucuya tek bir satır gönderme - sunucudan gönderilen tek bir satırı alma döngüsü her zaman uygun olmayabilir. Dolayısıyla, birbirinden bağımsız iki döngü oluşturacağız:

  • klavyeden girilen ve sunucuya gönderilecek komutları okuma döngüsü. Kullanıcı, fin anahtar kelimesiyle komutların bittiğini bildirecektir.
  • sunucudan gelen yanıtları alıp görüntüleyen bir döngü. Bu, sunucu tarafından ağ akışının kapatılmasıyla veya kullanıcı tarafından klavyeden fin komutunun girilmesiyle sonlandırılacak sonsuz bir döngü olacaktır.

Bu iki döngüyü birbirinden ayırmak için iki bağımsız iş parçacığına ihtiyacımız var. Genel tcp istemcimizin SMTP (SendMail Aktarım Protokolü) hizmetine bağlandığı bir yürütme örneğini inceleyelim. Bu hizmet, e-postaların alıcılara iletilmesinden sorumludur. 25 numaralı bağlantı noktasında çalışır ve metin satırları alışverişi türünde bir iletişim protokolüne sahiptir.


Dos>java clientTCPgenerique istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test

ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Bu istemci-sunucu iletişimlerini inceleyelim:

  • SMTP hizmeti, bir istemci ona bağlandığında bir hoş geldiniz mesajı gönderir:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • Bazı hizmetlerde, hizmetle kullanılabilecek komutlar hakkında bilgi veren help komutu bulunur. Burada durum böyle değildir. Örnekte kullanılan SMTP komutları şunlardır:
    • mail from: expéditeur, mesajın göndereninin e-posta adresini belirtmek için
    • rcpt to: destinataire, mesajın alıcısının e-posta adresini belirtmek için kullanılır. Birden fazla alıcı varsa, her alıcı için rcpt to: komutu gerektiği kadar tekrar edilir.
    • data, SMTP sunucusuna mesajın gönderileceğini bildirir. Sunucunun yanıtında belirtildiği gibi, bu mesaj, sadece nokta karakterini içeren bir satırla biten bir dizi satırdan oluşur. Bir mesajın, mesaj gövdesinden boş bir satırla ayrılmış başlıkları olabilir. Örneğimizde, Subject: anahtar kelimesini kullanarak bir konu başlığı ekledik:
  • Mesaj gönderildikten sonra, quit komutuyla sunucuya işin bittiğini bildirebiliriz. Sunucu daha sonra ağ bağlantısını kapatır. Okuma iş parçacığı bu olayı algılayabilir ve durabilir.
  • Kullanıcı, klavyeden "fin" yazarak klavyeden girilen komutları okuma iş parçacığını da durdurabilir.

Alınan postayı kontrol edersek, şunu görürüz (Outlook):

Image

SMTP hizmetinin, bir gönderenin geçerli olup olmadığını algılayamadığı dikkatinizi çekecektir. Bu nedenle, bir iletinin from alanına asla güvenilemez. Burada machin@univ-angers.fr göndereni mevcut değildi.

Bu genel tcp istemcisi, internet hizmetlerinin iletişim protokolünü keşfetmemizi ve buradan yola çıkarak bu hizmetlerin istemcileri için özel sınıflar oluşturmamızı sağlayabilir. Bir sunucuda depolanan e-postaları bulmamızı sağlayan POP hizmetinin (Post Office Protocol) iletişim protokolünü inceleyelim. Bu hizmet 110 numaralı bağlantı noktasında çalışır.


Dos> java clientTCPgenerique istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<--     by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<--     Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<--     by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<--     Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2                  Tl.  : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 ,  Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Başlıca komutlar şunlardır:

  • user login; burada, e-postalarımızın bulunduğu sunucudaki kullanıcı adımızı gireriz
  • password şifresini girin; burada, önceki oturum açma bilgisine ait şifre girilir
  • list, mesajların numara ve bayt cinsinden boyut bilgilerini içeren listesini almak için
  • retr i, i numaralı mesajı okumak için
  • quit, diyaloğu sonlandırmak için.

Şimdi, genellikle 80 numaralı bağlantı noktasında çalışan bir web sunucusu ile istemci arasındaki iletişim protokolünü inceleyelim:


Dos> java clientTCPgenerique istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise  jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Bir web istemcisi, komutlarını aşağıdaki şemaya göre sunucuya gönderir:

commande1
commande2
...
commanden
[ligne vide]

Web sunucusu ancak boş satırı aldıktan sonra yanıt verir. Örnekte sadece bir komut kullandık:

GET /index.html HTTP/1.0

Bu komut, sunucudan URL /index.html dosyasını ister ve HTTP protokolünün 1.0 sürümüyle çalıştığını belirtir. Bu protokolün en güncel sürümü 1.1'dir. Örnekte, sunucunun index.html dosyasının içeriğini geri göndererek yanıt verdiği ve ardından yanıt okuma iş parçacığının sona erdiği görüldüğünden bağlantıyı kapattığı görülmektedir. index.html dosyasının içeriğini göndermeden önce, web sunucusu boş bir satırla biten bir dizi başlık göndermiştir:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

<html> satırı, /index.html dosyasının ilk satırıdır. Yukarıdakiler, HTTP (HyperText Aktarım Protokolü) başlıkları olarak adlandırılır. Bu başlıkları burada ayrıntılı olarak ele almayacağız, ancak genel amaçlı istemcimizin bunlara erişim sağladığını hatırlayalım; bu, başlıkları anlamak açısından yararlı olabilir. Örneğin ilk satır:

<-- HTTP/1.1 200 OK

, erişilen web sunucusunun HTTP/1.1 protokolünü anladığını ve istenen dosyayı (200 OK) başarıyla bulduğunu gösterir; burada 200, bir HTTP yanıt kodudur. Şu satırlar:

<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html

satırları, istemciye HTML (HyperText İşaretleme Dili) metnini temsil eden 11251 bayt alacağını ve gönderimin sonunda bağlantının kapatılacağını bildirir.

Dolayısıyla burada çok kullanışlı bir TCP istemcisiyle karşı karşıyayız. Şüphesiz daha önce kullandığımız telnet programından daha az işlev görüyor, ancak bunu kendimiz yazmak ilginçti. Genel amaçlı TCP istemci programı şöyledir:

// ithal edilen paketler
import java.io.*;
import java.net.*;

public class clientTCPgenerique{

    // bir hizmetin özelliklerini şu biçimde parametre olarak alır
     // sunucu bağlantı noktası
     // hizmete bağlanır
     // klavyeden girilen komutları okumak için bir iş parçacığı oluşturur
     // bu komutlar sunucuya gönderilir
     // sunucudan gelen yanıtları okumak için bir iş parçacığı oluşturur
     // bu yanıtlar ekranda görüntülenecek
     // tüm işlem, klavyeden girilen "fin" komutuyla sona erer

   // örnek değişkeni
  private static Socket client;

    public static void main(String[] args){

         // sözdizimi
        final String syntaxe="pg serveur port";

         // argüman sayısı
        if(args.length != 2)
            erreur(syntaxe,1);

         // sunucu adı not edilir
        String serveur=args[0];

         // bağlantı noktası >0 olan bir tamsayı olmalıdır
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[1]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

        client=null;
         // sorunlar yaşanabilir
        try{
             // hizmete bağlanılır
            client=new Socket(serveur,port);
        }catch(Exception ex){
             // hata
            erreur("Impossible de se connecter au service ("+ serveur
                +","+port+"), erreur : "+ex.getMessage(),3);
             // son
            return;
        }//yakalama

         // okuma/yazma iş parçacıkları oluşturuluyor
    new ClientSend(client).start();
    new ClientReceive(client).start();

        // ana iş parçacığı sonu
        return;
    }// ana iş parçacığı

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
         // hata görüntüleme
        System.err.println(msg);
         // hata ile durdurma
        System.exit(exitCode);
    }//hata
}//sınıf  

class ClientSend extends Thread {
    // klavyeden girilen komutları okumakla görevli sınıf
     // ve bunları parametre olarak geçirilen bir TCP istemcisi aracılığıyla sunucuya göndermekle görevli sınıf

    private Socket client;    // TCP istemcisi

     // yapıcı
    public ClientSend(Socket client){
         // burada TCP istemcisi belirtilir
        this.client=client;
    }//oluşturucu

     // iş parçacığının Run yöntemi
    public void run(){

        // yerel veriler
        PrintWriter OUT=null;            // ağ yazma akışı
    BufferedReader IN=null;        // klavye akışı
        String commande=null;            // klavyeden okunan komut

         // hata yönetimi
        try{
             // ağ yazma akışının oluşturulması
            OUT=new PrintWriter(client.getOutputStream(),true);
      // klavye giriş akışının oluşturulması
      IN=new BufferedReader(new InputStreamReader(System.in));
            // komut girişi-gönderme döngüsü
            System.out.println("Commandes : ");
            while(true){
                 // klavyeden girilen komutun okunması
                commande=IN.readLine().trim();
                // bitti mi?
                if (commande.toLowerCase().equals("fin")) break;
                // komutu sunucuya gönderme
                OUT.println(commande);
                 // sonraki komut
            }//while
        }catch(Exception ex){
             // hata
            System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
        }//catch
         // son - akışlar kapatılıyor
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         // iş parçacığının sonu bildiriliyor
        System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
    }//çalıştır
}//sınıf

class ClientReceive extends Thread{
    // bir 
     // parametre olarak geçirilen bir TCP istemcisine yönelik metin satırlarını okumakla görevli sınıf

    private Socket client;    // TCP istemcisi

     // yapıcı
    public ClientReceive(Socket client){
         // TCP istemcisi kaydedilir
        this.client=client;
    }//oluşturucu

     // iş parçacığının Run yöntemi
    public void run(){

        // yerel veriler
        BufferedReader IN=null;        // ağ okuma akışı
        String réponse=null;        // sunucu yanıtı

         // hata yönetimi
        try{
             // ağ okuma akışının oluşturulması
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // IN akışındaki metin satırlarını okuma döngüsü
            while(true){
                 // ağ akışını okuma
                réponse=IN.readLine();
                 // akış kapalı mı?
                if(réponse==null) break;
                // görüntüleme
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // hata
            System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
        }//yakalama
         // son - akışlar kapatılıyor
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         // iş parçacığının sonu bildiriliyor
        System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
    }//çalıştır
}//sınıf

8.4.4. Genel bir TCP sunucusu

Şimdi,

  • bu sunucu, istemcilerinden gelen komutları ekrana yansıtır
  • ve yanıt olarak bir kullanıcının klavyeden girdiği metin satırlarını onlara gönderen bir sunucuya odaklanacağız. Dolayısıyla sunucu rolünü üstlenen kişi kullanıcıdır.

Program şu komutla çalıştırılır: java serveurTCPgenerique portEcoute; burada portEcoute, istemcilerin bağlanması gereken bağlantı noktasıdır. İstemciye hizmet, iki iş parçacığı tarafından sağlanacaktır:

  • bir iş parçacığı, yalnızca istemci tarafından gönderilen metin satırlarını okumaya adanmıştır
  • diğeri ise yalnızca kullanıcının klavyeden girdiği yanıtları okumaya adanmış bir iş parçacığıdır. Bu iş parçacığı, fin komutuyla istemciyle olan bağlantıyı sonlandıracağını bildirir.

Sunucu, her müşteri için iki iş parçacığı oluşturur. n müşteri varsa, aynı anda 2n aktif iş parçacığı olacaktır. Sunucu, kullanıcı tarafından klavyeden Ctrl-C tuşlarına basılmadıkça asla durmaz. Birkaç örneğe bakalım.

Sunucu 100 numaralı bağlantı noktasında çalıştırılır ve sunucuyla iletişim kurmak için genel amaçlı istemci kullanılır. İstemci penceresi şöyledir:

E:\data\serge\MSNET\c#\ağ\genel tcp istemcisi> java clientTCPgenerique localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

<-- ile başlayan satırlar sunucudan istemciye gönderilenler, diğerleri ise istemciden sunucuya gönderilenlerdir. Sunucu penceresi şu şekildedir:


Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]

<-- ile başlayan satırlar, istemciden sunucuya gönderilen satırlardır. N: ile başlayan satırlar, sunucudan N numaralı istemciye gönderilen satırlardır. Yukarıdaki sunucu hâlâ aktifken, istemci 1 işlemi tamamlanmıştır. Aynı sunucu için ikinci bir istemci başlatılır:


Dos> java clientTCPgenerique localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]

Sunucu penceresi şu şekildedir:


Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C

Şimdi, genel sunucumuzu 88 numaralı bağlantı noktasında başlatarak bir web sunucusu simülasyonu yapalım:


Dos> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88

Şimdi bir tarayıcı açalım ve URL adresindeki http://localhost:88/exemple.html sayfasını isteyelim. Tarayıcı, localhost makinesinin 88 bağlantı noktasına bağlanacak ve ardından /exemple.html sayfasını isteyecektir:

Image

Şimdi sunucumuzun penceresine bakalım:

Dos>java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Böylece tarayıcı tarafından gönderilen HTTP başlıklarını görüyoruz. Bu, HTTP protokolünü yavaş yavaş keşfetmemizi sağlıyor. Önceki bir örnekte, yalnızca GET komutunu gönderen bir web istemcisi oluşturmuştuk. Bu yeterli olmuştu. Burada tarayıcının sunucuya başka bilgiler de gönderdiğini görüyoruz. Bu bilgilerin amacı, sunucuya karşısındaki istemcinin türünü belirtmektir. Ayrıca HTTP başlıklarının boş bir satırla bittiğini de görüyoruz.

Müşterimize bir yanıt hazırlayalım. Burada klavyeyi kullanan kullanıcı asıl sunucudur ve yanıtı elle hazırlayabilir. Önceki bir örnekte bir web sunucusu tarafından verilen yanıtı hatırlayalım:

<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix)  (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>

Benzer bir cevap vermeye çalışalım:

...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]

2: ile başlayan satırlar, sunucudan 2 numaralı istemciye gönderilir. fin komutu, sunucuyla istemci arasındaki bağlantıyı sonlandırır. Yanıtımızda aşağıdaki HTTP başlıklarıyla sınırlı kaldık:

HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :

Göndereceğimiz dosyanın boyutunu (Content-Length) belirtmiyoruz, ancak dosyayı gönderdikten sonra bağlantıyı kapatacağımızı (Connection: close) söylüyoruz. Bu, tarayıcı için yeterlidir. Bağlantının kapandığını görünce, tarayıcı sunucudan gelen yanıtın tamamlandığını anlayacak ve kendisine gönderilen HTML sayfasını görüntüleyecektir. Bu sayfa şöyledir:

2 : <html>
2 :   <head><title>Serveur generique</title></head>
2 :   <body>
2 :     <center>
2 :       <h2>Reponse du serveur generique</h2>
2 :     </center>
2 :    </body>
2 : </html>

Kullanıcı daha sonra fin komutunu girerek istemciyle olan bağlantıyı kapatır. Tarayıcı, sunucudan gelen yanıtın tamamlandığını anlar ve yanıtı görüntüleyebilir:

Image

Yukarıdaki örnekte, tarayıcının ne aldığını görmek için View/Source komutunu girersek şu sonucu alırız:

Image

yani genel sunucudan gönderdiğimizin aynısı.

Genel TCP sunucusunun kodu şöyledir:

// paketler
import java.io.*;
import java.net.*;

public class serveurTCPgenerique{

    // ana program
    public static void main (String[] args){

    // dinleme bağlantı noktasını alır ve istemcilerin isteklerini dinler
     // istemcilerin isteklerini okumak için bir  parçacığı oluşturur
     // bu istekler ekranda görüntülenecek
     // klavyeden girilen komutları okumak için bir  parçacığı oluşturur
     // bunlar müşteriye yanıt olarak gönderilecektir
     // tüm işlem, klavyeden girilen "fin" komutuyla sona erer

    final String syntaxe="Syntaxe : pg port";
   // örnek değişkeni
         // bir argüman var 
     if(args.length != 1)
        erreur(syntaxe,1);

         // bağlantı noktası >0 olan bir tamsayı olmalıdır
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[0]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

     // dinleme hizmeti oluşturulur
    ServerSocket ecoute=null;
    int nbClients=0;    // işlenen istemci sayısı
        try{
             // hizmet oluşturuluyor
            ecoute=new ServerSocket(port);
             // izleme
            System.out.println("Serveur générique lancé sur le port " + port);

             // müşterilere hizmet döngüsü
            Socket client=null;
            while (true){ // sonsuz döngü - Ctrl-C ile durdurulur
                 // bir istemciyi bekliyor
                client=ecoute.accept();

                 // hizmet, ayrı  parçacıklarıyla sağlanır
                nbClients++;

                 // okuma/yazma  parçacıkları oluşturuluyor
        new ServeurSend(client,nbClients).start();
        new ServeurReceive(client,nbClients).start();

                // istekleri dinlemeye geri dönülüyor
            }// while döngüsünün sonu
        }catch(Exception ex){
             // hata bildirilir
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }// main sonu

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
         // hatanın görüntülenmesi
        System.err.println(msg);
         // hata ile durdurma
        System.exit(exitCode);
    }//hata
}//sınıf

class ServeurSend extends Thread{
    // klavyeden girilen yanıtları okumakla görevli sınıf
     // ve bunları yapıcıya aktarılan bir TCP istemcisi aracılığıyla bir istemciye göndermekle görevli sınıf

    Socket client;    // TCP istemcisi
    int numClient;        // istemci numarası

     // yapıcı
    public ServeurSend(Socket client, int numClient){
         // burada TCP istemcisi not edilir
        this.client=client;
         // ve numarası
        this.numClient=numClient;
    }//üretici

     //  parçacığının Run yöntemi
    public void run(){

        // yerel veriler
        PrintWriter OUT=null;        //  yazma akışı
        String réponse=null;        // klavyeden okunan yanıt
    BufferedReader IN=null;    // klavye akışı

        // izleme
        System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
         // hata yönetimi
        try{
             //  yazma akışının oluşturulması
            OUT=new PrintWriter(client.getOutputStream(),true);
      // klavye akışının oluşturulması
      IN=new BufferedReader(new InputStreamReader(System.in));
            // komut girişi-gönderme döngüsü
            while(true){
                 // müşteri kimlik doğrulaması
                System.out.print("--> " + numClient + " : ");
                 // klavyeden girilen yanıtın okunması
                réponse=IN.readLine().trim();
                // bitti mi?
                if (réponse.toLowerCase().equals("fin")) break;
                // yanıtın sunucuya gönderilmesi
                OUT.println(réponse);
                 // sonraki yanıt
            }//while
        }catch(Exception ex){
             // hata
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//yakalama
         // son - akışlar kapatılıyor
        try{
            OUT.close();client.close();
        }catch(Exception ex){}
         //  parçacığının sonu bildiriliyor
        System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
    }//çalıştır
}//sınıf

class ServeurReceive extends Thread{
    // sunucuya gönderilen metin satırlarını okumakla görevli sınıf 
     // yapıcıya aktarılan bir TCP istemcisi aracılığıyla

    Socket client;    // TCP istemcisi
    int numClient;        // istemci numarası

     // yapıcı
    public ServeurReceive(Socket client, int numClient){
         // TCP istemcisi not edilir
        this.client=client;
         // ve numarası
        this.numClient=numClient;
    }//üretici

     //  parçacığının Run yöntemi
    public void run(){

        // yerel veriler
        BufferedReader IN=null;        //  okuma akışı
        String réponse=null;        // sunucu yanıtı

         // izleme
        System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
         // hata yönetimi
        try{
             //  okuma akışının oluşturulması
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            // IN akışındaki metin satırlarını okuma döngüsü
            while(true){
                 //  akışını okuma
                réponse=IN.readLine();
                 // akış kapalı ?
                if(réponse==null) break;
                // görüntüleme
                System.out.println("<-- "+réponse);
            }//while
        }catch(Exception ex){
            // hata
            System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
        }//yakalama
         // son - akışlar kapatılıyor
        try{
            IN.close();client.close();
        }catch(Exception ex){}
         //  parçacığının sonu bildiriliyor
        System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
    }//çalıştır
}//sınıf

8.4.5. Bir web istemcisi

Önceki örnekte, bir tarayıcının gönderdiği HTTP başlıklarından bazılarını görmüştük:

<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

URL parametresini alan ve bu URL'in içeriğini ekranda görüntüleyen bir web istemcisi yazacağız. URL için bağlantı kurulan web sunucusunun HTTP 1.1 protokolünü desteklediğini varsayacağız. Yukarıdaki başlıklardan yalnızca aşağıdakileri kullanacağız:

<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
  • İlk başlık, hangi sayfayı istediğimizi belirtir
  • ikincisi, hangi sunucuya sorgu gönderdiğimizi
  • üçüncüsü ise, sunucunun yanıt verdikten sonra bağlantıyı kapatmasını istediğimizi belirtir.

Yukarıdaki örnekte, GET'i HEAD ile değiştirirsek, sunucu bize sadece HTTP başlıklarını gönderir, HTML sayfasını göndermez.

Web istemcimiz şu şekilde çağrılacaktır: java clientweb URL cmd; burada URL,istenen URL'tir ve cmd, sadece başlıkları (HEAD) mı yoksa sayfanın içeriğini de (GET) mi istediğimizi belirtmek için GET veya HEAD anahtar kelimelerinden biridir (GET). İlk örneğe bir göz atalım. Aynı makinede önce IIS sunucusunu, ardından web istemcisini başlatıyoruz:

dos>java clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private

Cevap

HTTP/1.1 302 Object moved

, istenen sayfanın yerinin değiştiği anlamına gelir (yani URL'ten). Yeni adres olan URL, Location: başlığıyla verilir:

Location: /IISSamples/Default/welcome.htm

Web istemcisine yapılan çağrıda HEAD yerine GET kullanırsak:

dos>java clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private

<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

HEAD ile elde ettiğimiz sonuçla aynı sonucu elde ediyoruz, buna ek olarak HTML sayfasının gövdesi de ekleniyor. Program şu şekildedir:

// ithal edilen paketler
import java.io.*;
import java.net.*;

public class clientweb{

    // URL'i ister
     // içeriğini ekranda görüntüler

    public static void main(String[] args){
        // sözdizimi
        final String syntaxe="pg URI GET/HEAD";

        // argüman sayısı
        if(args.length != 2)
            erreur(syntaxe,1);

         // URI'in talep edildiği belirtilmiştir
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // URI'in geçerliliğinin kontrolü
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI hatalı
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//yakalama
         // siparişin doğrulanması
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // sipariş hatalı
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // URL'ten yararlı bilgiler çıkarılıyor
    String path=url.getPath();
    if(path.equals("")) path="/";
    String query=url.getQuery();
    if(query!=null) query="?"+query; else query="";
    String host=url.getHost();
    int port=url.getPort();
    if(port==-1) port=url.getDefaultPort();

         // işe devam edilebilir
        Socket  client=null;                        // müşteri
        BufferedReader IN=null;                    // müşterinin okuma akışı
        PrintWriter OUT=null;                        // müşterinin yazma akışı
        String réponse=null;                        // sunucunun yanıtı
        try{
             // sunucuya bağlanıyoruz
            client=new Socket(host,port);

            // istemcinin giriş-çıkış akışları oluşturuluyor TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // URL isteniyor - başlıkların gönderilmesi HTTP
            OUT.println(commande + " " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println();
             // yanıt okunur
            while((réponse=IN.readLine())!=null){
                 // yanıt işlenir
                System.out.println(réponse);
            }//while
             // işlem tamamlandı
            client.close();
        } catch(Exception e){
            // istisna işleniyor
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
         // hata görüntüleme
        System.err.println(msg);
         // hata ile sonlandırma
        System.exit(exitCode);
    }//hata
}//sınıf

Bu programdaki tek yenilik, URL sınıfının kullanılmasıdır. Program, http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... biçiminde bir URL (Uniform Resource Locator) veya URI (Uniform Resource Identifier) alır. URL sınıfı, URL dizesini farklı bileşenlerine ayırmamızı sağlar. Bir URL nesnesi, parametre olarak alınan URLstring dizesinden oluşturulur:

         // URL'in geçerliliğinin kontrolü
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI hatalı
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//yakalama

Parametre olarak alınan URL dizesi geçerli bir URL değilse (protokol eksikliği, sunucu eksikliği vb.), bir istisna atılır. Bu, alınan parametrenin geçerliliğini kontrol etmemizi sağlar. URL nesnesi oluşturulduktan sonra, bu nesnenin çeşitli öğelerine erişilebilir. Dolayısıyla, önceki koddaki url nesnesi şu dizeden oluşturulmuşsa

http://sunucu:bağlantı noktası/cheminPageHTML?param1=val1;param2=val2;... 

dizesinden oluşturulmuşsa, şu sonuç elde edilir:

url.getHost()=serveur

url.getPort()=port veya bağlantı noktası belirtilmediyse -1

url.getPath()=cheminPageHTML veya yol yoksa boş dize

url.getQuery()=param1=val1;param2=val2;... veya istek yoksa null

uri.getProtocol()=http

8.4.6. Yönlendirmeleri yöneten web istemcisi

Önceki web istemcisi, talep ettiği URL'ten olası bir yönlendirmeyi yönetmez. Bir sonraki istemci bunu yönetir.

  1. Sunucu tarafından gönderilen HTTP başlıklarının ilk satırını okur ve yönlendirmeyi belirten "302 Object moved" dizesinin olup olmadığını kontrol eder
  2. Sonraki başlıkları okur. Yönlendirme varsa, istenen sayfanın yeni URL adresini veren "Location: url" satırını arar ve bu URL adresini not eder.
  3. sunucudan gelen yanıtın geri kalanını görüntüler. Yönlendirme varsa, 1'den 3'e kadar olan adımlar yeni URL ile tekrarlanır. Program birden fazla yönlendirmeyi kabul etmez. Bu sınır, değiştirilebilen bir sabit ile belirlenir.

İşte bir örnek:

Dos>java clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private

<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>

<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781

<html>

<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>

Program şu şekildedir:

// ithal edilen paketler
import java.io.*;
import java.net.*;
import java.util.regex.*;

public class clientweb2{

    // bir URL talep ediyor
     // içeriğini ekranda görüntüler

    public static void main(String[] args){
        // sözdizimi
        final String syntaxe="pg URL GET/HEAD";

        // argüman sayısı
        if(args.length != 2)
            erreur(syntaxe,1);

         // istenen URI not edilir
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // URI'in geçerliliğini kontrol eder
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI hatalı
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//yakalama
         // siparişin doğrulanması
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // sipariş hatalı
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // çalışılabilir
        Socket  client=null;                        // müşteri
        BufferedReader IN=null;                    // müşterinin okuma akışı
        PrintWriter OUT=null;                        // müşterinin yazma akışı
        String réponse=null;                        // sunucu yanıtı
        final int nbRedirsMax=1;                // en fazla bir yönlendirme kabul edilir
        int nbRedirs=0;                                    // devam eden yönlendirme sayısı
        String premièreLigne;                        // yanıtın ilk satırı
        boolean redir=false;                        // yönlendirme olup olmadığını gösterir
        String locationString="";                // olası bir yönlendirmenin URL dizesi

         // yönlendirme dizesi URL'i bulmak için düzenli ifade
        Pattern location=Pattern.compile("^Location: (.+?)$");

         // hata yönetimi
        try{
             // Yönlendirmeler varsa, sorgulanacak birden fazla URL olabilir
            while(nbRedirs<=nbRedirsMax){

                // URL'ten gerekli bilgiler çıkarılır
            String protocol=url.getProtocol();
            String path=url.getPath();
            if(path.equals("")) path="/";
            String query=url.getQuery();
            if(query!=null) query="?"+query; else query="";
            String host=url.getHost();
            int port=url.getPort();
            if(port==-1) port=url.getDefaultPort();

                 // sunucuya bağlanılır
                client=new Socket(host,port);

                // TCP istemcisinin giriş-çıkış akışları oluşturulur
                IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
                OUT=new PrintWriter(client.getOutputStream(),true);

                // URL dosyası istenir - başlıklar gönderilir HTTP
                OUT.println(commande + " " + path + query + " HTTP/1.1");    
                OUT.println("Host: " + host + ":" + port);
                OUT.println("Connection: close");
                OUT.println();

                 // yanıtın ilk satırı okunur
                premièreLigne=IN.readLine();
                 // ekran çıktısı
                System.out.println(premièreLigne);

                // yönlendirme mi?
        if(premièreLigne.endsWith("302 Object moved")){     
                    // yönlendirme var
                    redir=true;
                    nbRedirs++;
                }//if

                // başlıklar HTTP başlıkların sonunu belirten boş satır bulunana kadar devam eder
                boolean locationFound=false;
                while(!(réponse=IN.readLine()).equals("")){
                    // yanıt görüntülenir
                    System.out.println(réponse);
                     // yönlendirme varsa, Location başlığı aranır
                    if(redir && ! locationFound){
                        // satırı, location ilişkisel ifadesiyle karşılaştırılır
                        Matcher résultat=location.matcher(réponse);
                        if(résultat.find()){
                            // bulunursa, yönlendirme başlığı olan URL not edilir
                            locationString=résultat.group(1);             
                             // bulunduğunu not edilir
                            locationFound=true;
                        }//if
                    }//eğer
                    // aşağıdaki başlık
                }// süresince

                 // yanıtın aşağıdaki satırları
                System.out.println(réponse);
                while((réponse=IN.readLine())!=null){
                     // yanıt görüntülenir
                    System.out.println(réponse);
                }//while
                 // bağlantı kapatılır
                client.close();
                 // işlem tamamlandı mı?
                if ( ! locationFound || nbRedirs>nbRedirsMax)
                    break;
                 // yönlendirme yapılması gerekiyor - yeni URL oluşturuluyor
                URLString=protocol +"://"+host+":"+port+locationString;
                url=new URL(URLString);
                // takip
                System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
            }//while
        } catch(Exception e){
            // istisna işlenir
            erreur(e.getMessage(),4);
        }//yakalama
    }//ana işlev

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
         // hata görüntüleme
        System.err.println(msg);
         // hata ile sonlandırma
        System.exit(exitCode);
    }//hata
}//sınıf

8.4.7. Vergi Hesaplama Sunucusu

Daha önce çeşitli şekillerde ele aldığımız IMPOTS alıştırmasına geri dönüyoruz. Son versiyonunu hatırlayalım:

Bir temel vergi sınıfı oluşturuldu. Bu sınıfın öznitelikleri, üç sayı tablosundan oluşur:

public class impots{

  // vergi hesaplaması için gerekli veriler
   // harici bir kaynaktan geliyor

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

   // üretici alanı boş
  protected impots(){}

   // oluşturucu
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{

impots sınıfının iki yapıcı işlevi vardır:

  • verginin hesaplanması için gerekli olan üç veri dizisinin aktarıldığı bir oluşturucu
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
  • yalnızca alt sınıflar tarafından kullanılabilen, parametresiz bir oluşturucu
  protected impots(){}

Bu sınıftan türetilen impotsJDBC sınıfı, bir veritabanının içeriğinden limites, coeffR ve coeffN tablolarını doldurmaya olanak tanır:

public class impotsJDBC extends impots{
  // oluşturmaya olanak tanıyan bir oluşturucunun eklenmesi
   // tablodan limit, coeffr, coeffn tablolarını oluşturmaya yarayan bir oluşturucu
   // bir veritabanındaki vergiler
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS : veritabanının adı DSN
     // userIMPOTS, mdpIMPOTS : veritabanına erişim kullanıcı adı/şifresi

Bir grafik uygulaması yazılmıştı. Uygulama, impotsJDBC sınıfına ait bir nesneyi kullanıyordu. Uygulama ve bu nesne aynı makinede bulunuyordu. Test programını ve impotsJDBC nesnesini farklı makinelere yerleştirmeyi planlıyoruz. Böylece, uzak impotsJDBC nesnesinin sunucu görevi göreceği bir istemci-sunucu uygulaması elde edeceğiz. Yeni sınıfın adı ServeurImpots olup, impotsJDBC sınıfından türetilmiştir:

// ithal edilen paketler
import java.net.*;
import java.io.*;
import java.sql.*;

public class ServeurImpots extends impotsJDBC {

    // özellikler
    int portEcoute;                // istemci isteklerini dinleyen bağlantı noktası
    boolean actif;                // sunucu durumu

     // oluşturucu
    public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
      throws IOException, SQLException, ClassNotFoundException {
       // üst yapı
        super(DSNimpots, USERimpots, MDPimpots);
         // dinleme bağlantı noktası not edilir
        this.portEcoute=portEcoute;
         // şu anda etkin değil
        actif=false;
         // klavyeden girilen komutları okumak için bir  parçacığı oluşturur ve başlatır
         // sunucu bu komutlar aracılığıyla yönetilecektir
        Thread admin=new Thread(){
        public void run(){
          try{
            admin();
        }catch (Exception ignored){}
      }
    };
    admin.start();
    }//ServeurImpots

Oluşturucudaki tek yeni parametre, istemcilerin isteklerini dinlemek için kullanılan bağlantı noktasıdır. Diğer parametreler doğrudan impotsJDBC temel sınıfına aktarılır. Vergi sunucusu, klavyeden girilen komutlarla kontrol edilir. Bu nedenle, bu komutları okumak için bir iş parçacığı oluşturulur. İki olası komut olacaktır: Hizmeti başlatmak için start, hizmeti kalıcı olarak durdurmak için stop. Bu komutları yöneten admin yöntemi şöyledir:

    public void admin() throws IOException{
        // klavyeden girilen sunucu yönetim komutlarını okur
         // sonsuz bir döngü içinde
        String commande=null;
    BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
        while(true){
             // komut istemi görüntüler
            System.out.print("Serveur d'impôts>");
             // komut okuma
            commande=IN.readLine().trim().toLowerCase();
             // komutun yürütülmesi
            if(commande.equals("start")){
                // aktif mi?
                if(actif){
                     //hata
                    System.out.println("Le serveur est déjà actif");
                     // devam ediliyor
                    continue;
                }//if
                 // dinleme hizmeti oluşturuluyor ve başlatılıyor
                Thread ecoute=new Thread(){
                public void run(){
                  ecoute();
              }
            };
            ecoute.start();
            }//if
            else if(commande.equals("stop")){
                // tüm yürütme iş parçacıklarının sonu
                System.exit(0);
            }//eğer
            else {
                // hata
                System.out.println("Commande incorrecte. Utilisez (start,stop)");
            }//if
        }//while
    }//yönetici

Klavyeyle girilen komut start ise, istemci isteklerini dinleyen bir iş parçacığı başlatılır. Girilen komut stop ise, tüm iş parçacıkları durdurulur. Dinleme iş parçacığı, ecoute yöntemini yürütür:

    public void ecoute(){
         // istemcilerin isteklerini dinleyen iş parçacığı
         // dinleme hizmeti oluşturuluyor
        ServerSocket ecoute=null;
        try{
            // hizmet oluşturuluyor
            ecoute=new ServerSocket(portEcoute);
             // izleme
            System.out.println("Serveur d'impôts lancé sur le port " + portEcoute);

             // hizmet döngüsü
            Socket liaisonClient=null;
            while (true){ // sonsuz döngü
                 // istemci bekleme
                liaisonClient=ecoute.accept();

                 // hizmet başka bir görev tarafından sağlanıyor
                new traiteClientImpots(liaisonClient,this).start();

                 // istekleri dinlemeye geri dönülür
            }// while döngüsünün sonu
        }catch(Exception ex){
             // hata bildiriliyor
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
        }//catch
    }//dinleme iş parçacığı

portEcoute numaralı bağlantı noktasında dinleme yapan klasik bir TCP sunucusu bulunmaktadır. İstemcilerin istekleri, traiteCientImpots iş parçacığının run yöntemi tarafından işlenir; bu yöntemin oluşturucusuna iki parametre aktarılır:

  1. liaisonClient adlı Socket nesnesi, müşteriye erişim sağlayacaktır
  2. impotsJDBC ve this nesneleri, vergi hesaplama yöntemi olan this.calculer'e erişim sağlayacaktır.
// -------------------------------------------------------
// vergi sunucusunun bir müşterisine hizmet sağlar

class traiteClientImpots extends Thread{

    private Socket liaisonClient;            // istemciyle bağlantı
    private BufferedReader IN;                // giriş akışı
    private PrintWriter OUT;                    // çıkış akışı
    private impotsJDBC objImpots;            // Vergi nesnesi

         // oluşturucu
    public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
        this.liaisonClient=liaisonClient;
        this.objImpots=objImpots;
    }//üretici

run yöntemi, müşterilerin taleplerini işler. Bunlar, iki farklı biçimde olabilen metin satırlarıdır:

  1. evli olup olmadığı hesaplaması (evet/hayır) nbEnfants salaireAnnuel
  2. fincalculs

Biçim 1, vergi hesaplamasına izin verirken, biçim 2 müşteri-sunucu bağlantısını sonlandırır.

     // çalıştırma yöntemi
    public void run(){
         // hizmeti müşteriye sunar
        try{
             // giriş akışı
            IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
             // çıkış akışı
            OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
             // müşteriye hoş geldiniz mesajı gönderme
            OUT.println("Bienvenue sur le serveur d'impôts");

             // istek/yanıt okuma döngüsü
            String demande=null;
            String[] champs=null;    // isteğin bileşenleri
            String commande=null;    // müşterinin komutu: hesaplama veya hesaplamaların sonu
            while ((demande=IN.readLine())!=null){
                 // isteği alanlara ayrıştırma
                champs=demande.trim().toLowerCase().split("\\s+");
                 // kabul edilen iki istek: hesaplama ve hesaplamaların sonu
                commande=champs[0];
                if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
                     // müşteri hatası
                    OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
                     // sonraki sipariş
                    continue;
                }//if
                if(commande.equals("calcul")) calculerImpôt(champs);
                if(commande.equals("fincalculs")){
                     // müşteriye veda mesajı
                    OUT.println("Au revoir...");
                     // kaynakların serbest bırakılması
                    try{ OUT.close();IN.close();liaisonClient.close();}
                    catch(Exception ex){}
                     // son
                    return;
                }//if
                 //bir sonraki istek
            }//while
        }catch (Exception e){
            erreur("L'erreur suivante s'est produite ("+e+")",2);
        }// son try
    }// Run sonu

Vergi hesaplaması, müşterinin yaptığı talebin alan tablosunu parametre olarak alan calculerImpôt yöntemi ile gerçekleştirilir. Talebin geçerliliği kontrol edilir ve gerekirse vergi hesaplanarak müşteriye geri gönderilir.

     // vergi hesaplaması
    public void calculerImpôt(String[] champs){
         // başvuruyu işler: evli hesaplaması nbEnfants salaireAnnuel
         // alan tablosunda alanlara ayrılmış

        String marié=null;
        int nbEnfants=0;
        int salaireAnnuel=0;

         // argümanların geçerliliği
        try{
             // en az 4 alan gereklidir
            if(champs.length!=4) throw new Exception();
            // evli
            marié=champs[1];
            if (! marié.equals("o") && ! marié.equals("n")) throw new Exception();
            // çocuklar
            nbEnfants=Integer.parseInt(champs[2]);
             // maaş
            salaireAnnuel=Integer.parseInt(champs[3]);
        }catch (Exception ignored){
            // biçim hatası
            OUT.println(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel");
             // bitti
            return;
        }//eğer
         // vergi hesaplanabilir
        long impot=objImpots.calculer(marié.equals("o"),nbEnfants,salaireAnnuel);
         // cevap müşteriye gönderilir
        OUT.println(""+impot);
    }//hesapla

Bir test programı şu şekilde olabilir:

// çağrı: serveurImpots bağlantı noktası dsnImpots userImpots mdpImpots

import java.io.*;

public class testServeurImpots{
    public static final String syntaxe="Syntaxe : pg port dsnImpots userImpots mdpImpots";

    // ana program
    public static void main (String[] args){

        // 4 argüman gereklidir
        if(args.length != 4)
            erreur(syntaxe,1);

         // bağlantı noktası >0 olan bir tamsayı olmalıdır
        int port=0;
        boolean erreurPort=false;
        Exception E=null;
        try{
            port=Integer.parseInt(args[0]);
        }catch(Exception e){
            E=e;
            erreurPort=true;
        }
        erreurPort=erreurPort || port <=0;
        if(erreurPort)
            erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);

         // vergi sunucusu oluşturuluyor
        try{
            new ServeurImpots(port,args[1],args[2],args[3]);
        }catch(Exception ex){
             //hata
            System.out.println("L'erreur suivante s'est produite : "+ex.getMessage());
        }//catch
    }//Ana

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
         // hata görüntüleme
        System.err.println(msg);
         // hata ile durdurma
        System.exit(exitCode);
    }//hata
}// sınıf sonu

ServeurImpots nesnesinin oluşturulması için gerekli verileri test programına aktarıyoruz ve program bu verileri kullanarak söz konusu nesneyi oluşturuyor.

İlk çalıştırmayı deneyelim:

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

Komut

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots

komutu, henüz istemcilerin isteklerini dinlemeyen bir ServeurImpots nesnesi oluşturur. Bu dinleme işlemini başlatan, klavyeden girilen start komutudur. stop komutu ise sunucuyu durdurur. Şimdi bir istemci kullanalım. Daha önce oluşturduğumuz genel istemciyi kullanacağız. Sunucu başlatıldı:

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124

Genel istemci başka bir DOS penceresinde başlatılır:

Dos>java clientTCPgenerique localhost 124
Commandes :
<-- Bienvenue sur le serveur d'impôts

İstemcinin sunucudan karşılama mesajını başarıyla aldığını görüyoruz. Başka komutlar gönderiyoruz:

x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<--  syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]

Sunucuyu durdurmak için sunucu penceresine geri dönüyoruz:

dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

8.5. Alıştırmalar

8.5.1. Alıştırma 1 - TCP genel grafik istemcisi

8.5.1.1. Uygulamanın Tanıtımı

İnternet üzerinden başlıca TCP hizmetleriyle iletişim kurabilen bir program oluşturmayı hedefliyoruz. Buna genel amaçlı bir TCP istemcisi adını vereceğiz. Bu uygulamayı anladıktan sonra, tüm TCP istemcilerinin birbirine benzediğini görürüz. Programın penceresi şu şekildedir:

Image

Çeşitli denetimlerin anlamları şöyledir:

No
ad
tür
işlev
1
TxtRemoteHost
JTextField
istenen hizmeti sunan makinenin adı
2
TxtPort
JTextField
İstenen hizmetin bağlantı noktası
3
TxtSend
JTextField
istemci tarafından sunucuya gönderilecek mesaj metni
4
OptRCLF
OptLF
JCheckBox
istemci/sunucu iletişiminde satırların nasıl sonlandırılacağını belirten düğmeler
RCLF: satır başı (#13) + satır sonu (#10)
LF: satır sonu (#10)
5
LstSuivi
JList
istemci ile sunucu arasındaki iletişim durumuna ilişkin mesajları görüntüler
6
LstDialogue
JList
istemci (->) ve sunucu (<-) arasında alışverişi yapılan mesajları gösterir
7
CmdAnnuler
JButton
gizli - diyalog listesinin altında bulunur - Bağlantı kurulurken görünür ve sunucu yanıt vermezse bağlantıyı kesmeye olanak tanır

Kullanılabilir menü seçenekleri şunlardır:

seçenek
alt seçenekler
rol
Bağlan
Bağlan
istemciyi sunucuya bağlar
 
Bağlantıyı kes
bağlantıyı sonlandırır
 
Çık
Programı sonlandırır
Mesajlar
Gönder
TxtSend denetim mesajını sunucuya gönderir
 
RazSuivi
LstSuivi listesini sil
 
RazDialogue
LstDialogue listesini sil
Yazar
 
telif hakkı kutucuğunu görüntüler

8.5.1.2. FONCTIONNEMENT DE APPLICATION

Uygulamanın başlatılması

Uygulamanın ana sayfası yüklendiğinde, aşağıdaki işlemler gerçekleştirilir:

  • sayfa ekranın ortasına yerleştirilir
  • yalnızca Connexion/Quitter ve Auteur menü seçenekleri etkindir
  • Annuler düğmesi gizlenir
  • LstSuivi ve LstDialogue listeleri boştur
Oturum Açma/Bağlan menüsü

Bu seçenek, yalnızca Uzak Ana Bilgisayar ve Bağlantı Noktası No. alanları boş olmadığında ve şu anda aktif bir bağlantı bulunmadığında kullanılabilir. Bu seçeneğe tıklandığında aşağıdaki işlemler gerçekleştirilir:

  • bağlantı noktasının geçerliliği kontrol edilir: bu, 0'dan büyük bir tamsayı olmalıdır
  • sunucuya bağlantıyı sağlamak için bir iş parçacığı başlatılır
  • kullanıcının devam eden bağlantıyı kesebilmesi için Annuler düğmesi görünür
  • Quitter ve Auteur hariç tüm menü seçenekleri devre dışı bırakılır

Bağlantı çeşitli şekillerde sonlandırılabilir:

  1. Kullanıcı Annuler düğmesine bastı: bağlantı iş parçacığı durdurulur ve menü başlangıç durumuna geri döndürülür. Takip bölümünde, bağlantının kullanıcı tarafından kapatıldığı belirtilir.
  2. Bağlantı bir hata ile sonlanır: yukarıdaki işlemlerin aynısı yapılır ve ayrıca izleme bilgisinde hatanın nedeni belirtilir.
  3. Bağlantı başarıyla sona erer: Annuler düğmesini kaldırırız, izleme bölümünde bağlantının kurulduğunu belirtiriz, RazSuivi menüsünü etkinleştiririz, Connecter menüsünü devre dışı bırakırız, Déconnecter menüsünü etkinleştiririz
Bağlan/Bağlantıyı Kes Menüsü

Bu seçenek yalnızca sunucuyla bir bağlantı mevcut olduğunda kullanılabilir. Etkinleştirildiğinde sunucuyla olan bağlantıyı sonlandırır ve menüyü başlangıç durumuna geri döndürür. Takip bölümünde bağlantının istemci tarafından kapatıldığı belirtilir.

Bağlan/Çık menüsü

Bu seçenek, sunucuyla varsa mevcut bağlantıyı sonlandırır ve uygulamayı kapatır.

Mesajlar/Gönder Menüsü

Bu seçeneğe yalnızca aşağıdaki koşullar sağlandığında erişilebilir:

  • sunucuya bağlantı kurulmuşsa

  • gönderilecek bir mesaj var

Bu koşullar sağlanırsa, RCLF seçeneği işaretlenmişse, RCLF dizisiyle biten TxtSend (3) alanındaki metin sunucuya gönderilir; aksi takdirde LF dizisiyle biten metin gönderilir. Gönderim sırasında meydana gelebilecek bir hata, takip listesinde bildirilir.

RazSuivi ve RazDialogue menüleri

Sırasıyla LstSuivi ve LstDialogue listelerini temizler. İlgili listeler boş olduğunda bu seçenekler devre dışı kalır.

İptal düğmesi

Formun alt kısmında bulunan bu düğme, yalnızca müşteri sunucuya bağlanmaya çalışırken görünür. Sunucu yanıt vermediği veya hatalı yanıt verdiği için bu bağlantı kurulamayabilir. Bu durumda Annuler düğmesi, kullanıcıya bağlantı isteğini sonlandırma olanağı sunar.

İzleme listeleri

LstSuivi (5) listesi, bağlantıyı takip eder. Bu liste, bağlantının önemli anlarını gösterir:

  • istemci tarafından açılması

  • sunucu veya müşteri tarafından kapatılması

  • bağlantı aktif olduğu süre boyunca meydana gelebilecek tüm hatalar

LstDialogue (6) listesi, istemci ile sunucu arasında kurulan diyaloğu izler. Bir iş parçacığı, arka planda istemcinin iletişim soketinde neler olup bittiğini izler ve bunları 6 numaralı listede görüntüler.

Yazar seçeneği

Bu menü, Telif Hakkı adı verilen bir pencere açar:

Image

Hata Yönetimi

Bağlantı hataları 6 izleme listesinde, istemci/sunucu iletişimi ile ilgili hatalar ise 7 iletişim listesinde bildirilir. Bir bağlantı hatası meydana geldiğinde, istemci/sunucu iletişimi sonlandırılır ve form, yeni bir bağlantıya hazır olacak şekilde başlangıç durumuna geri döndürülür.

8.5.1.3. TRAVAIL A FAIRE

Yukarıda açıklanan işi iki şekilde gerçekleştirin:

  • bağımsız uygulama
  • applet

8.5.2. Alıştırma 2 - Bir kaynak sunucusu

8.5.2.1. INTRODUCTION

Bir kurumun, İnternet üzerinden erişilebilen birkaç güçlü hesaplama sunucusu bulunmaktadır. Bu hesaplama hizmetlerini kullanmak isteyen her makine, sunuculardan birinin 756 numaralı bağlantı noktasına bir veri dosyası gönderir. Bu dosya çeşitli bilgileri içerir: kullanıcı adı, şifre, istenen hesaplama türünü belirten komutlar ve hesaplamanın yapılacağı veriler. Veri dosyası doğruysa, seçilen hesaplama sunucusu bu dosyayı kullanır ve sonuçları bir metin dosyası biçiminde istemciye geri gönderir.

Böyle bir düzenlemenin birçok avantajı vardır:

  • Her tür istemci (PC, Mac, Unix,...) bu hizmeti kullanabilir
  • istemci İnternet'in herhangi bir yerinde olabilir
  • hesaplama kaynakları optimize edilmiştir: yalnızca birkaç güçlü makine gereklidir. Böylece, hesaplama kaynaklarına sahip olmayan küçük bir kuruluş bile, kullanılan hesaplama süresine göre hesaplanan bir mali katkı karşılığında bu hizmeti kullanabilir.

Makinelerin gücü ne kadar yüksek olursa olsun, bir hesaplama bazen birkaç saat sürebilir: bu durumda sunucu diğer müşteriler için kullanılamaz hale gelir. Bu durumda, müşteri için kullanılabilir bir hesaplama sunucusu bulma sorunu ortaya çıkar. Bunun için, bundan sonra GRC sunucusu olarak adlandırılacak bir “hesaplama kaynakları yöneticisi” kullanılır. Bu hizmet tek bir makineye yerleştirilmiştir ve TCP modunda 864 numaralı bağlantı noktasında çalışır. Hesaplama sunucusuna erişim isteyen bir müşteri, bu hizmete başvurur. Hesaplama sunucularının tam listesine sahip olan GRC sunucusu, müşteriye şu anda aktif olmayan bir sunucunun adını göndererek yanıt verir. Böylece istemcinin, kendisine belirtilen sunucuya verilerini göndermesi yeterlidir.

GRC sunucusunu yazmayı planlıyoruz.

8.5.2.2. INTERFACE VISUELLE

Görsel arayüz şu şekilde olacaktır:

Image

Arayüzde iki sunucu listesi bulunur:

  • sol tarafta, hesaplamalar için kullanılabilir olan aktif olmayan sunucuların listesi
  • sağda ise bir müşterinin hesaplamalarıyla meşgul olan sunucuların listesi yer almaktadır.

Menü yapısı şu şekildedir:

Ana Menü
İkincil Menü
Rol
Hizmet
Başlat
864 numaralı bağlantı noktasında tcp hizmetini başlat
 
Durdur
Hizmeti durdurur
 
Çık
Uygulamayı sonlandır
Yazar
 
Telif Hakkı Bilgileri

Formda bulunan denetimlerin yapısı şu şekildedir:

Ad
Tür
Rol
listLibres
JList
Boş sunucu listesi
listOccupés
JList
Meşgul sunucuların listesi

8.5.2.3. FONCTIONNEMENT DE APPLICATION

Uygulamanın yüklenmesi

Uygulama yüklendiğinde, listLibres listesi, GRC tarafından yönetilen hesaplama sunucularının adlarının listesiyle doldurulur. Bu sunucular, parametre olarak geçirilen bir Sunucular dosyasında tanımlanmıştır. Bu dosya, her satırda bir sunucu adı olacak şekilde bir sunucu adları listesi içerir ve bu nedenle listLibres listesini doldurmak için kullanılır. Başlat menüsü etkinleştirilir, Durdur menüsü devre dışı bırakılır.

Hizmet/Başlat seçeneği

Bu seçenek

  • makinenin 864 numaralı bağlantı noktasını dinleyen hizmeti başlatır
  • "Başlat" menüsünü devre dışı bırakır
  • "Durdur" menüsünü etkinleştirir
Hizmet/Durdur Seçeneği

Bu seçenek hizmeti durdurur:

  • meşgul sunucular listesi boşaltılır
  • boş sunucu listesi, Sunucular dosyasının içeriğiyle doldurulur
  • "Başlat" menüsü etkinleştirilir
  • "Sonlandır" menüsü devre dışı bırakılır
Hizmet/Çıkış Seçeneği

Uygulama sonlandırılır.

İstemci/sunucu iletişimi

İstemci/sunucu iletişimi, RCLF dizisiyle biten metin satırlarının alışverişi yoluyla gerçekleşir. GRC sunucusu iki komutu tanır: getserveur ve finservice. Bu iki komutun işlevlerini ayrıntılı olarak açıklıyoruz:

  • 1-getserveur

İstemci, kendisi için kullanılabilir bir hesaplama sunucusu olup olmadığını sorar.

GRC sunucusu, boş sunucu listesinde bulunan ilk sunucuyu seçer ve adını şu biçimde istemciye geri gönderir:

        100-nom du serveur

Ayrıca, müşteriye tahsis edilen sunucuyu şu biçimde meşgul sunucular listesine ekler:

        serveur (IP du client)

Aşağıdaki örnekte de görüldüğü gibi, calcul1.istia.univ-angers.fr sunucusu, IP ve 193.52.43.5 adresli istemcilere hizmet vermekle meşgul:

Image

Bir istemciye halihazırda bir hesaplama sunucusu atanmışsa, istemci getserveur komutunu gönderemez. Bu nedenle, istemciye yanıt vermeden önce GRC sunucusu, istemcinin IP adresinin meşgul sunucular listesinde kayıtlı adresler arasında bulunmadığını kontrol eder. Eğer bulunuyorsa, GRC sunucusu şu yanıtı verir:

        501-Vous avez actuellement une demande en cours

Son olarak, hiçbir hesaplama sunucusunun müsait olmadığı durum da söz konusudur: boş sunucu listesi boştur. Bu durumda, GRC sunucusu şu yanıtı verir:

        502- Il n’y a aucun serveur de calcul disponible

Her durumda, istemciye yanıt verdikten sonra, GRC sunucusu diğer istemcilere hizmet verebilmek için istemciyle olan bağlantıyı sonlandırır.

  • 2-finservice

İstemci, kullandığı hesaplama sunucusuna artık ihtiyaç duymadığını belirtir.

GRC sunucusu, öncelikle bu istemcinin gerçekten kendisine ait bir istemci olup olmadığını kontrol eder. Bunun için, müşterinin IP adresinin, meşgul sunucular listesinde kayıtlı adresler arasında olup olmadığını kontrol eder. Eğer değilse, GRC sunucusu şu yanıtı verir:

        503-Aucun serveur ne vous a été attribué

İstemci tanınırsa, GRC sunucusu şu yanıtı verir:

        101-Fin de service acceptée

ve bu müşteriye atanan hesaplama sunucusunu boş sunucular listesine aktarır. Önceki örneği ele alırsak, müşteri finservice komutunu gönderirse, GRC sunucusunun ekranı şu şekilde olur:

Image

Yanıt gönderildikten sonra, yanıtın içeriği ne olursa olsun, GRC sunucusu bağlantıyı sonlandırır.

8.5.2.4. TRAVAIL A FAIRE

Uygulamayı, örneğin telnet istemcisiyle veya önceki alıştırmadaki genel tcp istemcisiyle test edilebilecek bağımsız bir program olarak yazın.

8.5.3. Alıştırma 3 - bir SMTP istemcisi

8.5.3.1. INTRODUCTION

Burada, e-posta göndermeye olanak tanıyan SMTP (SendMail Aktarım Protokolü) hizmeti için bir istemci oluşturmak istiyoruz. Unix veya Windows altında, telnet programı, tcp protokolüyle çalışan bir istemcidir. Bu program, tcp dizisiyle biten metin biçimindeki komutları kabul eden tüm RCLF hizmetleriyle “iletişim kurabilir”; yani ASCII kodlu 13 ve 10 karakterlerini kabul eden hizmetlerle. Aşağıda, e-posta gönderme hizmeti smtp ile yapılan bir iletişim örneği yer almaktadır:

$ telnet istia.univ-angers.fr 25        // SMTP hizmetinin çağrılması

// SMTP sunucusundan gelen yanıt


Trying 193.52.43.2...
Connected to istia.univ-angers.fr.
Escape character is '^]'.
220-Istia.Istia.Univ-Angers.fr Sendmail 8.6.10/8.6.9 ready at Tue, 16 Jan 1996 07:53:12 +0100
220 ESMTP spoken here

// yorumlar --------------

telnet programı, şu sözdizimiyle herhangi bir hizmeti çağırabilir

telnet machine_service port_service

İstemci/sunucu iletişimi, RCLF dizisiyle biten metin satırlarıyla gerçekleştirilir.

smtp hizmetinin yanıtları şu biçimdedir:

Mesaj numarası veya

Mesaj numarası

SMTP sunucusu birden fazla yanıt satırı gönderebilir. Yanıtın son satırı, bir numara ve ardından bir boşluk ile belirtilirken, yanıtın önceki satırlarında numaranın ardından bir tire (-) işareti gelir.

500'e eşit veya 500'den büyük bir numara, bir hata mesajını belirtir.

// yorumların sonu

help                        // klavyeden komut verildi

// SMTP sunucusundan gelen yanıt

214-Commands:
214-    HELO    EHLO    MAIL    RCPT    DATA
214-    RSET    NOOP    QUIT    HELP    VRFY
214-    EXPN    VERB
214-For more info use "HELP <topic>".
214-To report bugs in the implementation send email to
214-    sendmail@CS.Berkeley.EDU.
214-For local information send email to Postmaster at your site.
214 End of HELP info
mail from: serge.tahe@istia.univ-angers.fr    // klavyeden yeni komut verildi

// yorumlar ---------

mail komutunun sözdizimi şöyledir:

mail from: mesajın göndereninin e-posta adresi

// yorumların sonu

// SMTP sunucusundan gelen yanıt

250 serge.tahe@istia.univ-angers.fr... Sender ok

// yorumlar

smtp sunucusu, gönderenin adresinin geçerliliğini kontrol etmez: adresi kendisine verildiği şekliyle kabul eder

// yorumların sonu

rcpt to: user1@istia.univ-angers.fr        // klavyeden yeni komut verildi

// yorumlar ---------

rcpt komutunun sözdizimi şöyledir:

rcpt to: mesajın alıcısının e-posta adresi

E-posta adresi, smtp sunucusunun çalıştığı makinedeki bir adres ise, sunucu adresin varlığını kontrol eder; aksi takdirde herhangi bir kontrol yapmaz. Kontrol yapılmış ve bir hata tespit edilmişse, bu hata >= 500 numaralı bir kodla bildirilir.

İstenilen sayıda rcpt to komutu verilebilir: bu, bir mesajı birden fazla kişiye göndermeyi sağlar.

// yorumların sonu

// SMTP sunucusundan gelen yanıt

250 user1@istia.univ-angers.fr... Recipient ok
data                        // klavyeden verilen yeni komut

// yorumlar ---------

data komutunun sözdizimi şöyledir:

data

satır1

satır2

...

.

Bunu, mesajı oluşturan metin satırları izler; mesaj, yalnızca "nokta" karakterini içeren bir satırla sona ermelidir.

Mesaj daha sonra rcpt komutuyla belirtilen alıcıya gönderilir.

// yorum sonu

// SMTP sunucusundan gelen yanıt

354 Enter mail, end with "." on a line by itself

// klavyeden girilen mesaj metni


subject: essai smtp

essai smtp a partir de telnet
.

// yorumlar

data komutunun metin satırlarına, e-postanın konusunu belirtmek için bir subject: satırı eklenebilir. Bu satırın ardından boş bir satır gelmelidir.

// SMTP sunucusundan gelen yanıt

250 HAA11627 Message accepted for delivery
quit                            // klavyeden verilen yeni komut

// yorumlar

quit komutu, smtp hizmetiyle olan bağlantıyı sonlandırır

// yorumların sonu

// SMTP sunucusundan gelen yanıt

221 Istia.Istia.Univ-Angers.fr closing connection

8.5.3.2. INTERFACE VISUELLE

Aşağıdaki görsel arayüze sahip bir program yazmayı planlıyoruz:

Image

Kontroller şu işlevlere sahiptir:

Numara
Tür
İşlev
1
JTextField
Virgülle ayrılmış e-posta adresleri dizisi
2
JTextField
Mesajın konu metni
3
JTextField
Virgülle ayrılmış e-posta adresleri dizisi
4
JTextField
Virgülle ayrılmış e-posta adresleri dizisi
5
JTextArea
Mesaj metni
6
JList
izleme listesi
7
JList
diyalog listesi
8
JButton
"İptal" düğmesi gösterilmiyor; bu düğme, istemci SMTP sunucusuna bağlantı talep ettiğinde görünür. Sunucu yanıt vermezse, kullanıcının bu talebi iptal etmesini sağlar.

8.5.3.3. LES MENUS

Uygulamanın menü yapısı şu şekildedir:

Ana Menü
İkincil Menü
Rol
Posta
  
 
Gönder
Kontrol 5'in mesajını gönder
 
Çık
Uygulamadan çık
Seçenekler
  
 
İzlemeyi Gizle
Kontrol 6'yı görünmez hale getir
 
İzlemeyi Sıfırla
Takip listesini boşalt 6
 
Diyalogu Gizle
Diyalog listesini gizler 7
 
Diyalog Sil
Diyalog listesini boşalt 7
 
Yapılandır
Kullanıcının aşağıdakileri belirlemesine olanak tanır
- program tarafından kullanılan SMTP sunucusunun adresini
- e-posta adresini
 
Kaydet...
Önceki yapılandırmayı bir .ini dosyasına kaydeder
Yazar
 
Telif Hakkı Bilgileri

8.5.3.4. FONCTIONNEMENT DE APPLICATION

Seçenekler/Yapılandır menüsü

Bu menü, aşağıdaki pencerenin açılmasını sağlar:

Image

OK düğmesinin etkin hale gelmesi için her iki alanın da doldurulması gerekir. Bu iki bilginin diğer modüller tarafından kullanılabilmesi için küresel değişkenlere kaydedilmesi gerekir.

Posta/Gönder Menüsü

Bu seçeneğe yalnızca aşağıdaki koşullar sağlandığında erişilebilir:

  • yapılandırma tamamlanmışsa
  • gönderilecek bir mesaj var
  • bir konu başlığı varsa
  • 1, 3 ve 4 numaralı alanlarda en az bir alıcı var

Bu koşullar sağlanırsa, olayların sırası şu şekildedir:

  • form, istemci/sunucu iletişimini engelleyebilecek tüm eylemlerin devre dışı bırakıldığı bir duruma getirilir
  • yapılandırmada belirtilen sunucunun 25 numaralı bağlantı noktasında bağlantı kurulur
  • ardından istemci, yukarıda açıklanan protokole göre SMTP sunucusuyla iletişim kurar
  • "From:" satırında, yapılandırmada belirtilen gönderenin e-posta adresi kullanılır
  • "rcpt to:" alanı, 1, 3 ve 4 numaralı alanlarda bulunan her bir e-posta adresi için kullanılır
  • data komutundan sonra gönderilen satırlarda şu metinler bulunur:
    • bir Subject: satırı, kontrol 2'nin konu metni
    • bir satır Cc: denetimin adresleri 3
    • bir satır Bcc: denetim adresleri 4
    • kontrol 5'in mesaj metni
    • son nokta
İptal düğmesi

Formun altında bulunan bu düğme, yalnızca müşteri smtp sunucusuna bağlanırken görünür. smtp sunucusu yanıt vermediği veya hatalı yanıt verdiği için bu bağlantı kurulamayabilir. Bu durumda Annuler düğmesi, kullanıcıya bağlantı isteğini sonlandırma olanağı sunar.

İzleme listeleri

(6) listesi, bağlantıyı takip eder. Bu liste, bağlantının önemli anlarını gösterir:

  • istemci tarafından açılması
  • sunucu veya müşteri tarafından kapatılması
  • tüm bağlantı hataları

(7) listesi, istemci ile sunucu arasında kurulan smtp diyaloğunu izler.

Bu iki liste, menüdeki şu seçeneklerle ilişkilidir:

Takip Listesi'ni Gizle
6 numaralı takip listesini ve üstündeki etiketi görünmez hale getirir. Bu iki kontrolün kapladığı yükseklik H ise, altındaki tüm kontroller H yükseklik kadar yukarı kaydırılır ve formun toplam boyutu H kadar azalır. Ayrıca, “Takibi Gizle” seçeneği, aşağıdaki RazSuivi seçeneğini görünmez hale getirir.
İzlemeyi Sıfırla
Takip listesini boşaltır 6
Diyalog Penceresi Gizle
7 diyalog listesini, üzerindeki etiketi ve aşağıdaki RazDialogue menü seçeneğini görünmez hale getirir. "Takibi Gizle" seçeneğinde olduğu gibi, altındaki denetimlerin konumu (örneğin Annuler düğmesi) yeniden hesaplanır ve pencere boyutu küçültülür.
Raz Diyalog
7 diyalog listesini boşaltır
Yazar seçeneği

Bu menü, Telif Hakkı adı verilen bir pencere açar:

Image

Hata Yönetimi

Bağlantı hataları 6 izleme listesinde, istemci/sunucu iletişimiyle ilgili hatalar ise 7 iletişim listesinde bildirilir. Bir hata oluştuğunda, kullanıcı bir hata kutusu ile uyarılır ve hatanın nedeni, daha önce gizlenmişse, bu nedeni içeren liste görüntülenir. Ayrıca, istemci/sunucu diyaloğu kapatılır ve form başlangıç durumuna geri döndürülür.

8.5.3.5. GESTION, UN, FICHIER, DE, CONFIGURATION

Kullanıcının yazılımı her kullandığında yeniden yapılandırmak zorunda kalmaması tercih edilir. Bunun için, Seçenekler/Çıkarken yapılandırmayı kaydet seçeneği işaretliyse, program kapatıldığında Options/Configurer seçeneği ile elde edilen iki bilgi ile iki takip listesinin durumu, programın .exe dosyasıyla aynı dizinde bulunan sendmail.ini adlı bir dosyaya kaydedilir. Bu dosya şu biçimdedir:

SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1

SmtpServer ve ReplyAddress satırları, Seçenekler/Yapılandır seçeneği ile alınan iki bilgiyi içerir. Suivi ve Dialogue satırları, Takip ve Diyalog listelerinin durumunu gösterir: 1 (mevcut), 0 (yok).

Program yüklendiğinde, sendmail.ini dosyası varsa okunur ve form buna göre yapılandırılır. sendmail.ini dosyası yoksa, sanki bu dosya varmış gibi işlem yapılır:

SmtpServer=
ReplyAddress=
Suivi=1
Dialogue=1

sendmail.ini dosyası mevcutsa ancak eksikse (satır eksik), eksik satır yukarıdaki karşılık gelen satırla değiştirilir. Dolayısıyla, Suivi=... satırı eksikse, sanki Suivi=1 dosyası varmış gibi işlem yapılır.

Şablona uymayan tüm satırlar:

    mot clé= valeur

şablonuna uymayan tüm satırlar ve anahtar kelimenin geçersiz olduğu satırlar göz ardı edilir. Anahtar kelime büyük veya küçük harflerle yazılabilir: bu fark etmez.

Options/Configurer seçeneğinde, şu anda geçerli olan SmtpServer ve ReplyAddress değerleri gösterilir. Kullanıcı isterse bu değerleri değiştirebilir.

8.5.3.6. TRAVAIL → FAIRE

Yukarıda açıklanan işlemi gerçekleştirin. Yapılandırma dosyasının yönetimini en son halletmeniz tavsiye edilir.

8.5.4. Alıştırma 4 - POPPASS istemcisi

8.5.4.1. Introduction

106 numaralı bağlantı noktasında çalışan POPPASSD sunucusuyla iletişim kurabilen bir TCP istemcisi oluşturulacaktır. Bu hizmet, UNIX makinesinde şifre değiştirilmesine olanak tanır. İstemci/Sunucu iletişim protokolü şu şekildedir:

1 - İletişim, RCLF dizisiyle biten mesajların alışverişi yoluyla gerçekleşir

2 - İstemci, sunucuya komutlar gönderir

  • Sunucu, 3 basamaklı sayılarla başlayan mesajlarla yanıt verir: XXX. Eğer XXX=200 ise, komut başarıyla yürütülmüştür; aksi takdirde bir hata oluşmuştur.

3 - İletişim akışı şu şekildedir:

A    - le client se connecte
  • sunucu bir hoş geldiniz mesajıyla yanıt verir
B    - le client envoie USER login
  • Sunucu, giriş kabul edilirse şifre sorarak yanıt verir; aksi takdirde bir hata mesajı gönderir
C    - le client envoie PASS mot_de_passe
  • Şifre kabul edilirse sunucu yeni şifre sorarak yanıt verir, aksi takdirde bir hata mesajı gönderir
D    - le client envoie NEWPASS nouveau_mot_de_passe
  • sunucu, yeni şifrenin kabul edildiğini onaylayarak yanıt verir; aksi takdirde bir hata mesajı gönderir
E    - le client envoie la commande QUIT
  • sunucu bir sonlandırma mesajı gönderir ve bağlantıyı kapatır

8.5.4.2. İstemci formu

Image

Çeşitli denetimlerin anlamları şöyledir:

No
ad
tür
işlev
1
txtRemoteHost
JTextField
sunucu adı
2
txtLogin
JTextField
kullanıcı adı
3
txtMdp
JTextField
Kullanıcı şifresi
4
txtNewMdp
JTextField
Kullanıcının yeni şifresi
5
txtConfirmation
JTextField
Yeni şifrenin onaylanması
6
lstSuivi
JList
Oturum açma izleme mesajları
7
lstDialogue
JList
İstemci/Sunucu diyalog mesajları
10
cmdAnnuler
JButton
gösterilmiyor - Sunucuya bağlantı kurulurken görünen düğme. Bağlantıyı sonlandırmaya yarar.

8.5.4.3. Menüler

Başlık
Kontrolün adı
İşlev
Bağlantı
mnuconnexion
 
Bağlan
mnuconnecter
sunucuya bağlantı başlat
Çık
mnuQuitter
uygulamadan çıkar
Mesajlar
mnuMessages
 
RazSuivi
mnuRazSuivi
listeyi sil lstSuivi
RazDialogue
mnuRazDialogue
listeyi sil lstDialogue
Yazar
mnuAuteur
telif hakkı kutucuğunu gösterir

8.5.4.4. Uygulamanın Çalışması

Uygulamanın başlatılması

Uygulamanın ana sayfası yüklendiğinde, aşağıdaki işlemler gerçekleştirilir:

  • sayfa ekranın ortasına yerleştirilir
  • yalnızca Connexion/Quitter ve Auteur menü seçenekleri etkindir
  • Annuler düğmesi gizlenir
  • LstSuivi ve LstDialogue listeleri boştur
Oturum Açma/Bağlan Menüsü

Bu seçenek yalnızca 1 ila 5 numaralı alanlar doldurulduğunda kullanılabilir. Bu seçeneğe tıklandığında aşağıdaki işlemler gerçekleştirilir:

  • sunucuya bağlantı sağlamak için bir iş parçacığı başlatılır
  • kullanıcının devam eden bağlantıyı kesebilmesi için Annuler düğmesi görünür
  • Quitter ve Auteur hariç tüm menü seçenekleri devre dışı bırakılır

Ardından olaylar şu sırayla gerçekleşir:

  1. Kullanıcı Annuler düğmesine bastı: bağlantı iş parçacığı durdurulur ve menü başlangıç durumuna geri getirilir. İzleme bölümünde, bağlantının kullanıcı tarafından kapatıldığı belirtilir.
  2. Bağlantı isteği sunucu tarafından kabul edilir. Ardından, şifreyi değiştirmek için sunucuyla iletişim başlatılır. Bu iletişimin verileri LstDialogue listesine kaydedilir. İletişim sona erdiğinde, sunucuyla olan bağlantı kapatılır ve form menüsü başlangıç durumuna geri döndürülür.
  3. İletişim devam ettiği sürece, kullanıcının isterse bağlantıyı sonlandırabilmesi için İptal düğmesi ekranda kalır.
  4. İletişim sırasında herhangi bir hata meydana gelirse, bağlantı sonlandırılır ve hata nedeni LstSuivi izleme listesinde görüntülenir.
Bağlan/Çık menüsü

Bu seçenek, sunucuyla aktif olan bağlantıyı sonlandırır ve uygulamayı kapatır.

RazSuivi ve RazDialogue menüleri

Sırasıyla LstSuivi ve LstDialogue listelerini boşaltır. İlgili listeler boş olduğunda bu seçenekler devre dışı kalır.

İptal düğmesi

Formun alt kısmında bulunan bu düğme, yalnızca müşteri sunucuya bağlanma aşamasındayken veya sunucuya bağlıyken görünür. Annuler düğmesi, kullanıcıya sunucuyla olan iletişimi kesme olanağı sağlar.

İzleme listeleri

LstSuivi (5) listesi, bağlantıyı takip eder. Bağlantının önemli anlarını gösterir:

  • istemci tarafından açılması

  • sunucu veya istemci tarafından kapatılması

  • bağlantı aktif olduğu sürece meydana gelebilecek tüm hatalar

LstDialogue (6) listesi, istemci ile sunucu arasında kurulan diyaloğu izler.

Yazar seçeneği

Bu menü, "Telif Hakkı" adı verilen bir pencere açar:

Image

Hata Yönetimi

İletişim hataları 6 izleme listesinde, istemci/sunucu iletişimi ile ilgili hatalar ise 7 iletişim listesinde bildirilir. Bağlantı hatası durumunda, istemci/sunucu iletişimi kapatılır ve form, yeni bir bağlantıya hazır olacak şekilde başlangıç durumuna geri döndürülür.

8.5.4.5. TRAVAIL A FAIRE

Yukarıda açıklanan işi önce bağımsız bir uygulama, ardından da bir applet olarak gerçekleştirin.