Skip to content

9. Programlama TCP-IP

9.1. Genel Bilgiler

9.1.1. İnternet protokolleri

Burada, iki ana protokolün adından esinlenerek TCP/IP (Transfer Control Protocol / Internet Protocol) 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

9.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:

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, başka 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 geçirdiği işlemin tersi bir işleme tabi tutulur.

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:

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 kodlanma şeklinin seçimi (analog veya dijital)
. iletim yönteminin 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.

9.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:

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 verilmiştir. 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
  • 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; bu sırada bir taşıyıcı dalganın varlığını veya yokluğunu algılar; 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şlar. Bu tekniğe CD (Çarpışma Algılama) denir. 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 (Internet Protocol)
Ağın iki düğümü arasında paketleri iletir
ICMP 
(Internet Control Message Protocol)
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
(Address Resolution Protocol)
makinenin İnternet adresi ile fiziksel adresi arasında eşleştirme yapar
RARP
(Reverse Address Resolution Protocol)
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 (Transmission Control Protocol)
İki istemci arasında bilgilerin güvenilir bir şekilde iletilmesini sağlar
UDP (User Datagram Protocol)
İki istemci arasında güvenilir olmayan bilgi aktarımını sağlar

Katmanlar: Uygulama/Sunum/Oturum

Burada çeşitli protokoller bulunmaktadır:

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 olanak tanır
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ımlanan, taşıma katmanından bağımsız, uzak uygulamalar arası bir iletişim protokolüdür. Bu protokol önemlidir: programcının taşıma katmanının ayrıntılarını bilme yükünü 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, önceki RPC protokolüne dayanır

9.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 sefer 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:

Bir örnek verelim: FTP 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 alındığı onayı 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.

9.1.5. İnternette adresleme

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 bilgisayar, hangi ağda bulunduğuna bakılmaksızın B ağındaki bir bilgisayarla 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. Fransa'da, INRIA adreslerinin tahsisi bu kurum tarafından yapılır. Aslında bu kurum, yerel ağınız için bir adres verir; örneğin, Angers Fen Fakültesi ağı için 193.49.144.0 adresi gibi. Bu ağın yöneticisi daha sonra IP 193.49.144.1 ile 193.49.144.254 arasındaki adresleri istediği gibi tahsis edebilir. Bu adres genellikle ağa bağlı her bir makinedeki özel bir dosyaya kaydedilir.

9.1.5.1. IP adres sınıfları

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

  • ağın 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:

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:

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:

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.

9.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:

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:

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 dosyalarından birinde kayıtlıdır; dolayısıyla makine bu dosyaya bakarak adresi öğrenebilir. 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 çeşitli 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 düğüm, tüm düğümlerin fiziksel adresleri ile IP adresleri arasındaki eşleşmeyi 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.

9.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:

Ö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ğının fiziksel çerçevesi örneğini vermiştik:

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 paketine böler ve her biri daha sonra fiziksel ağ üzerinden gönderilir. Bu paketler ancak nihai varış noktalarında yeniden birleştirilir.

9.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, bir 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ğ kısmı 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 tablolarında bulur. Bu adres, başlangıçta bir dosyadan, kalıcı bir bellekten veya ağ üzerinde dolaşan bilgiler aracılığıyla elde edilmiştir.

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

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.

9.1.6.2. Hata ve kontrol mesajları

Yine ağ katmanında, yani IP protokolüyle aynı düzeyde, 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.

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

9.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 "bağlantı noktası numarası" olarak adlandırılan bir sayıdır. 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 şekildedir:

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

9.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ğlayacak bir protokolü kendisi geliştirmelidir.

TCP protokolü (Transfer Control Protocol) 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.

9.1.8. Uygulama 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 işlemini gerçekleştirmek için, FTP komutunu ş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 alanlar 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, IP adreslerine sahip birkaç ad sunucusunun listesini bulacaktır. Bir ad sunucusu, bir makine adı ile IP adresi 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 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 gelişigüzel bir şekilde değil, istek sayısını en aza indirecek şekilde gerçekleştirilecektir. Makine nihayetinde bulunursa, yanıt A makinesine geri gönderilecektir.

XDR: (eXternal Veri Temsili)

Sun MicroSystems tarafından geliştirilen bu protokol, makinelerden bağımsız standart bir veri temsilini tanımlar.

RPC: (Uzaktan 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 kuran bir protokoldür. Bu protokol önemlidir: programcının aktarım 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 dayanmaktadı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.

9.1.9. Sonuç

Bu giriş bölümünde İnternet protokollerinin bazı temel özelliklerini 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

9.2. Ağ adreslerinin yönetimi

İnternet ağındaki bir makine, IP (İnternet Protokolü) adresi ile benzersiz bir şekilde tanımlanır; bu adresin biçimi I1.I2.I3.I4 şeklindedir ve burada In, 1 ile 254 arasında bir sayıdır. Ayrıca, yine benzersiz bir adla da tanımlanabilir. Bu ad zorunlu değildir; uygulamalar sonuçta her zaman cihazların IP adreslerini kullanır. Bu adlar, kullanıcıların işini kolaylaştırmak amacıyla kullanılır. Bu sayede, bir tarayıcıyla http://www.ibm.com adresini URL olarak sorgulamak, her iki yöntem de mümkün olsa da, URL http://129.42.17.99 şeklinde sorgulamaktan daha kolaydır. IP <--> nomMachine adres eşleşmesi, DNS (Etki Alanı Adı Sistemi) adı verilen dağıtık bir internet hizmeti tarafından sağlanır. .NET platformu, internet adreslerini yönetmek için Dns sınıfını sunar:

Image

Sınıfın yöntemlerinin çoğu statiktir. İlgilendiğimiz yöntemlere bir göz atalım:

Overloads Public Shared Function
 GetHostByAddress(ByVal address As
 String) As IPHostEntry
IP adresinden "I1.I2.I3.I4" biçiminde bir IPHostEntry adresi döndürür. address makinesi bulunamazsa bir istisna atar.
Public Shared Function
 GetHostByName(ByVal hostName As
 String) As IPHostEntry
Bir makine adından IPHostEntry adresini döndürür. name makinesi bulunamazsa bir istisna oluşturur.
Public Shared Function 
GetHostName() As String
Bu komutu çalıştıran programın üzerinde çalıştığı makinenin adını döndürür

IPHostEntry türündeki ağ adresleri şu biçimdedir:

Bizi ilgilendiren özellikler:

Public Property AddressList 
As IPAddress ()
Bir makinenin IP adreslerinin listesi. Bir IP adresi tek bir fiziksel makineyi belirtirse, bir fiziksel makine birden fazla IP adresine sahip olabilir. Bu durum, makinenin farklı ağlara bağlanan birden fazla ağ kartına sahip olması halinde geçerlidir.
Public Property Aliases 
As String ()
Bir makinenin takma adlarının listesi; bu makine bir ana ad ve takma adlarla tanımlanabilir
Public Property HostName 
As String
makinenin adı (varsa)

IPAddress sınıfından aşağıdaki yapıcı, özellik ve yöntemleri ele alacağız:

Image

Bir [IPAddress] nesnesi, ToString() yöntemi ile I1.I2.I3.I4 dizesine dönüştürülebilir. Tersine, statik IPAddress.Parse("I1.I2.I3.I4") yöntemi kullanılarak bir IPAddress nesnesi, bir I1.I2.I3.I4 dizesinden elde edilebilir. Aşağıdaki programı ele alalım; bu program, üzerinde çalıştığı makinenin adını görüntüler ve ardından etkileşimli bir şekilde IP adresi ile makine adı arasındaki eşleşmeleri gösterir:

dos>address1
Machine Locale=tahe

Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171

Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa

Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99

Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99

Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]

Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : fin

