Skip to content

11. İnternet Programlama

11.1. Généralités

11.1.1. İnternet Protokolleri

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

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

11.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 verilen addır. Ağ fiziksel olarak, çapı yaklaşık 1,27 cm ve uzunluğu en fazla 500 m olan bir koaksiyel kablodan oluşur. répéteurs aracılığıyla genişletilebilir; iki makine arasında en fazla iki tekrarlayıcı bulunabilir. Kablo pasiftir: tüm aktif bileşenler kabloya bağlı cihazlarda bulunur. Her cihaz, aşağıdakileri içeren bir ağ erişim kartı aracılığıyla kabloya bağlanır:

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

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

  • 10 Megabit/saniye kapasite.
  • Bus topolojisi: tüm cihazlar aynı kabloya bağlanır
  • Yayılım ağı - Verici bir makine, bilgileri alıcı makinenin adresiyle birlikte kablo üzerinden iletir. Bağlı tüm makineler bu bilgileri alır, ancak yalnızca bilgilerin gönderildiği makine bu bilgileri saklar.
  • Erişim yöntemi şu şekildedir: İ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ıra kendisine geldiğinde 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: kabloda 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

Uygulama/Sunum/Oturum Katmanları

Burada çeşitli protokoller bulunur:

TELNET
A makinesinin B makinesine terminal olarak bağlanmasını sağlayan terminal emülatörü
FTP (File Transfer Protocol)
dosya aktarımına olanak tanır
TFTP (Trivial File Transfer Protocol)
dosya aktarımına olanak tanır
SMTP (Simple Mail Transfer protocol)
ağ kullanıcıları arasında mesaj alışverişine izin verir
DNS (Domain Name System)
bir makine adını o makinenin İnternet adresine dönüştürür
XDR (eXternal Data Representation)
sun tarafından oluşturulan MicroSystems, makinelerden bağımsız standart bir veri temsilini belirtir
RPC(Remote Procedures Call)
Yine Sun tarafından tanımlanmıştır; bu, taşıma katmanından bağımsız, uzak uygulamalar arası 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

11.1.4. İnternet protokollerinin işleyişi

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

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 geçerli olup olmadığını kontrol eder: Alınan bitlere dayalı olarak bir kontrol toplamı hesaplar (checksum) ve bu toplamın paket başlığında da 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.

11.1.5. İnternetteki adresleme sorunları

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

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

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

IP adreslerinin hepsi birbirinden farklı olmalıdır. 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 193.49.144.1 ile 193.49.144.254 arasındaki IP adreslerini istediği gibi tahsis edebilir. Bu adres genellikle ağa bağlı her bir makinedeki özel bir dosyaya kaydedilir.

11.1.5.1. IP adres sınıfları

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

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

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

A sınıfı

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

R1 ağ adresidir

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

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

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.

11.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: dosyanın düzenlenmesi yeterlidir. Fiziksel adres ise ağ kartının belleğinde kayıtlıdır ve değiştirilemez.

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

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

11.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 yoluyla 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ü tipik 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.

11.1.6.2. Hata ve kontrol mesajları

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

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

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

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

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

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

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

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

11.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 Kontrol Protokolü) bu sorunları ortadan kaldırır. Özellikleri şunlardır:

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

11.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ı, makine B’ye bağlı bir terminale sahipmiş gibi davranır. Telnet, TCP protokolünü kullanır.

FTP: (Dosya Aktarım Protokolü)

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

TFTP: (Basit Dosya Aktarım Kontrolü)

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

DNS: (Etki Alanı Adı Sistemi)

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

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

Angers Üniversitesi'ne ait DPX2/320 makinesi

Angers'daki ISERPA Sun makinesi

Bir makineyi IP adresiyle değil de bir adla tanımlamanın daha kolay olacağı açıktır. Bu durumda, adların benzersizliği sorunu ortaya çıkar: birbirine bağlı milyonlarca makine vardır. Adların bir merkezi kurum tarafından tahsis edildiğini düşünebiliriz. Bu, şüphesiz oldukça zahmetli olurdu. Aslında, adların yönetimi alanlara dağıtılmıştır. Her alan adı, genellikle çok küçük çaplı bir kurum tarafından yönetilir ve bu kurum, makine isimlerinin seçiminde tam bir serbestliğe sahiptir. Dolayısıyla Fransa’daki makineler, Paris’teki Inria tarafından yönetilen “fr” alan adına aittir. İşleri daha da basitleştirmek için kontrol yetkisi bir kez daha dağıtılır: “fr” alan adı içinde alt 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 rastgele değil, istek sayısını en aza indirecek şekilde gerçekleştirilecektir. Makine nihayetinde bulunursa, yanıt Makine A’ya geri gönderilecektir.

XDR: (eXternal Veri Temsili)

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

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

Yine sun tarafından tanımlanan bu protokol, taşıma katmanından bağımsız olarak uzak uygulamalar arasında iletişim sağlayan bir protokoldü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 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.

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

11.2. IP adres yönetiminin .NET sınıfları

İnternet ağındaki bir makine, iki şekilde olabilen bir IP (İnternet Protokolü) adresi ile benzersiz bir şekilde tanımlanır:

  • IPv4: 32 bitlik kodlamaya sahiptir ve "I1.I2.I3.I4" biçiminde bir dizeyle temsil edilir; burada In, 1 ile 254 arasında bir sayıdır. Bunlar, günümüzde en yaygın olarak kullanılan IP adresleridir.
  • IPv6: 128 bitlik kodlamaya sahiptir ve "[I1.I2.I3.I4.I5.I6.I7.I8]" biçiminde bir dizeyle temsil edilir; burada In, 4 basamaklı bir onaltılık sayıdır. Bu belgede, IPv6 adreslerini kullanmayacağız.

Bir makine, yine benzersiz bir adla da tanımlanabilir. Bu ad zorunlu değildir; uygulamalar sonuçta her zaman makinelerin IP adreslerini kullanır. Bu adlar, kullanıcıların işini kolaylaştırmak için vardır. Bu sayede, bir tarayıcıyla URL adresindeki http://www.ibm.com sayfasını çağırmak, her iki yöntem de mümkün olsa da, URL ve http://129.42.17.99 adreslerini çağırmaktan daha kolaydır.

Bir cihaz, aynı anda fiziksel olarak birden fazla ağa bağlıysa birden fazla IP adresine sahip olabilir. Bu durumda, her ağda bir IP adresi bulunur.

Bir IP adresi, .NET içinde iki şekilde gösterilebilir:

  • "I1.I2.I3.I4" veya "[I1.I2.I3.I4.I5.I6.I7.I8]" şeklinde bir karakter dizisi olarak
  • IPAddress türünde bir nesne olarak

IPAddress sınıfı

IPAddress sınıfının M yöntemleri, P özellikleri ve C sabitleri arasında şunlar bulunur:

AddressFamily AddressFamily
P
IP adresinin ailesi. AddressFamily türü bir sıralamadır. Yaygın olarak kullanılan iki değer şunlardır:
AddressFamily.InterNetwork: IPv4 adresi için
AddressFamily.InterNetworkV6: IPv6 adresi için
IPAddress Any
C
IP adresi "0.0.0.0" anlamına gelir. Bir hizmet bu adrese atandığında, bu hizmetin çalıştığı makinedeki tüm IP adreslerinden istemcileri kabul ettiği anlamına gelir.
IPAddress LoopBack
C
IP "127.0.0.1" adresi. "Döngü adresi" olarak adlandırılır. Bir hizmet bu adrese atandığında, bu hizmet yalnızca kendisiyle aynı makinede bulunan istemcileri kabul eder.
IPAdress None
C
IP "255.255.255.255" adresi. Bir hizmet bu adrese atandığında, bu hizmetin hiçbir istemciyi kabul etmediği anlamına gelir.
bool TryParse(string ipString, out IPAddress address)
M
IP adresini "I1.I2.I3.I4" biçiminde ipString adresine dönüştürmeye çalışır. İşlem başarılı olursa true değerini döndürür.
bool IsLoopBack
M
IP adresi "127.0.0.1" ise true değerini döndürür
string ToString()
M
IP adresini "I1.I2.I3.I4" veya "[I1.I2.I3.I4.I5.I6.I7.I8]" biçiminde döndürür

IP <--> nomMachine adres eşlemesi, DNS (Etki Alanı Adı Sistemi) adı verilen dağıtık bir internet hizmeti tarafından sağlanır. Dns sınıfının statik yöntemleri, IP <--> nomMachine adres eşleşmesini oluşturmaya olanak tanır:

GetHostEntry (string hostNameOrdAddress)
, IP adresinden bir dize biçiminde veya bir makine adından IPHostEntry adresini döndürür. Makine bulunamazsa bir istisna atar.
GetHostEntry (IPAddress ip)
IPAddress türündeki IP adresinden IPHostEntry adresini döndürür. Makine bulunamazsa bir istisna oluşturur.
string GetHostName()
bu komutu çalıştıran programın üzerinde çalıştığı makinenin adını döndürür
IPAddress[] GetHostAddresses(string hostNameOrdAddress)
adıyla veya adreslerinden biriyle tanımlanan makinenin IP adreslerini döndürür.

Bir IPHostEntry örneği, IP adreslerini, takma adları ve bir makinenin adını kapsar. IPHostEntry türü şöyledir:

IPAddress[] AddressList
P
makinenin IP adres tablosu
String[] Aliases
P
Makinenin DNS takma adları. Bunlar, makinenin farklı IP adreslerine karşılık gelen adlardır.
string HostName
P
makinenin ana ana bilgisayar adı

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:


using System;
using System.Net;

namespace Chap9 {
    class Program {
        static void Main(string[] args) {
            // yerel makinenin adını görüntüler
            // ardından ağdaki makineler hakkında etkileşimli olarak bilgi verir
            // bir ad veya adresle tanımlanan IP

            // yerel makine
            Console.WriteLine("Machine Locale= {0}" ,Dns.GetHostName());

            // etkileşimli soru-cevaplar
            string machine;
            IPHostEntry ipHostEntry;
            while (true) {
                // aranan makinenin adı veya adresinin IP girilmesi
                Console.Write("Machine recherchée (rien pour arrêter) : ");
                machine = Console.ReadLine().Trim().ToLower();
                // bitti mi?
                if (machine == "") return;
                // istisna yönetimi
                try {
                    // makine arama
                    ipHostEntry = Dns.GetHostEntry(machine);
                    // makinenin adı
                    Console.WriteLine("Machine : " + ipHostEntry.HostName);
                    // makinenin adresleri IP
                    Console.Write("Adresses IP : {0}" , ipHostEntry.AddressList[0]);
                    for (int i = 1; i < ipHostEntry.AddressList.Length; i++) {
                        Console.Write(", {0}" , ipHostEntry.AddressList[i]);
                    }
                    Console.WriteLine();
                    // makinenin takma adları
                    if (ipHostEntry.Aliases.Length != 0) {
                        Console.Write("Alias : {0}" , ipHostEntry.Aliases[0]);
                        for (int i = 1; i < ipHostEntry.Aliases.Length; i++) {
                            Console.Write(", {0}" , ipHostEntry.Aliases[i]);
                        }
                        Console.WriteLine();
                    }
                } catch {
                    // makine mevcut değil
                    Console.WriteLine("Impossible de trouver la machine [{0}]",machine);
                }
            }
        }
    }
}

Çalıştırıldığında şu sonuçlar elde edilir:

