Sohbet programları haberleşmek için Winsock API�leri kullanır. Fakat
sohbet programları gibi ağ uygulamaları geliştirmek için saf API
kullanmak zahmetlidir. Bunun yerine MFC�nin CSocket sınıfını
kullanacağız. CSocket sınıfı, CAsyncSocket sınıfından türemiştir ve
CAsyncSocket�e göre WinsockAPI�den daha fazla soyutlanma sağlar. Genelde
uygulamalarda doğrudan CSocket�i kullanmak yerine, CSocket�ten
türetilmiş özel sınıflar kullanılır. CAsyncSocket�e ait olan Accept,
Receive, Send gibi sanal işlevler amaca göre değiştirilir.
Sohbet programlarında iletişim TCP/IP tabanlı olduğu için bir sunucuya
ve istemciye ihtiyaç vardır. İstemciler, dinleme durumundaki sunucunun
IP adresi bilgisini kullanarak sunucuya bağlanır ve bilgi aktarımı
sağlanır. Bundan dolayı sunucu ve istemci programımızı ayrı ayrı
yazacağız.
Uygulama
Sunucu ve istemci programlarımızı geliştirirken aşağıdaki adımlar
ortaktır. Öncelikle Visual C++ Projects kısmından MFC Application
seçilmeli, proje özelliklerinden de uygulamanın dialog tabanlı olduğu ve
Windows Sockets kullandığı belirtilmelidir.
Şekil 1 - Proje türü
Proje oluşturulduktan sonra kendi soket sınıfımızı oluşturmak için, ana
menüde Project�e tıklayarak Add Class seçilmeli. Bunu yaptıktan sonra,
eklemek istediğimiz sınıfın adını, türetileceği taban sınıfı ve hangi
dosyanın içine kaydedileceğini belirtebileceğimiz bir dialog
açılacaktır. Soket sınıfımızı CSocket�ten türeteceğimizi şu şekilde
belirtebiliriz:
Şekil 2 - CSocket�ten türemiş sınıf oluşturma
Türetilen bu sınıfın istemci için tasarımı:
class sohbetSoket : public CSocket
{
public:
sohbetSoket();
virtual ~sohbetSoket();
virtual **** OnAccept(int nErrorCode);
virtual **** OnClose(int nErrorCode);
virtual **** OnConnect(int nErrorCode);
virtual **** OnReceive(int nErrorCode);
virtual **** OnSend(int nErrorCode);
**** alinanlariAt(CEdit *sAlinan);
CEdit *alinan;
CString buf;
}; şeklinde olacak, ve sunucu versiyonunda fazladan sadece
sohbetSoket *kabul;
satırını içerecektir. Bu yeni türettiğimiz sınıfı ana dialog sınıfı
içinde kullanacağımız için başlık dosyasına:
#include"sohbetSoket.h"
satırını eklemeliyiz. Programımızın tasarımını Şekil 3�deki gibidir.
Şekil 3�deki kontrollerin üzerine tıkladığınızda, tıkladığınız kontrolün
ID�sini ve atanacak değişkeni görebilirsiniz.
Şekil 3 - Projenin görünümü ve kontrollere atanan değişkenler
Her iki formda da ortak olan değişkenlerden port isimli değişken,
haberleşmenin hangi porttan yapılacağını belirtir. alinan isimli metin
kutusu karşı taraftan alinanlari barindirmak için vardır. Gönder
düğmesine basılınca metin isimli metin kutusundaki yazılanlar
gönderilir.
Sunucu ve istemci sınıfının üye değişkenleri şunlardır:
bool bagli; //sadece istemci sınıfında
bool dinlemede; //sadece sunucu sınıfında
TSoket soket;
//kontrollere atanan otomatik oluşturulmuş değişkenler:
CString adres;
UINT port;
CString alinan;
CString metin; Öncelikle hem sunucu hem de istemci için TSoket sınıfına,
Receive işlevi ile aldığı paketleri hangi kontrole atacağını, ana
dialogun OnInitDialog işlevinde şu şekilde bildirmeliyiz:
soket.alinanlariAt((CEdit*)GetDlgItem(IDC_ALINAN)) ; TSoket sınıfının
alinanlaraAt işlevini daha sonra açıklayacağız. Sunucu tarafında Dinle
düğmesine tıklandığında işletilecek kodu yazarak devam edelim:
//Bağlan/Kop
if(!dinlemede)
{
UpdateData();
soket.Create(port);
soket.Bind(port);
soket.Listen();
dinlemede = true;
SetWindowText("Sunucu - Aktif");
SetDlgItemText(IDC_DINLE,"Vazgeç");
}
else
{
soket.Close();
SetWindowText("Sunucu - Aktif değil");
bagli = false;
dinlemede = false;
SetDlgItemText(IDC_DINLE,"Dinle");
} UpdateData işlevi port için belirtilen değeri kontrolden değişkene
atmak için kullanıldı. Sunucu tarafı soketi oluşturmak için kullanılan
Create işlevi, parametre olarak port numarasını alır. İstemci için
soketin yaratılması sırasında parametre verilmez. Bind işlevi,
uygulamamızın, aktif olarak çalışılan makineye -parametre olarak verilen
porttan yapılan- bütün bağlantıları üstlenmesini sağlar. Yani Bind(x)
işlevinin çağrılması ile x portunda yapılan bütün bağlantıları
programımız ele alacaktır. Son olarak Listen işlevi ile dinleyeme,
belirtilen porttan yapılacak bağlantıları izlemeye başlayabiliriz. Fakat
uygulamamız dinlemede ise Close işlevi ile soketi durdurmuş ve
kendisine yapılan referansları geçersiz kılmış oluyoruz. CAsyncSocket
sınıfının yokedicisi bu işlevi bizim için otomatikman çağırır. Kodda
geçen diğer işlevler ise dinlemede olup olmadığımız bilgisini
görüntülemek için kullanılmaktadır. TSoket sınıfının tasarımına geçmeden
önce, sınıfın sunucuya özel olan OnAccept işlevini de açıklayalım:
kabul = new TSoket();
kabul->alinanlariAt(alinan);
Accept(*kabul);
CSocket::OnAccept(nErrorCode); Sunucu soketlerde OnAccept işlevi
sunucuya bir bağlantı yapıldığında otomatik olarak çağrılır. Gelen
bağlantıyı kabul etmek için Accept işlevini çağırmalı ve parametre
olarak da bu bağlantıyla yapılacak olan bütün transferleri kontrol
edecek bir soket verilmelidir. Bu soketin kalıcı olması için TSoket
sınıfına üye bir değişken olarak tanımlanmalıdır.
Gönder düğmesine tıklandığında yapılacaklar ise şu şekildedir:
UpdateData();
if(!metin.IsEmpty())
{
for(UCHAR a=0; a<256-metin.GetLength();a++ )metin+=�%�;
soket.kabul->Send(metin.GetBuffer(),metin.GetLength());
} Burda yapılan şey, öncelikle UpdateData işlevi ile gönderilecek metnin
kontrolden, metin isimli değişkene aktarılması , ardından da metni 256
bayta % karakterleri ile tamamlanması ve Send işlevini metin ve boyutu
ile çağrılmasıdır. Programımız temel bi uygulama olduğu için göndereceği
bütün paketler 256 bayttan oluşmaktadır. Daha ileri uygulamalarda
boyut, dinamik olarak belirlenmeli ve paketin boyut bilgisi önden
gönderilmelidir. Send işlevi gönderme işlemini başaramadığı takdirde
SOCKET_ERROR döndürecektir.
İstemci uygulamamızın Bağlan düğmesine tıklayınca belirtilen IP adresine
bağlanması için yazılması gereken kod:
if(!bagli)
{
UpdateData();
soket.Create();
if(!soket.Connect(adres,port))
{
SetWindowText("İstemci - Bağlanamadı");
soket.Close();
soket.ShutDown();
}
else
{
SetWindowText("İstemci - Bağlı");
SetDlgItemText(IDC_BAGLAN,"Kop");
bagli = true;
}
} İstemci soketin bağlanma girişimi port ve adres bilgisini gerektir.
Başarısızlık durumunda Connect işlevi 0 döndürür. İstemciye spesifik
gönderme işlevi:
UpdateData();
if(!metin.IsEmpty())
{
for(UCHAR a=0; a<256-metin.GetLength();a++ )metin+=�%�;
soket.Send(metin.GetBuffer(),metin.GetLength());
} Sunucu uygulamanın gönderme işlevi, transferi kabul edilen bağlantı
üzerinden yaparken, istemci bağlantı soketini kullanmaktadır. TSoket
sınıfının istemci ve sunucu için ortak olan ve açıklanması gereken tek
işlev; alma işini yapan OnReceive işlevidir:
alinan->GetWindowText(buf);
char *tampon=new char[256];
UINT okunan = Receive(tampon,256);
tampon[okunan]=0;
CString str;
str.Format("%sMuhatabım:\r\n %s",buf,tampon);
str.Remove(�%�);
str+="\r\n";
alinan->SetWindowText(str);
CSocket::OnReceive(nErrorCode); Öncelikle aldığımız mesajları alınan
kutusunda görüntülemek için, alinanlaraAt işleviyle daha önceden adresi
bildirilen alinan kontrolünün içeriğini Başlık kısmında üye değişken
olarak tanımlanmış buf isimli değişkene atmalıyız. bunu GetWindowText
işlevi yapmaktadır. Daha sonra alınan paketleri içine atmak için,
tasarım gereği 256 baytlık olan bir karakter dizisi tanımladık. Receive
işlevi, parametre olarak bu tampon belleğin adresini ve kaç karakter
okunacağını verip çağrıldığında, alınan bayt sayısını döndürür. Alinan
bayt sayisini alinan isimli değişkene atmamızın nedeni ise tampon
karakter dizisinin içerdiği metnin sonuna, sonlandırıcı karakter
eklemektir. Sonraki aşamada alınan mesajları uygun bir biçimde
formatlama işlemi yapılmaktadır. Paket boyunu 256�ya tamamlamak için
eklenmiş olan % karakterlerini alınan mesajdan attıktan sonra, mesajın
son halini SetWindowText işlevi ile alinan isimli kontrole atarak paket
alma işlemini tamamlamış oluyoruz.
Çok temel bir haberleşme uygulaması yapmış olduk. İleri düzey bir sohbet
yazılımı isterseniz, çoklu kullanıcı desteği ve ekonomik bir paket
yönetimi için kolları sıvamalısınız.
sohbet programları gibi ağ uygulamaları geliştirmek için saf API
kullanmak zahmetlidir. Bunun yerine MFC�nin CSocket sınıfını
kullanacağız. CSocket sınıfı, CAsyncSocket sınıfından türemiştir ve
CAsyncSocket�e göre WinsockAPI�den daha fazla soyutlanma sağlar. Genelde
uygulamalarda doğrudan CSocket�i kullanmak yerine, CSocket�ten
türetilmiş özel sınıflar kullanılır. CAsyncSocket�e ait olan Accept,
Receive, Send gibi sanal işlevler amaca göre değiştirilir.
Sohbet programlarında iletişim TCP/IP tabanlı olduğu için bir sunucuya
ve istemciye ihtiyaç vardır. İstemciler, dinleme durumundaki sunucunun
IP adresi bilgisini kullanarak sunucuya bağlanır ve bilgi aktarımı
sağlanır. Bundan dolayı sunucu ve istemci programımızı ayrı ayrı
yazacağız.
Uygulama
Sunucu ve istemci programlarımızı geliştirirken aşağıdaki adımlar
ortaktır. Öncelikle Visual C++ Projects kısmından MFC Application
seçilmeli, proje özelliklerinden de uygulamanın dialog tabanlı olduğu ve
Windows Sockets kullandığı belirtilmelidir.
Şekil 1 - Proje türü
Proje oluşturulduktan sonra kendi soket sınıfımızı oluşturmak için, ana
menüde Project�e tıklayarak Add Class seçilmeli. Bunu yaptıktan sonra,
eklemek istediğimiz sınıfın adını, türetileceği taban sınıfı ve hangi
dosyanın içine kaydedileceğini belirtebileceğimiz bir dialog
açılacaktır. Soket sınıfımızı CSocket�ten türeteceğimizi şu şekilde
belirtebiliriz:
Şekil 2 - CSocket�ten türemiş sınıf oluşturma
Türetilen bu sınıfın istemci için tasarımı:
class sohbetSoket : public CSocket
{
public:
sohbetSoket();
virtual ~sohbetSoket();
virtual **** OnAccept(int nErrorCode);
virtual **** OnClose(int nErrorCode);
virtual **** OnConnect(int nErrorCode);
virtual **** OnReceive(int nErrorCode);
virtual **** OnSend(int nErrorCode);
**** alinanlariAt(CEdit *sAlinan);
CEdit *alinan;
CString buf;
}; şeklinde olacak, ve sunucu versiyonunda fazladan sadece
sohbetSoket *kabul;
satırını içerecektir. Bu yeni türettiğimiz sınıfı ana dialog sınıfı
içinde kullanacağımız için başlık dosyasına:
#include"sohbetSoket.h"
satırını eklemeliyiz. Programımızın tasarımını Şekil 3�deki gibidir.
Şekil 3�deki kontrollerin üzerine tıkladığınızda, tıkladığınız kontrolün
ID�sini ve atanacak değişkeni görebilirsiniz.
Şekil 3 - Projenin görünümü ve kontrollere atanan değişkenler
Her iki formda da ortak olan değişkenlerden port isimli değişken,
haberleşmenin hangi porttan yapılacağını belirtir. alinan isimli metin
kutusu karşı taraftan alinanlari barindirmak için vardır. Gönder
düğmesine basılınca metin isimli metin kutusundaki yazılanlar
gönderilir.
Sunucu ve istemci sınıfının üye değişkenleri şunlardır:
bool bagli; //sadece istemci sınıfında
bool dinlemede; //sadece sunucu sınıfında
TSoket soket;
//kontrollere atanan otomatik oluşturulmuş değişkenler:
CString adres;
UINT port;
CString alinan;
CString metin; Öncelikle hem sunucu hem de istemci için TSoket sınıfına,
Receive işlevi ile aldığı paketleri hangi kontrole atacağını, ana
dialogun OnInitDialog işlevinde şu şekilde bildirmeliyiz:
soket.alinanlariAt((CEdit*)GetDlgItem(IDC_ALINAN)) ; TSoket sınıfının
alinanlaraAt işlevini daha sonra açıklayacağız. Sunucu tarafında Dinle
düğmesine tıklandığında işletilecek kodu yazarak devam edelim:
//Bağlan/Kop
if(!dinlemede)
{
UpdateData();
soket.Create(port);
soket.Bind(port);
soket.Listen();
dinlemede = true;
SetWindowText("Sunucu - Aktif");
SetDlgItemText(IDC_DINLE,"Vazgeç");
}
else
{
soket.Close();
SetWindowText("Sunucu - Aktif değil");
bagli = false;
dinlemede = false;
SetDlgItemText(IDC_DINLE,"Dinle");
} UpdateData işlevi port için belirtilen değeri kontrolden değişkene
atmak için kullanıldı. Sunucu tarafı soketi oluşturmak için kullanılan
Create işlevi, parametre olarak port numarasını alır. İstemci için
soketin yaratılması sırasında parametre verilmez. Bind işlevi,
uygulamamızın, aktif olarak çalışılan makineye -parametre olarak verilen
porttan yapılan- bütün bağlantıları üstlenmesini sağlar. Yani Bind(x)
işlevinin çağrılması ile x portunda yapılan bütün bağlantıları
programımız ele alacaktır. Son olarak Listen işlevi ile dinleyeme,
belirtilen porttan yapılacak bağlantıları izlemeye başlayabiliriz. Fakat
uygulamamız dinlemede ise Close işlevi ile soketi durdurmuş ve
kendisine yapılan referansları geçersiz kılmış oluyoruz. CAsyncSocket
sınıfının yokedicisi bu işlevi bizim için otomatikman çağırır. Kodda
geçen diğer işlevler ise dinlemede olup olmadığımız bilgisini
görüntülemek için kullanılmaktadır. TSoket sınıfının tasarımına geçmeden
önce, sınıfın sunucuya özel olan OnAccept işlevini de açıklayalım:
kabul = new TSoket();
kabul->alinanlariAt(alinan);
Accept(*kabul);
CSocket::OnAccept(nErrorCode); Sunucu soketlerde OnAccept işlevi
sunucuya bir bağlantı yapıldığında otomatik olarak çağrılır. Gelen
bağlantıyı kabul etmek için Accept işlevini çağırmalı ve parametre
olarak da bu bağlantıyla yapılacak olan bütün transferleri kontrol
edecek bir soket verilmelidir. Bu soketin kalıcı olması için TSoket
sınıfına üye bir değişken olarak tanımlanmalıdır.
Gönder düğmesine tıklandığında yapılacaklar ise şu şekildedir:
UpdateData();
if(!metin.IsEmpty())
{
for(UCHAR a=0; a<256-metin.GetLength();a++ )metin+=�%�;
soket.kabul->Send(metin.GetBuffer(),metin.GetLength());
} Burda yapılan şey, öncelikle UpdateData işlevi ile gönderilecek metnin
kontrolden, metin isimli değişkene aktarılması , ardından da metni 256
bayta % karakterleri ile tamamlanması ve Send işlevini metin ve boyutu
ile çağrılmasıdır. Programımız temel bi uygulama olduğu için göndereceği
bütün paketler 256 bayttan oluşmaktadır. Daha ileri uygulamalarda
boyut, dinamik olarak belirlenmeli ve paketin boyut bilgisi önden
gönderilmelidir. Send işlevi gönderme işlemini başaramadığı takdirde
SOCKET_ERROR döndürecektir.
İstemci uygulamamızın Bağlan düğmesine tıklayınca belirtilen IP adresine
bağlanması için yazılması gereken kod:
if(!bagli)
{
UpdateData();
soket.Create();
if(!soket.Connect(adres,port))
{
SetWindowText("İstemci - Bağlanamadı");
soket.Close();
soket.ShutDown();
}
else
{
SetWindowText("İstemci - Bağlı");
SetDlgItemText(IDC_BAGLAN,"Kop");
bagli = true;
}
} İstemci soketin bağlanma girişimi port ve adres bilgisini gerektir.
Başarısızlık durumunda Connect işlevi 0 döndürür. İstemciye spesifik
gönderme işlevi:
UpdateData();
if(!metin.IsEmpty())
{
for(UCHAR a=0; a<256-metin.GetLength();a++ )metin+=�%�;
soket.Send(metin.GetBuffer(),metin.GetLength());
} Sunucu uygulamanın gönderme işlevi, transferi kabul edilen bağlantı
üzerinden yaparken, istemci bağlantı soketini kullanmaktadır. TSoket
sınıfının istemci ve sunucu için ortak olan ve açıklanması gereken tek
işlev; alma işini yapan OnReceive işlevidir:
alinan->GetWindowText(buf);
char *tampon=new char[256];
UINT okunan = Receive(tampon,256);
tampon[okunan]=0;
CString str;
str.Format("%sMuhatabım:\r\n %s",buf,tampon);
str.Remove(�%�);
str+="\r\n";
alinan->SetWindowText(str);
CSocket::OnReceive(nErrorCode); Öncelikle aldığımız mesajları alınan
kutusunda görüntülemek için, alinanlaraAt işleviyle daha önceden adresi
bildirilen alinan kontrolünün içeriğini Başlık kısmında üye değişken
olarak tanımlanmış buf isimli değişkene atmalıyız. bunu GetWindowText
işlevi yapmaktadır. Daha sonra alınan paketleri içine atmak için,
tasarım gereği 256 baytlık olan bir karakter dizisi tanımladık. Receive
işlevi, parametre olarak bu tampon belleğin adresini ve kaç karakter
okunacağını verip çağrıldığında, alınan bayt sayısını döndürür. Alinan
bayt sayisini alinan isimli değişkene atmamızın nedeni ise tampon
karakter dizisinin içerdiği metnin sonuna, sonlandırıcı karakter
eklemektir. Sonraki aşamada alınan mesajları uygun bir biçimde
formatlama işlemi yapılmaktadır. Paket boyunu 256�ya tamamlamak için
eklenmiş olan % karakterlerini alınan mesajdan attıktan sonra, mesajın
son halini SetWindowText işlevi ile alinan isimli kontrole atarak paket
alma işlemini tamamlamış oluyoruz.
Çok temel bir haberleşme uygulaması yapmış olduk. İleri düzey bir sohbet
yazılımı isterseniz, çoklu kullanıcı desteği ve ekonomik bir paket
yönetimi için kolları sıvamalısınız.