Program şu şekildedir:


' seçenekler
Option Explicit On 
Option Strict On

' ad alanları
Imports System
Imports System.Net
Imports System.Text.RegularExpressions

' test modülü
Public Module adresses

    Sub Main()
        ' yerel makinenin adını gösterir
        ' ardından ağdaki makineler hakkında etkileşimli bilgi verir
        ' bir ad veya adresle tanımlanan IP
        ' yerel makine
        Dim localHost As String = Dns.GetHostName()
        Console.Out.WriteLine(("Machine Locale=" + localHost))

        ' etkileşimli soru-cevaplar
        Dim machine As String
        Dim adresseMachine As IPHostEntry
        While True
            ' aranan makinenin adının girilmesi
            Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
            machine = Console.In.ReadLine().Trim().ToLower()
            ' bitti mi?
            If machine = "fin" Then
                Exit While
            End If

            ' I1.I2.I3.I4 adresi mi yoksa makine adı mı?
            Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
            ' istisna yönetimi
            Try
                If isIPV4 Then
                    adresseMachine = Dns.GetHostByAddress(machine)
                Else
                    adresseMachine = Dns.GetHostByName(machine)
                End If
                ' ad
                Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
                ' adresler IP
                Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
                Dim i As Integer
                For i = 1 To adresseMachine.AddressList.Length - 1
                    Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
                Next i
                Console.Out.WriteLine()
                ' takma adlar
                If adresseMachine.Aliases.Length <> 0 Then
                    Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
                    For i = 1 To adresseMachine.Aliases.Length - 1
                        Console.Out.Write(("," + adresseMachine.Aliases(i)))
                    Next i
                    Console.Out.WriteLine()
                End If
            Catch
                ' makine mevcut değil
                Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
            End Try
        End While
    End Sub
End Module

9.3. TCP-IP Programlama

9.3.1. Genel Bilgiler

A ve B adlı iki uzak makine arasındaki iletişimi ele alalım:

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

  • B makinesinin adresi IP 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 telefonunda sinyal biçiminde kodlanacak, telefon hatları üzerinden taşınacak, B telefonuna ulaşacak ve orada kod çözülecektir. B, bu sayede konuşmayı duyacaktır. İş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 iletişim türünü kullanacakları konusunda anlaşmaya varmalıdır. Örneğin, ftp hizmeti ile kurulan diyalog, pop hizmeti ile kurulan diyalogla aynı değildir: bu iki hizmet aynı komutları kabul etmez. Bu hizmetlerin diyalog protokolleri farklıdır.

9.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 burada hatırlatalı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ü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ındığı 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.
  • 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.

9.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.

9.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

9.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

9.3.6. TcpClient sınıfı

TcpClient sınıfı, TCP hizmetinin istemcisini temsil etmek için uygun sınıftır. Aşağıdaki gibi tanımlanmıştır:

Image

Bizi ilgilendiren yapıcılar, yöntemler ve özellikler şunlardır:

Public Sub New(ByVal hostname
 As String,ByVal port As Integer)
belirtilen makinenin (hostname) belirtilen bağlantı noktasında (port) çalışan sunucuyla bir TCP bağlantısı oluşturur. Örneğin, istia.univ-angers.fr makinesinin 80 numaralı bağlantı noktasına bağlanmak için new TcpClient("istia.univ-angers.fr",80) komutunu kullanabilirsiniz.
Public Sub Close()
, TCP sunucusuyla olan bağlantıyı sonlandırır
Public Function GetStream()
 As NetworkStream
sunucuya okuma ve yazma için bir akış NetworkStream elde eder. İstemci-sunucu arasındaki veri alışverişini sağlayan bu akıştır.

9.3.7. NetworkStream sınıfı

NetworkStream sınıfı, istemci ile sunucu arasındaki ağ akışını temsil eder. Sınıf şu şekilde tanımlanmıştır:

Image

NetworkStream sınıfı, Stream sınıfından türetilmiştir. Birçok istemci-sunucu uygulaması, satır sonu karakterleri "\r\n" ile biten metin satırlarını alışverişinde bulunur. Bu nedenle, ağ akışında bu satırları okumak ve yazmak için StreamReader ve StreamWriter nesnelerini kullanmak yararlıdır. İki makine iletişim kurduğunda, bağlantının her iki ucunda birer TcpClient nesnesi bulunur. Bu nesnenin GetStream yöntemi, iki makineyi birbirine bağlayan ağ akışına (NetworkStream) erişim sağlar. Dolayısıyla, bir M1 makinesi, bir TcpClient nesnesi aracılığıyla bir M2 makinesiyle bağlantı kurmuşsa ve bu iki makine metin satırları alışverişinde bulunuyorsa, okuma ve yazma akışlarını şu şekilde oluşturabilir:

Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true

