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:
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:
| |
Fiziksel katmanın fiziksel özelliklerini gizler. İletim hatalarını algılar ve düzeltir. | |
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. | |
Ö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. | |
Bu katmanda, bir uygulamanın uzak bir makinede bir çalışma oturumu açmasına ve sürdürmesine olanak tanıyan hizmetler bulunur. | |
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. | |
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.
Ağın iki düğümü arasında paketleri iletir | |
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. | |
makinenin İnternet adresi ile fiziksel adresi arasında eşleştirme yapar | |
makinenin fiziksel adresi ile makinenin İnternet adresi arasında eşleştirme yapar |
Taşıma/Oturum Katmanları
Bu katmanda aşağıdaki protokoller bulunur:
İki istemci arasında bilgilerin güvenilir bir şekilde iletilmesini sağlar | |
İki istemci arasında güvenilir olmayan bilgi aktarımını sağlar |
Uygulama/Sunum/Oturum Katmanları
Burada çeşitli protokoller bulunur:
A makinesinin B makinesine terminal olarak bağlanmasını sağlayan terminal emülatörü | |
dosya aktarımına olanak tanır | |
dosya aktarımına olanak tanır | |
ağ kullanıcıları arasında mesaj alışverişine izin verir | |
bir makine adını o makinenin İnternet adresine dönüştürür | |
sun tarafından oluşturulan MicroSystems, makinelerden bağımsız standart bir veri temsilini belirtir | |
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 | |
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:
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 | |
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. | |
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. | |
C | IP "255.255.255.255" adresi. Bir hizmet bu adrese atandığında, bu hizmetin hiçbir istemciyi kabul etmediği anlamına gelir. | |
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. | |
M | IP adresi "127.0.0.1" ise true değerini döndürür | |
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:
, IP adresinden bir dize biçiminde veya bir makine adından IPHostEntry adresini döndürür. Makine bulunamazsa bir istisna atar. | |
IPAddress türündeki IP adresinden IPHostEntry adresini döndürür. Makine bulunamazsa bir istisna oluşturur. | |
bu komutu çalıştıran programın üzerinde çalıştığı makinenin adını döndürür | |
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:
P | makinenin IP adres tablosu | |
P | Makinenin DNS takma adları. Bunlar, makinenin farklı IP adreslerine karşılık gelen adlardır. | |
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:
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:
- 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:
- 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:

[Affichage/Code source] kaynak kodunu görüntülemek istersek:
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:
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:
- 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:
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. | |
P | İstemcinin sunucuyla iletişim kurmak için kullandığı soket. | |
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. | |
M | bağlantıyı kapatır. NetworkStream soketi ve akışı da kapatılır | |
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
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:
M2 makinesinden gelen yanıtı okumak için şunu yazacağız:
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:
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. | |
C | Aynı işlevi yerine getirir, ancak dinleme işlemi yalnızca belirtilen IP adresinde gerçekleşir. | |
M | istemcilerin isteklerini dinlemeye başlar | |
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. | |
M | istemci isteklerini dinlemeyi durdurur | |
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:
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:
Ö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:
İstemci B'nin (no. 1) penceresinde ise:
Sunucunun penceresinde:
0 numaralı A istemcisi bağlantısını keser:
Sunucu konsolu:
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ü
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ü
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:
<-- ile başlayan satırlar sunucudan istemciye gönderilenler, diğerleri ise istemciden sunucuya gönderilenlerdir. Sunucu penceresi şöyledir:
<-- 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:
Sunucunun penceresi şu şekilde görünür:
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:
Ş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:
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:
En az düzeyde kalarak benzer bir yanıt vermeye çalışalım:
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:
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:
- İ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:
- 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¶m2=val2;... dizesinden oluşturulmuşsa, şu sonuç elde edilir:
- uri.Host=serveur,
- uri.Port=port,
- uri.Path=document,
- uri.Query=param1=val1¶m2=val2;...,
- uri.pathAndQuery= cheminPageHTML?param1=val1¶m2=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:
- 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:
- 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:
- 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:
- 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
- 11. satır: 6. satırdaki adrese yönlendirme yapılır
URL: http://www.gouv.fr
- 11. satır: 6. satırdaki adrese yönlendirme yapılır
URL: http://localhost
- 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:
Önceki web istemcisini şu şekilde başlatıyoruz:
İstenen URI, genel sunucunun URI'sidir. Sunucu, web istemcisi tarafından kendisine gönderilen HTTP başlıklarını görüntüler:
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:
- 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:
Komut tarafından oluşturulan istia.univ-angers.txt dosyası şöyledir:
- 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:
bir kaynağı bayt dizisi olarak indirmek için (örneğin resim) | |
bir kaynağı indirip yerel bir dosyaya kaydetmek için | |
bir kaynağı indirip karakter dizisi olarak almak için (örneğin bir HTML dosyası) | |
OpenRead'in karşılığıdır, ancak verileri sunucuya göndermek içindir | |
DownLoadData'in karşılığıdır, ancak sunucuya yöneliktir | |
DownLoadFile'in karşılığı, ancak sunucuya yöneliktir | |
DownLoadString'in karşılığı, ancak sunucuya yönlendirilmiş | |
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¶m2=valeur2&... biçiminde gönderilir:
Aynı belge, GET yöntemi ile de talep edilebilir:
İki yöntem arasındaki fark şudur: İstenen URI’yi görüntüleyen tarayıcı, POST durumunda /document’i, /document?param1=valeur1¶m2=valeur2&... durumunda ise /document?param1=valeur1¶m2=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:
Web istemcisi tarafından varsayılan olarak gönderilen HTTP başlıklarının şunlar olduğunu görmüştük:
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:
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:
- 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):
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:
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:
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:
Ç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:
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:
Çö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&translationDirection={0}&stext={1}"/>
<!--
<property name="ProxyHttp" value="pproxy.istia.uang:3128"/>
-->
<property name="RegexTraduction" value="<div class="txtTrad">(.*?)</div>"/>
<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 [&], < yerine [<], > yerine [>], " yerine ["].
- 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.
- 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.
- 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:
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.
- 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:
belirli bir bağlantı noktasında hizmeti başlatmak için | |
hizmeti durdurmak için. Daha sonra aynı bağlantı noktasında veya başka bir bağlantı noktasında yeniden başlatılabilir. | |
konsolda istemci/sunucu diyaloğunun yankısını etkinleştirmek için | |
yankıyı devre dışı bırakmak için | |
hizmetin etkin/etkin olmayan durumunu görüntülemek için | |
uygulamadan çıkmak için |
Sunucuyu başlatalım:
Şimdi, 11.8. paragrafta daha önce incelediğimiz asenkron grafiksel TCP istemcisini çalıştıralım.

İstemci bağlandı. Vergi hesaplama sunucusuna aşağıdaki komutları gönderebilir:
izin verilen komutların listesini almak için | |
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. | |
sunucuyla bağlantıyı sonlandırmak için |
İşte bir diyalog örneği:
![]() |
Sunucu tarafında, konsolda şunlar görüntülenir:
Echo özelliğini etkinleştirip grafik istemciden yeni bir diyalog başlatalım:
![]() |
Yönetim konsolu şu mesajı görüntüler:
- 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:
- 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:

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

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























































