Örnek Mesajlaşma Uygulaması

Eyl 06, 2013
  1. Uygulama için öncelikle Visual Studio'da boş bir proje açıllır ve adı "WCFChatUygulama" olarak belirlenir.



    Şimdi yazılacak olan sunucu ve istemci uygulamaları bu boş proje üzerine eklenecektir.

  2. Sunucu tarafının oluşturulması için öncelikle oluşturulan projeye sağ tıklanıp Add(Ekle) -> New Project(Yeni Proje) yolu izlenerek boş bir form uygulaması yaratılır ve adı "WCFSunucu" olarak belirlenir.



  3. Açılan ekranda References (Referanslar) kısmına sağ tıklanıp Add Reference (Referans Ekle) denilir ve gelen ekranda "System.ServiceModel" seçilerek Ok (Tamam) tıklanır.



  4. Sonrasında Solution Explorer (Çözüm Gezgini) kısmında "Form1.cs" klasörüne çift tıklanarak form uygulamasının tasarlanacağı ekran açılır ve açılan ekranda çıkan forma iki adet düğme eklenir. Bu düğmelerden biri sunucuya bağlanmayı, diğer düğme ise bağlantının kesilmesini sağlayacaktır.



  5. Solution Explorer (Çözüm Gezgini) kısmında "Form1.cs" klasörüne sağ tıklanarak "View Code" denilip kodların yazılacağı ekran açılır. Buraya, kullanılacak sınıfların bulunduğu aşağıdaki kütüphanelerin eklenmesi gerekmektedir.

    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.PeerResolvers;

  6. Kütüphanelerin eklenmesinin ardından, "CustomPeerResolverService" ve "ServiceHost" sınıflarından birer değişken oluşturulur ve formun çalışacağı başlangıç durumu için "Sunucu Durdur" düğmesinin "enabled" özelliği "false" olarak belirlenir:

            private CustomPeerResolverService cozucu;

            private ServiceHost sunucu;

     

            public WCFSunucu()

            {

                InitializeComponent();

                bt_SunucuDurdur.Enabled = false;

            }

     

    Ardından bu iki düğmenin click event (tıklanma olayları) içerisine, düğmelere basıldığında çalışacak metotlar yazılır.

  7. Öncelikle "Sunucu Başlat" düğmesinin metotlarıı incelenirse:

            private void bt_SunucuBaslat_Click
            (object sender, EventArgs e)

            {

                try

                {

                    cozucu = new CustomPeerResolverService();

                    cozucu.RefreshInterval = 
                    TimeSpan.FromSeconds(5);

                    sunucu = new ServiceHost(cozucu);

                    cozucu.ControlShape = true;

                    cozucu.Open();

                    sunucu.Open(TimeSpan.FromDays(1000000));

                    lb_Mesaj.Text = "Sunucu başarıyla başlatıldı";

                }

                catch (Exception ex)

                {

                    MessageBox.Show(ex.ToString());

                }

                finally

                {

                    bt_SunucuBaslat.Enabled = false;

                    bt_SunucuDurdur.Enabled = true;

                }

            }

    Burada öncelikle "Try" bloğunun içerisinde "CustomPeerResolverServices" sınıfından oluşturulan yeni nesne, önceden  yaratılmış "cozucu" değişkenine atanmıştır. Sonrasında "SeviceHost" sınıfından yeni bir nesne oluşturulup "cozucu" nesnesi bu nesneye parametre olarak verilmiştir ve bu nesne, önceden oluşturulan "sunucu" değişkenine atanmıştır. Ardından bu nesnelerin "RefreshInterval", "ControlShape" gibi birtakım özellikleri belirlenmiştir. Ardından ise"CustomPeerResolverServices" ve "ServiceHost" sınıflarının "Open" metotları kullanılarak sunucu başlatılmıştır. "Finally" bloğunun içerisinde ise "Sunucu Başlat" düğmesinin "enabled" özelliğine "false", "Sunucu Durdur" düğmesinin "enabled" özelliğine ise "true"  değerleri atanmıştır.

  8. Şimdi de "Sunucu Durdur" düğmesinin metotları incelensin:

            private void bt_SunucuDurdur_Click(object sender,
            EventArgs e)
            {
                try
                {
                    cozucu.Close();
                    sunucu.Close();
                    lb_Mesaj.Text = "Sunucu durduruldu";
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }
                finally
                {
                    bt_SunucuBaslat.Enabled = true;
                    bt_SunucuDurdur.Enabled = false;
                }
            }

    Burada "Try" bloğunun içerisinde, "CustomPeerResolverService" ve "ServiceHost" sınıflarının "Close" metotları kullanılarak sunucu durdurulmuştur. "Finally"bloğunun içerisinde ise "Sunucu Başlat" düğmesinin "enabled" özelliğine "true", "Sunucu Durdur" düğmesinin "enabled" özelliğine ise "false" değerleri atanmıştır.

  9. İstemci tarafının oluşturulması için oluşturulan çözüme sağ tıklanıp Add(Ekle) -> New Project(Yeni Proje) yolu izlenerek boş bir form uygulaması yaratılır ve adı "WCFIstemci" olarak belirlenir.

     

  10. Form eklendikten sonra "Form1.cs" üzerine çift tıklanarak formun tasarlanacağı ekran açılır ve gerekli tasarım yapıldıktan sonra aşağıdaki gibi bir görünüm elde edilir. 

  11. Sunucu tarafında yapıldığı gibi istemci tarafında da "System.ServiceModel" projeye referans olarak eklenmelidir. Sonrasında kodların yazılacağı "Form1.cs"sayfasına aşağıda belirtilen kütüphaneler eklenmelidir.

    using System.ServiceModel;
    using System.ServiceModel.Channels;

  12. Gerekli kütüphaneler ve referanslar eklendikten sonra chat uygulaması için gerekli sınıflar oluşturulur. Öncelikle servis sözleşmesinin yapıldığıinterface(arayüz) aşağıdaki gibi yaratılsın:

        [ServiceContract(CallbackContract = typeof(IChatServisi))]
        public interface IChatServisi
        {
            [OperationContract(IsOneWay = true)]
                void Katil(string KullaniciAdi);
            [OperationContract(IsOneWay = true)]
               void Ayril(string KullaniciAdi);
            [OperationContract(IsOneWay = true)]
                void MesajGonder(string KullaniciAdi, 
                string mesaj);
        }

    Burada yaratılan sözleşme bir arayüz olarak tasarlanmış ve "ServiceContract" sözleşmesiyle imzalanmıştır. Arayüzün içerisinde tanımlanan her metot ise"OperationContract" sözleşmesiyle imzalanmıştır. "OperationContract" kullanılması bir zorunluluk değildir. Fakat bu servisi kullanan uygulamalarda kullanılması istenen bütün fonksiyonlar, bu kontrat ile imzalanmalıdır. Aksi halde kullanılamazlar. Burada yer alan "Katil", "MesajGonder" ve "Ayril"operasyonları asenkrondur ve tek yönlü çalışmaktadır. Bu yüzden "IsOneWay" özelliği "true" olarak atanmıştır. 

    Bu örnekte çift yönlü iletişim kurulacağından dolayı "DuplexChannel" sınıfı kullanılacaktır. Bunun için "CallbackContract" adıyla bir geri bildirim sözleşmesi tanımının yapılması gerekmektedir. Bu sözleşmede, oluşturulmuş olunan "IChatService" arayüzünün kendisi bildirilmektedir. Bu sayede ağ üzerindeki istemciler birbirleri arasındaki operasyonları yürütebilirler. 

    WCF'de bir serviste kullanılacak sınıflar "DataContract", üyeleri ise "DataMember" sözleşmeleriyle imzalanmalıdır.

  13. Şimdi de uygulamada kullanılacak diğer bir arayüz oluşturulur.

    public interface IChatKanal : IChatServisi, IClientChannel
    { }

    "IChatKanal" arayüzü, sözleşmelerin yapıldığı IChatServisi ve eklenen referanslar ile gelen IClientChannel'dan oluşturulmuştur. Bu arayüz, kendisinden türetilecek sınıflarda her iki arayüzdeki metotların kullanılmasının sağlanması amacıyla oluşturulmuştur.

  14. "ChatIstemci" sınıfından yazılmış sınıf incelenirse:

        public partial class ChatIstemci : Form, IChatServisi
        {
            private delegate void KullaniciKatildi(string isim);
            private delegate void KullaniciMesajGonder
            (string isim, string mesaj);
            private delegate void KullaniciAyrildi(string isim); 

            private static event KullaniciKatildi YeniKatilim;
            private static event KullaniciMesajGonder MesajGonder;
            private static event KullaniciAyrildi 
            KullaniciSil;            

            private string KullaniciAd;
            private IChatKanal kanal;
            private DuplexChannelFactory<IChatKanal> fabrika; 

            public ChatIstemci()
            {
                InitializeComponent();
                this.AcceptButton = bt_katilim;
            } 

            public ChatIstemci(string KullaniciAd)
            {
                this.KullaniciAd = KullaniciAd;
            } 

            private void bt_katilim_Click(object sender,
            EventArgs e)
            {
                if (!string.IsNullOrEmpty
                (tb_kullaniciAd.Text.Trim()))
                {
                    try
                    {
                        YeniKatilim += new KullaniciKatildi
                        (ChatIstemci_YeniKatilim);
                        MesajGonder += new KullaniciMesajGonder
                        (ChatIstemci_MesajGonder);
                        KullaniciSil += new KullaniciAyrildi
                        (ChatIstemci_KullaniciAyril);

                        kanal = null;

                        this.KullaniciAd =
                        tb_kullaniciAd.Text.Trim();
                        InstanceContext context =
                        new InstanceContext(
                            new ChatIstemci
                            (tb_kullaniciAd.Text.Trim()));
                        fabrika =
                            new DuplexChannelFactory<IChatKanal>
                            (context, "ChatEndPoint");
                        kanal = fabrika.CreateChannel();
                        kanal.Open();
                        kanal.Katil(this.KullaniciAd);
                        bt_katilim.Enabled = false;
                        tb_kullaniciAd.Enabled = false;
                    }

                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.ToString());
                    }
                }
            }

            #region KullanıcıListesi
            void ChatIstemci_KullaniciAyril(string isim)
            {
                try
                {
                    tb_mesajlar.AppendText("\r\n");
                    tb_mesajlar.AppendText(isim+" left at " + 
                    DateTime.Now.ToString());
                    lst_KullaniciListesi.Items.Remove(isim);
                }

                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }
            }

            void ChatIstemci_MesajGonder(string isim,
            string mesaj)
            {
                if (!lst_KullaniciListesi.Items.Contains(isim))
                {
                    lst_KullaniciListesi.Items.Add(isim);
                }

     

                tb_mesajlar.AppendText("\r\n");
                tb_mesajlar.AppendText(isim + "=>" + mesaj);
            }

            void ChatIstemci_YeniKatilim(string isim)
            {
                tb_mesajlar.AppendText("\r\n");
                tb_mesajlar.AppendText(isim+" --Katilim--->: [" + 
                DateTime.Now.ToString()+"]");
                lst_KullaniciListesi.Items.Add(isim);
            }

            #endregion

            #region IChatServisi Members

            public void Katil(string KullaniciAdi)
            {
                if (YeniKatilim != null)
                {
                    YeniKatilim(KullaniciAdi);
                }
            }

            public void Ayril(string KullaniciAdi)
            {
                if (KullaniciSil != null)
                {
                    KullaniciSil(KullaniciAdi);
                }
            }

            void IChatServisi.MesajGonder(string KullaniciAdi, 
            string mesaj)
            {
                if (MesajGonder != null)
                {
                    MesajGonder(KullaniciAdi, mesaj);
                }
            }

            #endregion

            private void bt_gonder_Click_1(object sender,
            EventArgs e)
            {
                    kanal.MesajGonder(this.KullaniciAd, 
                    tb_mesaj.Text.Trim());
                    tb_mesaj.Clear();
                    tb_mesaj.Select();
                    tb_mesaj.Focus();
            }
        }


    Burada öncelikli olarak "KullaniciKatildi", "KullaniciMesajGonder" ve "KullaniciAyrildi" isimli üç adet metot yaratılmış ve bu metotların türünden üç adet statik olay tanımı yapılarak "IChatKanal" ve "DuplexChannelFactory<IChatKanal>" türlerinden değişkenler tanımlanmıştır. Ardından sınıfın default constructor (yapıcı metot) tanımları yapılmıştır. 

    Örnekte "bt_katilim_Click" olayı incelendiğinde, "Kullanıcı Adı" için tanımlanan metin kutusuna girilen değer katılımcı olarak rol oynamaktadır. Bu yüzden"InstanceContext" örneği oluşturulduğunda, parametre olarakmetin kutusuna girilen değer alınır. Ardından "DuplexChannelFactory<IChatKanal>"sınıfından bir nesne örneği, oluşturulan "InstanceContext" nesnesi ve yapılandırma dosyasında belirtilen bitiş noktası değeri için yaratılmaktadır. Oluşturulan bu generic sınıfı sayesinde, çift yönlü iletişimi sağlayacak bir referans oluşturulmuş olunur. Ardından kullanıcıların her biri için, çift yönlü iletişimi sağlayacak kanalın oluşturulması için "CreateChannel" metodu kullanılmaktadır. Sonrasında ise oluşturulan bu kanalın "Open", "Katil", "Select" ve "Focus" metotları kullanılmıştır.

    "bt_gonder_Click" olayının içerisinde ise iletişimi sağlayan kanalın "MesajGonder" metodu kullanılarak bu metoda parametre olarak mesajı gönderen kullanıcının kullanıcı adı ve gönderilen mesajın yazıldığı metin kutusundaki değeri verilir.

  15. Son olarakta konfigürasyon(yapılandırma) dosyasındaki ayarların yapılması gerekmektedir. Sunucu tarafındaki ayarlar aşağıdaki gibi olmalıdır.


    <configuration>
      <system.serviceModel>
        <services>
         <service 
         name= "System.ServiceModel.PeerResolvers.CustomPeerResolverService">
            <host>
              <baseAddresses>
                <add baseAddress="net.tcp://127.0.0.1/WCFSunucu"/>
              </baseAddresses>
            </host>
            <endpoint address="net.tcp://127.0.0.1/WCFSunucu" 
                      binding="netTcpBinding"
                      bindingConfiguration="TcpConfig"
                      contract="System.ServiceModel.PeerResolvers
                      .IPeerResolverContract">
            </endpoint>
          </service>
        </services>

        <bindings>
          <netTcpBinding>
            <binding name="TcpConfig">
              <security mode="None"></security>
            </binding>
          </netTcpBinding>
        </bindings>
      </system.serviceModel>
    </configuration>

    Yukarıdaki yapılandırma dosyası için projede "service" kısmı ile belirtilen yerde projede kullanılan servis modelinin belirtilmesi gerekmektedir."baseAddresses" kısmında ise kullanılan yolun IP adresi verilmesi gerekmektedir. Yani sunucu olarak kullanılacak bilgisayarın ethernet kartının IP adresi verilmesi gerekmektedir. Bu da bütün bilgisayarlarda sabit olduğu için yukarıdaki örnekteki gibi "127.0.0.1" verilebilir. Ayrıca bu IP adresinde çalışan uygulamanın ismininde verilmesi gerekmektedir. "endpoint" olarak yazılan kısım da istemcilerden gelen istekler için bu uygulama son nokta olarak görev yapacağı için gerekli düzenlemeler ve bağlantılar belirtilmiştir. 

  16. İstemci tarafındaki yapılandırma da aşağıdaki gibi olmalıdır:

    <configuration>
      <system.serviceModel>
        <client>
          <endpoint name="ChatEndPoint" 
                    address="net.p2p://chatMesh/WCFSunucu"
                    binding="netPeerTcpBinding" 
                    bindingConfiguration="PeerTcpConfig"
                    contract="WCFIstemci.IChatServisi">   
          </endpoint>
        </client>

        <bindings>
          <netPeerTcpBinding>
            <binding name="PeerTcpConfig" port="0">
              <security mode="None"></security>
              <resolver mode="Custom">
                <custom address="net.tcp://127.0.0.1/WCFSunucu" 
                        binding="netTcpBinding"
                        bindingConfiguration="TcpConfig">
                </custom>
              </resolver>
            </binding>      
          </netPeerTcpBinding>

          <netTcpBinding>
            <binding name="TcpConfig">
              <security mode="None"></security>
            </binding>
          </netTcpBinding>
        </bindings>
      </system.serviceModel>
    </configuration>

    İstemci için yazılan yapılandırma dosyasında ise yine “endpoint” için gerekli ayarlamalar yapıldıktan sonra “custom” kısmında sunucun hangi IP de çalışdığı yazılmalıdır.

Yukarıdaki ayarlamalar da yapıldıktan sonra sunucuya bağlanan istemciler aralarında yazışma işlemini gerçekleştirebilirler.