Şu komut

out1.AutoFlush=true

komutu, client1'in yazma akışının bir ara tampon üzerinden geçmeyeceğini, doğrudan ağa gönderileceğini ifade eder. Bu nokta önemlidir. Genellikle client1, ortağına bir metin satırı gönderdiğinde bir yanıt bekler. Eğer satır aslında M1 makinesinde arabelleğe alınmış ve hiç gönderilmemişse, bu yanıt asla gelmeyecektir. M2 makinesine bir metin satırı göndermek için şunu yazacağız:

client1.WriteLine("un texte")

M2'ten gelen yanıtı okumak için şunu yazacağız:

Dim réponse as String=client1.ReadLine()

9.3.8. Bir internet istemcisinin temel mimarisi

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


    Dim client As TcpClient = Nothing     ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing     ' le flux d'écriture du client
    Dim demande As String = Nothing         ' demande du client
    Dim réponse As String = Nothing         ' réponse du serveur

    Try
      ' M makinesinin P bağlantı noktasında çalışan servise bağlanılıyor
      client = New TcpClient(nomServeur, port)

      ' TCP istemcisinin giriş-çıkış akışları oluşturuluyor
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      ' istek-yanıt döngüsü
      While True
        ' istek hazırlanır
        demande = ...
        ' sunucuya gönderilir
        OUT.WriteLine(demande)
        ' sunucudan gelen yanıt okunur
        réponse = [IN].ReadLine()
        ' yanıt işleniyor
        ...
            End While
            ' işlem tamamlandı
            client.Close()
        Catch ex As Exception
      ' istisna işleniyor
...
        End Try

9.3.9. TcpListener sınıfı

TcpListener sınıfı, bir TCP hizmetini temsil etmek için uygun sınıftır. Bu sınıf şu şekilde tanımlanmıştır:

Image

Bizi ilgilendiren yapıcılar, yöntemler ve özellikler şunlardır:

Public Sub New(ByVal localaddr 
As IPAddress,ByVal port As Integer)
TCP adlı bir hizmet oluşturur; bu hizmet, (listen) parametre olarak geçirilen ve port olarak adlandırılan bağlantı noktasında (IP localadr adresindeki yerel makinenin dinleme bağlantı noktası), istemcilerin isteklerini bekleyen bir hizmet oluşturur.
Public Function AcceptTcpClient() 
As TcpClient
bir müşterinin isteğini kabul eder. Sonuç olarak, hizmet bağlantı noktası olarak adlandırılan başka bir bağlantı noktasına bağlı bir TcpClient nesnesi döndürür.
Public Sub Start()
müşteri isteklerini dinlemeye başlar
Public Sub Stop()
müşteri taleplerini dinlemeyi durdurur

9.3.10. Bir İnternet sunucusunun temel mimarisi

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


    ' dinleme hizmeti oluşturulur
    Dim ecoute As TcpListener = Nothing
    Dim port As Integer = ...
    Try
      ' hizmet oluşturuluyor
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' hizmet başlatılıyor
      ecoute.Start()
      ' hizmet döngüsü
      Dim liaisonClient As TcpClient = Nothing
            While not fini
                ' istemci bekleniyor
                liaisonClient = ecoute.AcceptTcpClient()
                ' hizmet başka bir görev tarafından sağlanıyor
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
                tache.Start()
            End While
        Catch ex As Exception
            ' hata bildiriliyor
....
        End Try
        ' hizmetin sonu
        ecoute.Stop()

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


Public Class Service

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' yapıcı
    Public Sub New(ByVal liaisonClient As TcpClient, ...)
        Me.liaisonClient = liaisonClient
        ...
    End Sub

    ' run yöntemi
    Public Sub Run()
        ' hizmeti müşteriye geri döndürür
        Try
            ' giriş akışı
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' çıkış akışı
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' istek/yazma-yanıt okuma döngüsü
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' istek işleniyor
                ...
                ' yanıt gönderiliyor
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' sonraki istek
                demande = [IN].ReadLine
            End While
            ' bağlantı sonu
            liaisonClient.Close()
        Catch e As Exception
            ...
        End Try
        ' hizmetin sonu
    End Sub

9.4. Örnekler

9.4.1. Yankı sunucusu

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

serveurEcho port

Sunucu, parametre olarak geçirilen bağlantı noktasında çalışır. Sunucu, istemcinin kendisine gönderdiği isteği istemciye geri gönderir. Program şu şekildedir:


' seçenekler
Option Explicit On 
Option Strict On

' ad alanları
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

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

Public Class serveurEcho
  Private Shared syntaxe As String = "Syntaxe : serveurEcho port"

  ' ana program
  Public Shared Sub Main(ByVal args() As String)

    ' bir argüman var mı
    If args.Length <> 1 Then
      erreur(syntaxe, 1)
    End If
    ' bu argüman >0 tamsayı olmalıdır
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(0))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    ' dinleme hizmeti oluşturulur
    Dim ecoute As TcpListener = Nothing
    Dim nbClients As Integer = 0 ' nbre de clients traités
    Try
      ' hizmet oluşturuluyor
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' hizmet başlatılıyor
      ecoute.Start()
      ' izleme
      Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
      Console.Out.WriteLine(ecoute.LocalEndpoint)

      ' hizmet döngüsü
      Dim liaisonClient As TcpClient = Nothing
            While True
                ' sonsuz döngü - Ctrl-C ile durdurulacak
                ' bir istemciyi bekliyor
                liaisonClient = ecoute.AcceptTcpClient()

                ' hizmet başka bir görev tarafından sağlanıyor
                nbClients += 1
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
                tache.Start()
            End While
            ' istekleri dinlemeye geri dönülür
        Catch ex As Exception
            ' hata bildiriliyor
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
        ' hizmetin sonu
        ecoute.Stop()
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hatanın görüntülenmesi
        System.Console.Error.WriteLine(msg)
        ' hata ile durdurma
        Environment.Exit(exitCode)
    End Sub
End Class