Machine Locale= LISA-AUTO2005A
Machine recherchée (rien pour arrêter) : localhost
Machine : LISA-AUTO2005A
Adresses IP : 127.0.0.1
Machine recherchée (rien pour arrêter) : 127.0.0.1
Machine : LISA-AUTO2005A
Adresses IP : 127.0.0.1
Machine recherchée (rien pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171
Machine recherchée (rien pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Machine recherchée (rien pour arrêter) : xx
Impossible de trouver la machine [xx]

11.3. İnternet programlamasının temelleri

11.3.1. Genel Bilgiler

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

A makinesindeki bir uygulama AppA, İnternet üzerindeki B makinesindeki bir uygulama AppB ile iletişim kurmak istediğinde, birkaç şeyi bilmesi gerekir:

  • IP adresi veya B makinesinin adı
  • AppB uygulamasının çalıştığı bağlantı noktası numarası. Aslında 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 hizmetiyle yapılan diyalog, pop hizmetiyle yapılan diyalogla aynı değildir: bu iki hizmet aynı komutları kabul etmez. Farklı bir diyalog protokolüne sahiptirler.

11.3.2. TCP protokolünün özellikleri

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

  • Veriyi göndermek isteyen işlem, öncelikle göndereceği bilgilerin alıcı işlemiyle bir bağlantı kurar. Bu bağlantı, gönderen makinenin bir bağlantı noktası ile alıcı makinenin bir bağlantı noktası arasında kurulur. İki bağlantı noktası arasında böylece sanal bir yol oluşturulur ve bu yol, bağlantıyı kuran sadece bu iki işleme ayrılır.
  • Kaynak işlem tarafından gönderilen tüm paketler bu sanal yolu takip eder ve gönderildikleri sırayla ulaşır
  • Gönderilen bilgi sürekli bir yapıya sahiptir. Gönderen süreç, kendi hızında bilgi gönderir. Bu bilgiler mutlaka hemen gönderilmez: TCP protokolü, gönderilmek üzere yeterli miktarda bilgi birikene kadar bekler. Bilgiler, TCP segmenti adı verilen bir yapıda depolanır. Bu segment dolduğunda, IP katmanına iletilir ve burada bir IP paketine kapsüllenir.
  • TCP protokolü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.
  • 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.

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

11.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
fermer la connexion

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

11.4. 'i ve internet iletişim protokollerini keşfedin

11.4.1. Giriş

Bir istemci bir sunucuya bağlandığında, aralarında bir iletişim kurulur. Bu iletişimin niteliği, sunucu iletişim protokolü olarak adlandırılır. İnternette en yaygın kullanılan protokoller arasında şunlar yer alır:

  • HTTP: HyperText Transfer Protokolü - bir web sunucusuyla (HTTP sunucusu) iletişim kurmak için kullanılan protokol
  • SMTP: Simple Mail Transfer Protocol (Basit Posta Aktarım Protokolü) – e-posta gönderme sunucusuyla (SMTP sunucusu) iletişim kurmak için kullanılan protokol
  • POP: Post Office Protokolü - e-posta depolama sunucusuyla iletişim protokolü (POP sunucusu). Bu protokol, e-postaları göndermek değil, alınan e-postaları almak içindir.
  • FTP: Dosya Aktarım Protokolü (FTP) - dosya depolama sunucusuyla iletişim protokolüdür (FTP sunucusu).

Tüm bu protokollerin ortak özelliği, metin tabanlı protokoller olmalarıdır: istemci ve sunucu arasında metin satırları alışverişi yapılır. Eğer şu işlevleri yerine getirebilen bir istemci varsa:

  • bir TCP sunucusuyla bağlantı kurabilen
  • sunucunun gönderdiği metin satırlarını konsolda görüntülemek
  • kullanıcının gireceği metin satırlarını sunucuya gönderebilen

o zaman, bu protokolün kurallarını bildiğimiz sürece, metin satırları protokolüne sahip bir TCP sunucusuyla iletişim kurabiliriz.

Unix veya Windows sistemlerinde bulunan telnet programı bu tür bir istemcidir. Windows sistemlerinde ayrıca putty adlı bir araç da bulunur ve burada onu kullanacağız. putty, [http://www.putty.org/] adresinden indirilebilir. Bu, doğrudan çalıştırılabilen bir yürütülebilir dosyadır (.exe). Programı şu şekilde yapılandıracağız:

  • [1]: Bağlanmak istediğimiz TCP sunucusunun adresi (IP) veya adı
  • [2]: TCP sunucusunun dinleme bağlantı noktası
  • [3]: Ham bir TCP bağlantısını belirten Raw modunu seçin.
  • [4]: Sunucu bağlantıyı keserse putty istemci penceresinin kapanmasını önlemek için Never modunu seçin.
  • [6,7]: Konsolun sütun/satır sayısı
  • [5]: Bellekte tutulacak maksimum satır sayısı. Bir HTTP sunucusu çok sayıda satır gönderebilir. Bu satırlarda "kaydırma" yapılabilmesi gerekir.
  • [8,9]: Önceki ayarları saklamak için, yapılandırmaya bir ad verin ([8]) ve kaydedin ([9]).
  • [11,12]: Kaydedilmiş bir yapılandırmayı geri yüklemek için [11]'i seçin ve [12]'e yükleyin.

Bu araç bu şekilde yapılandırıldıktan sonra, birkaç protokolü inceleyelim: TCP.

11.4.2. HTTP protokolü (HyperText Aktarım Protokolü)

[1] istemcimizi, istia.univ-angers.fr makinesindeki web sunucusuna, [2], 80 numaralı bağlantı noktası [3] üzerinden bağlayalım:

putty konsolunda, şu HTTP diyaloğunu oluşturuyoruz:

GET / HTTP/1.1
Host: istia.univ-angers.fr:80
Connection: close

HTTP/1.1 200 OK
Date: Sat, 03 May 2008 07:53:47 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
Set-Cookie: fe_typo_user=0d2e64b317; path=/
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html;charset=iso-8859-1

693f
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"                                                                        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr_FR" lang="fr_FR">
....
         </html>
0
  • 1-4. satırlar, klavyeyle girilen istemci isteğidir
  • 5-19. satırlar, sunucunun yanıtıdır
  • 1. satır: GET UrlDocument HTTP/1.1 sözdizimi - / URL'sini istiyoruz, c.a.d. web sitesinin kök dizini [istia.univ-angers.fr].
  • 2. satır: Host: makine:bağlantı noktası sözdizimi
  • 3. satır: Connection: [mode de la connexion] sözdizimi. [close] modu, sunucuya yanıtını gönderdikten sonra bağlantıyı kapatmasını bildirir. [Keep-Alive] modu ise bağlantının açık kalmasını ister.
  • 4. satır: boş satır. 1-3. satırlar HTTP başlıkları olarak adlandırılır. Burada gösterilenlerin dışında başka başlıklar da olabilir. HTTP başlıklarının sonu, bir boş satırla belirtilir.
  • 5-13. satırlar: Sunucunun yanıtındaki HTTP başlıkları - bunlar da burada boş bir satırla sona erer.
  • satır 14-19: sunucu tarafından gönderilen belge; burada bir HTML belgesi
  • 5. satır: HTTP/1.1 msg kodu sözdizimi - 200 kodu, istenen belgenin bulunduğunu gösterir.
  • 6. satır: Sunucunun tarihi ve saati
  • 7. satır: Web hizmetini sağlayan yazılımın kimliği – burada Linux / Debian üzerinde çalışan bir Apache sunucusu
  • 8. satır: belge, PHP tarafından dinamik olarak oluşturulmuştur
  • 9. satır: İstemcinin kimlik çerezi – istemci bir sonraki bağlantısında tanınmak istiyorsa, başlıklarında bu çerezi (HTTP) geri göndermelidir.
  • 10. satır: İstenen belge sunulduktan sonra sunucunun bağlantıyı kapatacağını belirtir
  • 11. satır: belge tek bir blok halinde değil, parçalar halinde (chunked) iletilecektir.
  • satır 12: belgenin türü: burada bir HTML belgesi
  • 13. satır: Sunucunun HTTP başlıklarının sonunu belirten boş satır
  • 14. satır: Belgenin ilk bloğundaki karakter sayısını belirten onaltılık sayı. Bu sayı 0 olduğunda (19. satır), istemci belgenin tamamını aldığını anlar.
  • satır 15-18: Alınan belgenin bir kısmı.

Bağlantı kapatıldı ve putty istemcisi etkin değil. [1] ile yeniden bağlanalım ve ekranı önceki görüntülemelerden temizleyelim [2,3]:

Bu seferki diyalog şöyledir:

GET /inconnu HTTP/1.1
Host: istia.univ-angers.fr:80
Connection: Close

HTTP/1.1 404 Not Found
Date: Sat, 03 May 2008 08:16:02 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=iso-8859-1

11a
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
                                                  <HTML><HEAD>
                                                              <TITLE>404 Not Found</TITLE>
                                                                                          </HEAD><BODY>
                                                                                                       <H1>Not Found</H1>
 The requested URL /inconnu was not found on this server.<P>
                                                            <HR>
                                                                <ADDRESS>Apache/1.3.34 Server at www.istia.univ-angers.fr Port 80</ADDRESS>
                   </BODY></HTML>

0
  • 1. satır: mevcut olmayan bir belge istendi
  • 5. satır: HTTP sunucusu, istenen belgenin bulunamadığını belirten 404 koduyla yanıt verdi.

Bu belgeyi Firefox tarayıcısıyla talep edersek:

Image

[Affichage/Code source] kaynak kodunu görüntülemek istersek:

1
2
3
4
5
6
7
8
9
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD>
<TITLE>404 Not Found</TITLE>
</HEAD><BODY>
<H1>Not Found</H1>
The requested URL /inconnu was not found on this server.<P>
<HR>
<ADDRESS>Apache/1.3.34 Server at www.istia.univ-angers.fr Port 80</ADDRESS>
</BODY></HTML>

putty istemcimiz tarafından alınan 13-22. satırları elde ediyoruz. Bunun önemi, bize yanıtın HTTP başlıklarını da göstermesidir. Bunları Firefox ile de elde etmek mümkündür.

11.4.3. i SMTP (Basit Posta Aktarım Protokolü)

SMTP sunucuları genellikle 25 numaralı [2] bağlantı noktasında çalışır. [1] sunucusuna bağlanılır. Burada genellikle,

çünkü çoğu zaman SMTP sunucuları, kendileriyle aynı etki alanına ait makinelerden gelen istekleri kabul edecek şekilde yapılandırılmıştır. Ayrıca, kişisel bilgisayarların güvenlik duvarları veya antivirüs programları da sıklıkla, dışarıdan gelen bir bilgisayarın 25 numaralı bağlantı noktasına bağlantı kabul etmeyecek şekilde yapılandırılmıştır. Bu durumda, bu güvenlik duvarını veya antivirüs programını yeniden yapılandırmak gerekebilir.

putty istemci penceresindeki SMTP iletişim kutusu şu şekildedir:

220 neuf-infra-smtp-out-sp604001av.neufgp.fr neuf telecom Service relais mail ready
HELO istia.univ-angers.fr
250 neuf-infra-smtp-out-sp604002av.neufgp.fr hello [84.100.189.193], Banniere OK , pret pour envoyer un mail
mail from: @expéditeur
250 2.1.0 <@expéditeur> sender ok
rcpt to: @destinataire
250 2.1.5 <@destinataire> destinataire ok
data
354 enter mail, end with "." on a line by itself
ligne1
ligne2
.
250 2.0.0 LwiU1Z00V4AoCxw0200000 message ok
quit
221 2.0.0 neuf-infra-smtp-out-sp604002av.neufgp.fr neuf telecom closing connection

Aşağıda (D) istemciden gelen bir istek, (R) ise sunucudan gelen bir yanıttır.

  • 1. satır: (R) SMTP sunucusundan gelen karşılama mesajı
  • 2. satır: (D) merhaba demek için HELO komutu
  • 3. satır: (R) sunucunun yanıtı
  • 4. satır: (D) gönderen adresi, örneğin mail from: someone@gmail.com
  • satır 5: (R) sunucunun yanıtı
  • 6. satır: (D) alıcı adresi, örneğin rcpt to: someoneelse@gmail.com
  • 7. satır: (R) sunucunun yanıtı
  • 8. satır: (D) mesajın başlangıcını belirtir
  • 9. satır: (R) sunucunun yanıtı
  • satır 10-12: (D) gönderilecek mesaj, sadece bir nokta içeren bir satırla sona erer.
  • 13. satır: (R) sunucunun yanıtı
  • satır 14: (D) istemci, işlemi tamamladığını bildirir
  • satır 15: (R) sunucunun yanıtı; ardından sunucu bağlantıyı kapatır

11.4.4. POP protokolü (Post Office Protocol)

POP sunucuları genellikle 110 numaralı [2] bağlantı noktasında çalışır. [1] sunucusuna bağlanıyoruz. putty istemci penceresindeki POP diyalogu şu şekildedir:

+OK Hello there.
user xx
+OK Password required.
pass yy
+OK logged in.
list
+OK POP3 clients that break here, they violate STD53.
1 10105
2 55875
...
64 1717
.
retr 64
+OK 1717 octets follow.
Return-Path: <xx@neuf.fr>
X-Original-To: xx@univ-angers.fr
Delivered-To: xx@univ-angers.fr
....
Date: Sat,  3 May 2008 10:59:25 +0200 (CEST)
From: xx@neuf.fr
To: undisclosed-recipients:;

ligne1
ligne2
.
quit
+OK Bye-bye.
  • 1. satır: (R) POP sunucusundan gelen hoş geldiniz mesajı
  • 2. satır: (D) istemci, POP, c.a.d kimlik bilgilerini verir. E-postalarını okumak için kullandığı kullanıcı adı
  • satır 3: (R) sunucunun yanıtı
  • 4. satır: (D) müşterinin şifresi
  • 5. satır: (R) sunucunun yanıtı
  • 6. satır: (D) istemci, e-postalarının listesini ister
  • 7-12. satırlar: (R) müşterinin posta kutusundaki mesajların listesi, [N° du message taille en octets du message] biçiminde
  • 13. satır: (D) 64 numaralı mesaj isteniyor
  • satır 14-25: (R) 64 numaralı mesaj; satır 15-22 mesaj başlıkları, satır 23-24 ise mesaj gövdesi.
  • 26. satır: (D) istemci işlemi tamamladığını bildirir
  • 27. satır: (R) sunucunun yanıtı; ardından sunucu bağlantıyı kapatır.

11.4.5. FTP protokolü (Dosya Aktarım Protokolü)

FTP protokolü, daha önce sunulanlardan daha karmaşıktır. İstemci ile sunucu arasında alışverişi yapılan metin satırlarını incelemek için FileZilla [http://www.filezilla.fr/] gibi bir araç kullanılabilir.

Filezilla, dosya aktarımı yapmak için bir Windows arayüzü sunan bir FTP istemcisidir. Kullanıcının Windows arayüzündeki eylemleri, FTP komutlarına dönüştürülür ve bu komutlar [1]'te günlüğe kaydedilir. Bu, FTP protokolünün komutlarını keşfetmek için iyi bir yoldur.

11.5. İnternet programlamasında .NET sınıfları

11.5.1. Uygun sınıfı seçme

.NET çerçeve yapısı, ağ ile çalışmak için çeşitli sınıflar sunar:

  • Socket sınıfı, ağa en yakın şekilde çalışan sınıftır. Bu sınıf, ağ bağlantısını ayrıntılı bir şekilde yönetmeye olanak tanır. socket terimi bir elektrik prizini ifade eder. Bu terim, yazılım tabanlı bir ağ prizini ifade etmek üzere genişletilmiştir. A ve B adlı iki makine arasındaki bir TCP-IP iletişiminde, birbirleriyle iletişim kuran iki sockets vardır. Bir uygulama, sockets'lerle doğrudan çalışabilir. Yukarıdaki A uygulaması buna bir örnektir. Bir soket, client veya serveur tipi bir soket olabilir.
  • Socket sınıfından daha düşük bir düzeyde çalışmak istenirse,
  • TcpClient sınıflarını kullanarak bir TCP istemcisi
  • TcpListener sınıfını kullanarak bir TCP sunucusu oluşturmak

Bu iki sınıf, bunları kullanan uygulamaya, soket yönetiminin teknik ayrıntılarını kendi adına yöneterek ağ iletişimi konusunda daha basit bir bakış açısı sunar.

  • .NET, belirli protokollere özgü sınıflar sunar:
  • SmtpClient sınıfı, e-posta gönderen bir SMTP sunucusuyla iletişim kuran SMTP protokolünü yönetmek için
  • WebClient sınıfı, bir web sunucusuyla iletişim kurmak için HTTP veya FTP protokollerini yönetmek için kullanılır.

Socket sınıfının tek başına tüm TCP/IP iletişimini yönetmek için yeterli olduğu unutulmamalıdır; ancak TCP/IP uygulamasının yazımını kolaylaştırmak amacıyla öncelikle daha üst düzey sınıfların kullanılması tercih edilecektir.

11.5.2. TcpClient sınıfı

TcpClient sınıfı, çoğu durumda bir TCP hizmetinin istemcisini oluşturmak için uygun olan sınıftır. Bu sınıfın C yapıcıları, M yöntemleri ve P özellikleri arasında şunlar bulunur:

TcpClient(string hostname, int port)
C
belirtilen makinenin (hostname) belirtilen bağlantı noktasında (port) çalışan hizmetle 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) komutu kullanılır.
Socket Client
P
İstemcinin sunucuyla iletişim kurmak için kullandığı soket.
NetworkStream GetStream()
M
sunucuya yönelik bir okuma ve yazma akışı elde eder. İstemci-sunucu arasındaki veri alışverişini sağlayan bu akıştır.
void Close()
M
bağlantıyı kapatır. NetworkStream soketi ve akışı da kapatılır
bool Connected()
P
bağlantı kurulmuşsa true

NetworkStream sınıfı, istemci ile sunucu arasındaki ağ akışını temsil eder. Bu 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ışındaki bu satırları okumak ve yazmak için StreamReader ve StreamWriter nesnelerini kullanmak yararlıdır. Örneğin, 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:

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

Şu komut

out1.AutoFlush=true;

komutu, client1'in yazma akışının ara bir tampon üzerinden geçmeyeceği, doğrudan ağa gönderileceği anlamına gelir. Bu nokta önemlidir. Genel olarak, client1 bir metin satırını ortağına gönderdiğinde, buna bir yanıt bekler. Eğer söz konusu satır aslında M1 makinesinde tamponlanmış ve M2 makinesine 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 makinesinden gelen yanıtı okumak için şunu yazacağız:

string réponse=client1.ReadLine();

Artık, sunucuyla aşağıdaki temel iletişim protokolünü kullanan bir internet istemcisinin temel mimarisini yazmak için gerekli unsurlara sahibiz:

  • istemci, tek bir satırda yer alan bir istek gönderir
  • sunucu, tek bir satırda yer alan bir yanıt gönderir

using System;
using System.IO;
using System.Net.Sockets;

namespace ... {
    class ... {
        static void Main(string[] args) {
            ...
            try {
                // hizmete bağlanılıyor
                using (TcpClient tcpClient = new TcpClient(serveur, port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // çıkış akışı tamponlanmamış
                                writer.AutoFlush = true;
                                // istek-yanıt döngüsü
                                while (true) {
                                    // istek klavyeden geliyor
                                    Console.Write("Demande (bye pour arrêter) : ");
                                    demande = Console.ReadLine();
                                    // bitti mi?
                                    if (demande.Trim().ToLower() == "bye")
                                        break;
                                    // istek sunucuya gönderiliyor
                                    writer.WriteLine(demande);
                                    // sunucudan gelen yanıt okunuyor
                                    réponse = reader.ReadLine();
                                    // yanıt işleniyor
                                    ...
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // hata
                ...
            }
        }
    }
}
  • 11. satır: istemci bağlantısının kurulması - using ifadesi, bu bağlantıyla ilişkili kaynakların using ifadesinden çıkıldığında serbest bırakılmasını sağlar.
  • 12. satır: using maddesinde ağ akışının açılması
  • 13. satır: using maddesinde okuma akışının oluşturulması ve kullanılması
  • satır 14: using maddesinde yazma akışının oluşturulması ve kullanılması
  • satır 16: çıkış akışını arabelleğe almama
  • satır 18-31: istemci isteği / sunucu yanıtı döngüsü
  • satır 26: istemci, isteğini sunucuya gönderir
  • 28. satır: İstemci, sunucudan yanıt bekler. Bu, klavyeden okuma işlemi gibi engelleyici bir işlemdir. Bekleme, "\n" ile biten bir dizenin gelmesiyle veya akışın sona ermesiyle sona erer. Bu durum, sunucunun istemciyle açtığı bağlantıyı kapatmasıyla gerçekleşir.

11.5.3. TcpListener sınıfı

TcpListener sınıfı, çoğu durumda bir TCP hizmeti oluşturmak için uygun olan sınıftır. Bu sınıfın C yapıcıları, M yöntemleri ve P özellikleri arasında şunlar bulunur:

TcpListener(int port)
C
, dinleme bağlantı noktası olarak adlandırılan ve parametre olarak geçirilen bir bağlantı noktasında (port) istemcilerin isteklerini bekleyecek (listen) bir TCP hizmeti oluşturur. Makine birden fazla IP ağına bağlıysa, hizmet her bir ağda dinleme yapar.
TcpListener(IPAddress ip, int port)
C
Aynı işlevi yerine getirir, ancak dinleme işlemi yalnızca belirtilen IP adresinde gerçekleşir.
void Start()
M
istemcilerin isteklerini dinlemeye başlar
TcpClient AcceptTcpClient()
M
bir müşterinin talebini kabul eder. Ardından, bu müşteriyle “hizmet bağlantısı” adı verilen yeni bir bağlantı açar. Sunucu tarafında kullanılan bağlantı noktası rastgele olup sistem tarafından seçilir. Buna “hizmet bağlantı noktası” denir. AcceptTcpClient, sunucu tarafında hizmet bağlantısıyla ilişkili olan TcpClient nesnesini sonuç olarak döndürür.
void Stop()
M
istemci isteklerini dinlemeyi durdurur
Socket Server
P
sunucunun dinleme soketi

Aşağıdaki protokole göre müşterileriyle iletişim kuran bir TCP sunucusunun temel yapısı:

  • müşteri, tek bir satırda yer alan bir istek gönderir
  • sunucu, tek bir satırda yer alan bir yanıt gönderir

şu şekilde olabilir:


using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace ... {
    public class ... {
            ...
            // dinleme hizmeti oluşturuluyor
            TcpListener ecoute = null;
            try {
                // hizmet oluşturulur - makinenin tüm ağ arayüzlerinde dinleme yapacaktır
                ecoute = new TcpListener(IPAddress.Any, port);
                // hizmet başlatılıyor
                ecoute.Start();
                // hizmet döngüsü
                TcpClient tcpClient = null;
                // sonsuz döngü - Ctrl-C ile durdurulur
                while (true) {
                    // bir istemci bekleniyor
                    tcpClient = ecoute.AcceptTcpClient();
                    // hizmet başka bir görev tarafından sağlanıyor
                    ThreadPool.QueueUserWorkItem(Service, tcpClient);
                    // sonraki istemci
                }
            } catch (Exception ex) {
                // hata bildiriliyor
                ...
            } finally {
                // hizmetin sonu
                ecoute.Stop();
            }
        }

        // -------------------------------------------------------
        // bir müşteriye hizmet veriliyor
        public static void Service(Object infos) {
            // hizmet verilecek müşteri alınır
            Client client = infos as Client;
            // TcpClient bağlantısı işleniyor
            try {
                using (TcpClient tcpClient = client.CanalTcp) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // tamponlanmamış çıkış akışı
                                writer.AutoFlush = true;
                                // istek/yazma-yanıt okuma döngüsü
                                bool fini=false;
                                while (! fini) != null) {
                                    // istemci isteği bekleniyor - engelleyici işlem
                                    demande=reader.ReadLine();
                                    // yanıt hazırlığı
                                    réponse=...;
                                    // yanıtın müşteriye gönderilmesi
                                    writer.WriteLine(réponse);
                                    // sonraki istek
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // hata
                ...
            } finally {
                // müşteri işlemi sonu
                ...
            }
        }
    }
}
  • 14. satır: Dinleme hizmeti, belirli bir bağlantı noktası ve belirli bir IP adresi için oluşturulur. Burada unutulmaması gereken nokta, bir makinenin en az iki adrese sahip olduğudur: IP; bunlardan biri, makinenin kendi kendine döngü adresi olan "127.0.0.1" adresi, diğeri ise bağlı olduğu ağdaki "I1.I2.I3.I4" adresidir. Birden fazla ağa bağlıysa, başka adreslere de sahip olabilir. IP. IPAddress.Any, bir makinenin tüm adreslerini ifade eder.
  • 16. satır: Dinleme hizmeti başlatılır. Daha önce oluşturulmuştu ancak henüz dinleme modunda değildi. Dinleme, istemcilerin isteklerini beklemek anlamına gelir.
  • 20-26. satırlar: Her yeni müşteri için “müşteri isteği / müşteri hizmeti” döngüsü tekrarlanır
  • 22. satır: Bir müşterinin isteği kabul edilir. AcceptTcpClient yöntemi, hizmet olarak adlandırılan bir TcpClient örneği döndürür:
    • müşteri, kendi tarafında TcpClient adlı bir örnekle talepte bulunmuştur; buna TcpClientDemande diyeceğiz
    • sunucu bu talebi AcceptTcpClient ile kabul eder. Bu yöntem, sunucu tarafında TcpClient adlı bir örnek oluşturur; buna TcpClientService diyeceğiz. Böylece, her iki ucunda TcpClientDemande <--> TcpClientService örneklerinin bulunduğu açık bir TCP bağlantısı oluşur.
    • Bundan sonra gerçekleşen istemci/sunucu iletişimi bu bağlantı üzerinden yapılır. Dinleme hizmeti artık devreye girmez.
  • 24. satır: Sunucunun aynı anda birden fazla istemciyi işleyebilmesi için hizmet, her istemci için 1 iş parçacığı olmak üzere iş parçacıkları tarafından sağlanır.
  • 32. satır: Dinleme hizmeti kapatılır
  • 38. satır: Hizmet iş parçacığı tarafından bir müşteriye yönelik olarak yürütülen yöntem. Bu yöntem, hizmet verilecek müşteriye zaten bağlı olan TcpClient örneğini parametre olarak alır.
  • 38-71. satırlar: Daha önce incelediğimiz temel TCP istemcisindekine benzer bir kod görülmektedir.

11.6. TCP istemci/sunucu örnekleri

11.6.1. Bir yankı sunucusu

DOS penceresinden şu komutla başlatılacak bir yankı sunucusu yazmayı öneriyoruz:

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 şöyledir:


using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Net;

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

namespace Chap9 {
    public class ServeurEcho {
        public const string syntaxe = "Syntaxe : [serveurEcho] port";

        // ana program
        public static void Main(string[] args) {

            // bir argüman var mı?
            if (args.Length != 1) {
                Console.WriteLine(syntaxe);
                return;
            }
            // bu argüman >0 tamsayı olmalıdır
            int port = 0;
            if (!int.TryParse(args[0], out port) || port<=0) {
                Console.WriteLine("{0} : {1}Port incorrect", syntaxe, Environment.NewLine);
                return;
            }
            // dinleme hizmeti oluşturulur
            TcpListener ecoute = null;
            int numClient = 0;    // sonraki müşteri numarası
            try {
                // hizmet oluşturuluyor - makinenin tüm ağ arayüzlerinde dinleme yapacak
                ecoute = new TcpListener(IPAddress.Any, port);
                // hizmet başlatılıyor
                ecoute.Start();
                // izleme
                Console.WriteLine("Serveur d'écho lancé sur le port {0}", ecoute.LocalEndpoint);
                // hizmet iş parçacıkları
                ThreadPool.SetMinThreads(10, 10);
                ThreadPool.SetMaxThreads(10, 10);
                // hizmet döngüsü
                TcpClient tcpClient = null;
                // sonsuz döngü - Ctrl-C ile durdurulur
                while (true) {
                    // bir istemciyi bekliyor
                    tcpClient = ecoute.AcceptTcpClient();
                    // hizmet başka bir görev tarafından sağlanıyor
                    ThreadPool.QueueUserWorkItem(Service, new Client() { CanalTcp = tcpClient, NumClient = numClient });
                    // sonraki istemci
                    numClient++;
                }
            } catch (Exception ex) {
                // hata bildiriliyor
                Console.WriteLine("L'erreur suivante s'est produite sur le serveur : {0}", ex.Message);
            } finally {
                // hizmetin sonu
                ecoute.Stop();
            }
        }

        // -------------------------------------------------------
        // yankı sunucusundaki bir müşteriye hizmet sunuluyor
        public static void Service(Object infos) {
            // hizmet verilecek istemci alınır
            Client client = infos as Client;
            // istemciye hizmet sunuluyor
            Console.WriteLine("Début de service au client {0}", client.NumClient);
            // bağlantı işletimi TcpClient
            try {
                using (TcpClient tcpClient = client.CanalTcp) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // tamponlanmamış çıkış akışı
                                writer.AutoFlush = true;
                                // okuma/yazma isteği-yanıt döngüsü
                                string demande = null;
                                while ((demande = reader.ReadLine()) != null) {
                                    // konsol izleme
                                    Console.WriteLine("<--- Client {0} : {1}", client.NumClient, demande);
                                    // isteğin istemciye yankılanması
                                    writer.WriteLine("[{0}]", demande);
                                    // konsol izleme
                                    Console.WriteLine("---> Client {0} : {1}", client.NumClient, demande);
                                    // istemci "bye" gönderdiğinde hizmet durur
                                    if (demande.Trim().ToLower() == "bye")
                                        break;
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // hata
                Console.WriteLine("L'erreur suivante s'est produite lors du service au client {0} : {1}", client.NumClient, e.Message);
            } finally {
                // istemci sonu
                Console.WriteLine("Fin du service au client {0}", client.NumClient);
            }
        }
    }

    // istemci bilgileri
    internal class Client {
        public TcpClient CanalTcp { get; set; }        // istemciyle bağlantı
        public int NumClient { get; set; }            // müşteri numarası
    }
}

Echo sunucusunun yapısı, daha önce açıklanan temel TCP sunucu mimarisine uygundur. Burada sadece "istemciye hizmet" kısmını ele alacağız:

  • 79. satır: Müşterinin talebi okunur
  • satır 83: istek, köşeli parantezler içinde müşteriye geri gönderilir
  • 79. satır: İstemci bağlantıyı kapattığında hizmet durur

Bir DOS penceresinde, C# projesinin yürütülebilir dosyasını kullanıyoruz:

...\Chap9\02\bin\Release>dir
 03/05/2008  11:46             7 168 ServeurEcho.exe
...>ServeurEcho 100
Serveur d'écho lancé sur le port 0.0.0.0:100

Ardından, localhost makinesinin 100 numaralı bağlantı noktasına bağladığımız iki putty istemcisini başlatıyoruz:

 

Yankı sunucusunun konsol ekranı şu şekilde olur:

1
2
3
Serveur d'écho lancé sur le port 0.0.0.0:100
Début de service au client 0
Début de service au client 1

Önce 1 numaralı istemci, ardından 0 numaralı istemci aşağıdaki metinleri gönderir:

  • [1]: 1 numaralı istemci
  • [2]: 0 numaralı istemci
  • [3]: yankı sunucusunun konsolu
  • [4]'te: 1 numaralı istemci, bye komutuyla bağlantısını keser.
  • [5]: sunucu bunu algılar

Sunucu, Ctrl-C tuşlarıyla durdurulabilir. 0 numaralı istemci bunu algılar: [6].

11.6.2. Yankı sunucusu için bir istemci

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

ClientEcho nomServeur bağlantı noktası

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


using System;
using System.IO;
using System.Net.Sockets;

namespace Chap9 {
    // bir yankı sunucusuna bağlanıyor
    // klavyeden girilen her satır yankı olarak alınır
    class ClientEcho {
        static void Main(string[] args) {
            // sözdizimi
            const string syntaxe = "pg machine port";

            // argüman sayısı
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }

            // sunucunun adı not edilir
            string serveur = args[0];

            // bağlantı noktası >0 olan bir tamsayı olmalıdır
            int port = 0;
            if (!int.TryParse(args[1], out port) || port <= 0) {
                Console.WriteLine("{0}{1}port incorrect", syntaxe, Environment.NewLine);
                return;
            }

            // çalışılabilir
            string demande = null;        // istemci isteği
            string réponse = null;        // sunucudan gelen yanıt
            try {
                // hizmete bağlanılıyor
                using (TcpClient tcpClient = new TcpClient(serveur, port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // çıkış akışı tamponlanmamış
                                writer.AutoFlush = true;
                                // istek-yanıt döngüsü
                                while (true) {
                                    // istek klavyeden geliyor
                                    Console.Write("Demande (bye pour arrêter) : ");
                                    demande = Console.ReadLine();
                                    // bitti mi?
                                    if (demande.Trim().ToLower() == "bye")
                                        break;
                                    // istek sunucuya gönderiliyor
                                    writer.WriteLine(demande);
                                    // sunucudan gelen yanıt okunuyor
                                    réponse = reader.ReadLine();
                                    // yanıt işleniyor
                                    Console.WriteLine("Réponse : {0}", réponse);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // hata
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            }
        }
    }
}

Bu istemcinin yapısı, Tcp istemcileri için önerilen genel temel mimariye uygundur. Aşağıdaki yapılandırmada elde edilen sonuçlar şunlardır:

  • 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 A'nın (no: 0) penceresinde şu çıktılar görülmektedir:

1
2
3
4
5
6
...\Chap9\03\bin\Release>ClientEcho localhost 100
Demande (bye pour arrêter) : ligne1A
Réponse : [ligne1A]
Demande (bye pour arrêter) : ligne2A
Réponse : [ligne2A]
Demande (bye pour arrêter) :

İstemci B'nin (no. 1) penceresinde ise:

1
2
3
4
5
6
...\Chap9\03\bin\Release>ClientEcho localhost 100
Demande (bye pour arrêter) : ligne1B
Réponse : [ligne1B]
Demande (bye pour arrêter) : ligne2B
Réponse : [ligne2B]
Demande (bye pour arrêter) :

Sunucunun penceresinde:

...\Chap9\02\bin\Release>ServeurEcho 100
Serveur d'écho lancé sur le port 0.0.0.0:100
Début de service au client 0
<--- Client 0 : ligne1A
---> Client 0 : ligne1A
<--- Client 0 : ligne2A
---> Client 0 : ligne2A
Début de service au client 1
<--- Client 1 : ligne1B
---> Client 1 : ligne1B
<--- Client 1 : ligne2B
---> Client 1 : ligne2B

0 numaralı A istemcisi bağlantısını keser:

1
2
3
4
Demande (bye pour arrêter) : ligne1A
Réponse : [ligne1A]
...
Demande (bye pour arrêter) : bye

Sunucu konsolu:

1
2
3
Serveur d'écho lancé sur le port 0.0.0.0:100
...
Fin du service au client 0

11.6.3. Genel bir TCP istemcisi

Aşağıdaki şekilde başlatılacak genel bir TCP istemcisi yazacağız: ClientTcpGenerique sunucu bağlantı noktası. Bu istemci, putty istemcisine benzer şekilde çalışacak, ancak bir konsol arayüzüne sahip olacak ve yapılandırma seçeneği sunmayacaktır.

Önceki uygulamada, iletişim protokolü belliydi: istemci tek bir satır gönderir ve sunucu tek bir satırla yanıt verirdi. Her hizmetin kendine özgü bir protokolü vardır ve aşağıdaki durumlarla da karşılaşılabilir:

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

Bu nedenle, sunucuya tek bir satır gönderme / sunucudan gönderilen tek bir satırı alma döngüsü her zaman uygun olmayabilir. Echo protokolünden daha karmaşık protokolleri yönetmek için, genel TCP istemcisi iki iş parçacığına sahip olacaktır:

  • ana iş parçacığı, klavyeden girilen metin satırlarını okuyacak ve bunları sunucuya gönderecektir.
  • İkincil iş parçacığı ise paralel olarak çalışacak ve sunucu tarafından gönderilen metin satırlarını okumaya ayrılacaktır. Bir satır alır almaz, bunu konsolda görüntüler. Bu iş parçacığı, sunucu bağlantıyı sonlandırana kadar durmaz. Dolayısıyla sürekli çalışır.

Kod şu şekildedir:


using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;

namespace Chap9 {
    // bir hizmetin özelliklerini şu biçimde parametre olarak alır: sunucu bağlantı noktası
    // hizmete bağlanır
    // klavyeden girilen her satırı sunucuya gönderir
    // sunucu tarafından gönderilen metin satırlarını sürekli olarak okumak için bir iş parçacığı oluşturur
    class ClientTcpGenerique {
        static void Main(string[] args) {
            // sözdizimi
            const string syntaxe = "pg serveur port";

            // argüman sayısı
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }

            // sunucunun adı not edilir
            string serveur = args[0];

            // bağlantı noktası >0 olan bir tamsayı olmalıdır
            int port = 0;
            if (!int.TryParse(args[1], out port) || port <= 0) {
                Console.WriteLine("{0}{1}port incorrect", syntaxe, Environment.NewLine);
                return;
            }
            // hizmete bağlanılır
            TcpClient tcpClient = null;
            try {
                tcpClient = new TcpClient(serveur, port);
            } catch (Exception ex) {
                // hata
                Console.WriteLine("Impossible de se connecter au service ({0},{1}) : erreur {2}", serveur, port, ex.Message);
                // son
                return;
            }

            // sunucu tarafından gönderilen metin satırlarını okumak için ayrı bir iş parçacığı başlatılır
            ThreadPool.QueueUserWorkItem(Receive, tcpClient);

            // klavye komutları ana iş parçacığında okunur
            Console.WriteLine("Tapez vos commandes (bye pour arrêter) : ");
            string demande = null;        // istemci isteği
            try {
                // istemci bağlantısı kullanılır
                using (tcpClient) {
                    // sunucuya yazma akışı oluşturulur
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamWriter writer = new StreamWriter(networkStream)) {
                            // çıkış akışı tamponlanmamıştır
                            writer.AutoFlush = true;
                            // istek-yanıt döngüsü
                            while (true) {
                                demande = Console.ReadLine();
                                // bitti mi?
                                if (demande.Trim().ToLower() == "bye")
                                    break;
                                // sunucuya istek gönderiliyor
                                writer.WriteLine(demande);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // hata
                Console.WriteLine("L'erreur suivante s'est produite dans le thread principal : {0}", e.Message);
            }
        }

        // istemci <-- sunucu okuma iş parçacığı
        public static void Receive(object infos) {
            // yerel veriler
            string réponse = null;    // sunucudan gelen yanıt
            // giriş akışının oluşturulması
            try {
                using (TcpClient tcpClient = infos as TcpClient) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            // giriş akışındaki metin satırlarının sürekli okuma döngüsü
                            while ((réponse = reader.ReadLine()) != null) {
                                // konsol görüntüleme
                                Console.WriteLine("<-- {0}", réponse);
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                // hata
                Console.WriteLine("Flux de lecture : l'erreur suivante s'est produite : {0}", ex.Message);
            } finally {
                // okuma iş parçacığının sonu bildiriliyor
                Console.WriteLine("Fin du thread de lecture des réponses du serveur. Si besoin est, arrêtez le thread de lecture console avec la commande bye.");
            }
        }
    }
}
  • 34. satır: İstemci sunucuya bağlanır
  • 43. satır: Sunucudan gelen metin satırlarını okumak için bir iş parçacığı başlatılır. Bu iş parçacığı, 73. satırdaki Receive yöntemini çalıştırmalıdır. Sunucuya bağlanmış olan TcpClient örneği bu yönteme aktarılır.
  • 57-64. satırlar: Klavye komutlarının girilmesi / sunucuya komut gönderilmesi döngüsü. Klavye komutlarının girilmesi ana iş parçacığı tarafından gerçekleştirilir.
  • 75-98. satırlar: Metin satırlarını okuma iş parçacığı tarafından yürütülen Receive yöntemi. Bu yöntem, sunucuya bağlanmış olan TcpClient örneğini parametre olarak alır.
  • 84-87. satırlar: Sunucu tarafından gönderilen metin satırlarını okumak için sürekli döngü. Bu döngü, yalnızca sunucu istemciyle açık olan bağlantıyı kapattığında sona erer.

Aşağıda, 11.4. paragrafta putty istemcisiyle kullanılan örneklerin aynıları yer almaktadır. İstemci, bir DOS konsolunda çalıştırılır.

HTTP Protokolü

...\Chap9\04\bin\Release>ClientTcpGenerique istia.univ-angers.fr 80
Tapez vos commandes (bye pour arrêter) :
GET /inconnu HTTP/1.1
Host: istia.univ-angers.fr:80
Connection: Close

<-- HTTP/1.1 404 Not Found
<-- Date: Sat, 03 May 2008 12:35:11 GMT
<-- Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29

<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html; charset=iso-8859-1
<--
<-- 11a
<-- <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<-- <HTML><HEAD>
<-- <TITLE>404 Not Found</TITLE>
<-- </HEAD><BODY>
<-- <H1>Not Found</H1>
<-- The requested URL /inconnu was not found on this server.<P>
<-- <HR>
<-- <ADDRESS>Apache/1.3.34 Server at www.istia.univ-angers.fr Port 80</ADDRESS>
<-- </BODY></HTML>
<--
<-- 0
<--
[Fin du thread de lecture des réponses du serveur]
bye

...\Chap9\04\bin\Release>

Okuyucunun 11.4.2 numaralı paragrafta verilen açıklamaları tekrar okumasını öneririz. Biz sadece uygulamaya özgü hususları ele alacağız:

  • 28. satır: 27. satırın gönderilmesinden sonra, HTTP sunucusu bağlantıyı kapattı; bu da okuma iş parçacığının sonlanmasına neden oldu. Klavyeden girilen komutları okuyan ana iş parçacığı ise hâlâ aktiftir. Klavyeden girilen 29. satırdaki komut, bu iş parçacığını durdurur.

SMTP Protokolü

...\Chap9\04\bin\Release>ClientTcpGenerique smtp.neuf.fr 25
Tapez vos commandes (bye pour arrêter) :
<-- 220 neuf-infra-smtp-out-sp604002av.neufgp.fr neuf telecom Service relais mail ready
HELO istia.univ-angers.fr
<-- 250 neuf-infra-smtp-out-sp604002av.neufgp.fr hello [84.100.189.193], Banniere OK , pret pour envoyer un mail
mail from: xx@neuf.fr
<-- 250 2.1.0 <xx@neuf.fr> sender ok
rcpt to: yy@univ-angers.fr
<-- 250 2.1.5 <yy@univ-angers.fr> destinataire ok
data
<-- 354 enter mail, end with "." on a line by itself
ligne1
ligne2
.
<-- 250 2.0.0 M0jL1Z0044AoCxw0200000 message ok
quit
<-- 221 2.0.0 neuf-infra-smtp-out-sp604002av.neufgp.fr neuf telecom closing connection
[Fin du thread de lecture des réponses du serveur]
bye

...\Chap9\04\bin\Release>

Okuyucunun 11.4.3 numaralı paragrafta verilen açıklamaları tekrar okumasını ve putty istemcisiyle kullanılan diğer örnekleri denemesini öneririz.

11.6.4. : Genel bir TCP sunucusu

Şimdi,

  • bu sunucu, istemcilerinden gelen 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 odaklanacağız. Dolayısıyla sunucu rolünü üstlenen kişi kullanıcıdır.

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

  • ana iş parçacığı:
    • Müşterileri paralel olarak değil, sırayla işleyecektir.
    • kullanıcı tarafından klavyeden girilen satırları okuyacak ve bunları müşteriye gönderecektir. Kullanıcı, "bye" komutuyla müşteri ile olan bağlantısını sonlandıracağını bildirecektir. Konsol aynı anda iki müşteri için kullanılamadığından, sunucumuz bir seferde yalnızca bir müşteriyi işler.
  • İkincil iş parçacığı ise yalnızca istemci tarafından gönderilen metin satırlarını okumaya adanmıştır

Sunucu ise 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ıyor ve onunla iletişim kurmak için paragraphe11.6.3'in genel istemcisi kullanılıyor. İstemci penceresi şu şekildedir:

1
2
3
4
5
6
7
...\Chap9\04\bin\Release>ClientTcpGenerique localhost 100
Tapez vos commandes (bye pour arrêter) :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
bye

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

...\Chap9\05\bin\Release>ServeurTcpGenerique 100
Serveur générique lancé sur le port 0.0.0.0:100
Client 127.0.0.1:4165
Tapez vos commandes (bye pour arrêter) :
<-- commande 1 du client 1
réponse 1 au client 1
<-- commande 2 du client 1
réponse 2 au client 1
[Fin du thread de lecture des demandes du client]
bye

<-- ile başlayan satırlar istemciden sunucuya gönderilenler, diğerleri ise sunucudan istemciye gönderilenlerdir. 9. satır, istemcinin isteklerini okuyan iş parçacığının durduğunu gösterir. Sunucunun ana iş parçacığı, klavyeden girilen komutları alıp istemciye göndermek için hâlâ bekleme durumundadır. Bu durumda, bir sonraki istemciye geçmek için 10. satırdaki bye komutunu klavyeden girmek gerekir. İstemci 1 tamamlanmış olsa da sunucu hâlâ aktiftir. Aynı sunucu için ikinci bir istemci başlatılır:

1
2
3
4
5
...\Chap9\04\bin\Release>ClientTcpGenerique localhost 100
Tapez vos commandes (bye pour arrêter) :
commande 3 du client 2
<-- réponse 3 au client 2
bye

Sunucunun penceresi şu şekilde görünür:

1
2
3
4
5
6
Tapez vos commandes (bye pour arrêter) :
Client 127.0.0.1:4166
<-- commande 3 du client 2
réponse 3 au client 2
[Fin du thread de lecture des demandes du client]
bye

Yukarıdaki 6. satırdan sonra, sunucu yeni bir istemciyi bekleme moduna geçmiştir. Ctrl-C tuşlarıyla durdurulabilir.

Şimdi, 88 numaralı bağlantı noktasında genel sunucumuzu başlatarak bir web sunucusunu simüle edelim:

1
2
3
...\Chap9\05\bin\Release>ServeurTcpGenerique 88

Serveur générique lancé sur le port 0.0.0.0:88

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

 

Şimdi sunucumuzun penceresine bakalım:

Serveur générique lancé sur le port 0.0.0.0:88
Client 127.0.0.1:4167
Tapez vos commandes (bye pour arrêter) :
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-appl
ication, application/x-silverlight, */*
<-- Accept-Language: fr,en-US;q=0.7,fr-FR;q=0.3
<-- UA-CPU: x86
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.
4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.590; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Tarayıcı tarafından gönderilen HTTP başlıklarını görüyoruz. Bu sayede, daha önce karşılaştıklarımızdan farklı başka HTTP başlıklarını da keşfedebiliyoruz. İ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: Sat, 03 May 2008 07:53:47 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
Set-Cookie: fe_typo_user=0d2e64b317; path=/
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html;charset=iso-8859-1

693f
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"                                                                        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr_FR" lang="fr_FR">
....
         </html>
0

En az düzeyde kalarak benzer bir yanıt vermeye çalışalım:

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

<html>
<head><title>Serveur generique</title></head>
<body><h2>Reponse du serveur generique</h2></body>
</html>
bye
Flux de lecture des lignes de texte du client : l'erreur suivante s'est produite : Unable to read data from the transport connection: Une opération de blocage a été interrompue par un appel à WSACancelBlockingCall.
[Fin du thread de lecture des demandes du client]

Yanıtımızda 1-4. satırlardaki HTTP başlıklarıyla sınırlandık. Göndereceğimiz belgenin boyutunu (Content-Length) belirtmiyoruz, ancak belgeyi gönderdikten sonra bağlantıyı kapatacağımızı (Connection: close) söylemekle yetiniyoruz. Bu, tarayıcı için yeterlidir. Bağlantının kapandığını gören tarayıcı, sunucunun yanıtının tamamlandığını anlayacak ve kendisine gönderilen HTML sayfasını görüntüleyecektir. Bu sayfa, 6-9. satırlarda yer alan sayfadır. Kullanıcı daha sonra klavyeden 10. satırdaki bye komutunu girerek istemciyle olan bağlantıyı kapatır. Bu klavye komutu üzerine ana iş parçacığı istemciyle olan bağlantıyı kapatır. Bu, 11. satırdaki istisnayı tetikler. İstemciden gelen metin satırlarını okuyan iş parçacığı, istemciyle olan bağlantının kapatılması nedeniyle aniden kesintiye uğramış ve bir istisna oluşturmuştur. 12. satırdan sonra, sunucu yeni bir istemciyi beklemeye geçer.

İstemci tarayıcısı artık şunu görüntüler:

Yukarıdaki örnekte, tarayıcının ne aldığını görmek için Affichage/Source komutunu çalıştırırsak, [2] sonucunu alırız; yani genel sunucudan gönderdiğimizin aynısıdır.

Genel TCP sunucusunun kodu şöyledir:


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Chap9 {
    public class ServeurTcpGenerique {
        public const string syntaxe = "Syntaxe : ServeurGénérique Port";

        // ana program
        public static void Main(string[] args) {

            // bir argüman var mı?
            if (args.Length != 1) {
                Console.WriteLine(syntaxe);
                Environment.Exit(1);
            }
            // bu argüman >0 tamsayı olmalıdır
            int port = 0;
            if (!int.TryParse(args[0], out port) || port <= 0) {
                Console.WriteLine("{0} : {1}Port incorrect", syntaxe, Environment.NewLine);
                Environment.Exit(2);
            }
            // dinleme hizmeti oluşturuluyor
            TcpListener ecoute = null;
            try {
                // hizmet oluşturuluyor
                ecoute = new TcpListener(IPAddress.Any, port);
                // hizmet başlatılıyor
                ecoute.Start();
                // izleme
                Console.WriteLine("Serveur générique lancé sur le port {0}", ecoute.LocalEndpoint);
                while (true) {
                    // istemci bekleniyor
                    Console.WriteLine("Attente du client suivant...");
                    TcpClient tcpClient = ecoute.AcceptTcpClient();
                    Console.WriteLine("Client {0}", tcpClient.Client.RemoteEndPoint);
                    // müşteri tarafından gönderilen metin satırlarını okumak için ayrı bir iş parçacığı başlatılıyor
                    ThreadPool.QueueUserWorkItem(Receive, tcpClient);
                    // klavye komutlarının okunması ana iş parçacığında yapılır
                    Console.WriteLine("Tapez vos commandes (bye pour arrêter) : ");
                    string réponse = null;        // sunucu yanıtı
                    // istemci bağlantısı işleniyor
                    using (tcpClient) {
                        // istemciye yönelik bir yazma akışı oluşturulur
                        using (NetworkStream networkStream = tcpClient.GetStream()) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // çıkış akışı tamponlanmamıştır
                                writer.AutoFlush = true;
                                // klavyeden gelen yanıtları alma döngüsü
                                while (true) {
                                    réponse = Console.ReadLine();
                                    // bitti mi?
                                    if (réponse.Trim().ToLower() == "bye")
                                        break;
                                    // istek müşteriye gönderiliyor
                                    writer.WriteLine(réponse);
                                }
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                // hata bildiriliyor
                Console.WriteLine("Main : l'erreur suivante s'est produite : {0}", ex.Message);
            } finally {
                // dinleme sonu
                ecoute.Stop();
            }
        }

        // sunucu <-- istemci okuma iş parçacığı
        public static void Receive(object infos) {
            // yerel veriler
            string demande = null;    // istemciden gelen istek
            string idClient=null;    // istemci kimliği

            // istemci bağlantısı işleme
            try {
                using (TcpClient tcpClient = infos as TcpClient) {
                    // istemci kimliği
                    idClient = tcpClient.Client.RemoteEndPoint.ToString();
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            // giriş akışındaki metin satırlarının sürekli okuma döngüsü
                            while ((demande = reader.ReadLine()) != null) {
                                // konsol ekranı
                                Console.WriteLine("<-- {0}", demande);
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                // hata
                Console.WriteLine("Flux de lecture des lignes de texte du client {1} : l'erreur suivante s'est produite : {0}", ex.Message,idClient);
            } finally {
                // okuma iş parçacığının sonu bildiriliyor
                Console.WriteLine("Fin du thread de lecture des lignes de texte du client {0}. Si besoin est, arrêtez le thread de lecture console du serveur pour ce client, avec la commande bye.", idClient);
            }
        }
    }
}
  • 29. satır: Dinleme hizmeti oluşturuldu ancak başlatılmadı. Makinenin tüm ağ arayüzlerini dinliyor.
  • 31. satır: Dinleme hizmeti başlatılır
  • 34. satır: istemcileri bekleme sonsuz döngüsü. Kullanıcı, Ctrl-C tuşlarıyla sunucuyu durduracaktır.
  • 37. satır: bir istemci bekleniyor – engelleyici işlem. İstemci geldiğinde, AcceptTcpClient yöntemi tarafından oluşturulan TcpClient örneği, istemciyle açık bir bağlantının sunucu tarafını temsil eder.
  • 40. satır: Müşteri isteklerinin okuma akışı ayrı bir iş parçacığına devredilir.
  • satır 45: Her ne olursa olsun bağlantının kapatılacağından emin olmak için, using cümlesinde istemci bağlantısı kullanılır.
  • 47. satır: Ağ akışının bir using cümlesinde kullanılması
  • 48. satır: using cümlesinde ağ akışına bir yazma akışı oluşturulur
  • 50. satır: Yazma akışı tamponlanmayacaktır
  • satır 52-59: İstemciye gönderilecek komutların klavyeden girilmesi döngüsü
  • satır 69: dinleme hizmetinin sonu. Sunucu Ctrl-C ile durdurulduğu için bu komut burada asla yürütülmeyecektir.
  • 78. satır: Receive yöntemi, istemci tarafından gönderilen metin satırlarını konsolda sürekli olarak görüntüler. Burada, genel TCP istemcisi için görülenler tekrarlanmaktadır.

11.6.5. Bir Web İstemcisi

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

<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-appl
ication, application/x-silverlight, */*
<-- Accept-Language: fr,en-US;q=0.7,fr-FR;q=0.3
<-- UA-CPU: x86
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.
4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.590; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Bir 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. Önceki başlıklardan yalnızca aşağıdakileri kullanacağız:

1
2
3
4
<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
<--
  • İlk başlık istenen belgeyi belirtir
  • ikincisi, sorgulanan sunucuyu belirtir
  • üçüncüsü ise, sunucunun yanıt verdikten sonra bağlantıyı kapatmasını istediğimizi belirtir.

Yukarıdaki 1. satırda, GET ifadesini HEAD ile değiştirirsek, sunucu bize sadece HTTP başlıklarını gönderecek ve 1. satırda belirtilen belgeyi göndermeyecektir.

Web istemcimiz şu şekilde çağrılacaktır: ClientWeb URL cmd; burada URL,URL'tir ve cmd, 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:

...\Chap9\06\bin\Release>ClientWeb http://istia.univ-angers.fr:80 HEAD
HTTP/1.1 200 OK
Date: Sat, 03 May 2008 14:05:24 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
Set-Cookie: fe_typo_user=e668408ac1; path=/
Connection: close
Content-Type: text/html;charset=iso-8859-1

...\Chap9\06\bin\Release>
  • 1. satırda, sadece başlıkları istiyoruz: HTTP (HEAD)
  • 2-9. satırlar: sunucunun yanıtı

Web istemcisine yapılan çağrıda HEAD yerine GET kullanırsak, HEAD ile elde ettiğimiz sonuçla aynı sonucu alırız, ayrıca istenen belgenin gövdesi de eklenir.

Web istemcisinin kodu şöyledir:


using System;
using System.IO;
using System.Net.Sockets;

namespace Chap9 {
    class ClientWeb {
        static void Main(string[] args) {
            // sözdizimi
            const string syntaxe = "pg URI GET/HEAD";

            // argüman sayısı
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }

            // URI talebi kaydedildi
            string stringURI = args[0];
            string commande = args[1].ToUpper();

            // URI'in geçerliliğinin doğrulanması
            if(! stringURI.StartsWith("http://")){
                Console.WriteLine("Indiquez une Url de la forme http://machine[:port]/document");
                return;
            }
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI hatalı
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }
            // komutun doğrulanması
            if (commande != "GET" && commande != "HEAD") {
                // sipariş hatalı
                Console.WriteLine("Le second paramètre doit être GET ou HEAD");
                return;
            }

            try {
                // hizmete bağlanılıyor
                using (TcpClient tcpClient = new TcpClient(uri.Host, uri.Port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // çıkış akışı tamponlanmamış
                                writer.AutoFlush = true;
                                // URL isteniyor - başlıkların gönderilmesi HTTP
                                writer.WriteLine(commande + " " + uri.PathAndQuery + " HTTP/1.1");
                                writer.WriteLine("Host: " + uri.Host + ":" + uri.Port);
                                writer.WriteLine("Connection: close");
                                writer.WriteLine();
                                // yanıt okunuyor
                                string réponse = null;
                                while ((réponse = reader.ReadLine()) != null) {
                                    // yanıt konsolda görüntüleniyor
                                    Console.WriteLine(réponse);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // istisna görüntüleniyor
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            }
        }
    }
}

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.

  • 26-33. satırlar: Parametre olarak alınan stringURI dizesinden bir Uri nesnesi oluşturulur. Parametre olarak alınan URI dizesi geçerli bir URI değilse (protokolün veya sunucunun eksikliği gibi), 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/document?param1=val1&param2=val2;... dizesinden oluşturulmuşsa, şu sonuç elde edilir:
    • uri.Host=serveur,
    • uri.Port=port,
    • uri.Path=document,
    • uri.Query=param1=val1&param2=val2;...,
    • uri.pathAndQuery= cheminPageHTML?param1=val1&param2=val2;...,
    • uri.Scheme=http.

11.6.6. Yönlendirmeleri yöneten bir web istemcisi

Yukarıdaki web istemcisi, kendisi tarafından istenen URL adresine yönelik olası bir yönlendirmeyi yönetememektedir. İşte bir örnek:

...\Chap9\06\bin\Release>ClientWeb http://www.ibm.com GET
HTTP/1.1 302 Found
Date: Sat, 03 May 2008 14:50:52 GMT
Server: IBM_HTTP_Server
Location: http://www.ibm.com/us/
Content-Length: 206
Kp-eeAlive: timeout=10, max=73
Connection: Keep-Alive
Content-Type: text/html

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.ibm.com/us/">here</a>.</p>
</body></html>
  • 2. satır: 302 Found kodu bir yönlendirmeye işaret eder. Tarayıcının yönlendirilmesi gereken adres, belgenin gövdesinde, 16. satırda yer almaktadır.

İkinci bir örnek:

...\Chap9\06\bin\Release>ClientWeb http://www.bull.com GET
HTTP/1.1 301 Moved Permanently
Date: Sat, 03 May 2008 14:52:31 GMT
Server: Apache/1.3.33 (Unix) WS_filter/2.1.15 PHP/4.3.4
X-Powered-By: PHP/4.3.4
Location: http://www.bull.com/index.php
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html

0
  • 2. satır: 301 Moved Permanently kodu bir yönlendirmeyi belirtir. Tarayıcının yönlendirilmesi gereken adres, 6. satırda, HTTP Location başlığında belirtilmiştir.

Üçüncü bir örnek:

1
2
3
4
5
6
7
...\Chap9\06\bin\Release>ClientWeb http://www.gouv.fr GET
HTTP/1.1 302 Moved Temporarily
Server: AkamaiGHost
Content-Length: 0
Location: http://www.premier-ministre.gouv.fr/fr/
Date: Sat, 03 May 2008 14:56:53 GMT
Connection: close
  • 2. satır: 302 Moved Temporarily kodu bir yönlendirmeyi belirtir. Tarayıcının yönlendirilmesi gereken adres, 5. satırda HTTP Location başlığında belirtilmiştir.

Makineye yerel bir IIS sunucusu içeren dördüncü bir örnek:

...\istia\Chap9\06\bin\Release>ClientWeb.exe http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.1
Date: Sun, 04 May 2008 10:16:56 GMT
Connection: close
Location: localstart.asp
Content-Length: 121
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQASDQAB=FDJLADLCOLDHGKGNIPMLHIIA; path=/
Cache-control: private
  • 2. satır: 302 Object moved kodu, bir yönlendirme olduğunu gösterir. Tarayıcının yönlendirilmesi gereken adres, 5. satırda, HTTP Location başlığında belirtilmiştir. Önceki örneklerin aksine, yönlendirme adresinin göreceli olduğuna dikkat edilmelidir. Tam adres aslında http://localhost/localstart.asp şeklindedir.

HTTP başlıklarının ilk satırında moved anahtar kelimesi (büyük/küçük harf duyarlı değildir) yer alıyorsa ve yönlendirme adresi HTTP Location başlığında bulunuyorsa, yönlendirmeleri yönetmeyi öneriyoruz.

Son üç örneği ele alırsak, şu sonuçları elde ederiz:

URL: http://www.bull.com

...\Chap9\06B\bin\Release>ClientWebAvecRedirection http://www.bull.com HEAD
HTTP/1.1 301 Moved Permanently
Date: Sun, 04 May 2008 10:22:48 GMT
Server: Apache/1.3.33 (Unix) WS_filter/2.1.15 PHP/4.3.4
X-Powered-By: PHP/4.3.4
Location: http://www.bull.com/index.php
Connection: close
Content-Type: text/html


<--Redirection vers l'URL http://www.bull.com/index.php-->

HTTP/1.1 200 OK
Date: Sun, 04 May 2008 10:22:49 GMT
Server: Apache/1.3.33 (Unix) WS_filter/2.1.15 PHP/4.3.4
X-Powered-By: PHP/4.3.4
Connection: close
Content-Type: text/html
  • 11. satır: 6. satırdaki adrese yönlendirme yapılır

URL: http://www.gouv.fr

...\Chap9\06B\bin\Release>ClientWebAvecRedirect
ion http://www.gouv.fr HEAD
HTTP/1.1 302 Moved Temporarily
Server: AkamaiGHost
Content-Length: 0
Location: http://www.premier-ministre.gouv.fr/fr/
Date: Sun, 04 May 2008 10:30:38 GMT
Connection: close


<--Redirection vers l'URL http://www.premier-ministre.gouv.fr/fr/-->

HTTP/1.1 200 OK
Server: Apache
X-Powered-By: PHP/4.4.1
Last-Modified: Sun, 04 May 2008 10:29:48 GMT
Content-Type: text/html
Expires: Sun, 04 May 2008 10:40:38 GMT
Date: Sun, 04 May 2008 10:30:38 GMT
Connection: close
  • 11. satır: 6. satırdaki adrese yönlendirme yapılır

URL: http://localhost

...\Chap9\06B\bin\Release>ClientWebAvecRedirection.exe http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.1
Date: Sun, 04 May 2008 10:37:11 GMT
Connection: close
Location: localstart.asp
Content-Length: 121
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQASDQAB=GDJLADLCJCMPCHFFEJEFPKMK; path=/
Cache-control: private


<--Redirection vers l'URL http://localhost/localstart.asp-->

HTTP/1.1 401 Access Denied
Server: Microsoft-IIS/5.1
Date: Sun, 04 May 2008 10:37:11 GMT
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="localhost"
Connection: close
Content-Length: 4766
Content-Type: text/html
  • 13. satır: 6. satırdaki adrese yönlendirme yapılır
  • 15. satır: http://localhost/localstart.asp sayfasına erişim reddedildi.

Yönlendirmeyi yöneten program şöyledir:


using System;
using System.IO;
using System.Net.Sockets;
using System.Text.RegularExpressions;

namespace Chap9 {
    class ClientWebAvecRedirection {
        static void Main(string[] args) {
            // sözdizimi
            const string syntaxe = "pg URI GET/HEAD";

            // argüman sayısı
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }

            // URI'in talep edildiği belirtilir
            string stringURI = args[0];
            string commande = args[1].ToUpper();

            // URI'in geçerliliğinin doğrulanması
            if (!stringURI.StartsWith("http://")) {
                Console.WriteLine("Indiquez une Url de la forme http://machine[:port]/document");
                return;
            }
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI hatalı
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }
            // komutun doğrulanması
            if (commande != "GET" && commande != "HEAD") {
                // komut hatalı
                Console.WriteLine("Le second paramètre doit être GET ou HEAD");
                return;
            }

            const int nbRedirsMax = 1;        // en fazla bir yönlendirme kabul edilir
            int nbRedirs = 0;                            // devam eden yönlendirme sayısı

            // bir yönlendirme URL'i bulmak için düzenli ifade
            Regex location = new Regex(@"^Location: (.+?)$");
            try {
                // Yönlendirmeler varsa, sorgulanacak birden fazla URL olabilir
                while (nbRedirs <= nbRedirsMax) {
                    // yönlendirme yönetimi
                    bool redir = false;
                    bool locationFound = false;
                    string locationString = null;
                    // hizmete bağlanılır
                    using (TcpClient tcpClient = new TcpClient(uri.Host, uri.Port)) {
                        using (StreamReader reader = new StreamReader(tcpClient.GetStream())) {
                            using (StreamWriter writer = new StreamWriter(tcpClient.GetStream())) {
                                // tamponlanmamış çıkış akışı
                                writer.AutoFlush = true;
                                // URL istenir - başlıkların gönderilmesi HTTP
                                writer.WriteLine(commande + " " + uri.PathAndQuery + " HTTP/1.1");
                                writer.WriteLine("Host: " + uri.Host + ":" + uri.Port);
                                writer.WriteLine("Connection: close");
                                writer.WriteLine();
                                // yanıtın ilk satırı okunuyor
                                string premièreLigne = reader.ReadLine();
                                // ekran yankısı
                                Console.WriteLine(premièreLigne);

                                // yönlendirme mi?
                                if (Regex.IsMatch(premièreLigne.ToLower(), @"\s+moved\s*")) {
                                    // yönlendirme var
                                    redir = true;
                                    nbRedirs++;
                                }

                                // başlıklar HTTP başlıkların sonunu belirten boş satır bulunana kadar devam eder
                                string réponse = null;
                                while ((réponse = reader.ReadLine()) != "") {
                                    // yanıt görüntülenir
                                    Console.WriteLine(réponse);
                                    // Yönlendirme varsa, Location başlığı aranır
                                    if (redir && !locationFound) {
                                        // geçerli satırı location ilişkisel ifadesiyle karşılaştırılır
                                        Match résultat = location.Match(réponse);
                                        if (résultat.Success) {
                                            // eğer bulunursa, yönlendirme kodu URL not edilir
                                            locationString = résultat.Groups[1].Value;
                                            // bulunduğu kaydedilir
                                            locationFound = true;
                                        }
                                    }
                                }

                                // HTTP başlıkları tükendi - boş satır yazılır
                                Console.WriteLine(réponse);
                                // ardından belgenin ana metnine geçin
                                while ((réponse = reader.ReadLine()) != null) {
                                    Console.WriteLine(réponse);
                                }
                            }
                        }
                    }
                    // bitti mi?
                    if (!locationFound || nbRedirs > nbRedirsMax)
                        break;
                    // bir yönlendirme yapılması gerekiyor - yeni URI oluşturuluyor
                    try {
                        if (locationString.StartsWith("http")) {
                            // tam http adresi
                            uri = new Uri(locationString);
                        } else {
                            // geçerli URI'ye göre göreceli HTTP adresi
                            uri = new Uri(uri, locationString);
                        }
                        // konsol günlüğü
                        Console.WriteLine("\n<--Redirection vers l'URL {0}-->\n", uri);
                    } catch (Exception ex) {
                        // URI ile ilgili sorun
                        Console.WriteLine("\n<--L'adresse de redirection {0} n'a pas été comprise : {1} -->\n", locationString, ex.Message);
                    }
                }
            } catch (Exception e) {
                // istisna görüntüleniyor
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            }
        }
    }
}

Önceki sürüme kıyasla değişiklikler şunlardır:

  • 46. satır: HTTP Location: adresi başlığından yönlendirme adresini almak için kullanılan düzenli ifade.
  • 49. satır: Daha önce tek bir Uri için çalıştırılan kod, artık birden fazla Uri için art arda çalıştırılabilir.
  • 66. satır: Sunucu tarafından gönderilen HTTP başlıklarının ilk satırı okunur. İstenen belge taşınmışsa, moved anahtar kelimesini içeren satır budur.
  • 71-75. satırlar: İlk satırda moved anahtar kelimesi olup olmadığı kontrol edilir. Varsa, not edilir.
  • 79-93. satırlar: Sonunu belirten boş satıra rastlanana kadar diğer HTTP başlıkları okunur. İlk satır bir yönlendirme bildiriyorsa, HTTP Location: adresi başlığı üzerinde durulur ve yönlendirme adresi locationString'te kaydedilir.
  • 98-100. satırlar: HTTP sunucusunun yanıtının geri kalanı konsola görüntülenir.
  • 105-106. satırlar: İstenen URI tamamen işlenip görüntülenmiştir. Yapılacak bir yönlendirme yoksa veya izin verilen yönlendirme sayısı aşılmışsa, programdan çıkılır.
  • 108-122. satırlar: Yönlendirme varsa, istenecek yeni URI hesaplanır. Bulunan yönlendirme adresinin mutlak (111. satır) mı yoksa göreceli (114. satır) mi olduğuna göre yapılması gereken küçük bir işlem vardır.

11.7. İnternetin belirli bir protokolüne özgü .NET sınıfları

Önceki web istemcisi örneklerinde, HTTP protokolü bir TCP istemcisiyle yönetiliyordu. Dolayısıyla, kullanılan özel iletişim protokolünü kendimiz yönetmemiz gerekiyordu. Benzer şekilde, bir SMTP veya POP istemcisi de oluşturabilirdik. .NET çerçeve yapısı, HTTP ve SMTP protokolleri için özel sınıflar sunar. Bu sınıflar, istemci ile sunucu arasındaki iletişim protokolünü bilir ve geliştiricinin bunları yönetme zahmetinden kurtarır. Şimdi bunları tanıtacağız.

11.7.1. classeWebClient

Bir web sunucusuyla iletişim kurabilen WebClient sınıfı mevcuttur. 11.6.5 numaralı paragrafta ele alınan web istemcisi örneğini, burada WebClient sınıfı ile ele alalım.


using System;
using System.IO;
using System.Net;
namespace Chap9 {
    public class Program {
        public static void Main(string[] args) {
            // sözdizimi: [prog] URI
            const string syntaxe = "pg URI";

            // argüman sayısı
            if (args.Length != 1) {
                Console.WriteLine(syntaxe);
                return;
            }

            // URI talebi kaydedildi
            string stringURI = args[0];

            // URI'in geçerliliğinin doğrulanması
            if (!stringURI.StartsWith("http://")) {
                Console.WriteLine("Indiquez une Url de la forme http://machine[:port]/document");
                return;
            }
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI hatalı
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }

            try {
                // web müşterisi oluşturuldu
                using (WebClient client = new WebClient()) {
                    // bir başlık ekleniyor HTTP 
                    client.Headers.Add("user-agent", "st");
                    using (Stream stream = client.OpenRead(uri)) {
                        using (StreamReader reader = new StreamReader(stream)) {
                            // web sunucusunun yanıtının görüntülenmesi
                            Console.WriteLine(reader.ReadToEnd());
                            // sunucu yanıt başlıklarının görüntülenmesi
                            Console.WriteLine("---------------------");
                            foreach (string clé in client.ResponseHeaders.Keys) {
                                Console.WriteLine("{0}: {1}", clé, client.ResponseHeaders[clé]);
                            }
                            Console.WriteLine("---------------------");
                        }
                    }
                }
            } catch (WebException e1) {
                Console.WriteLine("L'exception suivante s'est produite : {0}", e1);
            } catch (Exception e2) {
                Console.WriteLine("L'exception suivante s'est produite : {0}", e2);
            }
        }
    }
}
  • 35. satır: web istemcisi oluşturuldu ancak henüz yapılandırılmadı
  • 37. satır: Yapılacak olan HTTP isteğine bir HTTP başlığı eklenir. Varsayılan olarak başka başlıkların da gönderileceğini göreceğiz.
  • 38. satır: Web istemcisi, kullanıcı tarafından belirtilen URI’yi ister ve gönderilen belgeyi okur. [WebClient].OpenRead(URI), Uri ile bağlantı kurar ve yanıtı okur. Bu sınıfın önemi de budur. Web sunucusuyla iletişimi bu sınıf sağlar. OpenRead yönteminin sonucu Stream türündedir ve istenen belgeyi temsil eder. Sunucu tarafından gönderilen ve yanıtta belgenin öncesinde yer alan HTTP başlıkları, belgenin bir parçası değildir.
  • 39. satır: Yanıtın tamamını okumak için bir StreamReader kullanılır ve 41. satırda, bunun ReadToEnd yöntemi kullanılır.
  • 44-46. satırlar: Sunucunun yanıtındaki HTTP başlıkları görüntülenir. [WebClient].ResponseHeaders, anahtarları HTTP başlıklarının adları olan ve değerleri bu başlıklarla ilişkili karakter dizeleri olan bir değerli koleksiyonu temsil eder.
  • 51. satır: İstemci-sunucu iletişimi sırasında ortaya çıkan istisnalar, WebException türündedir.

Birkaç örneğe bakalım.

6.4.6 numaralı paragrafta oluşturulan genel TCP sunucusunu başlatıyoruz:

...\Chap9\05\bin\Release>ServeurTcpGenerique.exe 88
Serveur générique lancé sur le port 0.0.0.0:88

Önceki web istemcisini şu şekilde başlatıyoruz:

...\Chap9\09\bin\Release>09 http://localhost:88

İstenen URI, genel sunucunun URI'sidir. Sunucu, web istemcisi tarafından kendisine gönderilen HTTP başlıklarını görüntüler:

1
2
3
4
5
6
7
Client 127.0.0.1:1415
Tapez vos commandes (bye pour arrêter) :
<-- GET / HTTP/1.1
<-- User-Agent: st
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

Böylece şunu görüyoruz:

  • web istemcisinin varsayılan olarak 3 adet HTTP başlığı gönderdiği (3., 5. ve 6. satırlar)
  • 4. satır: bizim kendimiz oluşturduğumuz başlık (kodun 37. satırı)
  • web istemcisinin varsayılan olarak GET yöntemini kullandığı (3. satır). POST ve HEAD gibi başka yöntemler de mevcuttur.

Şimdi var olmayan bir kaynak isteyelim:

1
2
3
4
5
...\Chap9\09\bin\Release>09 http://istia.univ-angers.fr/bilinmiyor
L'exception suivante s'est produite : System.Net.WebException: The remote server returned an error: (404) Not Found.
   at System.Net.WebClient.OpenRead(Uri address)
   at System.Net.WebClient.OpenRead(String address)
   at Chap9.WebClient1.Main(String[] args) in C:\data\2007-2008\c# 2008\poly\istia\Chap9\09\Program.cs:satır 16
  • 2. satır: Sunucu, istenen kaynağın mevcut olmadığını belirtmek için 404 Not Found koduyla yanıt verdiği için WebException türünde bir istisna oluştu.

Son olarak, mevcut bir kaynağı talep ederek bitirelim:

...\istia\Chap9\09\bin\Release>09 http://istia.univ-angers.fr >istia.univ-angers.txt

Komut tarafından oluşturulan istia.univ-angers.txt dosyası şöyledir:

<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr_FR" lang="fr_FR">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
...
</html>
---------------------
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html;charset=iso-8859-1
Date: Sun, 04 May 2008 14:30:53 GMT
Set-Cookie: fe_typo_user=22eaaf283a; path=/
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
---------------------
  • 1. satır: İstenen HTML belgesi.
  • 3-10. satırlar: HTTP yanıtının başlıkları; bunlar, gönderildikleri sırayla olmayabilir.

WebClient sınıfı, bir belgeyi almak (DownLoad yöntemleri) veya göndermek (UpLoad yöntemleri) için yöntemlere sahiptir:

DownLoadData
bir kaynağı bayt dizisi olarak indirmek için (örneğin resim)
DownLoadFile
bir kaynağı indirip yerel bir dosyaya kaydetmek için
DownLoadString
bir kaynağı indirip karakter dizisi olarak almak için (örneğin bir HTML dosyası)
OpenWrite
OpenRead'in karşılığıdır, ancak verileri sunucuya göndermek içindir
UpLoadData
DownLoadData'in karşılığıdır, ancak sunucuya yöneliktir
UpLoadFile
DownLoadFile'in karşılığı, ancak sunucuya yöneliktir
UpLoadString
DownLoadString'in karşılığı, ancak sunucuya yönlendirilmiş
UpLoadValues
bir komutun verilerini sunucuya göndermek için POST ve sonuçları bir bayt tablosu biçiminde almak için. POST komutu, gönderilecek gerçek belgeyi belirlemek için gerekli bilgileri sunucuya iletirken bir belge talep eder. Bu bilgiler sunucuya bir belge olarak gönderilir; bu nedenle yöntemin adı UpLoad’tir. Bu bilgiler, HTTP başlık satırındaki boş satırın ardından param1=valeur1&param2=valeur2&... biçiminde gönderilir:
POST /document HTTP/1.1
...
[ligne vide]
param1=valeur1&param2=valeur2&...
Aynı belge, GET yöntemi ile de talep edilebilir:
GET /document?param1=valeur1&param2=valeur2&...
...
[ligne vide]
İki yöntem arasındaki fark şudur: İstenen URI’yi görüntüleyen tarayıcı, POST durumunda /document’i, /document?param1=valeur1&param2=valeur2&... durumunda ise /document?param1=valeur1&param2=valeur2&...’i gösterecektir.

11.7.2. WebRequest / WebResponse sınıfları

Bazen WebClient sınıfı, istenen işlemi gerçekleştirmek için yeterince esnek olmayabilir. 11.6.6. paragrafında incelenen yönlendirmeli web istemcisi örneğine geri dönelim. HTTP başlığını göndermemiz gerekiyor:

HEAD /document HTTP/1.1

Web istemcisi tarafından varsayılan olarak gönderilen HTTP başlıklarının şunlar olduğunu görmüştük:

1
2
3
<-- GET / HTTP/1.1
<-- Host: machine:port
<-- Connection: Keep-Alive

Ayrıca, [WebClient].Headers koleksiyonu ile bunlara HTTP başlıklarını eklemenin mümkün olduğunu da gördük. Yalnızca 1. satır, anahtar:değer biçimine sahip olmadığı için Headers koleksiyonuna ait bir başlık değildir. WebClient sınıfından yola çıkarak 1. satırdaki GET'i HEAD olarak nasıl değiştirebileceğimi bulamadım (belki de doğru şekilde aramadım?). WebClient sınıfı sınırlarına ulaştığında, WebRequest / WebResponse sınıflarına geçilebilir:

  • WebRequest: Web istemcisinin talebinin tamamını temsil eder.
  • WebResponse: Web sunucusunun yanıtının tamamını temsil eder

WebClient sınıfının http:, https:, ftp: ve file: şemalarını yönettiğini belirtmiştik. Bu farklı protokollere ait istek ve yanıtlar aynı biçime sahip değildir. Bu nedenle, bu öğelerin genel türü olan WebRequest ve WebResponse yerine, tam türlerini işlemek gerekir. Bu nedenle şu sınıfları kullanacağız:

  • HttpWebRequest, HttpWebResponse bir istemci için HTTP
  • FtpWebRequest, FtpWebResponse sınıfları, FTP müşterisi için

Şimdi, 11.6.6. paragrafında incelenen yönlendirmeli web istemcisi örneğini HttpWebRequest ve HttpWebresponse sınıflarıyla ele alıyoruz. Kod şu şekildedir:


using System;
using System.IO;
using System.Net.Sockets;
using System.Net;

namespace Chap9 {
    class WebRequestResponse {
        static void Main(string[] args) {
            // sözdizimi
            const string syntaxe = "pg URI GET/HEAD";

            // argüman sayısı
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }

            // istenen URI not edilir
            string stringURI = args[0];
            string commande = args[1].ToUpper();

            // URI'in geçerliliğinin doğrulanması
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI hatalı
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }
            // komutun doğrulanması
            if (commande != "GET" && commande != "HEAD") {
                // sipariş hatalı
                Console.WriteLine("Le second paramètre doit être GET ou HEAD");
                return;
            }

            try {
                // istek yapılandırılıyor
                HttpWebRequest httpWebRequest = WebRequest.Create(uri) as HttpWebRequest;
                httpWebRequest.Method = commande;
                httpWebRequest.Proxy = null;
                // yürütülüyor
                HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
                // sonuç
                Console.WriteLine("---------------------");
                Console.WriteLine("Le serveur {0} a répondu : {1} {2}", httpWebResponse.ResponseUri,(int)httpWebResponse.StatusCode, httpWebResponse.StatusDescription);
                // başlıklar HTTP
                Console.WriteLine("---------------------");
                foreach (string clé in httpWebResponse.Headers.Keys) {
                    Console.WriteLine("{0}: {1}", clé, httpWebResponse.Headers[clé]);
                }
                Console.WriteLine("---------------------");
                // belge
                using (Stream stream = httpWebResponse.GetResponseStream()) {
                    using (StreamReader reader = new StreamReader(stream)) {
                        // yanıt konsolda görüntülenir
                        Console.WriteLine(reader.ReadToEnd());
                    }
                }
            } catch (WebException e1) {
                // yanıt alınır
                HttpWebResponse httpWebResponse = e1.Response as HttpWebResponse;
                Console.WriteLine("Le serveur {0} a répondu : {1} {2}", httpWebResponse.ResponseUri, (int)httpWebResponse.StatusCode, httpWebResponse.StatusDescription);
            } catch (Exception e2) {
                // istisna görüntülenir
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e2.Message);
            }
        }
    }
}
  • 40. satır: WebRequest türünde bir nesne, WebRequest.Create(Uri uri) statik yöntemi ile oluşturulur; burada uri, indirilecek belgenin URI'sidir. Uri protokolünün HTTP olduğu bilindiğinden, HTTP protokolüne özgü öğelere erişebilmek için sonucun türü HttpWebRequest olarak değiştirilir.
  • 41. satır: HTTP başlıklarının 1. satırındaki yöntemi GET / POST / HEAD olarak belirliyoruz. Burada bu, GET veya HEAD olacaktır.
  • 42. satır: Bir şirketin özel ağında, güvenlik nedenleriyle şirketin bilgisayarlarının internetten izole edilmesi yaygın bir durumdur. Bu amaçla, özel ağ, internet yönlendiricileri tarafından yönlendirilmeyen internet adresleri kullanır. Özel ağ, hem şirketin özel ağına hem de internete bağlı olan ve proxy olarak adlandırılan özel bilgisayarlar aracılığıyla internete bağlanır. IP, birden fazla adrese sahip bilgisayarların bir örneğidir. Özel ağdaki bir cihaz, örneğin bir web sunucusu gibi bir internet sunucusuyla kendi başına bağlantı kuramaz. Bunu kendi adına yapması için bir proxy cihazından talepte bulunması gerekir. proxy numaralı bir cihaz, farklı protokoller için proxy numaralı sunucuları barındırabilir. Özel ağdaki bilgisayarlar adına HTTP isteklerini gerçekleştiren hizmetten bahsederken HTTP proxy'si terimi kullanılır. Böyle bir HTTP proxy sunucusu varsa, bunu [WebRequest].proxy alanında belirtmek gerekir. Örneğin şöyle yazılır:
[WebRequest].proxy=new WebProxy("pproxy.istia.uang:3128");

HTTP proxy'si, pproxy.istia.uang makinesinin 3128 numaralı bağlantı noktasında çalışıyorsa. Makinenin internete doğrudan erişimi varsa ve bir proxy üzerinden geçmesi gerekmiyorsa, [WebRequest].proxy alanına null yazılır.

  • 44. satır: GetResponse() yöntemi, Uri'siyle tanımlanan belgeyi ister ve bir WebRequestResponse nesnesi döndürür; bu nesne burada bir HttpWebResponse nesnesine dönüştürülür. Bu nesne, belge talebine sunucunun verdiği yanıtı temsil eder.
  • 47. satır:
    • [HttpWebResponse].ResponseUri: Belgeyi gönderen sunucunun URI'sidir. Yönlendirme durumunda, bu URI başlangıçta sorgulanan sunucunun URI'sinden farklı olabilir. Kodun yönlendirmeyi yönetmediğine dikkat edilmelidir. Yönlendirme, GetResponse yöntemi tarafından otomatik olarak yönetilir. Bu da, TCP protokolünün temel sınıflarına kıyasla üst düzey sınıfların bir avantajıdır.
    • [HttpWebResponse].StatusCode, [HttpWebResponse].StatusDescription, yanıtın ilk satırını temsil eder; örneğin: HTTP/1.1 200 OK. StatusCode 200'dür ve StatusDescription, OK'tir.
  • 50. satır: [HttpWebResponse].Headers, yanıtın HTTP başlıklarının koleksiyonudur.
  • 55. satır: [HttpWebResponse].GetResponseStream: yanıtta bulunan belgeyi elde etmeyi sağlayan akıştır.
  • 61. satır: WebException türünde bir istisna meydana gelebilir
  • 63. satır: [WebException].Response, istisnanın ortaya çıkmasına neden olan yanıttır.

İşte bir yürütme örneği:

...\Chap9\09B\bin\Release>09B http://www.gouv.fr HEAD
---------------------
Le serveur http://www.premier-ministre.gouv.fr/fr/ yanıtladı: 200 OK
---------------------
Connection: keep-alive
Content-Type: text/html; charset=iso-8859-1
Date: Mon, 05 May 2008 13:02:29 GMT
Expires: Mon, 05 May 2008 13:07:20 GMT
Last-Modified: Mon, 05 May 2008 12:56:59 GMT
Server: Apache
X-Powered-By: PHP/4.4.1
---------------------
  • 1. ve 3. satırlar: Yanıt veren sunucu, sorgulanan sunucuyla aynı değildir. Dolayısıyla bir yönlendirme gerçekleşmiştir.
  • 5-11. satırlar: Sunucu tarafından gönderilen HTTP başlıkları

11.7.3. Uygulama: Bir çeviri web sunucusunun proxy istemcisi

Şimdi, önceki sınıfların web kaynaklarından nasıl yararlanmamızı sağladığını göstereceğiz.

11.7.3.1. L'application

Web'de çeviri siteleri bulunmaktadır. Burada kullanılacak site http://trans.voila.fr/traduction_voila.php adresindedir:

Çevrilecek metin [1]'e girilir, çeviri yönü [2]'te seçilir. Çeviri, [3] ile talep edilir ve [4]'te elde edilir.

Yukarıdaki uygulamanın bir Windows istemci uygulamasını yazacağız. Bu uygulama, [trans.voila.fr] sitesindeki uygulamadan başka bir şey yapmayacaktır. Arayüzü şu şekilde olacaktır:

11.7.3.2. Uygulamanın mimarisi

Uygulama, aşağıdaki gibi 2 katmanlı bir mimariye sahip olacaktır:

11.7.3.3. Visual Studio projesi

Visual Studio projesi aşağıdaki gibi olacaktır:

  • [1]'te, çözüm iki projeden oluşmaktadır:
  • [2]: biri [dao] katmanı ve bu katman tarafından kullanılan varlıklar için,
  • [3]: diğeri ise Windows arayüzü içindir

11.7.3.4. [dao] projesi

[dao] projesi aşağıdaki öğelerden oluşur:

  • IServiceTraduction.cs: [ui] katmanına sunulan arayüz
  • ServiceTraduction: Bu arayüzün uygulaması
  • WebTraductionsException: uygulamaya özgü bir istisna

IServiceTraduction arayüzü şu şekildedir:


using System.Collections.Generic;

namespace dao {
    public interface IServiceTraduction {
        // kullanılan diller
        IDictionary<string, string> LanguesTraduites { get; }
        // çeviri
        string Traduire(string texte, string deQuoiVersQuoi);
    }
}
  • 6. satır: LanguesTraduites özelliği, çeviri sunucusu tarafından kabul edilen dillerin sözlüğünü döndürür. Bu sözlükte, ["fe","Français-Anglais"] biçiminde girdiler bulunur; burada değer bir çeviri yönünü belirtir (bu örnekte Fransızca'dan İngilizce'ye) ve "fe" anahtarı, trans.voila.fr çeviri sunucusu tarafından kullanılan bir koddur.
  • 8. satır: Traduire yöntemi, çeviri yöntemidir:
    • texte, çevrilecek metindir
    • deQuoiVersQuoi, çevrilen dillerin sözlüğündeki anahtarlardan biridir
    • Yöntem, metnin çevirisini gerçekleştirir

ServiceTraduction, IServiceTraduction arayüzünün bir uygulama sınıfıdır. Bunu aşağıdaki bölümde ayrıntılı olarak ele alacağız.

WebTraductionsException, aşağıdaki istisna sınıfıdır:


using System;

namespace entites {
    public class WebTraductionsException : Exception {

        // hata kodu
        public int Code { get; set; }

        // üreticiler
        public WebTraductionsException() {
        }
        public WebTraductionsException(string message)
            : base(message) {
        }
        public WebTraductionsException(string message, Exception e)
            : base(message, e) {
        }
    }
}
  • 7. satır: bir hata kodu

11.7.3.5. [ServiceTraduction] web istemcisi

Uygulamamızın mimarisine geri dönelim:

Yazmamız gereken [ServiceTraduction] sınıfı, [trans.voila.fr] çeviri web hizmetinin bir istemcisidir. Bunu yazmak için,

  • çeviri sunucusunun istemcisinden ne beklediğini
  • ve müşterisine ne tür bir yanıt gönderdiğini

Bir çeviri sırasında gerçekleşen istemci-sunucu diyaloğunu bir örnek üzerinden inceleyelim. Uygulamanın giriş bölümünde sunulan örneği tekrar ele alalım:

Çevrilecek metin [1]'e eklenir, çeviri yönü [2]'te seçilir. Çeviri, [3] tarafından talep edilir ve [4]'te elde edilir.

[4] çevirisini almak için tarayıcı, aşağıdaki GET isteğini gönderdi (adres çubuğunda görüntülenir):

http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection=fe&stext=köpek+hasta

Bu istek oldukça basit bir şekilde anlaşılabilir:

  • http://trans.voila.fr/traduction_voila.php, çeviri hizmetinin URL'sidir
  • isText=1, metinle ilgili olduğunu ifade ediyor gibi görünüyor
  • translationDirection, çevirinin yönünü belirtir; bu örnekte Français-Anglais
  • stext, "URL kodlanmış" olarak adlandırılan biçimde çevrilecek metindir. Nitekim, bazı karakterler bir URL'de görünemez. Örneğin, burada + işaretiyle kodlanmış boşluk karakteri buna bir örnektir. .NET çerçeve yapısı, bu kodlama işini yapmak için System.Web.HttpUtility.UrlEncode adlı statik yöntemi sunar.

Bundan, çeviri sunucusuna sorgu göndermek için [ServiceTraduction] sınıfımızın şu dizeyi kullanabileceği sonucuna varabiliriz:

"http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1}"

kullanabileceğimiz sonucuna varırız; burada {0} ve {1} işaretçileri sırasıyla çeviri anlamı ve çevrilecek metin ile değiştirilecektir.

Sunucu tarafından kabul edilen çeviri dillerini nasıl öğrenebiliriz? Yukarıdaki ekran görüntüsünde, çevrilen diller açılır listede yer almaktadır. Tarayıcıda sayfanın HTML koduna (Görünüm / Kaynak) bakıldığında, açılır liste için şu kod görülür:

<select name="translationDirection" class="champs">
    <option selected value='fe'>Fran&ccedil;ais vers Anglais
    <option  value='ef'>Anglais vers Fran&ccedil;ais
    <option  value='fg'>Fran&ccedil;ais vers Allemand
    <option  value='gf'>Allemand vers Fran&ccedil;ais
    <option  value='fs'>Fran&ccedil;ais vers Espagnol
    <option  value='sf'>Espagnol vers Fran&ccedil;ais
    <option  value='fr'>Fran&ccedil;ais vers Russe
    <option  value='rf'>Russe vers Fran&ccedil;ais
    <option  value='es'>Anglais vers Espagnol
    <option  value='se'>Espagnol vers Anglais
    <option  value='eg'>Anglais vers Allemand
    <option  value='ge'>Allemand vers Anglais
    <option  value='ep'>Anglais vers Portugais
    <option  value='pe'>Portugais vers Anglais
    <option  value='ie'>Italien vers Anglais
    <option  value='gs'>Allemand vers Espagnol
    <option  value='sg'>Espagnol vers Allemand
</select>

Bu, her bir <option> etiketinin normalde bir </option> etiketiyle kapatılması gerektiği düşünüldüğünde, çok temiz bir HTML kodu değildir. Bununla birlikte, value öznitelikleri bize sunucuya gönderilmesi gereken çeviri kodlarının listesini verir. LanguesTraduites arayüzünün IServiceTraduction sözlüğünde, anahtarlar yukarıdaki value öznitelikleri, değerler ise açılır listede görüntülenen metinler olacaktır.

Şimdi (Görünüm / Kaynak) seçeneğiyle, çeviri sunucusu tarafından gönderilen çevirinin HTML sayfasında nerede yer aldığını inceleyelim:

...                                                                
<strong>Texte traduit : </strong><div class="txtTrad">this dog is sick</div> 
...

Çeviri, gönderilen HTML sayfasının tam ortasında yer almaktadır. Bunu nasıl bulabiliriz? <div class="txtTrad">...</div> dizisini içeren bir düzenli ifade kullanabiliriz, çünkü <div class="txtTrad"> etiketi HTML sayfasının yalnızca bu bölümünde yer almaktadır. Çevrilmiş metni almak için kullanılacak C# düzenli ifadesi şöyledir:

@"<div class=""txtTrad"">(.*?)</div>"

Artık IServiceTraduction arayüzünün ServiceTraduction uygulama sınıfını yazmak için gerekli öğelere sahibiz:


using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using entites;

namespace dao {
    public class ServiceTraduction : IServiceTraduction {
        // hizmetin otomatik yapılandırma özellikleri
        public IDictionary<string, string> LanguesTraduites { get; set; }
        public string UrlServeurTraduction { get; set; }
        public string ProxyHttp { get; set; }
        public String RegexTraduction { get; set; }

        // çeviri
        public string Traduire(string texte, string deQuoiVersQuoi) {
            // istenen çeviri mümkün mü?
            if (!LanguesTraduites.ContainsKey(deQuoiVersQuoi)) {
                throw new WebTraductionsException(String.Format("Le sens de traduction [{0}] n'est pas reconnu")) { Code = 10 };
            }
            // çevrilecek metin
            string texteATraduire = HttpUtility.UrlEncode(texte);
            // istenecek URI
            string uri = string.Format(UrlServeurTraduction, deQuoiVersQuoi, texteATraduire);
            // yanıtta çeviriyi bulmak için düzenli ifade
            Regex patternTraduction = new Regex(RegexTraduction);
            // istisna
            WebTraductionsException exception = null;
            // çeviri
            string traduction = null;
            try {
                // isteği yapılandırıyoruz
                HttpWebRequest httpWebRequest = WebRequest.Create(uri) as HttpWebRequest;
                httpWebRequest.Method = "GET";
                httpWebRequest.Proxy = ProxyHttp == null ? null : new WebProxy(ProxyHttp); ;
                // yürütülüyor
                HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
                // belge
                using (Stream stream = httpWebResponse.GetResponseStream()) {
                    using (StreamReader reader = new StreamReader(stream)) {
                        bool traductionTrouvée = false;
                        string ligne = null;
                        while (!traductionTrouvée && (ligne = reader.ReadLine()) != null) {
                            // geçerli satırda çeviri aranıyor
                            MatchCollection résultats = patternTraduction.Matches(ligne);
                            // çeviri bulundu mu?
                            if (résultats.Count != 0) {
                                traduction = résultats[0].Groups[1].Value.Trim();
                                traductionTrouvée = true;
                            }
                        }
                        // çeviri bulundu mu?
                        if (!traductionTrouvée) {
                            exception = new WebTraductionsException("Le serveur n'a pas renvoyé de réponse") { Code = 12 };
                        }
                    }
                }
            } catch (Exception e) {
                exception = new WebTraductionsException("Erreur rencontrée lors de la traduction", e) { Code = 11 };
            }
            // istisna mı?
            if (exception != null) {
                throw exception;
            } else {
                return traduction;
            }
        }
    }
}
  • 12. satır: IServiceTraduction arayüzünün LanguesTraduites özelliği - dışarıdan başlatılmıştır
  • 13. satır: UrlServeurTraduction özelliği, çeviri sunucusuna gönderilecek URL'dir: http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1} burada {0} işaretçisi çeviri yönüyle, {1} işaretçisi ise çevrilecek metinle değiştirilmelidir - dışarıdan başlatılır
  • 14. satır: ProxyHttp özelliği, kullanılacak olası HTTP proxy'sidir; örneğin: pproxy.istia.uang:3128 - dışarıdan başlatılır
  • 15. satır: RegexTraduction özelliği, çeviri sunucusu tarafından gönderilen HTML akışından çeviriyi almak için kullanılan düzenli ifadedir; örneğin: @"<div class=""txtTrad"">(.*?)</div>" - dışarıdan başlatılır
  • Bu dört özellik, uygulamamızda Spring tarafından başlatılacaktır.
  • 20-22. satırlar: İstenen çeviri yönünün çevrilmiş diller sözlüğünde mevcut olup olmadığı kontrol edilir. Aksi takdirde bir istisna atılır.
  • 24. satır: Çevrilecek metin, bir URL'nin parçası olabilmesi için kodlanır
  • 26. satır: Çeviri hizmetinin URI'si oluşturulur. UrlServeurTraduction özelliği http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1} ise, {0} işaretçisi çeviri metniyle, {1} işaretçisi ise çevrilecek metinle değiştirilir.
  • 28. satır: Çeviri sunucusunun HTML yanıtında çeviriyi arama şablonu oluşturulur.
  • 33. ve 60. satırlar: Çeviri sunucusuna sorgu gönderme işlemi bir try / catch bloğu içinde gerçekleştirilir
  • 35. satır: Çeviri sunucusuna sorgu göndermek için kullanılacak HttpWebRequest nesnesi, istenen belgenin URI'si ile oluşturulur.
  • 36. satır: Sorgulama yöntemi GET'tir. GET muhtemelen HttpWebRequest nesnesinin varsayılan yöntemi olduğu için bu komut kullanılmasa da olur.
  • 37. satır: HttpWebRequest nesnesinin Proxy özelliğini ayarlıyoruz.
  • 39. satır: Çeviri sunucusuna istek gönderilir ve HttpWebResponse türündeki yanıt alınır.
  • 41-42. satırlar: Sunucudan gelen HTML yanıtının her satırını okumak için bir StreamReader kullanılır.
  • 45-53. satırlar: Yanıtın her satırında çeviri aranır. Çeviri bulunduğunda, HTML yanıtının okunması durdurulur ve açılmış olan tüm akışlar kapatılır.
  • 55-57. satırlar: HTML yanıtında çeviri bulunamazsa, bunu bildirmek için WebTraductionsException türünde bir istisna hazırlanır.
  • 60-62. satırlar: İstemci/sunucu iletişimi sırasında bir istisna meydana gelirse, bunu bildirmek için WebTraductionsException türünde bir istisna içine alınır.
  • 64-68. satırlar: Bir istisna kaydedilmişse bu istisna tetiklenir, aksi takdirde bulunan çeviri döndürülür.

Örneğimizde, HTTP proxy'sinin kimlik doğrulama gerektirmediği varsayılmaktadır. Aksi durumda, şunun gibi bir kod yazılırdı:


httpWebRequest.Proxy = ProxyHttp == null ? null : new WebProxy(ProxyHttp); ;
httpWebRequest.Proxy.Credentials=new NetworkCredential("login","password");

Burada WebClient yerine WebRequest / WebResponse kullandık, çünkü çeviri sunucusundan gelen HTML yanıtının tamamını işlememiz gerekmiyor. Bu yanıtta çeviri bulunduktan sonra, yanıtın geri kalan satırlarına artık ihtiyacımız kalmaz. WebClient sınıfı bunu yapmaya izin vermez.

İşte ServiceTraduction sınıfını test eden bir program:


using System;
using System.Collections.Generic;
using dao;
using entites;

namespace ui {
    class Program {
        static void Main(string[] args) {
            try {
                // çeviri hizmeti oluşturma
                ServiceTraduction serviceTraduction = new ServiceTraduction();
                // çeviriyi bulmak için düzenli ifade
                serviceTraduction.RegexTraduction = @"<div class=""txtTrad"">(.*?)</div>";
                // çeviri sunucusunun URL'si
                serviceTraduction.UrlServeurTraduction = "http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1}";
                // çevrilen dillerin sözlüğü
                Dictionary<string, string> languesTraduites = new Dictionary<string, string>();
                languesTraduites["fe"]= "Français-Anglais";
                languesTraduites["fs"]= "Français-Espagnol";
                languesTraduites["ef"]= "Anglais-Français";
                serviceTraduction.LanguesTraduites = languesTraduites;
                // proxy
                //serviceTraduction.ProxyHttp = "pproxy.istia.uang:3128";
                // çeviri
                string texte = "ce chien est perdu";
                string deQuoiVersQuoi = "fe";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
                texte = "l'été sera chaud";
                deQuoiVersQuoi = "fs";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
                texte = "my tailor is rich";
                deQuoiVersQuoi = "ef";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
                texte = "xx";
                deQuoiVersQuoi = "ef";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
            } catch (WebTraductionsException e) {
                // hata
                Console.WriteLine("L'erreur suivante de code {1} s'est produite : {0}", e.Message, e.Code);
            }
        }
    }
}

Elde edilen sonuçlar şunlardır:

1
2
3
4
Traduction [Français-Anglais] de [ce chien est perdu] : [this dog is lost]
Traduction [Français-Espagnol] de [l'été sera chaud] : [el verano será caliente]
Traduction [Anglais-Français] de [my tailor is rich] : [mon tailleur est riche]
Traduction [Anglais-Français] de [xx] : [xx]

Çözümdeki [dao] projesi, DLL ve HttpTraductions.dll olarak derlenir:

 

11.7.3.6. Uygulamanın grafik kullanıcı arayüzü

Uygulamamızın mimarisine tekrar göz atalım:

Şimdi [ui] katmanını yazıyoruz. Bu katman, geliştirilmekte olan çözümün [ui] projesinin konusudur:

[lib] [3] klasörü, [4] projesi tarafından referans verilen bazı DLL dosyalarını içerir:

  • Spring için gerekli olanlar: Spring.Core, Common.Logging, antlr.runtime
  • [dao] katmanına ait olan: HttpTraductions

[App.config] dosyası Spring yapılandırmasını içerir:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <description>Traductions sur le web</description>
            <!-- çeviri hizmeti -->
            <object name="ServiceTraduction" type="dao.ServiceTraduction, HttpTraductions">
                <property name="UrlServeurTraduction" value="http://trans.voila.fr/traduction_voila.php?isText=1&amp;translationDirection={0}&amp;stext={1}"/>
                <!--
                <property name="ProxyHttp" value="pproxy.istia.uang:3128"/>
                -->
                <property name="RegexTraduction" value="&lt;div class=&quot;txtTrad&quot;&gt;(.*?)&lt;/div&gt;"/>
                <property name="LanguesTraduites">
                    <dictionary key-type="string" value-type="string">
                        <entry key="fe" value="Français-Anglais"/>
                        <entry key="ef" value="Anglais-Français"/>
...
                        <entry key="ei" value="Anglais-Italien"/>
                        <entry key="ie" value="Italien-Anglais"/>
                    </dictionary>
                </property>
            </object>
        </objects>
    </spring>
</configuration>
  • 15. satır: Spring tarafından örneklenecek nesneler. Bunlardan sadece biri olacaktır; 18. satırdaki nesne, DLL ve HttpTraductions dosyalarında bulunan ServiceTraduction sınıfı ile çeviri hizmetini örneklendirir.
  • 19. satır: ServiceTraduction sınıfının UrlServeurTraduction özelliği. URL'deki & karakteriyle ilgili bir sorun var. Bu karakter, bir XML dosyasında belirli bir anlama sahiptir. Bu nedenle korunması gerekir. Dosyanın ilerleyen kısımlarında karşılaşacağımız diğer karakterler için de durum aynıdır. Bunlar, [&code;] dizisiyle değiştirilmelidir: & yerine [&amp;], < yerine [&lt;], > yerine [&gt;], " yerine [&quot;].
  • 21. satır: ServiceTraduction sınıfının ProxyHttp özelliği. null özelliği ise başlatılmamıştır. Bu özelliğin tanımlanmaması, Http proxy'sinin olmadığı anlamına gelir.
  • 23. satır: ServiceTraduction sınıfının RegexTraduction özelliği. Düzenli ifadede, [< > "] karakterlerinin korumalı eşdeğerleriyle değiştirilmesi gerekmiştir.
  • 24-33. satırlar: ServiceTraduction sınıfının LanguesTraduites özelliği.

[Program.cs] programı, uygulamanın başlatılmasıyla birlikte çalıştırılır. Kod şöyledir:


using System;
using System.Text;
using System.Windows.Forms;
using dao;
using Spring.Context;
using Spring.Context.Support;

namespace ui {
    static class Program {
        /// <summary>
        /// Uygulamanın ana giriş noktası.
        /// </summary>
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
    
            // --------------- Geliştirici kodu
            // çeviri hizmetinin örneklenmesi
            IApplicationContext ctx = null;
            Exception ex = null;
            ServiceTraduction serviceTraduction = null;
            try {
                // Spring bağlamı
                ctx = ContextRegistry.GetContext();
                // çeviri hizmetinden referans isteniyor
                serviceTraduction = ctx.GetObject("ServiceTraduction") as ServiceTraduction;
            } catch (Exception e1) {
                // istisna kaydı
                ex = e1;
            }
            // görüntülenecek form
            Form form = null;
            // Bir istisna oluştu mu?
            if (ex != null) {
                // evet - görüntülenecek hata mesajı oluşturuluyor
                StringBuilder msgErreur = new StringBuilder(String.Format("Chaîne des exceptions : {0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                Exception e = ex;
                while (e != null) {
                    msgErreur.Append(String.Format("{0}: {1}{2}", e.GetType().FullName, e.Message, Environment.NewLine));
                    msgErreur.Append(String.Format("{0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                    e = e.InnerException;
                }
                // görüntülenecek hata mesajının aktarıldığı hata penceresi oluşturuluyor
                Form2 form2 = new Form2();
                form2.MsgErreur = msgErreur.ToString();
                // Görüntülenecek pencere bu olacak
                form = form2;
            } else {
                // her şey yolunda gitti
                // çeviri hizmetine ilişkin referansı aktardığımız [Form1] adlı grafik arayüzü oluşturuluyor
                Form1 form1 = new Form1();
                form1.ServiceTraduction = serviceTraduction;
                // bu, görüntülenecek pencere olacaktır
                form = form1;
            }
            // pencerenin görüntülenmesi
            Application.Run(form);
        }
    }
}

Bu kod, Vergiler uygulaması sürüm 6'nın 7.6.2. paragrafında daha önce kullanılmıştır.

  • Çeviri hizmeti, Spring tarafından 27. satırda oluşturulur. Oluşturma işlemi başarılı olursa, [Form1] formu görüntülenir (52-55. satırlar); aksi takdirde, [Form2] hata formu görüntülenir (36-48. satırlar).

[Form2] formu, Vergiler uygulaması sürüm 6'da kullanılan formdur ve 7.6.4. paragrafında açıklanmıştır.

[Form1] formu şöyledir:

No.
tip
ad
rol
1
TextBox
textBoxTexteATraduire
çevrilecek metnin girileceği alan
MultiLine=true
2
ComboBox
comboBoxLangues
çeviri anlamları listesi
3
Düğme
buttonTraduire
[1] metninin [2] yönüne çevrilmesini istemek için
4
TextBox
textBoxTraduction
[1] metninin çevirisi

[Form1] form kodunun değeri şöyledir:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using dao;

namespace ui {
    public partial class Form1 : Form {
        // çeviri hizmeti
        public ServiceTraduction ServiceTraduction { get; set; }
        // dil sözlüğü
        Dictionary<string, string> languesInversées = new Dictionary<string, string>();

        // oluşturucu
        public Form1() {
            InitializeComponent();
        }

        // formun ilk yüklemesi
        private void Form1_Load(object sender, EventArgs e) {
            // ters dil sözlüğü oluşturma
            foreach (string code in ServiceTraduction.LanguesTraduites.Keys) {
                // diller
                string langues = ServiceTraduction.LanguesTraduites[code];
                // ters sözlüğe (diller, kod) ekleme
                languesInversées[langues] = code;
            }
            // dillerin alfabetik sırasına göre açılır menüyü doldurma
            string[] languesCombo = languesInversées.Keys.ToArray();
            Array.Sort<string>(languesCombo);
            foreach (string langue in languesCombo) {
                comboBoxLangues.Items.Add(langue);
            }
            // 1. dilin seçilmesi
            if (comboBoxLangues.Items.Count != 0) {
                comboBoxLangues.SelectedIndex = 0;
            }
        }

        private void buttonTraduire_Click(object sender, EventArgs e) {
            // çevrilecek bir şey var mı?
            string texte = textBoxTexteATraduire.Text.Trim();
            if (texte == "") return;
            // çeviri
            try {
                textBoxTraduction.Text = ServiceTraduction.Traduire(texte, languesInversées[comboBoxLangues.SelectedItem.ToString()]);
            } catch (Exception ex) {
                textBoxTraduction.Text = ex.Message;
            }
        }
    }
}
  • 10. satır: çeviri hizmetine ilişkin bir referans. Bu genel özellik, [Program.cs], satır 53 tarafından başlatılmıştır. Dolayısıyla, Form1_Load (satır 20) veya buttonTraduire_Click (satır 40) yöntemleri çalıştırıldığında, bu alan zaten başlatılmış durumdadır.
  • 12. satır: ["Français-Anglais","fe"] ve c.a.d türündeki girdileri içeren çevrilmiş diller sözlüğü. Çeviri hizmeti tarafından döndürülen LanguesTraduites sözlüğünün tersi.
  • 20. satır: Form yüklendiğinde Form1_Load yöntemi çalıştırılır.
  • 22-27. satırlar: serviceTraduction.LanguesTraduites ve ["fe","Français-Anglais"] sözlükleri kullanılarak languesInversées ve ["Français-Anglais", "fe"] sözlükleri oluşturulur.
  • 29. satır: languesCombo, languesInversées ve c.a.d sözlüklerinin anahtar tablosudur. ["Français-Anglais"] öğelerinden oluşan bir tablo
  • 30. satır: Bu dizi, açılır menüde çeviri anlamlarını alfabetik sırayla göstermek üzere sıralanmıştır
  • 31-33. satırlar: Dil açılır menüsü doldurulur.
  • 40. satır: Kullanıcı [Traduire] düğmesine tıkladığında yürütülen yöntem
  • satır 46: Çeviriyi istemek için serviceTraduction.Traduire yöntemini çağırmak yeterlidir. İlk parametre çevrilecek metindir, ikincisi ise çeviri yönünün kodudur. Bu kod, dil açılır menüsünden seçilen öğeye göre languesInversées sözlüğünden bulunur.
  • satır 48: Bir istisna varsa, çevirinin yerine bu istisna görüntülenir.

11.7.3.7. Conclusion

Bu uygulama, .NET çerçevesinin web istemcileriyle web kaynaklarından yararlanabileceğimizi göstermiştir. Teknik her seferinde benzerdir:

  • sorgulanacak URI'yi belirlemek. Bu URI çoğu zaman parametrelerle belirlenir.
  • sorgulamak
  • sunucunun yanıtında düzenli ifadeler yardımıyla aradığımızı bulmak

Bu teknik belirsizdir. Nitekim zamanla, sorgulanan URI veya beklenen sonucu bulmaya yarayan düzenli ifade değişebilir. Dolayısıyla bu iki bilgiyi bir yapılandırma dosyasına kaydetmek faydalıdır. Ancak bu da yetersiz kalabilir. Bir sonraki bölümde, web üzerinde daha istikrarlı kaynakların olduğunu göreceğiz: web hizmetleri.

11.7.4. SMTP (Basit Posta Aktarım Protokolü) istemcisi ve SmtpClient sınıfı

Bir SMTP istemcisi, bir SMTP sunucusunun istemcisidir; bu sunucu, posta gönderme sunucusudur. .NET SmtpClient sınıfı, böyle bir istemcinin ihtiyaçlarını tamamen kapsar. Geliştiricinin SMTP protokolünün ayrıntılarını bilmesi gerekmez. Bu protokolü biz biliyoruz. Protokol, 11.4.3. paragrafında tanıtılmıştır.

SmtpClient sınıfını, ekli e-postalar gönderebilen temel bir Windows uygulaması kapsamında tanıtıyoruz. Uygulama, bir SMTP sunucusunun 25 numaralı bağlantı noktasına bağlanacaktır. Çoğu Windows sisteminde, güvenlik duvarları veya antivirüs programlarının 25 numaralı bağlantı noktasına yapılan bağlantıları engellediğini hatırlatmak isteriz. Bu nedenle, uygulamayı test etmek için bu korumayı devre dışı bırakmak gerekir:

SMTP istemcisi tek katmanlı bir mimariye sahip olacaktır:

Visual Studio projesi aşağıdaki gibidir:

  

Uygulamanın grafik arayüzü [SendMailForm.cs] şu şekildedir:

No.
tür
ad
rol
1
TextBox
textBoxServeur
bağlanılacak sunucunun adı: SMTP
2
NumericUpDown
numericUpDownPort
bağlanılacak bağlantı noktası
3
TextBox
textBoxExpediteur
mesajın göndereninin adresi
4
TextBox
textBoxTo
alıcı adresleri şu biçimde: adres1, adres2, ...
5
TextBox
textBoxCc
kopya alıcı adresleri (CC=Carbon Copy) şu biçimde: adres1,adres2, ...
6
TextBox
textBoxBcc
gizli kopya (Bcc) alıcı adresleri (BCC=Blind Carbon Copy) şu biçimde: adres1, adres2, ... Bu üç giriş alanındaki tüm adresler, aynı eklerle birlikte aynı mesajı alacaktır. Mesajın alıcıları, 4. ve 5. alanlarda yer alan adresleri görebilirler, ancak 6. alandakileri göremezler. Dolayısıyla Gizli Kopya (Bcc), mesajın diğer alıcılarının haberi olmadan birini kopyaya eklemenin bir yoludur.
7
Düğme
buttonAjouter
e-postaya ek eklemek için
8
ListBox
listBoxPiecesJointes
e-postaya eklenecek eklerin listesi
9
TextBox
textBoxSujet
mektubun konusu
10
TextBox
textBoxMessage
mesaj metni.
MultiLine=true
11
Düğme
buttonEnvoyer
mesajı ve varsa ekleri göndermek için
12
TextBox
textBoxRésultat
gönderilen mesajın özetini gösterir veya bir sorunla karşılaşılırsa bir hata mesajı görüntüler
13
Düğme
buttonEffacer
silmek için [12]
 
OpenfileDialog
openFileDialog1
yerel dosya sisteminden bir ek seçmeye olanak tanıyan görsel olmayan denetim

Önceki örnekte, [12]'te görüntülenen özet şöyledir:

Envoi réussi...
Sujet : votre demande
Destinataires : y2000@hotmail.com
Cc : 
Bcc : 
Pièces jointes :
C:\data\travail\2007-2008\recrutements 0809\ing3\documents\ing3.zip
Texte : Bonjour,

Vous trouverez ci-joint le dossier de candidature à l'ISTIA.

Cordialement,

ST

[SendMailForm.cs] form kodunun içeriği şöyledir:


using System;
using System.Windows.Forms;
using System.Net.Mail;
using System.Text.RegularExpressions;
using System.Text;

namespace Chap9 {
    public partial class SendMailForm : Form {
        public SendMailForm() {
            InitializeComponent();
        }

        // ek dosya ekleme
        private void buttonAjouter_Click(object sender, EventArgs e) {
            // openfileDialog1 iletişim kutusunu ayarlıyoruz
            openFileDialog1.InitialDirectory = Application.ExecutablePath;
            openFileDialog1.Filter = "Tous les fichiers (*.*)|*.*";
            openFileDialog1.FilterIndex = 0;
            openFileDialog1.FileName = "";
            // iletişim kutusu görüntülenir ve sonucu alınır
            if (openFileDialog1.ShowDialog() == DialogResult.OK) {
                // dosya adı alınır
                listBoxPiecesJointes.Items.Add(openFileDialog1.FileName);
            }
        }

        private void textBoxServeur_TextChanged(object sender, EventArgs e) {
            setStatutEnvoyer();
        }

        private void setStatutEnvoyer() {
            buttonEnvoyer.Enabled = textBoxServeur.Text.Trim() != "" && textBoxTo.Text.Trim() != "" && textBoxSujet.Text.Trim() != "";
        }

        // bir eki kaldır
        private void buttonRetirer_Click(object sender, EventArgs e) {
            // Ek seçildi mi?
            if (listBoxPiecesJointes.SelectedIndex != -1) {
                // ekli kaldırılır
                listBoxPiecesJointes.Items.RemoveAt(listBoxPiecesJointes.SelectedIndex);
                // "Kaldır" düğmesini güncelliyoruz
                buttonRetirer.Enabled = listBoxPiecesJointes.Items.Count != 0;
            }
        }

        private void listBoxPiecesJointes_SelectedIndexChanged(object sender, EventArgs e) {
            // Ek seçildi mi?
            if (listBoxPiecesJointes.SelectedIndex != -1) {
                // "Kaldır" düğmesi güncelleniyor
                buttonRetirer.Enabled = true;
            }
        }

        // mesaj ekleriyle birlikte gönderiliyor
        private void buttonEnvoyer_Click(object sender, EventArgs e) {
....
        }

        private void textBoxTo_TextChanged(object sender, EventArgs e) {
            setStatutEnvoyer();
        }

        private void textBoxSujet_TextChanged(object sender, EventArgs e) {
            setStatutEnvoyer();
        }

        private void buttonEffacer_Click(object sender, EventArgs e) {
            textBoxResultat.Text = "";
        }
    }
}

Herhangi bir yenilik içermeyen bu kod hakkında yorum yapmayacağız. 14. satırdaki buttonAjouter_Click yöntemini anlamak için okuyucunun 7.5.1. paragrafını tekrar okumasını öneririz.

  1. satırdaki, postayı gönderen buttonEnvoyer_Click yöntemi şöyledir:

private void buttonEnvoyer_Click(object sender, EventArgs e) {
            try {
                // kum saati
                Cursor = Cursors.WaitCursor;
                // SMTP istemcisi
                SmtpClient smtpClient = new SmtpClient(textBoxServeur.Text.Trim(), (int)numericUpDownPort.Value);
                // mesaj
                MailMessage message = new MailMessage();
                // gönderen
                message.Sender = new MailAddress(textBoxExpéditeur.Text.Trim());
                message.From = message.Sender;
                // alıcılar
                Regex marqueur = new Regex("\\s*,\\s*");
                string[] destinataires = marqueur.Split(textBoxTo.Text.Trim());
                foreach (string destinataire in destinataires) {
                    if (destinataire.Trim() != "") {
                        message.To.Add(new MailAddress(destinataire));
                    }
                }
                // CC
                string[] copies = marqueur.Split(textBoxCc.Text.Trim());
                foreach (string copie in copies) {
                    if (copie.Trim() != "") {
                        message.CC.Add(new MailAddress(copie));
                    }
                }
                // BCC
                string[] blindCopies = marqueur.Split(textBoxBcc.Text.Trim());
                foreach (string blindCopie in blindCopies) {
                    if (blindCopie.Trim() != "") {
                        message.Bcc.Add(new MailAddress(blindCopie));
                    }
                }
                // konu
                message.Subject = textBoxSujet.Text.Trim();
                // mesaj metni
                message.Body = textBoxMessage.Text;
                // ekler
                foreach (string attachement in listBoxPiecesJointes.Items) {
                    message.Attachments.Add(new Attachment(attachement));
                }
                // mesajın gönderilmesi
                smtpClient.Send(message);
                // Tamam - özet görüntülenir
                StringBuilder msg = new StringBuilder(String.Format("Envoi réussi...{0}", Environment.NewLine));
                msg.Append(String.Format("Sujet : {0}{1}", textBoxSujet.Text.Trim(), Environment.NewLine));
                textBoxSujet.Clear();
                msg.Append(String.Format("Destinataires : {0}{1}", textBoxTo.Text.Trim(), Environment.NewLine));
                textBoxTo.Clear();
                msg.Append(String.Format("Cc : {0}{1}", textBoxCc.Text.Trim(), Environment.NewLine));
                textBoxCc.Clear();
                msg.Append(String.Format("Bcc : {0}{1}", textBoxBcc.Text.Trim(), Environment.NewLine));
                textBoxBcc.Clear();
                msg.Append(String.Format("Pièces jointes :{0}", Environment.NewLine));
                foreach (string attachement in listBoxPiecesJointes.Items) {
                    msg.Append(String.Format("{0}{1}", attachement, Environment.NewLine));
                }
                msg.Append(String.Format("Texte : {0}{1}", textBoxMessage.Text, Environment.NewLine));
                listBoxPiecesJointes.Items.Clear();
                textBoxResultat.Text = msg.ToString();
            } catch (Exception ex) {
                // hata görüntüleniyor
                textBoxResultat.Text = String.Format("L'erreur suivante s'est produite {0}", ex);
            }
            // normal imleç
            Cursor = Cursors.Arrow;
        }
  • 6. satır: SMTP istemcisi oluşturulur. Bunun için iki parametreye ihtiyaç vardır: SMTP sunucu adı ve sunucunun çalıştığı bağlantı noktası
  • 8. satır: MailMessage türünde bir mesaj oluşturulur. Gönderilecek mesajın tamamını bu mesaj kapsayacaktır.
  • 10. satır: Gönderenin e-posta adresi (Sender) girilir. Bir e-posta adresi, "xx@yy.zz" karakter dizisinden oluşturulan MailAddress türünde bir nesnedir. Bu dizenin bir e-posta adresi için beklenen biçime sahip olması gerekir; aksi takdirde bir istisna atılır. Bu durumda, istisna textBoxResultat alanında (63. satır) okunması zor bir biçimde görüntülenir.
  • 13-19. satırlar: Alıcıların e-posta adresleri, mesajın "To" listesine eklenir. Bu adresler, textBoxTo alanından alınır. 13. satırdaki düzenli ifade, virgülle ayrılmış farklı adresleri almayı sağlar.
  • 21-26. satırlar: Aynı işlem tekrarlanarak, mesajın CC alanı, textBoxCc alanındaki "kopya" adresleriyle doldurulur.
  • 28-33. satırlar: textBoxBcc alanındaki gizli kopya adresleriyle mesajın Bcc alanını başlatmak için aynı işlem tekrarlanır.
  • 35. satır: Mesajın Subject alanı, textBoxSujet alanındaki konu ile doldurulur.
  • satır 37: Mesajın Body alanı, textBoxMessage mesajındaki metinle doldurulur.
  • 39-41. satırlar: Ekler mesaja eklenir. Her ek, mesajın Attachments alanına bir Attachment nesnesi olarak eklenir. Bir Attachment nesnesi, yerel dosya sisteminde eklenecek dosyanın tam yolundan oluşturulur.
  • 43. satır: Mesaj, Smtp istemcisinin Send yöntemi kullanılarak gönderilir.
  • 45-60. satırlar: Gönderim özetinin textBoxResultat alanına yazılması ve formun sıfırlanması.
  • 63. satır: Olası bir hatanın görüntülenmesi

11.8. Asenkron genel bir TCP istemcisi

11.8.1. Giriş

Bu bölümdeki tüm örneklerde, istemci/sunucu iletişimi, blokajlı mod olarak da adlandırılan senkron modda gerçekleştirilmiştir:

  • bir istemci bir sunucuya bağlandığında, devam etmeden önce sunucunun bu isteğe yanıt vermesini bekler.
  • Bir istemci, sunucu tarafından gönderilen bir metin satırını okuduğunda, sunucu bu satırı gönderene kadar bloke olur.
  • Sunucu tarafında, istemciye hizmet sunan hizmet iş parçacıkları da yukarıdaki gibi çalışır.

Grafik kullanıcı arayüzlerinde, uzun süren işlemler sırasında kullanıcının bloke edilmemesi genellikle gereklidir. Sıkça örnek olarak gösterilen durum, büyük bir dosyanın indirilmesidir. İndirme işlemi sırasında, kullanıcının grafik kullanıcı arayüzüyle etkileşime devam etmesine izin verilmelidir.

Burada, 11.6.3 numaralı paragrafta yer alan genel TCP istemcisini aşağıdaki değişiklikleri yaparak yeniden yazmayı öneriyoruz:

  • arayüz grafiksel olacak
  • sunucuyla iletişim aracı bir Socket nesnesi olacak
  • iletişim modu asenkron olacaktır:
    • istemci, sunucuya bir bağlantı başlatacak ancak bağlantının kurulmasını beklerken bloke kalmayacaktır
    • istemci, sunucuya veri gönderimini başlatacak, ancak gönderimin tamamlanmasını bekleyerek bloke kalmayacaktır
    • istemci, sunucudan gelen verilerin alınmasını başlatacak, ancak bu işlemin tamamlanmasını bekleyerek bloke kalmayacaktır.

TCP istemci/sunucu iletişiminde Socket nesnesinin hangi aşamada yer aldığını hatırlayalım:

Socket sınıfı, ağa en yakın şekilde çalışan sınıftır. Ağ bağlantısını hassas bir şekilde yönetmeye olanak tanır. socket terimi, bir elektrik prizini ifade eder. Bu terim, yazılım tabanlı bir ağ bağlantısını ifade etmek üzere genişletilmiştir. A ve B adlı iki makine arasındaki bir TCP-IP iletişiminde, birbirleriyle iletişim kuran iki sockets vardır. Bir uygulama, sockets'lerle doğrudan çalışabilir. Yukarıdaki A uygulaması buna bir örnektir. Bir soket, client veya serveur tipi bir soket olabilir.

11.8.2. Asenkron TCP istemcisinin grafik arayüzü

Visual Studio uygulaması şöyledir:

  

[ClientTcpAsynchrone.cs], grafik arayüzdür. Bu arayüz şu şekildedir:

No.
tür
ad
rol
1
TextBox
textBoxNomServeur
bağlanılacak TCP sunucusunun adı
2
NumericUpDown
numericUpDownPortServeur
bağlanılacak bağlantı noktası
3
RadioButton
radioButtonLF
radioButtonRCLF
İstemcinin kullanması gereken satır sonu işaretini belirtmek için: LF "\n" veya RCLF "\r\n"
4
Düğme
buttonConnexion
[1] sunucusunun [2] bağlantı noktasına bağlanmak için. İstemci bir sunucuya bağlı değilken düğmenin etiketi [Connecter], bağlıyken ise [Déconnecter] olur.
5
TextBox
textBoxMsgToServeur
bağlantı kurulduktan sonra sunucuya gönderilecek mesaj. Kullanıcı [Entrée] tuşuna bastığında, mesaj [3]'te seçilen satır sonu işaretiyle birlikte gönderilir
6
ListBox
listBoxEvts
İstemci/sunucu bağlantısının başlıca olaylarının (bağlantı, bağlantı kesme, akış kapatma, iletişim hataları) görüntülendiği liste
7
ListBox
listBoxDialogue
istemci/sunucu diyaloğundaki mesajların görüntülendiği liste
8
Düğme
buttonRazEvts
Listeyi silmek için [6]
4
Düğme
buttonRazDialogue
listeyi silmek için [7]

Bu arayüzün çalışma prensipleri şunlardır:

  • kullanıcı, [1, 2, 3, 4] aracılığıyla grafik TCP istemcisini bir TCP hizmetine bağlar.
  • Asenkron bir iş parçacığı, TCP sunucusu tarafından gönderilen tüm verileri sürekli olarak alır ve bunları [7] listesinde görüntüler. Bu iş parçacığı, arayüzün diğer faaliyetlerinden bağımsızdır.
  • Kullanıcı, [5] aracılığıyla sunucuya istediği sıklıkta mesaj gönderebilir. Her mesaj, asenkron bir iş parçacığı tarafından gönderilir. Asla durmayan alım iş parçacığının aksine, gönderim iş parçacığı mesaj gönderilir gönderilmez sona erer. Bir sonraki mesaj için yeni bir asenkron iş parçacığı kullanılır.
  • İstemci/sunucu iletişimi, taraflardan biri bağlantıyı sonlandırdığında sona erer. Kullanıcı, bağlantı kurulduktan sonra [Déconnecter] olarak adlandırılan [4] düğmesini kullanarak bu işlemi başlatabilir.

İşte bir çalıştırma ekran görüntüsü:

  • [1]'te: POP hizmetine bağlantı
  • [2]: bağlantı sırasında meydana gelen olayların görüntülenmesi
  • [3]: Bağlantı tamamlandıktan sonra POP sunucusu tarafından gönderilen mesaj
  • [4]: [Connecter] düğmesi, [Déconnecter] düğmesine dönüştü
  • [1]'te, quit komutu POP sunucusuna gönderildi. Sunucu +OK goodbye yanıtını verdi ve bağlantıyı kapattı
  • [2]'te, sunucu tarafındaki bu bağlantı kesintisi algılandı. Bunun üzerine istemci de kendi tarafında bağlantıyı kapattı.
  • [3]'te, [Déconnecter] düğmesi yeniden [Connecter] düğmesine dönüştü

11.8.3. Sunucuya asenkron bağlantı

[Connecter] düğmesine basıldığında aşağıdaki yöntem çalıştırılır:


        private void buttonConnexion_Click(object sender, EventArgs e) {
            // oturum açma veya kapatma?
            if (buttonConnexion.Text == "Déconnecter")
                déconnexion();
            else
                connexion();
}
  • 3. satır: Düğmenin etiketi [Connecter] veya [Déconnecter] olabilir.

Bağlantı yöntemi şöyledir:


using System.Net.Sockets;
...

namespace Chap9 {
    public partial class ClientTcp : Form {
        const int tailleBuffer = 1024;
        private Socket client = null;
        private byte[] data = new byte[tailleBuffer];
        private string réponse = null;
        private string finLigne = "\r\n";

        // yetkililer
        public delegate void writeLog(string log);

        public ClientTcp() {
            InitializeComponent();
        }
....................................
    private void connexion() {
            // veri doğrulamaları
            string nomServeur = textBoxNomServeur.Text.Trim();
            if (nomServeur == "") {
                logEvent("indiquez le nom du serveur");
                return;
            }
            // izleme
            logEvent(String.Format("connexion en cours au serveur {0}", nomServeur));
            try {
                 // soket oluşturma
                client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                // asenkron bağlantı
                client.BeginConnect(Dns.GetHostEntry(nomServeur).AddressList[0],(int)numericUpDownPortServeur.Value, connecté, client);

            } catch (Exception ex) {
                logEvent(String.Format("erreur de connexion : {0}", ex.Message));
                return;
            }
        }

         // bağlantı kuruldu
        private void connecté(IAsyncResult résultat) {
            // istemciden soket alınır
            Socket client = résultat.AsyncState as Socket;
    ...
        }


        // işlemin takibi
        private void logEvent(string msg) {
....
        }
    }
}
  • 1. satır: Socket sınıfı, System.Net.Sockets ad alanının bir parçasıdır.

Formdaki birkaç yöntem arasında paylaşılması gereken bazı veriler vardır. Bunlar şunlardır:

  • 7. satır: client, sunucu ile iletişim kuran sokettir
  • 6. ve 8. satırlar: İstemci, mesajlarını data adlı bir bayt dizisinde alacaktır.
  • 9. satır: "response", sunucu tarafından gönderilen yanıttır.
  • 10. satır: finLigne, TCP istemcisi tarafından kullanılan satır sonu işaretidir; varsayılan olarak RCLF olarak başlatılır, ancak kullanıcı tarafından [3] radyo düğmeleriyle değiştirilebilir.
  1. satırdaki connexion prosedürü, TCP sunucusuna bağlantıyı kurar:
  • 21-25. satırlar: Sunucu adının boş olmadığından emin olunur. Aksi takdirde, olay 49. satırdaki logEvent yöntemi ile listBoxEvts'e kaydedilir.
  • 27. satır: Bağlantının kurulacağı bildirilir
  • 30. satır: TCP-IP iletişimi için gerekli Socket nesnesi oluşturulur. Oluşturucu üç parametre kabul eder:
    • AddressFamily addressFamily: İstemci ve sunucunun IP adres ailesi; burada IPv4 (AddressFamily.InterNetwork) adresleri
    • SocketType socketType: soket türü. SocketType.Stream türü, TCP/IP bağlantıları için uygundur
    • ProtocolType protocolType: Kullanılan internet protokolü türü; burada TCP protokolü
  • 32. satır: Bağlantı asenkron olarak kurulur. Bağlantı başlatılır, ancak işlem bunun tamamlanmasını beklemeden devam eder. [Socket].BeginConnect yöntemi dört parametre kabul eder:
    • IPAddress ipAddress: Bağlanılması gereken hizmetin çalıştığı makinenin IP adresi
    • Int32 port: hizmetin bağlantı noktası
    • AsyncCallBack asyncCallBack: AsyncCallBack bir delege türüdür:
public void AsyncCallBack(IAsyncResult ar);

BeginConnect yönteminin 3. parametresi olarak geçirilen asyncCallBack yöntemi, IAsyncCallBack türünü kabul eden ve hiçbir sonuç döndürmeyen bir yöntem olmalıdır. Bağlantı kurulduğunda çağrılacak olan yöntem budur. Burada 3. parametre olarak 41. satırdaki connecté yöntemini aktarıyoruz.

  • (devamı)
    • state nesnesi: asyncCallBack yöntemine aktarılacak bir nesne. Bu yöntem (yukarıdaki delegeye bakınız), IAsyncResult türünde bir ar parametresi alır. state nesnesi, ar.AsyncState'ten (43. satır) alınabilir. Burada 4. parametre olarak istemcinin soketini aktarıyoruz.
  • 38. satır: Yöntem sona ermiştir. Kullanıcı, grafik kullanıcı arayüzüyle yeniden etkileşime girebilir. Bağlantı, grafik kullanıcı arayüzündeki olayların yönetilmesiyle paralel olarak arka planda gerçekleşir. Yine paralel olarak, bağlantının sonunda, bağlantının başarılı ya da başarısız sonuçlanmasına bakılmaksızın, 41. satırdaki connecté yöntemi çağrılacaktır.

connecté yönteminin kodu şöyledir:


// bağlantı kuruldu
        private void connecté(IAsyncResult résultat) {
            // istemcinin soketi alınıyor
            Socket client = résultat.AsyncState as Socket;
            try {
                // asenkron işlem sonlandırılıyor
                client.EndConnect(résultat);
                // izleme
                logEvent(String.Format("connecté au service {0}", client.RemoteEndPoint));
                // form
                buttonConnexion.Text = "Déconnecter";
                // sunucudan gelen verilerin asenkron okunması
                réponse = "";
                client.BeginReceive(data, 0, tailleBuffer, SocketFlags.None, lecture, client);
            } catch (SocketException e) {
                logEvent(String.Format("erreur de connexion : {0}", e.Message));
                return;
            }
}

        // veri alımı
        private void lecture(IAsyncResult résultat) {
            // istemcinin soketini alıyoruz
            Socket client = résultat.AsyncState as Socket;
...
        }

  • 4. satır: İstemcinin soketi, yöntem tarafından alınan résultat parametresinden alınır. Bu nesnenin, BeginConnect yöntemine 4. parametre olarak geçirilen nesne olduğunu hatırlatırız.
  • 7. satır: Bağlantı denemesi, yöntemin aldığı résultat parametresinin kendisine aktarılması gereken EndConnect yöntemi ile sonlandırılır.
  • 9. satır: Olay, olay listesine kaydedilir
  • 11. satır: Kullanıcının bağlantıyı kesmesini talep edebilmesi için [Connecter] düğmesi, [Déconnecter] düğmesine dönüşür.
  • 13. satır: Sunucu yanıtı başlatılır. Bu yanıt, BeginReceive asenkron yöntemine yapılan tekrarlı çağrılarla güncellenecektir.
  • 14. satır: BeginReceive asenkron yöntemine ilk çağrı yapılır. Bu yöntem aşağıdaki parametrelerle çağrılır:
    • byte[] buffer: Alınacak verilerin yerleştirileceği tampon - burada tampon data'tir
    • int offset: Alınacak verilerin tamponun hangi konumundan itibaren yerleştirileceği - burada ofset 0'dır, c.a.d. Veriler, tamponun ilk baytından itibaren yerleştirilir.
    • int size: tamponun bayt cinsinden boyutu - burada boyut tailleBuffer'tir.
    • SocketFlags socketFlags: soket yapılandırması – burada herhangi bir yapılandırma yoktur
    • AsyncCallBack asyncCallBack: Alım işlemi tamamlandığında çağrılacak geri çağırma yöntemi. Bu durum, ya tamponun veri alması ya da bağlantının kapatılması nedeniyle gerçekleşir. Burada geri çağırma yöntemi, 22. satırdaki lecture yöntemidir.
    • state nesnesi: asyncCallBack geri çağırma yöntemine aktarılacak nesne. Burada, yine istemcinin soketi aktarılır.

Tüm bunların, [Connecter] düğmesiyle yapılan ilk bağlantı isteği dışında, kullanıcı tarafından herhangi bir işlem yapılmasına gerek kalmadan gerçekleştiği unutulmamalıdır. connecté yönteminin sonunda, arka planda başka bir yöntem yürütülür: şimdi inceleyeceğimiz lecture yöntemi.


// verilerin alınması
        private void lecture(IAsyncResult résultat) {
            // istemcinin soketini alıyoruz
            Socket client = résultat.AsyncState as Socket;
            int nbOctetsReçus = 0;
            bool erreur = false;
            try {
                // alınan bayt sayısı
                nbOctetsReçus = client.EndReceive(résultat);
                if (nbOctetsReçus == 0) {
                    // sunucu artık yanıt vermiyor
                    logEvent("le serveur a fermé la connexion");
                }
            } catch (Exception e) {
                // alımda bir sorun yaşandı
                logEvent(String.Format("erreur de réception : {0}", e.Message));
                erreur = true;
            }
            // bitti mi?
            if (nbOctetsReçus == 0 || erreur) {
                // gerekirse istemcinin bağlantısı kesilir
                déconnexion();
                // yanıtın sonu görüntüleniyor
                afficherRéponseServeur(réponse, true);
                // okuma bitti
                return;
            }
            // Alınan verileri toplanır
            string données = Encoding.UTF8.GetString(data, 0, nbOctetsReçus);
            // bunlar daha önce alınan verilere eklenir
            réponse += données;
            // yanıt görüntülenir
            afficherRéponseServeur(réponse, false);
            // okumaya devam ediliyor
            client.BeginReceive(data, 0, tailleBuffer, SocketFlags.None, lecture, client);
        }
  • 2. satır: lecture yöntemi, data arabelleği veri aldığında veya bağlantı sunucu tarafından kapatıldığında arka planda tetiklenir.
  • 9. satır: Asenkron okuma isteği, EndReceive tarafından sonlandırılır. Burada da bu yöntem, geri çağırma işlevi tarafından alınan parametre ile çağrılmalıdır. EndReceive yöntemi, okuma tamponuna alınan bayt sayısını döndürür.
  • 10. satır: Bayt sayısı sıfırsa, bağlantı sunucu tarafından kapatılmış demektir.
  • 12. satır: Olay, olay listesine kaydedilir
  • 14. satır: Olası bir istisna işlenir
  • satır 16-17: olay, olay listesine kaydedilir ve hata not edilir
  • 20. satır: Bağlantının kapatılması gerekip gerekmediğine bakılır
  • 22. satır: Daha sonra ele alacağımız déconnexion yöntemi ile istemci tarafında bağlantı kapatılır.
  • 24. satır: Sunucudan gelen yanıt, c.a.d. réponse küresel değişkeni, afficherRéponseServeur özel yöntemi aracılığıyla listBoxDialogue diyalog listesinde görüntülenir.
  • 26. satır: Asenkron yöntem lecture'in sonu
  • 29. satır: Alınan baytlar, UTF8 biçiminde bir karakter dizisine yerleştirilir.
  • satır 31: Bu baytlar, oluşturulmakta olan yanıta eklenir
  • 33. satır: Yanıt, listBoxDialogue listesinde görüntülenir.
  • satır 35: sunucudan gelecek verileri beklemeye devam edilir

Sonuç olarak, lecture asenkron yöntemi asla durmaz. Sürekli olarak sunucudan gelen verileri okur ve bunları listBoxDialogue listesinde görüntüler. Yalnızca bağlantı, sunucu tarafından ya da kullanıcının kendisi tarafından kapatıldığında durur.

11.8.4. Sunucudan bağlantı kesme

[Déconnecter] düğmesine basıldığında aşağıdaki yöntem çalıştırılır:


        private void buttonConnexion_Click(object sender, EventArgs e) {
            // bağlanmak mı, bağlantıyı kesmek mi?
            if (buttonConnexion.Text == "Déconnecter")
                déconnexion();
            else
                connexion();
}
  • 3. satır: Düğmenin etiketi [Connecter] veya [Déconnecter] olabilir.

déconnexion yöntemi, istemcinin bağlantısını keser:


private void déconnexion() {
            // soketi kapat
            if (client != null && client.Connected) {
                try {
                    // izleme
                    logEvent(String.Format("déconnexion du service {0}", client.RemoteEndPoint));
                    // bağlantı kesme
                    client.Shutdown(SocketShutdown.Both);
                    client.Close();
                    // form
                    buttonConnexion.Text = "Connecter";
                } catch (Exception ex) {
                    // takip
                    logEvent(String.Format("erreur de lors de la déconnexion : {0}", ex.Message));
                }
            }
        }
  • 3. satır: İstemci mevcutsa ve bağlıysa
  • 6. satır: listBoxEvts ile bağlantı kesildiğini bildirir. client.RemoteEndPoint özelliği, bağlantının diğer ucundaki (IP adresi, bağlantı noktası) çifti verir; burada c.a.d, sunucuyu gösterir.
  • 8. satır: Soketin veri akışı, ShutDown yöntemi ile kapatılır. Bir soketin veri akışı çift yönlüdür: soket veri gönderir ve alır. ShutDown yönteminin parametresi şu şekilde olabilir: Alım akışını kapatmak için ShutDown.Receive, gönderim akışını kapatmak için Shutdonw.Send veya her iki akışı da kapatmak için ShutDown.Both.
  • 9. satır: soketle ilişkili kaynaklar serbest bırakılır
  • 11. satır: [Déconnecter] düğmesi, [Connecter] düğmesine dönüşür
  • 12-15. satırlar: Olası bir istisnanın yönetimi

11.8.5. Sunucuya asenkron veri gönderimi

Kullanıcı textBoxMsgToServeur alanındaki mesajı onayladığında, aşağıdaki yöntem yürütülür:


        private void textBoxMsgToServeur_KeyPress(object sender, KeyPressEventArgs e) {
            // tuş [Entrée] ?
            if (e.KeyChar == 13 && client.Connected) {
                envoyerMessage();
            }
}
  • 3-5. satırlar: Kullanıcı [Entrée] tuşuna basmışsa ve istemci soketi bağlıysa, textBoxMsgToServeur alanındaki mesaj envoyerMessage yöntemi ile gönderilir.

envoyerMessage yöntemi şöyledir:


        private void envoyerMessage() {
            // asenkron olarak mesaj gönderme
            // mesaj
            byte[] message = Encoding.UTF8.GetBytes(textBoxMsgToServeur.Text.Trim() + finLigne);
            // mesaj gönderildi
            client.BeginSend(message, 0, message.Length, SocketFlags.None, écriture, client);
            // diyalog
            logDialogue("--> " + textBoxMsgToServeur.Text.Trim());
            // mesajı sıfırla
            textBoxMsgToServeur.Clear();
}
  • 4. satır: Mesaja müşterinin satır sonu işaretini ekleyip message bayt tablosuna yerleştirilir.
  • 6. satır: BeginSend yöntemi ile asenkron bir aktarım başlatıldı. BeginSend yönteminin parametreleri, BeginReceive yönteminkilerle aynıdır. Mesajın asenkron gönderim işlemi tamamlandığında, écriture yöntemi çağrılacaktır.
  • 8. satır: Gönderilen mesaj, istemci/sunucu diyaloğunun takibi için listBoxDialogue listesine eklenir
  • 10. satır: Gönderilen mesaj, grafik arayüzden silinir

écriture geri çağırma yöntemi şu şekildedir:


        private void écriture(IAsyncResult résultat) {
            // mesaj gönderiminin sonucu
            Socket client = résultat.AsyncState as Socket;
            try {
                client.EndSend(résultat);
            } catch (Exception e) {
                // gönderim sırasında bir sorun yaşandı
                logEvent(String.Format("erreur d'émission : {0}", e.Message));
            }
}
  • 4. satır: écriture geri çağırma yöntemi, IAsyncResult türünde bir sonuç parametresi alır.
  • 3. satır: résultat parametresinde, istemcinin soketi alınır. Bu soket, BeginSend yönteminin 5. parametresiydi.
  • 5. satır: Asenkron gönderim işlemi sonlandırılır.

Bir mesajın gönderilmesinin tamamlanmasını beklemeden kontrol kullanıcıya geri verilir. Böylece kullanıcı, ilk mesajın gönderimi tamamlanmamışken ikinci bir mesaj gönderebilir.

11.8.6. Olayların ve istemci/sunucu diyaloğunun görüntülenmesi

Olaylar, logEvents yöntemi ile görüntülenir:


        // işlemin takibi
        private void logEvent(string msg) {
            listBoxEvts.Invoke(new writeLog(logEventCallBack), msg);
        }

        private void logEventCallBack(string msg) {
            // mesaj görüntüleme
            msg = msg.Replace(finLigne, " ");
            listBoxEvts.Items.Insert(0, String.Format("{0:hh:mm:ss} : {1}", DateTime.Now, msg));
}
  • 2. satır: logEvents yöntemi, listBoxEvts listesine eklenecek mesajı parametre olarak alır.
  • 3. satır: listBoxEvents bileşenini doğrudan kullanamazsınız. Çünkü logEvents yöntemi iki tür iş parçacığı tarafından çağrılır:
    • grafik arayüzün sahibi olan ana iş parçacığı; örneğin, bir bağlantı denemesi yapıldığını bildirdiğinde
    • asenkron bir işlemi gerçekleştiren ikincil bir iş parçacığı. Bu tür iş parçacıkları bileşenlerin sahibi değildir ve bir C bileşenine erişimi, C.Invoke işlemi ile kontrol edilmelidir. Bu işlem, C denetimine bir iş parçacığının üzerinde işlem yapmak istediğini bildirir. Invoke yöntemi iki parametre kabul eder:
      • delegate türünde bir geri çağırma işlevi. Bu geri çağırma işlevi, C.Invoke yöntemini çalıştıran iş parçacığı tarafından değil, grafik arayüzün sahibi olan iş parçacığı tarafından yürütülecektir.
      • geri çağırma işlevine aktarılacak bir nesne.

Burada, Invoke yöntemine geçirilen ilk parametre, aşağıdaki delege nesnesinin bir örneğidir:


        public delegate void writeLog(string log);

writeLog delege, string türünde bir parametreye sahiptir ve herhangi bir sonuç döndürmez. Bu parametre, listBoxEvts'e kaydedilecek mesaj olacaktır.

  1. satırda, Invoke yöntemine geçirilen ilk parametre, 6. satırdaki logEventCallBack yöntemidir. Bu, writeLog delege imzasına tam olarak uymaktadır. Invoke yöntemine geçirilen ikinci parametre, logEventCallBack yöntemine parametre olarak geçirilecek mesajdır.

Invoke işlemi senkron bir işlemdir. Kontrolün sahibi olan iş parçacığı geri çağırma yöntemini çalıştırana kadar ikincil iş parçacığının yürütülmesi engellenir.

  • 6. satır: Grafik kullanıcı arayüzü iş parçacığı tarafından yürütülen geri çağırma yöntemi, listBoxEvts denetiminde görüntülenecek mesajı alır.
  • 9. satır: En son olayların listenin en üstünde yer alması için olay, listenin 1. sırasına kaydedilir.

İstemci/sunucu diyalog mesajları, logDialogue yöntemi ile görüntülenir:


        // diyalog takibi
        private void logDialogue(string msg) {
            listBoxDialogue.Invoke(new writeLog(logDialogueCallBack), msg);
        }
        private void logDialogueCallBack(string msg) {
            // mesajın görüntülenmesi
            msg = msg.Replace(finLigne, " ");
            listBoxDialogue.Items.Add(String.Format("{0:hh:mm:ss} : {1}", DateTime.Now, msg));
}

Prensip, logEvent yöntemindekiyle aynıdır.

İstemci tarafından alınan mesajlar, afficherRéponseServeur yöntemi ile görüntülenir:


        private void afficherRéponseServeur(String msg, bool dernièreLigne) {
...
}

İlk parametre, görüntülenecek mesajdır. Bu mesaj bir dizi satırdan oluşabilir. Nitekim istemci, sunucudan gelen verileri tailleBuffer (1024) baytlık bloklar halinde okur. Bu 1024 bayt içinde, “\n” satır sonu işaretiyle tanınan çeşitli satırlar bulunabilir. Son satır eksik olabilir; satır sonu işareti, onu takip eden 1024 bayt içinde yer alabilir. Yöntem, mesajda "\n" ile biten satırları bulur ve ardından logDialogue'ten bunları görüntülemesini ister. Yöntemin ikinci parametresi, bulunan son satırın görüntülenip görüntülenmeyeceğini veya bir sonraki mesajla tamamlanmak üzere tampon içinde bırakılıp bırakılmayacağını belirtir. Kod oldukça karmaşıktır ve burada ele alınmasına gerek yoktur. Bu nedenle kod üzerinde yorum yapılmayacaktır.

11.8.7. Sonuç

Aynı örnek senkron işlemlerle de ele alınabilirdi. Burada grafik arayüzün asenkron özelliği kullanıcıya pek bir fayda sağlamaz. Bununla birlikte, kullanıcı bağlandıktan sonra sunucunun "artık yanıt vermediğini" fark ederse, asenkron işlemler yürütülürken grafik arayüzün olaylara yanıt vermeye devam etmesi sayesinde bağlantısını kesme imkânına sahiptir. Bu oldukça karmaşık örnek, bize yeni kavramları tanıtma fırsatı verdi:

  • soketlerin kullanımı
  • asenkron yöntemlerin kullanımı. Burada ele alınanlar bir standardın parçasıdır. Aynı model üzerinde çalışan başka asenkron yöntemler de mevcuttur.
  • ikincil iş parçacıklarıyla bir grafik arayüzün denetimlerinin güncellenmesi.

Asenkron TCP/IP iletişimi, bir sunucu için önceki örnekte gösterilenlerden daha önemli avantajlar sunar. Sunucunun, ikincil iş parçacıkları yardımıyla istemcilere hizmet verdiği bilinmektedir. İş parçacığı havuzunda N adet iş parçacığı varsa, bu, sunucunun aynı anda yalnızca N istemciye hizmet verebileceği anlamına gelir. Eğer N iş parçacığının tümü engelleyici (senkron) bir işlem yapıyorsa, engelleyici işlemlerden biri tamamlanıp bir iş parçacığını serbest bırakana kadar yeni bir müşteri için kullanılabilir iş parçacığı kalmaz. İş parçacıklarında senkron işlemler yerine asenkron işlemler yapılırsa, hiçbir iş parçacığı asla engellenmez ve yeni müşteriler için hızla yeniden kullanılabilir.

11.9. Örnek uygulama, sürüm 8: Vergi hesaplama sunucusu

11.9.1. Yeni sürümün mimarisi

Daha önce çeşitli şekillerde ele aldığımız vergi hesaplama uygulamasını tekrar ele alıyoruz. 9.8. paragrafındaki sürüm 7'deki son halini hatırlayalım.

Veriler bir veritabanında bulunuyordu ve [ui] katmanı bir grafik arayüzdü:

 

Bu mimariyi yeniden ele alıp iki makineye dağıtacağız:

  • [serveur] adlı makine, sürüm 7'ye ait [metier] ve [dao] katmanlarını barındıracak. İnternet kullanıcılarının vergi hesaplama hizmetine erişebilmelerini sağlamak amacıyla bir TCP/IP katmanı ([serveur] ve [1]) oluşturulacaktır.
  • Bir [client] makinesi, sürüm 7'nin [ui] katmanını barındıracaktır. [client] [2] TCP/IP katmanı, [ui] katmanının vergi hesaplama hizmetine sorgu gönderebilmesini sağlamak amacıyla oluşturulacaktır.

Burada mimari köklü bir değişiklik geçiriyor. Sürüm 7, tek kullanıcılı bir Windows uygulamasıydı. Sürüm 8 ise internet tabanlı bir istemci/sunucu uygulaması haline geliyor. Sunucu, aynı anda birden fazla istemciye hizmet verebilecek.

Öncelikle uygulamanın [serveur] kısmını yazacağız.

11.9.2. Vergi hesaplama sunucusu

11.9.2.1. Visual Studio projesi

Visual Studio projesi şu şekilde olacaktır:

  • [1] adlı proje. Bu projede şu öğeler bulunur:
  • [ServeurImpot.cs]: Konsol uygulaması biçiminde vergi hesaplama TCP/IP sunucusu.
  • [dbimpots.sdf]: 9.8.5. paragrafında açıklanan sürüm 7'ye ait SQL Server Compact veritabanı.
  • [App.config]: Uygulamanın yapılandırma dosyası.
  • [2] içinde, [lib] klasörü proje için gerekli olan DLL dosyalarını içerir:
    • [ImpotsV7-dao]: Sürüm 7'nin [dao] katmanı
    • [ImpotsV7-metier]: Sürüm 7'nin [metier] katmanı
    • [antlr.runtime, CommonLogging, Spring.Core] (Spring için)
  • [3]'te, proje referansları

11.9.2.2. Uygulama yapılandırması

[App.config] dosyası Spring tarafından kullanılır. İçeriği şöyledir:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
                <constructor-arg index="0" value="System.Data.SqlServerCe.3.5"/>
                <constructor-arg index="1" value="Data Source=|DataDirectory|\dbimpots.sdf;" />
                <constructor-arg index="2" value="select data1, data2, data3 from data"/>
            </object>
            <object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
                <constructor-arg index="0" ref="dao"/>
            </object>
        </objects>
    </spring>
</configuration>
  • 16-20. satırlar: [dao] tabanına ilişkin [dao] katmanının yapılandırması, SQL Server compact
  • 21-23. satırlar: [metier] katmanının yapılandırması.

Bu, sürüm 7'deki [ui] katmanında kullanılan yapılandırma dosyasıdır. Bu dosya, 9.8.4. paragrafında tanıtılmıştır.

11.9.2.3. Sunucunun Çalışması

Sunucu başlatıldığında, sunucu uygulaması [metier] ve [dao] katmanlarını başlatır ve ardından bir yönetim konsolu arayüzü görüntüler:

  

Yönetim konsolu aşağıdaki komutları kabul eder:

start port
belirli bir bağlantı noktasında hizmeti başlatmak için
stop
hizmeti durdurmak için. Daha sonra aynı bağlantı noktasında veya başka bir bağlantı noktasında yeniden başlatılabilir.
echo start
konsolda istemci/sunucu diyaloğunun yankısını etkinleştirmek için
echo stop
yankıyı devre dışı bırakmak için
status
hizmetin etkin/etkin olmayan durumunu görüntülemek için
quit
uygulamadan çıkmak için

Sunucuyu başlatalım:

1
2
3
Serveur de calcul d'impôt >start 27
Serveur de calcul d'impôt lancé sur le port 27
Serveur de calcul d'impôt >

Şimdi, 11.8. paragrafta daha önce incelediğimiz asenkron grafiksel TCP istemcisini çalıştıralım.

Image

İstemci bağlandı. Vergi hesaplama sunucusuna aşağıdaki komutları gönderebilir:

aide
izin verilen komutların listesini almak için
impot marié nbEnfants salaireAnnuel
nbEnfants çocuğu olan ve salaireAnnuel avro maaşı alan bir kişinin vergisini hesaplamak için. marié, kişi evliyse o, değilse n değerindedir.
aurevoir
sunucuyla bağlantıyı sonlandırmak için

İşte bir diyalog örneği:

Sunucu tarafında, konsolda şunlar görüntülenir:

1
2
3
4
Serveur de calcul d'impôt >start 27
Serveur de calcul d'impôt >Serveur de calcul d'impôt lancé sur le port 27
Début du service au client 0
Fin du service au client 0

Echo özelliğini etkinleştirip grafik istemciden yeni bir diyalog başlatalım:

 

Yönetim konsolu şu mesajı görüntüler:

1
2
3
4
5
6
7
echo start
Serveur de calcul d'impôt >Début du service au client 1
<--- Client 1 : aide
---> Client 1 : Commandes acceptées
1-aide
2-impot marié(O/N) nbEnfants salaireAnnuel
3-aurevoir
  • 1. satır: istemci/sunucu diyaloğunun yankısı etkinleştirildi
  • 2. satır: bir istemci geldi
  • 3. satır: [aide] komutunu gönderdi
  • satır 4-7: 4 satırlık sunucu yanıtı.

Hizmeti durduralım:

1
2
3
stop
L'erreur suivante s'est produite sur le serveur : Une opération de blocage a été interrompue par un appel à WSACancelBlockingCall
Serveur de calcul d'impôt >
  • satır 1: hizmetin durdurulması isteniyor (uygulamanın kendisinin değil)
  • 2. satır: Sunucu, bir istemci beklemesi sırasında kilitlenmişken, dinleme hizmetinin kapatılması nedeniyle aniden kesintiye uğradığı için bir istisna oluştu.
  • 3. satır: Hizmet artık start port komutuyla yeniden başlatılabilir veya quit komutuyla durdurulabilir.

Dinleme hizmeti durdurulmadan önce, başka bir bağlantı üzerinden bir istemciye hizmet veriliyordu. Bu bağlantı, dinleme soketinin kapatılmasıyla sonlandırılmaz. İstemci komut göndermeye devam edebilir: dinleme hizmeti kapatılmadan önce ona atanmış olan hizmet iş parçacığı, istemciye yanıt vermeye devam eder:

Image

11.9.3. Vergi hesaplama TCP sunucusunun kodu

1
  

[ServeurImpot.cs] sunucusunun kodu şöyledir:


...
namespace Chap9 {
    public class ServeurImpot {

        // iş parçacıkları ve yöntemler arasında paylaşılan veriler
        private static IImpotMetier metier = null;
        private static int port;
        private static TcpListener service;
        private static bool actif = false;
        private static bool echo = false;

        // ana program
        public static void Main(string[] args) {
            // [metier] ve [dao] katmanlarının örneklenmesi
            IApplicationContext ctx = null;
            metier = null;
            try {
                // Spring bağlamı
                ctx = ContextRegistry.GetContext();
                // [metier] katmanında bir referans isteniyor
                metier = (IImpotMetier)ctx.GetObject("metier");

                // iş parçacığı havuzu yapılandırması
                ThreadPool.SetMinThreads(10, 10);
                ThreadPool.SetMaxThreads(10, 10);

                // klavyeden girilen sunucu yönetim komutlarını sonsuz bir döngü içinde okur
                string commande = null;
                string[] champs = null;
                while (true) {
                    // komut istemi
                    Console.Write("Serveur de calcul d'impôt >");
                    // komut okuma
                    commande = Console.ReadLine().Trim().ToLower();
                    champs = Regex.Split(commande, @"\s+");
                    // komut çalıştırma
                    switch (champs[0]) {
                        case "start":
                            // aktif mi?
                            if (actif) {
                                //hata
                                Console.WriteLine("Le serveur est déjà actif");
                            } else {
                                // bağlantı noktası kontrolü
                                if (champs.Length != 2 || !int.TryParse(champs[1], out port) || port <= 0) {
                                    Console.WriteLine("Syntaxe : start port. Port incorrect");
                                } else {
                                    // dinleme hizmeti başlatılıyor
                                    ThreadPool.QueueUserWorkItem(doEcoute, null);
                                }
                            }
                            break;
                        case "echo":
                            // echo başlatma / durdurma
                            if (champs.Length != 2 || (champs[1] != "start" && champs[1] != "stop")) {
                                Console.WriteLine("Syntaxe : echo start / stop");
                            } else {
                                echo = champs[1] == "start";
                            }
                            break;
                        case "stop":
                            // hizmet sonu
                            if (actif) {
                                service.Stop();
                                actif = false;
                            }
                            break;
                        case "status":
                            // sunucu durumu
                            if (actif) {
                                Console.WriteLine("Le service est lancé sur le port {0}", port);
                            } else {
                                Console.WriteLine("Le service n'est pas lancé}");
                            }
                            break;
                        case "quit":
                            // uygulamadan çıkılıyor
                            Console.WriteLine("Fin du service");
                            Environment.Exit(0);
                            break;
                        default:
                            // hatalı komut
                            Console.WriteLine("Commande incorrecte. Utilisez (start,stop,echo, status, quit)");
                            break;
                    }
                }
            } catch (Exception e1) {
                // istisna görüntüleme
                Console.WriteLine("L'erreur suivante s'est produite à l'initialisation de l'application : {0}", e1.Message);
                return;
            }
        }


        private static void doEcoute(Object data) {
...
        }

....
    }
}
  • 18-21. satırlar: [metier] ve [dao] katmanları, [App.config] ile yapılandırılan Spring tarafından örneklenir. Ardından, 6. satırdaki metier küresel değişkeni başlatılır.
  • 24-25. satırlar: Uygulamanın iş parçacığı havuzu, minimum ve maksimum 10 iş parçacığı olacak şekilde yapılandırılır.
  • 30-86. satırlar: Hizmetin yönetim komutlarının (start, stop, quit, echo, status) girilmesi döngüsü.
  • 32. satır: Her yeni komut için sunucu istemidir
  • 34. satır: Yönetici komutunun okunması
  • satır 35: Komut, analiz edilebilmesi için alanlara bölünür
  • 38-52. satırlar: dinleme hizmetini başlatmayı amaçlayan start port komutu
    • satır 40: hizmet zaten aktifse, yapılacak bir şey yoktur
    • satır 45: bağlantı noktasının mevcut ve doğru olup olmadığı kontrol edilir. Eğer öyleyse, 7. satırdaki port küresel değişkeni ayarlanır.
    • 49. satır: Dinleme hizmeti, ana iş parçacığının konsol komutlarını yürütmeye devam edebilmesi için ikincil bir iş parçacığı tarafından yönetilecektir. doEcoute yöntemi bağlantıyı başarıyla kurarsa, 8. satırdaki service ve 9. satırdaki actif küresel değişkenleri başlatılır.
  • 53-60. satırlar: Konsolda istemci/sunucu diyaloğunun yankısını etkinleştiren/devre dışı bırakan echo start / stop komutu
    • 58. satır: 7. satırdaki echo küresel değişkeni ayarlanır
  • 61-67. satırlar: Dinleme hizmetini durduran stop komutu.
    • 64. satır: Dinleme hizmetinin durdurulması
  • 68-75. satırlar: hizmetin etkin/etkin olmayan durumunu görüntüleyen status komutu
  • satır 76-80: Her şeyi durduran quit komutu.

İstemcilerin isteklerini dinlemekle görevli iş parçacığı, aşağıdaki doEcoute yöntemini yürütür:


        private static void doEcoute(Object data) {
            // istemci isteklerini dinleyen iş parçacığı
            try {
                // hizmet oluşturuluyor
                service = new TcpListener(IPAddress.Any, port);
                // hizmet başlatılıyor
                service.Start();
                // sunucu aktif
                actif = true;
                // izleme
                Console.WriteLine("Serveur de calcul d'impôt lancé sur le port {0}", port);
                // müşterilere hizmet döngüsü
                TcpClient tcpClient = null;
                // müşteri numarası
                int numClient = 0;
                // sonsuz döngü
                while (true) {
                    // müşteri bekliyor
                    tcpClient = service.AcceptTcpClient();
                    // hizmet başka bir görev tarafından sağlanıyor
                    ThreadPool.QueueUserWorkItem(doService, new Client() { CanalTcp = tcpClient, NumClient = numClient });
                    // sonraki müşteri
                    numClient++;
                }
            } catch (Exception ex) {
                // hata bildiriliyor
                Console.WriteLine("L'erreur suivante s'est produite sur le serveur : {0}", ex.Message);
            }
        }

        // müşteri bilgileri
        internal class Client {
            public TcpClient CanalTcp { get; set; }        // müşteri ile bağlantı
            public int NumClient { get; set; }            // müşteri numarası
}

Burada, 11.6.1 numaralı paragrafta incelenen yankı sunucusundaki koda benzer bir kod bulunmaktadır. Yalnızca farklı olan kısımları açıklayacağız:

  • 7. satır: Dinleme hizmeti başlatıldı
  • 9. satır: Hizmetin artık aktif olduğu belirtiliyor
  1. satır: Müşteriler, aşağıdaki doService yöntemini çalıştıran hizmet iş parçacıkları tarafından hizmet almaktadır:

private static void doService(Object infos) {
            // hizmet verilecek müşteri alınır
            Client client = infos as Client;
            // müşteriye hizmet sunulur
            Console.WriteLine("Début du service au client {0}", client.NumClient);
            // bağlantı işleme TcpClient
            try {
                using (TcpClient tcpClient = client.CanalTcp) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // tamponlanmamış çıkış akışı
                                writer.AutoFlush = true;
                                // müşteriye bir hoş geldiniz mesajı gönderilir
                                writer.WriteLine("Bienvenue sur le serveur de calcul de l'impôt");
                                // okuma/yazma isteği-yanıt döngüsü
                                string demande = null;
                                bool serviceFini = false;
                                while (!serviceFini && (demande = reader.ReadLine()) != null) {
                                    // konsol izleme
                                    if (echo) {
                                        Console.WriteLine("<--- Client {0} : {1}", client.NumClient, demande);
                                    }
                                    // isteğin analizi
                                    demande = demande.Trim().ToLower();
                                    // boş istek mi?
                                    if (demande.Length == 0) {
                                        // hatalı istek
                                        writeClient(writer,client.NumClient,"Commande non reconnue. Utilisez la commande aide.");
                                        return;
                                    }

                                    // isteği alanlara ayırma
                                    string[] champs = Regex.Split(demande, @"\s+");
                                    // analiz
                                    switch (champs[0].ToLower()) {
                                        case "aide":
                                            writeClient(writer, client.NumClient, "Commandes acceptées\n1-aide\n2-impot marié(O/N) nbEnfants salaireAnnuel\n3-aurevoir");
                                            break;
                                        case "impot":
                                            // vergi hesaplanıyor
                                            writeClient(writer, client.NumClient, calculImpot(writer, client.NumClient, champs));
                                            break;
                                        case "aurevoir":
                                            serviceFini = true;
                                            writeClient(writer, client.NumClient, "Au revoir...");
                                            break;
                                        default:
                                            writeClient(writer, client.NumClient, "Commande non reconnue. Utilisez la commande aide.");
                                            break;
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // hata
                Console.WriteLine("L'erreur suivante s'est produite lors du service au client {0} : {1}", client.NumClient, e.Message);
            } finally {
                Console.WriteLine("Fin du service au client {0}", client.NumClient);
            }
        }

        private static void writeClient(StreamWriter writer, int numClient, string message) {
            // konsol çıktısı?
            if (echo) {
                Console.WriteLine("---> Client {0} : {1}", numClient, message);
            }
            // müşteriye mesaj gönderme
            writer.WriteLine(message);
}

Yine, burada 11.6.1 numaralı paragrafta incelenen yankı sunucusundaki koda benzer bir kodla karşılaşıyoruz. Sadece farklı olan kısımları açıklayacağız:

  • 15. satır: Müşteri bağlandıktan sonra, sunucu ona bir hoş geldin mesajı gönderir.
  • 19-52. satırlar: istemciden gelen komutları okuma döngüsü. İstemci "aurevoir" komutunu gönderdiğinde döngü sona erer.
  • 27. satır: boş komut durumu
  • 34. satır: istek, analiz edilebilmesi için alanlara ayrılır
  • 37. satır: aide komutu: müşteri, izin verilen komutların listesini ister
  • 40. satır: impot komutu: müşteri vergi hesaplaması talep eder. Yanıt olarak, birazdan ayrıntılı olarak ele alacağımız calculImpot yöntemi tarafından döndürülen mesaj gönderilir.
  • 44. satır: aurevoir komutu: müşteri işlemi tamamladığını belirtir.
  • satır 45: Müşteri taleplerini okuma döngüsünden (satır 19-52) çıkmaya hazırlanıyoruz
  • satır 46: müşteriye bir veda mesajı ile yanıt verilir
  • 48. satır: Hatalı bir komut. Müşteriye bir hata mesajı gönderilir.

impot siparişinin işlenmesi, aşağıdaki calculImpot yöntemi ile gerçekleştirilir:


private static string calculImpot(StreamWriter writer, int numClient, string[] champs) {
            // evli olup olmadığı sorusu (E/H) nbEnfants salaireAnnuel
            // 4 alan gereklidir
            if (champs.Length != 4) {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // alanlar [1]
            string marié = champs[1];
            if (marié != "o" && marié != "n") {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // alanlar [2]
            int nbEnfants;
            if (!int.TryParse(champs[2], out nbEnfants)) {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // alanlar [3]
            int salaireAnnuel;
            if (!int.TryParse(champs[3], out salaireAnnuel)) {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // tamam - vergiyi hesaplıyoruz
            int impot = 0;
            try {
                impot = metier.CalculerImpot(marié == "o", nbEnfants, salaireAnnuel);
                return impot.ToString();
            } catch (Exception ex) {
                return ex.Message;
            }
        }
  • 1. satır: Yöntem, 3. parametre olarak impot siparişinin alan tablosunu alır. Bu tablo doğru şekilde oluşturulmuşsa, "impot marié nbEnfants salaireAnnuel" biçimindedir. Yöntem, müşteriye gönderilecek yanıtı sonuç olarak döndürür.
  • 4. satır: Komutun 4 alan içerdiği kontrol edilir
  • 8. satır: marié alanının geçerli olup olmadığı kontrol edilir
  • 14. satır: nbEnfants alanının geçerli olup olmadığı kontrol edilir
  • 19. satır: salaireAnnuel alanının geçerli olup olmadığı kontrol edilir
  • 25. satır: Vergi, [metier] katmanındaki CalculerImpot yöntemi kullanılarak hesaplanır. Bu katmanın bir DLL içinde kapsüllendiği hatırlatılmalıdır.
  • 26. satır: [metier] katmanı bir sonuç döndürdüyse, bu sonuç müşteriye iletilir.
  • 28. satır: [metier] katmanı bir istisna oluşturduysa, istisna mesajı müşteriye iletilir.

11.9.4. Vergi hesaplama TCP sunucusunun grafik istemcisi

11.9.4.1. u Visual Studio Projesi

Grafik istemcisinin Visual Studio projesi şu şekilde olacaktır:

  • [1]'te, çözümün iki projesi, uygulamanın her iki katmanı için birer tane
  • [2]'te, [metier] katmanı için [ui] katmanının rolünü üstlenen TCP istemcisi. Dolayısıyla her iki terimi de kullanacağız.
  • [3]'te, 7. sürümün [ui] katmanı; bununla ilgili daha sonra bahsedeceğimiz küçük bir ayrıntı dışında

11.9.4.2. [metier] katmanı

IImpotMetier arayüzü değişmemiştir. Hâlâ sürüm 7'deki arayüzdür:


namespace Metier {
    public interface IImpotMetier {
        int CalculerImpot(bool marié, int nbEnfants, int salaire);
    }
}

Bu arayüzün uygulaması, aşağıdaki [ImpotMetierTcp] sınıfıdır:


using System.Net.Sockets;
using System.IO;
namespace Metier {
    public class ImpotMetierTcp : IImpotMetier {

        // bilgiler [serveur]
        private string Serveur { get; set; }
        private int Port { get; set; }

        // vergi hesaplaması
        public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
                // hizmete bağlanıyoruz
                using (TcpClient tcpClient = new TcpClient(Serveur, Port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // tamponlanmamış çıkış akışı
                                writer.AutoFlush = true;
                                // karşılama mesajını atla
                                reader.ReadLine();
                                // istek
                                writer.WriteLine(string.Format("impot {0} {1} {2}",marié ? "o" : "n",nbEnfants, salaire));
                                // yanıt
                                return int.Parse(reader.ReadLine());
                            }
                        }
                    }
                }
            }
        }
    }
  • 7. satır: Vergi hesaplama TCP sunucusunun adı veya IP adresi
  • 8. satır: bu sunucunun dinleme bağlantı noktası
  • Bu iki özellik, [ImpotMetierTcp] sınıfı örneklendirildiğinde Spring tarafından başlatılacaktır.
  • 11. satır: Vergi hesaplama yöntemi. Bu yöntem çalıştırıldığında, Serveur ve Port özellikleri zaten başlatılmış olacaktır. Kodda, klasik bir TCP istemcisinin izlediği adımlar görülmektedir
  • 13. satır: sunucuyla bağlantı açılır
  • 14-16. satırlar: Bu bağlantıyla ilişkili ağ akışı alınır (14. satır) ve buradan bir okuma akışı (15. satır) ile bir yazma akışı (16. satır) elde edilir.
  • 18. satır: yazma akışı tamponlanmamalıdır
  • 20. satır: Burada, bağlantının açılması sırasında sunucunun istemciye "Vergi hesaplama sunucusuna hoş geldiniz" şeklindeki hoş geldiniz mesajını içeren bir ilk satır gönderdiğini hatırlamak gerekir. Bu mesaj okunur ve göz ardı edilir.
  • 22. satır: Sunucuya "impot o 2 60000" biçiminde bir komut gönderilir; bu komutla, 2 çocuğu olan ve yıllık geliri 60.000 avro olan evli bir kişinin vergisinin hesaplanması istenir.
  • 24. satır: Sunucu, vergi tutarını "4282" biçiminde yanıtlar ya da komutun biçimi yanlışsa (burada böyle bir durum olmayacaktır) veya vergi hesaplamasında bir sorun yaşanırsa bir hata mesajı gönderir. Burada bu son durum işlenmemiştir, ancak bunu yapmak kesinlikle daha "temiz" bir çözüm olurdu. Nitekim, okunan satır bir hata mesajı ise, tamsayıya dönüştürme işlemi başarısız olacağından bir istisna atılacaktır. Grafik kullanıcı arayüzü tarafından yakalanan istisna bir dönüştürme hatası olarak görünecek, oysa asıl istisna tamamen farklı bir nitelikte olacaktır. Okuyucunun bu kodu iyileştirmesi önerilir.
  • 25-28. satırlar: "using" cümlesiyle kullanılan tüm kaynakların serbest bırakılması.

[metier] katmanı, DLL ve ImpotsV8-metier.dll içinde derlenir:

Image

11.9.4.3. [ui] katmanı

[ui] ve [1,3] katmanları, sürüm 7'nin 9.8.4 numaralı paragrafında incelenen katmanlardır; ancak üç ayrıntı dışında:

  • [metier] katmanının [App.config] içindeki yapılandırması, bu katmanın uygulamasının değişmesi nedeniyle farklıdır
  • [Form1.cs] grafik arayüzü, olası bir istisnayı görüntülemek üzere değiştirilmiştir
  • [metier] katmanı, DLL [ImpotsV8-metier.dll] içinde yer almaktadır.

[App.config] dosyası şu şekildedir:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="metier" type="Metier.ImpotMetierTcp, ImpotsV8-metier">
                <property name="Serveur" value="localhost"/>
                <property name="Port" value="27"/>
            </object>
        </objects>
    </spring>
</configuration>
  • 16. satır: DLL ve ImpotsV8-metier.dll'teki Metier.ImpotMetierTcp sınıfı kullanılarak [metier] katmanının örneklenmesi
  • 17-18. satırlar: Metier.ImpotMetierTcp sınıfının Sunucu ve Bağlantı Noktası özellikleri başlatılır. Sunucu, localhost makinesinde yer alacak ve 27 numaralı bağlantı noktasında çalışacaktır.

Kullanıcıya sunulan grafik arayüz şu şekildedir:

  • [1]'te, olası bir istisnayı görüntülemek için bir TextBox eklenmiştir. Bu alan önceki sürümde mevcut değildi.

Bu ayrıntı dışında, formun kodu 6.4.3. paragrafında incelenen kodla aynıdır. Okuyucunun bu bölüme başvurması önerilir. [2]'te, aşağıdaki şekilde başlatılan bir sunucu ile elde edilen bir yürütme örneği görülmektedir:

1
2
3
4
5
6
7
8
9
Serveur de calcul d'impôt >start 27
Serveur de calcul d'impôt lancé sur le port 27
Serveur de calcul d'impôt >echo start
Serveur de calcul d'impôt >
...
Début du service au client 9
<--- Client 9 : impot o 2 60000
---> Client 9 : 4282
Fin du service au client 9

Müşterinin [2] ekran görüntüsü, yukarıdaki müşteri 9'un satırlarına karşılık gelir.

11.9.5. Sonuç

Yine, mevcut kodu hiçbir değişiklik yapmadan (sunucudaki [metier] ve [dao] katmanları) veya çok az değişiklikle (istemcideki [ui] katmanı) yeniden kullanabildik. Bu, arayüzleri sistematik olarak kullanmamız ve bunları Spring ile örneklendirmemiz sayesinde mümkün oldu. Eğer sürüm 7'de iş mantığı kodunu doğrudan grafik arayüzün olay işleyicilerine yerleştirmiş olsaydık, bu iş mantığı kodu yeniden kullanılamaz olurdu. Bu, tek katmanlı mimarilerin en büyük dezavantajıdır.

Son olarak, [ui] katmanının, vergi tutarını hesaplayanın uzak bir sunucu olduğu konusunda hiçbir bilgisi olmadığına dikkat çekmek gerekir.