Sonunda uzun zamandır ertelediğim bu yazıyı da yayınladım ve kafamdan sildim. Aşağıda göreceğiniz kodlar basit bir sohbet programının kodlarıdır. Ayırca java'da soket programlama için de bir örnek teşkil etmektedir. Programı NetBeans ile yazdım ve NetBeans tarafından otomatik olarak eklenen kodlar aşağıda yer almamaktadır.
Kodları ve programın işleyişini daha iyi anlayabilmeniz için bir takım terimleri ve yapıları bilmeniz gerektiğini düşünerekten öncelikle bu bilgileri verelim.
Protokol Nedir?
Herhangi bir ağ içerisinde bulunan cihazların birbirleriyle haberleşmeleri için bellirli protokoller tanımlanmıştır. Bu protokoller sayesinde kullanıcılar alt yapıdaki detaylarla uğraşmadan veri alışverişini gerçekletirebilir.
Farklı amaçlar için farklı protokoller geliştirilmiştir. Bu farklı protokollerin birbirlerine göre üstünlük ve zayıflıkları vardır. Kullanım alanına göre doğru protokol seçilmelidir.
Yaztığım programda User Datagram Protokol – UDP protokolünü kullandım. UDP protokolü veri iletişimini bağlantı kurmaksızın paketler gönderir. Bağlantı kurulmadığından dolayı protokolden mesajın iletildiğine dair herhangi bir bilgi edinilemez. Bu tip bir onay sistemi kullanıcı tarafından isteğe bağlı olarak ayrıca yazılmalıdır. UDP’de gönderilen paketlerin herhangi bir sıra numarası ve sırayla ulaşma garantisi de yoktur. Bu tip özellikler için ekstra başlık bigileri gerekmektedir, UDP bu başlık bilgilerini içermediğinden başlık bilgisi de diğer protokllere göre (ör:TCP) daha küçüktür. Başlık bilgilerinin küçük olması sayesinde UDP daha az bant genişliğine ihtiyaç duyar. Bu avantajından dolayı gerçek zamanlı ses veya görüntü iletişiminde tercih edilir.
Port Nedir ?
Portlar bir bilgisayarın ağda sahip olduğu IP adresinden dışarıya açılan birbirinden bağımsız kapılar olarak tanımlanabilir. Port numaraları 16bit ile ifade edildiğinden 65536 adet port mevcuttur. Herhangi bir uygulamanın kullanacağı port numarası seçilirken daha önceden başka bir programın bu portu kullanıp kullanmadığına dikkat edilmelidir. Aksi halde iki uygulama da düzgün çalışmayabilir.
UDP protokolünde veri iletişimi yapmak için IP ve PORT ikilisini kullanarak adresleme yapmak gerekir. Bu ikili kullanar bir soket yaratılır ve iletişim soket üzerinden yapılır.
Soket Nedir ?
Soket IP adresi ve Port birleşiminden oluşan uygulamalarda ağ içerisinde bulunan alt düzey ayrıntılarla uğraşmadan veri alışverişi yapmayı sağlayan ve unix sistemlerde bulunan dosya yapısına benzer bir yapıdır. TCP/IP protokolünde mevcut olan soketler uygulama seviyesinde bağlantıları simgelemeye yarar.
Yaygın olarak kullanılan iki türlü soket yapısı mevcuttur. Bunlardan biri SOCK_STREAM ile tanımlanan ve streaming yapmaya yarıyan soket tipidir. Bu soket yapısında bağlantılı (connection oriented) haberleşme kullanılır. Yani haberleşme sırasında aktif olan bağlantı ve soket yapısı mevcuttur. TCP protokolü SOCK_STREAM tipinde soketler üzerinden haberleşme yapar. Bu soketlerden yapılan iletişim tamamen güvenlidir. Yani yanlış veri iletimi olması durumunda protokolden dolayı sistem kararlı ve doğru veriyi iletene kadar tekrar gönderme/isteme işlemini yapar. Ayrıca bu soketler üzerinden gönderilern verilerin alıcı tarafa da aynı sırayla ulaşacağı garanti edilmiştir. “A” ve “B” datasını içeren iki mesajı gönderdiğimizde alıcı tarafta yine “A” ve “B” sırasıyla alınacağı garanti edilmiştir.
Diğer bir soket türü ise SOCK_DGRAM ile tanımlanan datagram tipi soketlerdir. TCP’ye göre güvensiz veri iletişimi yapar. Bunun nedeni gönderilerecek datayı UDP protokolü üzerinden yollaması ve paketi bir kez ağa bıraktıktan sınra herhangi bir takip işlemi yapmamasıdır. Yani kullanıcıya verinin ulaşıp ulaşmadığına dair herhangi bir bilgi sağlamaz. Buna karşılık alıcıdan onay mesajı yollatarak iletimi biraz daha olsun kararlı hale getirebilinir. Bu soketin tercih edilme sebebi ise stream türünde yayınlardır. Her ne kadar streaming soket “streaming” için daha uygun gibi görünsede datagram tipi soketler bu iş için daha uygundur. Bunun sebebi kayıp paketlerin tekrar yollanması, aşırı başlık bilgisi, çok bant genişliği harcaması, bağlantının sürekli olarak aktif kalması gibi dezavantajlarından dolayı datagram soketler streaming işlemi için tercih edilmektedir.
Paket Nedir ?
UDP ile bağlantısız haberleşme yapıldığından veriler birbirinden bağımsız paketler ile gönderilir. TCP protokolünde bağlantılı haberleşme yapıldığından veri iletişimi başlarken bir bağlantı kurulur ve sürekli olarak bu bağlantı üzerinden haberleşme sağlanır. Veri iletişimi olmasa dahi bağlantı açık kalır. UDP protokolü için Java dilinde belirlenen paket değişken tipi Datagram Packet’dir. Her bir paket içerisinde gönderilecek veri ve paketteki veri miktarını belirten değişkenler vardır. Bunun yanı sıra paketi gönderdiğimiz bilgisayarın IP adresi ve port numarası da pakete dahil edilmelidir.
Gönderici ve alıcı adresleri de UDP paketinin içinde olmasına rağmen bu paketler tek başına iletişim için yeterli değildir. UDP paketleri de ağda iletim için IP protokolünü kullanır. Bundan dolayı her paketin başına IP başlık bilgileri de eklenir. Aşağıdaki şekilde bir IP başlığı ve altında ise UDP paketinin temel yapısı gösterilmiştir:
Programın Genel Yapısı
Program sunucu/istemci mimarisine dayanarak çalışmaktadır. İstemci kısmı Applet olarak yazıldı ve web üzerinden sohbet edebilme imkanı sağlamaktadır. Sunucu kısmı ise masaüstü uygulaması olarak yazılmıştır. Sunucu çalışmaya başlar başlamaz ilgili portu dinleyerek gelen paketleri değerlendirerek mesajları istemciler arasında iletmektedir. İstemciler birbirleri arasında direk mesajlaşmak yerine sunucu üzerinden mesajlaşmaktadır. Bundan dolayı istemcilerde diğer istemcilerin adresleri yerine sadece isimleri tutulmaktadır.
İstemci Tarafı
İstemci kodu iki temel sınıftan oluşmaktadır. ClientMsgHandler ve ClientGUI. ClientMsgHandler veri iletim fonksiyonlarını içermektedir. ClientGUI ise arayüzü ve ClientMsgHandler’dan gelen verileri kullanıcıya göstermektedir. ClientMsgHandler thread sınıfını implement ederek oluşturulmuştur. Bu sayede arayüzde herhangi bir takılma olmadan veri iletişim işlemleri eş zamanlı olarak yapılmaktadır.
Programda iki türde istemci vardır. Birincisi sunucu üzerinde çalışan ve web üzerinden hizmet veren istemcidir. Web üzerinden sunucuya bağlanan istemciler için yazılmıştır ve sunucu adresini bilmeksizin her istemci bu sunucuya bağlanabilir. Diğer istemci türünde ise istemci kodu bilgisayara indirilip web tarayıcısı üzerinden local olarak çalıştırılabilir. Bu istemcide ise sunucunun ip adresini de ayrıca bilmek gerekmektedir.
İstemci Tarafındaki Sınıflar ve Fonksiyonları
ClientMsgHandler sınıfına ait fonksiyonlar:
Send:String tipinde tutulan gönderilecek mesajı byte dizisine çevirir. Daha sonra DatagramSocket üzerinden yollamak üzere yeni bir PatagramPacket hazırlayak oluşturulan byte dizsini, alıc adresini ve iletişimde kullanılacak port numarasını da pakete ekler. Son olarak paketi DatagramSocket üzerinden gönderir.
Recieve: DatagramSocket üzerinden gelen paketi alabilmek için yeni bir DatagramSocket yaratır. Okunan veriyi bu pakete yazdıktan sonra mesaj kısmını byte dizisinden string tipine çevirir. Daha sonra mesajı işlemek üzere ParseMsg ve HandleMsg fonkisyonlarını çağırır.
SetHost: Parametre olarak host adresini alıp bu adresi InetAddress türüne çevirir.
**
BuildMsg: Parametre olarak aldığı mesaj türüne göre gönderilecek mesajı string olarak oluşturur. String türündeki mesajın formatı aşağıdaki gibidir:
gönderici_adı > alıcı_adı > mesaj_türü > mesaj_içeriği
Bu yapıda “>” sembolü ayraç olarak kullanılmıştır.
Mesaj türleri ise aşağıdaki şekildedir:
ADD_ME – Kullanıcı ekleme
REMOVE_ME – Kullanıcı silme
SEND_MSG – Normal mesaj
GET_USERS – Kullanıcı listesi isteği
ACK_MSG – Onay mesajı
ParseMsg: Alınan mesajı “>” ayracına göre bölümleyerek mesajın içeriğini gerekli değişkenlere atar.
HandleMsg: Parse edilen mesajın türüne bağlı olarak işlemleri gerçekleştirir. Mesaj türlerine göre yaptığı işlemler aşağıdaki gibidir:
**
SEND_MSG: Gelen mesajı arayüzde göstermek üzere ClientGUI sınıfına gönderir.**
REMOVE_ME: Gelen kullanıcı adını ClientGUI’de gösterilen listeden silmek üzere ClientGUI sınıfına iletir. Aynı zamanda istemcilerin tutulduğu vector yapısından ilgili istemciyi silmesi için RemoveClient fonksiyonuna kullanıcı adını iletir.**
ACK_MSG: Bağlantının kurulduğunu onaylayan bu mesaj alındığı zaman arayüzde gerekli değişiklikler yapılarak mesaj iletimine uygun yapı oluşturulur.**
ADD_ME: Gelen kullanıcı adını istemcilerin tutulduğu vector yapısına ekler ve arayüz sınıfının da bu kullanıcı adını göstermesini sağlar.**
GET_USERS: Kullanıcı isteğinin karşılığı niteliğindedir. İlk kez bağlantı kuruluduğunda liste alınarak arayüzde gösterilir. Aynı zamanda istemcilerin her birini vector yapısına ekler. Bu mesaj alındıktan sonra bağlantı tamamlanmış ve kullanıcı listesi alınmış olduğundan dolayı mesaj dinleme işlemini başlatacak thread çalıştırılr. Sonsuz bir döngü içinde ilgili port sürekli olarak dinlenir.
Connect: ClientGUI’den alınan kullanıcı adını içeren bir mesaj ile sunucuya bağlantı isteğini gönderir. Send methodunu çağırarak mesajın iletimini sağlar, daha sonra Recieve methoduyla bağlantı isteğine gelcek cevabı bekler. Cevap gelmeme ihtimaline karşı soket’in bloklanma süresi 2sn olarak ayarlanmıştır. Sunucundan cevap alınırsa veya soket süresi dolarsa bağlantı kurulamadığı ClientGUI’de gösterilir ve soket’in bloklanma süresi tekrar sıfıra setlenir (sonsuz).
Disconnect: Sunucuylya yapılan bağlantının bitirilmesi isteğini gönderir. Bu sayede bağlantıyı kapatan istemciler diğer istemcilerin listesinden silinecektir. UDP gibi bağlantısız bir iletişim protokolü kullanıldığından dolayı bu işlem gereklidir. Aksi halde sunucu – istemci arasında sürekli ping gönderek her iki tarafında hala hatta olduğu kontrol edilmelidir.
GetClientList: Sunucu ile ilk defa bağlantı kurulduğu zaman varolan istemcilerin listesini almak için gerekli mesajı oluşturup gönderir ve cevabı alır. Kullanıcı listesi alınmadan önce ilgili port ayrı bir thread ile dinlenmediği için bu fonksiyon içinden Recieve fonkisyonu çağrılmıştır.
BuildClientList: Sunucu istemciye diğer istemcilerin isim listesini gönderirken isimleri “ | ” ayracı ile ayırır. HandleMsg fonksiyonu bu ayraça göre gelen veriyi bölerek BuildClientList fonksiyonuna string dizisi olarak iletir. BuildClientList fonksiyonu ise gelen bu diziyi vector yapısına çevirerek kaydeder. Vector yapısı kullanılmasının amacı istemci sayısının dinamik olarak değişmesi ve ClientGUI’de ki listede istemcilerin kolaylıkla gösterilmesidir. |
RemoveClient: Herhangi bir istemci sunucuya ayrıdığını bildirdiğinde sunucu geride kalan bütün istemcilere bu mesajı iletir ve istemciler bu fonksiyon ile ayrılan istemcinin adını vector yapısından ve ClientGUI’de kullanılan listeden siler.
**ClientGUI **sınıfına ait fonkisyonlar:
SendMessage: TextField2’ye girilen mesajı ve JList1’den seçilen kullanıcı adını ClientMsgHandler sınıfına göndererek mesajın sunucuya iletilmesini sağlar.
AddMessage: ClientMsgHandler tarafından alınan mesajları jTextArea’ya kimden geldiğini göstererek ekler.
UpdateUserList: Sunucuya herhangi bir istemci bağlandığında veya ayrıldığında arayüzde gösterilen istemci isimlerini yeniler.
SetStatus: Sunucuya bağlanıp bağlanılmadığını ve mesaj gönderirken hangi kullanıcının seçildiğini jTextFeild3’e yazdırır.
Connect: Bağlan butonuna basıldığı zaman kullanıcıdan alınan ip adresi (veya host adı) ve kullanıcı adıyla sunucuya bağlanmak için ClientMsgHandler’da bulunan Connect fonksiyonun çağırır.
Destroy: Kullanıcı tarayıcıyı kapattığı zaman sunucuya bağlantıyı sonlandırma mesajını gönderir.
**Sunucu Tarafı **
Sunucu işlemleri genel olarak istemcilerin listesini tutmak ve gelen mesajları iletmek üzerine kuruludur. ClientHandler sınıfı sürekli olarak ilgili portu dinleyerek mesaj iletim işini üstlenmektedir. Onun haricinde serverGUI mesajlaşmaların ve sisteme gelip giden istemcilerin özetini gösteren bir arayüz oluşturmaktadır.
ClientHandler **sınıfına ait fonksiyonlar:
**
BuildMsg: Parametre olarak aldığı mesaj türüne göre gönderilecek mesajı string olarak oluşturur. String türündeki mesajın formatı aşağıdaki gibidir:
gönderici_adı > alıcı_adı > mesaj_türü > mesaj_içeriği
Bu yapıda “>” sembolü ayraç olarak kullanılmıştır.
Mesaj türleri ise aşağıdaki şekildedir:
**
ADD_ME – Kullanıcı ekleme
REMOVE_ME – Kullanıcı silme
SEND_MSG – Normal mesaj
GET_USERS – Kullanıcı listesi isteği
ACK_MSG – Onay mesajı
ParseMsg: Alınan mesajı “>” ayracına göre bölümleyerek mesajın içeriğini gerekli değişkenlere atar.
HandleMsg: Parse edilen mesajın türüne bağlı olarak işlemleri gerçekleştirir. Mesaj türlerine göre yaptığı işlemler aşağıdaki gibidir:
**
SEND_MSG: Mesaj gönderilecek istemcinin adını clients vector yapısı içinde arar. Bulduğu elemandan adresi alarak mesajı ilgili istemciye gönderir. Ayrıca sunucu arayüzünde de mesajı ve kimden kime gönderildiğini yazar.
**
REMOVE_ME: İstemci sunucudan ayrıldığı zaman client vector yapısı içinden bu istemciyi siler ve diğer tüm istemcilere ilgili istemci adını yollayarak listelerinden silmesini bildirir. Buna ek olarak sunucu arayüzünde de çıkan istemcinin adını yazar.
ADD_ME: Yeni istemcinin adını ve adresini arayüzde gösterir. Daha sonra istemcilerin tutulduğu vektöre yeni bir istemci sınıfı nesnesi yaratıp ekler. Bundan sonra ise istemciye bağlantının kabul edildiğine dair bir onay mesajı yollar. Yeni gelen kullanıcıya onay mesajı gönderip bağlantıyı kabul ettikten sonra önceden bağlanan istemcilere yeni gelen istemcinin adını gönderir.
GET_USERS: Bu mesajdan sonra sunucu mesajı gönderen istemciye o anda bağlı olan istemcilerin isimlerini gönderir. İsimler birbirinden “ | ” ayracı ile ayrılmıştır. |
ServerSend: Gönderilecek mesajı byte dizisine çevirerek datagram paketi içine koyar ve datagram soketi üzerinden gönderir.
ServerRecieve: İlgili portu dinleyerek gelen mesajları string tipine çevirir. Ayrıca gönderenin adresini de datagram paketinden çıkararak kaydeder.
NotifyAllClients: Belirlenen mesajı o anda bağlı olan istemcilerin hepsine gönderir.
BuildClientList: Bağlı olan istemcilerin isimlerini bir string’de birleştirerek mesajı gönderilmeye hazır hale getirir.
GetReceiverAddressByName: Sunucuya gelen mesajlarda sadece gönderilecek istemcinin adı bulunmaktadır. Adresler ise sunucuda tutulan clients vector yapısındadır. Bu fonksiyon mesaj gönderilecek istemcinin adını vector elemanlarıyla karşılaştırıp ilgili adresi bulur.
RemoveClient: Parametre olarak aldığı kullanıcı adını clients vector yapısında arar ve ilgili istemciyi vector’den siler. Daha sonra geride kalan istemcilere bu silme işlemini bildirir.