' -------------------------------------------------------
' yankı sunucusunun bir müşterisine hizmet veriyor
Public Class traiteClientEcho

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private numClient As Integer    ' n° de client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' yapıcı
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
        Me.liaisonClient = liaisonClient
        Me.numClient = numClient
    End Sub

    ' run yöntemi
    Public Sub Run()
        ' istemciye hizmet veriyor
        Console.Out.WriteLine(("Début de service au client " & numClient))
        Try
            ' giriş akışı
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' çıkış akışı
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' okuma/yazma isteği-yanıt döngüsü
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' izleme
                Console.Out.WriteLine(("Client " & numClient & " : " & demande))
                ' müşteri dosya sonu işaretini gönderdiğinde hizmet durur
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' istemci "son" gönderdiğinde hizmet durur
                If demande.Trim().ToLower() = "fin" Then
                    Exit While
                End If
                ' sonraki istek
                demande = [IN].ReadLine
            End While
            ' bağlantı sonu
            liaisonClient.Close()
        Catch e As Exception
            erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
        End Try
        ' hizmetin sonu
        Console.Out.WriteLine(("Fin de service au client " & numClient))
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleme
        System.Console.Error.WriteLine(msg)
        ' hata ile durdurma
        Environment.Exit(exitCode)
    End Sub
End Class

Sunucunun yapısı, genel TCP sunucu mimarisine uygundur.

9.4.2. Yankı sunucusu için bir istemci

Şimdi önceki sunucu için bir istemci yazacağız. Bu istemci şu şekilde çağrılacaktır:

clientEcho nomServeur port

nomServeur makinesine port numaralı bağlantı noktası üzerinden bağlanır ve sunucuya metin satırları gönderir; sunucu da bunları yankı olarak geri gönderir.


' seçenekler
Option Explicit On 
Option Strict On

' ad alanları
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class clientEcho

  ' bir yankı sunucusuna bağlanır
  ' klavyeden girilen her satır yankı olarak alınır
  Public Shared Sub Main(ByVal args() As String)
    ' sözdizimi
    Const syntaxe As String = "pg machine port"

    ' argüman sayısı
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' sunucunun adı not edilir
    Dim nomServeur As String = args(0)

    ' bağlantı noktası >0 olan bir tamsayı olmalıdır
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If

        ' çalışmaya başlanabilir
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim demande As String = Nothing ' demande du client
    Dim réponse As String = Nothing ' réponse du serveur
    Try
      ' M makinesinin P bağlantı noktasında çalışan servise bağlanılır
      client = New TcpClient(nomServeur, port)

      ' TCP istemcisinin giriş-çıkış akışları oluşturulur
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      ' istek-yanıt döngüsü
      While True
        ' istek klavyeden gelir
        Console.Out.Write("demande (fin pour arrêter) : ")
        demande = Console.In.ReadLine()
        ' sunucuya gönderilir
        OUT.WriteLine(demande)
        ' sunucudan gelen yanıt okunur
        réponse = [IN].ReadLine()
        ' yanıt işleniyor
        Console.Out.WriteLine(("Réponse : " + réponse))
        ' bitti mi?
        If demande.Trim().ToLower() = "fin" Then
          Exit While
                End If
            End While
            ' bitti
            client.Close()
        Catch ex As Exception
      ' istisna işleniyor
      erreur(ex.Message, 3)
        End Try
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleme
        System.Console.Error.WriteLine(msg)
        ' hata ile durduruldu
        Environment.Exit(exitCode)
    End Sub
End Class

Bu istemcinin yapısı, tcp.Voici istemcilerinin genel mimarisine uygundur; aşağıdaki yapılandırmada elde edilen sonuçlar:

  • sunucu, aynı makinede bir DOS penceresinde 100 numaralı bağlantı noktasında çalıştırılır
  • Aynı makinede, iki ayrı DOS penceresinde iki istemci başlatılır

İstemci 1 penceresinde şu sonuçlar elde edilir:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]

Müşteri 2'ninkinde:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]

Sunucudakinde:

dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C

Sunucunun aynı anda iki müşteriye hizmet verebildiği görülmektedir.

9.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 alışverişi yoluyla gerçekleşir. Aşağıdaki şekilde başlatılacak genel bir TCP istemcisi yazacağız: cltgen 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ığı kullanılıyor? O 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 şu 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 gelen 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 kesilecek 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 amaçlı 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>cltgen 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 gönderilir.
    • data, SMTP sunucusuna mesajın gönderileceğini bildirir. Sunucunun yanıtında da belirtildiği gibi, bu mesaj bir dizi satırdan oluşur ve en sonda sadece nokta karakteri içeren bir satırla biter. Bir mesajda, boş bir satırla mesaj gövdesinden ayrılmış başlıklar bulunabilir. Ö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 bağlantıyı kapatır (réseau.Le). 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ığına dikkat edilmelidir. 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 iletişim protokolünü (Post Office Protocol) inceleyelim. Bu protokol 110 numaralı bağlantı noktasında çalışır.

dos>cltgen 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 makinedeki kullanıcı adımızı gireriz
  • pass password, burada önceki kullanıcı adıyla ilişkili ş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 sunucusuyla istemci arasındaki iletişim protokolünü inceleyelim:

dos>cltgen 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. Satırlar

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

istemciye, HTML (HyperText İşaretleme Dili) metnini temsil eden 11251 bayt alacağını ve gönderim bittiğinde bağlantının kapatılacağını bildirir. Dolayısıyla burada çok kullanışlı bir TCP istemcisiyle karşı karşıyayız. Aslında bu istemci, telnet adıyla makinelerde zaten mevcut; ancak onu kendimiz yazmak ilginçti. Genel amaçlı TCP istemcisi programı şöyledir:


' ad alanları
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

' sınıf
Public Class clientTcpGénérique
   
    
   ' 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
   ' bunlar ekranda görüntülenecektir
   ' her şey klavyeden girilen "fin" komutuyla sona erer
  Public Shared Sub Main(ByVal args() As String)

    ' sözdizimi
    Const syntaxe As String = "pg serveur port"

    ' argüman sayısı
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' sunucunun adı not edilir
    Dim serveur As String = args(0)

    ' bağlantı noktası >0 olan bir tamsayı olmalıdır
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    Dim client As TcpClient = Nothing
    ' sorunlar yaşanabilir
    Try
      ' hizmete bağlanılır
      client = New TcpClient(serveur, port)
    Catch ex As Exception
      ' hata
      Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
      ' son
      Return
        End Try
        ' okuma/yazma iş parçacıkları oluşturuluyor
        Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
        Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))

        ' iki iş parçacığının yürütülmesi başlatılıyor
        thSend.Start()
        thReceive.Start()

        ' ana iş parçacığı sona erdi
        Return
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleniyor
        System.Console.Error.WriteLine(msg)
        ' hata ile durduruluyor
        Environment.Exit(exitCode)
    End Sub
End Class

Public Class clientSend
    ' klavyeden girilen komutları okumakla görevli sınıf
    ' ve bunları, yapıcıya aktarılan bir TCP istemcisi aracılığıyla bir sunucuya göndermekle görevli sınıf
    Private client As TcpClient    ' le client tcp

    ' yapıcı
    Public Sub New(ByVal client As TcpClient)
        ' TCP istemcisi belirtilir
        Me.client = client
    End Sub

    ' iş parçacığının Run yöntemi
    Public Sub Run()

        ' yerel veriler
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim commande As String = Nothing        ' commande lue au clavier
        ' hata yönetimi
        Try
            ' ağ yazma akışının oluşturulması
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' komut girişi-gönderme döngüsü
            Console.Out.WriteLine("Commandes : ")
            While True
                ' klavyeden girilen komutun okunması
                commande = Console.In.ReadLine().Trim()
                ' bitti mi?
                If commande.ToLower() = "fin" Then
                    Exit While
                End If
                ' komutun sunucuya gönderilmesi
                OUT.WriteLine(commande)
            End While
        Catch ex As Exception
            ' hata
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' son - akışlar kapatılıyor
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' iş parçacığının sonu bildiriliyor
        Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
    End Sub
End Class


Public Class clientReceive
    ' bir 
    ' yapıcıya aktarılan bir TCP istemcisine yönelik metin satırlarını okumakla görevli sınıf
    Private client As TcpClient    ' le client tcp

    ' yapıcı
    Public Sub New(ByVal client As TcpClient)
        ' TCP istemcisi kaydediliyor
        Me.client = client
    End Sub

    'oluşturucu
    ' iş parçacığının Run yöntemi
    Public Sub Run()

        ' yerel veriler
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' hata yönetimi
        Try
            ' ağ okuma akışının oluşturulması
            [IN] = New StreamReader(client.GetStream())
            ' IN akışından metin satırlarını okuma döngüsü
            While True
                ' ağ akışını okuma
                réponse = [IN].ReadLine()
                ' akış kapalı mı?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' görüntüleme
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' hata
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' son - akışlar kapatılıyor
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' iş parçacığının sonu bildiriliyor
        Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
    End Sub
End Class

9.4.4. Genel bir TCP sunucusu

Şimdi ise,

  • bu sunucu, istemcileri tarafından gönderilen komutları ekrana yansıtır
  • ve yanıt olarak kullanıcının klavyeden girdiği metin satırlarını onlara gönderen bir sunucuya odaklanıyoruz. Dolayısıyla sunucu rolünü üstlenen bu sonuncusudur.

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

  • biri, yalnızca müşteri tarafından gönderilen metin satırlarını okumaya adanmış
  • 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ırdığı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:

dos>cltgen 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>srvgen 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>cltgen 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>srvgen 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>srvgen 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ı, 88 makinesinin 88 bağlantı noktasına bağlanacak ve ardından /exemple.html sayfasını isteyecektir:

Image

Şimdi sunucumuzun penceresine bakalım:

dos>srvgen 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ı keşfediyoruz. Bu da bize HTTP protokolünü yavaş yavaş keşfetme imkanı sağlıyor. Önceki bir örnekte, yalnızca GET komutunu gönderen bir web istemcisi oluşturmuştuk. Bu yeterli olmuştu. Burada ise 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. İstemcimize 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 Affichage/Source komutunu girersek şu sonucu alırız:

Image

yani genel sunucudan gönderdiğimizin aynısıdır. Genel TCP sunucusunun kodu şöyledir:


' ad alanları
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class serveurTcpGénérique

    ' ana program
    Public Shared Sub Main(ByVal args() As String)

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

        Const syntaxe As String = "Syntaxe : pg port"

        ' bir argüman var mı
        If args.Length <> 1 Then
            erreur(syntaxe, 1)
        End If
        ' bu argüman >0 olan bir tamsayı olmalıdır
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' dinleme hizmeti oluşturulur
        Dim ecoute As TcpListener = Nothing
        Dim nbClients As Integer = 0     ' nbre de clients traités
        Try
            ' hizmet oluşturulur
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
            ' hizmet başlatılıyor
            ecoute.Start()
            ' izleme
            Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))

            ' müşterilere hizmet döngüsü
            Dim client As TcpClient = Nothing
            While True        ' boucle infinie - sera arrêtée par Ctrl-C
                ' müşteri bekleniyor
                client = ecoute.AcceptTcpClient()

                ' hizmet, ayrı iş parçacıklarıyla sağlanır
                nbClients += 1
                ' müşteri isteklerini okuma iş parçacığı
                Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
                ' kullanıcının klavyeden girdiği yanıtları okuma iş parçacığı
                Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))

                ' iki iş parçacığının yürütülmesi başlatılır
                thSend.Start()
                thReceive.Start()
            End While
            ' istekleri dinlemeye geri dönülür
        Catch ex As Exception
            ' hata bildirilir
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleniyor
        System.Console.Error.WriteLine(msg)
        ' hata ile durdurma
        Environment.Exit(exitCode)
    End Sub
End Class


Public Class serveurSend
    ' 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
    Private client As TcpClient    ' le client tcp
    Private numClient As Integer    ' n° de client

    ' yapıcı
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' TCP istemcisi not edilir
        Me.client = client
        ' ve numarasını
        Me.numClient = numClient
    End Sub

    ' iş parçacığının Run yöntemi
    Public Sub Run()

        ' yerel veriler
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim réponse As String = Nothing        ' réponse lue au clavier
        ' izleme
        Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
        ' hata yönetimi
        Try
            ' ağ yazma akışının oluşturulması
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' komutların girilmesi ve gönderilmesi döngüsü
            While True
                ' müşteri tanımlama
                Console.Out.Write((numClient & " : "))
                ' klavyeyle yazılan yanıtın okunması
                réponse = Console.In.ReadLine().Trim()
                ' bitti mi?
                If réponse.ToLower() = "fin" Then
                    Exit While
                End If
                ' yanıt sunucuya gönderiliyor
                OUT.WriteLine(réponse)
            End While
            ' sonraki yanıt
        Catch ex As Exception
            ' hata
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' son - akışlar kapatılıyor
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' iş parçacığının sonu bildiriliyor
        Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
    End Sub
End Class

Public Class serveurReceive
    ' sunucuya gönderilen metin satırlarını okumakla görevli sınıf 
    ' yapıcıya aktarılan bir TCP istemcisi aracılığıyla
    Private client As TcpClient     ' le client tcp
    Private numClient As Integer    ' n° de client

    ' yapıcı
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' TCP istemcisi not edilir
        Me.client = client
        ' ve numarası
        Me.numClient = numClient
    End Sub

    ' iş parçacığının Run yöntemi
    Public Sub Run()
        ' yerel veriler
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' izleme
        Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
        ' hata yönetimi
        Try
            ' ağ okuma akışının oluşturulması
            [IN] = New StreamReader(client.GetStream())
            ' IN akışından metin satırlarını okuma döngüsü
            While True
                ' ağ akışını okuma
                réponse = [IN].ReadLine()
                ' akış kapalı mı?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' görüntüleme
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' hata
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' son - akışlar kapatılıyor
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' iş parçacığının sonu bildiriliyor
        Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
    End Sub
End Class

9.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 sunucu tarafından gönderilen metni ekranda görüntüleyen bir web istemcisi yazacağız. Sunucunun 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 ve HTML sayfasını göndermez.

Web istemcimiz şu şekilde çağrılacaktır: clientweb URL cmd; burada URL,URL'tir ve cmd ise, sadece başlıkları (HEAD) mı yoksa sayfanın içeriğini de (GET) isteyip istemediğ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>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>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:


' ad alanları
Imports System
Imports System.Net.Sockets
Imports System.IO


Public Class clientWeb1

    ' bir URL talep ediyor
    ' içeriğini ekranda görüntüler
    Public Shared Sub Main(ByVal args() As String)
        ' sözdizimi
        Const syntaxe As String = "pg URI GET/HEAD"

        ' argüman sayısı
        If args.Length <> 2 Then
            erreur(syntaxe, 1)
        End If
        ' istenen URI not edilir
        Dim URIstring As String = args(0)
        Dim commande As String = args(1).ToUpper()

        ' URI'in geçerliliğini kontrol eder
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI hatalı
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try
        ' komutun doğrulanması
        If commande <> "GET" And commande <> "HEAD" Then
            ' sipariş hatalı
            erreur("Le second paramètre doit être GET ou HEAD", 3)
        End If

        ' çalışılabilir
        Dim client As TcpClient = Nothing        ' le client
        Dim [IN] As StreamReader = Nothing        ' le flux de lecture du client
        Dim OUT As StreamWriter = Nothing        ' le flux d'écriture du client
        Dim réponse As String = Nothing        ' réponse du serveur
        Try
            ' sunucuya bağlanılıyor
            client = New TcpClient(uri.Host, uri.Port)

            ' müşterinin giriş-çıkış akışları oluşturuluyor TCP
            [IN] = New StreamReader(client.GetStream())
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True

            ' URL isteniyor - başlıkların gönderilmesi HTTP
            OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
            OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
            OUT.WriteLine("Connection: close")
            OUT.WriteLine()
            ' yanıt okunur
            réponse = [IN].ReadLine()
            While Not (réponse Is Nothing)
                ' yanıt işlenir
                Console.Out.WriteLine(réponse)
                ' yanıt okunuyor
                réponse = [IN].ReadLine()
            End While
            ' işlem tamamlandı
            client.Close()
        Catch e As Exception
            ' istisna işleniyor
            erreur(e.Message, 4)
        End Try
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleme
        System.Console.Error.WriteLine(msg)
        ' hata ile durdurma
        Environment.Exit(exitCode)
    End Sub
End Class

Bu programdaki tek yenilik, Uri 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. Uri sınıfı, URL dizesini farklı bileşenlerine ayırmamızı sağlar. Bir Uri nesnesi, parametre olarak alınan URIstring dizesinden oluşturulur:


        ' URI'in geçerliliğinin kontrolü
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI hatalı
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try

Parametre olarak alınan URI dizesi geçerli bir URI 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. Uri nesnesi oluşturulduktan sonra, bu Uri'nin çeşitli öğelerine erişilebilir. Dolayısıyla, önceki koddaki uri nesnesi http://serveur:port/cheminPageHTML?param1=val1;param2=val2;... dizesinden oluşturulmuşsa, şu sonuç elde edilir:

uri.Host=serveur, uri.Port=port, uri.Path=cheminPageHTML, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.

9.4.6. Yönlendirmeleri yöneten web istemcisi

Önceki web istemcisi, kendisinin talep ettiği URL adresinden olası bir yönlendirmeyi yönetmez. Bir sonraki istemci ise 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>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:


' ad alanları
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic

' web istemcisi sınıfı
Public Class clientWeb
   
  ' bir URL ister ve içeriğini ekranda görüntüler
  Public Shared Sub Main(ByVal args() As String)
    ' sözdizimi
    Const syntaxe As String = "pg URI GET/HEAD"

    ' argüman sayısı
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' istenen URI not edilir
    Dim URIstring As String = args(0)
    Dim commande As String = args(1).ToUpper()

    ' URI'in geçerliliğini kontrol eder
    Dim uri As Uri = Nothing
    Try
      uri = New Uri(URIstring)
    Catch ex As Exception
      ' URI hatalı
      erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
    End Try 'catch
    ' komutun doğrulanması
    If commande <> "GET" And commande <> "HEAD" Then
      ' sipariş hatalı
      erreur("Le second paramètre doit être GET ou HEAD", 3)
    End If

    ' çalışılabilir
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim réponse As String = Nothing ' réponse du serveur
    Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection acceptée
    Dim nbRedirs As Integer = 0 ' nombre de redirections en cours
    Dim premièreLigne As String ' 1ère ligne de la réponse
    Dim redir As Boolean = False ' indique s'il y a redirection ou non
    Dim locationString As String = "" ' la chaîne URI d'une éventuelle redirection
    ' bir URL yönlendirmesini bulmak için düzenli ifade
    Dim location As New Regex("^Location: (.+?)$") '

        ' hata yönetimi
    Try
      ' Yönlendirmeler varsa, birden fazla URL sorgulanabilir
      While nbRedirs <= nbRedirsMax
        ' sunucuya bağlanılır
        client = New TcpClient(uri.Host, uri.Port)

        ' istemcinin giriş-çıkış akışları oluşturulur TCP
        [IN] = New StreamReader(client.GetStream())
        OUT = New StreamWriter(client.GetStream())
        OUT.AutoFlush = True

        ' HTTP başlıklarını göndererek URL'i talep ediyoruz
        OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
        OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
        OUT.WriteLine("Connection: close")
        OUT.WriteLine()

        ' yanıtın ilk satırı okunur
        premièreLigne = [IN].ReadLine()
        ' ekran çıktısı
        Console.Out.WriteLine(premièreLigne)

        ' yönlendirme mi?
        If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
          ' yönlendirme var
          redir = True
          nbRedirs += 1
                End If

                ' başlıklar HTTP başlıkların sonunu belirten boş satır bulunana kadar devam eder
                Dim locationFound As Boolean = False
                réponse = [IN].ReadLine()
                While réponse <> ""
                    ' yanıt görüntülenir
                    Console.Out.WriteLine(réponse)
                    ' Yönlendirme varsa, Location başlığı aranır
                    If redir And Not locationFound Then
                        ' satırı, location ilişkisel ifadesiyle karşılaştırılır
                        Dim résultat As Match = location.Match(réponse)
                        If résultat.Success Then
                            ' bulunursa, yönlendirme kodu olan URL not edilir
                            locationString = résultat.Groups(1).Value
                            ' bulunduğunu not edilir
                            locationFound = True
                        End If
                    End If
                    ' bir sonraki satır
                    réponse = [IN].ReadLine()
                End While

                ' yanıtın sonraki satırları
                Console.Out.WriteLine(réponse)
                réponse = [IN].ReadLine()
                While Not (réponse Is Nothing)
                    ' yanıtı görüntüleyin
                    Console.Out.WriteLine(réponse)
                    ' sonraki satır
                    réponse = [IN].ReadLine()
                End While

                ' bağlantı kesiliyor
                client.Close()
                ' iş bitti mi?
                If Not locationFound Or nbRedirs > nbRedirsMax Then
                    Exit While
                End If

                ' bir yönlendirme yapılması gerekiyor - yeni URI oluşturuluyor
                URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
                uri = New Uri(URIstring)
                ' izleme
                Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
            End While
        Catch e As Exception
      ' istisna işleniyor
      erreur(e.Message, 4)
        End Try
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleme
        System.Console.Error.WriteLine(msg)
        ' hata ile sonlandırma
        Environment.Exit(exitCode)
    End Sub
End Class

9.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 vergi sınıfı oluşturuldu. Bu sınıfın öznitelikleri üç sayı tablosundan oluşuyor:


Public Class impôt
    ' vergi hesaplaması için gerekli veriler
    ' harici bir kaynaktan geliyor
    Private limites(), coeffR(), coeffN() as double

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

  • verginin hesaplanması için gerekli olan üç veri dizisinin aktarıldığı bir oluşturucu
     // üretici 1
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
         ' üç sınır tablosunu, coeffR, coeffN'i
         ' yapıcıya aktarılan parametrelerden
  • bir oluşturucuya, DSN adlı bir veritabanından ODBC adı aktarılır

    ' yapıcı 2
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        ' üç sınır dizisini, coeffR, coeffN'i
        ' ODBC veritabanındaki Timpots tablosunun içeriğinden DSNimpots
        ' colLimites, colCoeffR, colCoeffN bu tablonun üç sütunudur
        ' bir istisna oluşturabilir

Bir test programı yazılmıştı:

dos>vbc /r:impots.dll testimpots.vb

dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F

Burada test programı ve impôt nesnesi aynı makinedeydi. Test programını ve impôt nesnesini farklı makinelere yerleştirmeyi planlıyoruz. Uzak impôt nesnesinin sunucu görevi göreceği bir istemci-sunucu uygulamamız olacak. Yeni sınıfın adı ServeurImpots olup, impôt sınıfından türetilmiştir:


Public Class ServeurImpots
    Inherits impôt

    ' özellikleri
    Private portEcoute As Integer    ' le port d'écoute des demandes clients
    Private actif As Boolean    ' état du serveur

    ' yapıcı
    Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
        ' dinleme bağlantı noktası belirtilmiştir
        Me.portEcoute = portEcoute
        ' şu anda etkin değil
        actif = False
        ' klavyeden girilen komutları okumak için bir iş parçacığı oluşturur ve başlatır
        ' sunucu bu komutlar aracılığıyla yönetilecektir
        Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
        threadLecture.Start()
    End Sub

Oluşturucudaki tek yeni parametre, istemcilerin isteklerini dinleyeceği bağlantı noktasıdır. Diğer parametreler doğrudan temel sınıf olan impôt'e 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 Sub admin()
        ' klavyeden girilen sunucu yönetim komutlarını okur
        ' sonsuz bir döngü içinde
        Dim commande As String = Nothing
        While True
            ' komut istemi
            Console.Out.Write("Serveur d'impôts>")
            ' komut okuma
            commande = Console.In.ReadLine().Trim().ToLower()
            ' komut çalıştırma
            If commande = "start" Then
                ' aktif mi?
                If actif Then
                    'hata
                    Console.Out.WriteLine("Le serveur est déjà actif")
                Else
                    ' dinleme hizmeti başlatılıyor
                    Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
                    threadEcoute.Start()
                End If
            Else
                If commande = "stop" Then
                    ' tüm yürütme iş parçacıkları sona erdi
                    Environment.Exit(0)
                Else
                    ' hata
                    Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
                End If
            End If
        End While
    End Sub

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 Sub ecoute()
        ' istemcilerin isteklerini dinleyen iş parçacığı
        ' dinleme hizmeti oluşturuluyor
        Dim ecoute As TcpListener = Nothing
        Try
            ' hizmet oluşturuluyor
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
            ' hizmet başlatılıyor
            ecoute.Start()
            ' izleme
            Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))

            ' hizmet döngüsü
            Dim liaisonClient As TcpClient = Nothing
            While True            ' boucle infinie
                ' istemci bekleniyor
                liaisonClient = ecoute.AcceptTcpClient()
                ' hizmet başka bir görev tarafından sağlanıyor
                Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
                threadClient.Start()
            End While
            ' istekleri dinlemeye geri dönülür
        Catch ex As Exception
            ' hata bildiriliyor
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleniyor
        System.Console.Error.WriteLine(msg)
        ' hata ile durdurma
        Environment.Exit(exitCode)
    End Sub

portEcoute numaralı bağlantı noktasında dinleme yapan klasik bir TCP sunucusu bulunmaktadır. İstemcilerin istekleri, iki parametre aktarılan bir nesnenin Run yöntemiyle işlenir:

  1. müşteriye erişim sağlayacak olan TcpClient nesnesi
  2. vergi hesaplama yöntemi olan this.calculer’e erişim sağlayacak impôt this nesnesi.

' -------------------------------------------------------
' vergi sunucusunun bir müşterisine hizmet sunuluyor
Public Class traiteClientImpots

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie
    Private objImpôt As impôt     ' objet Impôt

    ' oluşturucu
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
        Me.liaisonClient = liaisonClient
        Me.objImpôt = objImpôt
    End Sub

Run yöntemi, müşterilerin taleplerini işler. Bu talepler iki şekilde olabilir:

  1. evli hesaplama (evet/hayır) nbEnfants salaireAnnuel
  2. fincalculs

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


    ' Run yöntemi
    Public Sub Run()
        ' istemciye hizmet sunuyor
        Try
            ' giriş akışı
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' çıkış akışı
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' müşteriye hoş geldiniz mesajı gönderme
            OUT.WriteLine("Bienvenue sur le serveur d'impôts")

            ' istek/yanıt okuma döngüsü
            Dim demande As String = Nothing
            Dim champs As String() = Nothing            ' les éléments de la demande
            Dim commande As String = Nothing            ' la commande du client : calcul ou fincalculs
            demande = [IN].ReadLine()
            While Not (demande Is Nothing)
                ' isteği alanlara ayrıştırma
                champs = Regex.Split(demande.Trim().ToLower(), "\s+")
                ' iki istek kabul edildi: hesaplama ve hesaplamaların sonu
                commande = champs(0)
                Dim erreur As Boolean = False
                If commande <> "calcul" And commande <> "fincalculs" Then
                    ' müşteri hatası
                    OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
                End If
                If commande = "calcul" Then
                    calculerImpôt(champs)
                End If
                If commande = "fincalculs" Then
                    ' müşteriye veda mesajı
                    OUT.WriteLine("Au revoir...")
                    ' kaynakların serbest bırakılması
                    Try
                        OUT.Close()
                        [IN].Close()
                        liaisonClient.Close()
                    Catch
                    End Try
                    ' son
                    Return
                End If
                ' yeni istek
                demande = [IN].ReadLine()
            End While
        Catch e As Exception
            erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
        End Try
    End Sub

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 Sub calculerImpôt(ByVal champs() As String)
        ' talebi işler: evli hesaplaması nbEnfants salaireAnnuel
        ' alanlar tablosunda alanlara ayrılmış
        Dim marié As String = Nothing
        Dim nbEnfants As Integer = 0
        Dim salaireAnnuel As Integer = 0

        ' argümanların geçerliliği
        Try
            ' en az 4 alan gereklidir
            If champs.Length <> 4 Then
                Throw New Exception
            End If
            ' evli
            marié = champs(1)
            If marié <> "o" And marié <> "n" Then
                Throw New Exception
            End If
            ' çocuklar
            nbEnfants = Integer.Parse(champs(2))
            ' maaş
            salaireAnnuel = Integer.Parse(champs(3))
        Catch
            OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
            ' bitti
            Exit Sub
        End Try
        ' vergi hesaplanabilir
        Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
        ' cevap müşteriye gönderilir
        OUT.WriteLine(impot.ToString)
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleme
        System.Console.Error.WriteLine(msg)
        ' hata ile durdurma
        Environment.Exit(exitCode)
    End Sub

Bu sınıf,

dos>vbc /r:impots.dll /r:system.dll /t:library srvimpots.vb

tarafından derlenir; burada impots.dll, impôt sınıfının kodunu içerir. Bir test programı şu şekilde olabilir:


' ad alanları
Imports System
Imports System.IO
Imports Microsoft.VisualBasic

Public Class testServeurImpots
    Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"

    ' ana program
    Public Shared Sub Main(ByVal args() As String)

        ' 6 argüman gereklidir
        If args.Length <> 6 Then
            erreur(syntaxe, 1)
        End If
        ' bağlantı noktası >0 tamsayı olmalı
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' vergi sunucusu oluşturuluyor
        Try
            Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
        Catch ex As Exception
            'hata
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
    End Sub

    ' hataların görüntülenmesi
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleme
        System.Console.Error.WriteLine(msg)
        ' hata nedeniyle durduruldu
        Environment.Exit(exitCode)
    End Sub
End Class

Test programına, bir ServeurImpots nesnesinin oluşturulması için gerekli veriler aktarılır ve program bu verileri kullanarak söz konusu nesneyi oluşturur. Bu test programı şu şekilde derlenir:

dos>vbc /r:srvimpots.dll /r:impots.dll testimpots.vb

İşte ilk test:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

Şu satır

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn

satırı, 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>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>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> clttcpgenerique localhost 124Commandes :
<-- 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>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop