"Herkese selamlar, dostlar. Bir önceki konumuzda (Game Server Management Tool) Metin2, Knight Online gibi oyunlarda uzaktan sunuculara bağlanmak için kullanıcı adı ve şifre girilmesi sonucunda sunucuya giriş sağlamıştık. 'Bu ne işimize yaradı?' der gibisiniz.
İşte bu konuda yavaş yavaş onlara değineceğiz. Öncelikle, bir oyun sunucusuna bağlanmak—bunu panel olarak da adlandırabiliriz—konusuna değinelim. Sunucu panelinde belirli işlemler yapılır: sunucuya reset atılır, kaç kanal açılacağı veya kapanacağı belirlenir. Sunucu veritabanı yedeklenir. Daha saymakla bitiremeyeceğim birçok özellik eklenebilir; bu, yazılımcıya göre değişir. Peki, bir sunucu açtık ve bunun yanında bir de client'imiz var, bunu değerlendirelim. Bir chat sunucusu oluşturalım ve link attığımız herkes .exe ile giriş yapabilsin, fakat sadece bizim istediğimiz kişiler giriş yapabilsin. Bu eklentiyi de ekleyelim ki yazılımımız daha eğlenceli bir hal alsın."
Dilerseniz başlayalım.
Bu makaledeki yazılım işlemleri c++ dili yazılmış olup; iostream, string, thread, vector, algorithm, winsock2.h, ws2tcpip.h, fstream kütüphanelerinden yararlanılmıştır.
Öncelikle Server.cpp dosyamıza gelelim ve orada bir fonksiyon tanımlayalım. Bu fonksiyonum işlevi ise sunucuya bağlanan tüm clientlere mesajları iletmek olsun. Yani Bir client bir şeyler yazdığında bu sunucuya ulaşsın bu sunucuya ulaşan bilgi mesajı gönderen client hariç diğer tüm clientlere iletsin.
"Yukarıdaki kodda öncelikle void client_bekleyici() adında bir fonksiyon tanımladık. Daha önceki konumuzdan da hatırlayacağınız üzere clientSocket adında, accept() işlemleri için bir soket tanımlamıştık. Bu soketi client_bekleyici fonksiyonumuzda da kullanacağımızdan ötürü parametre olarak belirtiyoruz ki fonksiyon içerisinde çağırdığımızda sorun çıkmasın. Daha sonra, istemciden gelen mesajları tutması için bir char buf tanımlıyoruz. Her ihtimale karşı içeriğini sıfırlıyoruz, çünkü içi dolu olabilir veya boş olabilir; işimizi garantiye almış olduk. Bir verial int değişkeni tanımlayıp recv fonksiyonu ile bufdan gelen verileri veriala yazdık.
Kodda önemli olan diğer bir parametre ise for döngüsü içerisinde yer alan auto anahtar kelimesidir. Hatırlarsanız önceki konumuzda istemcilerin tutulduğu clients adında bir soketimiz vardı. Bağlanan tüm istemciler, clients soketinde depolanıyordu. Bu işlem, vector sayesinde olmaktaydı. Clients soketimizde yer alan tüm istemcilere, yani istemcilerimize gelen mesaj paketimizi iletiyoruz. Auto anahtar kelimesi, clients içerisindeki istemcinin adres bilgisini clienta yazar. Daha sonra, if koşuluna girer; eğer istemci ile gönderen aynı değilse o istemciye paket ulaştırılır, aksi halde geçilir. Bu sayede gönderen hariç herkese mesaj gider."
Eğer client yukarıdaki socket_error koşuluna yada verial == 0 koşuluna girerse break durumuna düşer ve soketi kapatıp istemciyi silmemiz gerekir.
Şimdi bu fonksiyonu bağlantıların sürekli dinlendiği while döngümüze dahil edelim fakat burada bir çakışma söz konusu olacaktır bu neden thread ile yeni bir iş parçacığı oluşturup bu fonksiyonu bağımsız halde çalıştırmamız gerekecek bu sayede while döngüsü ile hem istemci bağlantımız dinlenecek hem de gelen mesajları sürekli olarak dinleyebileceğiz.
Görüldüğü üzere iş parçacığımızı oluşturduk ve clientSocket üzerinden gelen bilgileri client_bekleyici fonksiyonuna aktarım işlemi yapmak için socketimizi belirttik. Eğer bu durum else durumuna düşerse yani kullanıcı bilgiler fonksiyonundan false cevap alırsa soketi kapatıyoruz ve clienti serverdan dışarı atıyoruz. Server.cpp dosyamızdaki işlemler bu kadar.
Şimdi gelelim client.cpp dosyamıza bu dosyamızda iki fonksiyon tanımlamamız gerekiyor. İlk fonksiyon sunucudan gelen mesajı dinlemek aslında diğer clientlerden gelen mesajı almak için yazıyoruz çünkü server tarafından iletilmekteydi. İkinci fonksiyonumuz ise mesaj gönderme fonksiyonumuz olacaktır.
İşlemler görüldüğü üzere aynı. Bu aşamaları anlatmıştık zaten burayı geçiyorum ve mesaj yazma kısmına geliyorum.
"Burada önemli olan parametremiz, send fonksiyonunun içerisindeki parametrelerdir. sock parametresi, soketimizin adıdır yani istemci ile bağlantı kurduğumuz soket ismidir. userInput.c_str() parametresi ile userInput'dan alınan verileri send fonksiyonunun anlayacağı şekle çeviriyoruz. userInput.size() + 1 parametresi ise input'tan girilen bilgilerin tamamının alındığını garantiye almak için +1 ifadesini ekliyoruz. Yani, gönderilen mesajın sonunda \0 (null terminator) karakteri de bulunur, bu da cümlenin sonunu belirtir. 0 parametresi ise flag olarak adlandırılır ve genellikle varsayılan olarak 0 girilir.
İşlemler tamam. Bu fonksiyonumuzu da main fonksiyonumuza ekleyelim. Daha önceki konumuzda client için thread oluşturmuştuk. Burada iki fonksiyonumuz için thread oluşturacağız: biri sunucudan mesaj almak için, diğeri ise mesaj göndermek için. Çünkü ikisi de while döngüsüne sokuldu ve paralel çalışmaya tabi tutulmazsa birisi çalışır, diğeri bekler."
Evet anlattığım üzere artık sorunsuz şekilde iki fonksiyonda aynı anda çalışacak. Şimdi serverimizi ve clientimizi compiler edip başlatıp giriş yapıp konuşmaya çalışalım.
Görüldüğü üzere başarılı şekilde çalışıyor. Local ağdan çıkartıp port açarak yada ngrok üzerinden tünelleme kurarak global şekilde sohbet edebilirsiniz. Bir önceki konumda bu aşamayıda göstermiştim. Bir konumuzun daha sonuna geldik umarım eğlenmişsinizdir.
Dilerseniz başlayalım.
Bu makaledeki yazılım işlemleri c++ dili yazılmış olup; iostream, string, thread, vector, algorithm, winsock2.h, ws2tcpip.h, fstream kütüphanelerinden yararlanılmıştır.
Öncelikle Server.cpp dosyamıza gelelim ve orada bir fonksiyon tanımlayalım. Bu fonksiyonum işlevi ise sunucuya bağlanan tüm clientlere mesajları iletmek olsun. Yani Bir client bir şeyler yazdığında bu sunucuya ulaşsın bu sunucuya ulaşan bilgi mesajı gönderen client hariç diğer tüm clientlere iletsin.
"Yukarıdaki kodda öncelikle void client_bekleyici() adında bir fonksiyon tanımladık. Daha önceki konumuzdan da hatırlayacağınız üzere clientSocket adında, accept() işlemleri için bir soket tanımlamıştık. Bu soketi client_bekleyici fonksiyonumuzda da kullanacağımızdan ötürü parametre olarak belirtiyoruz ki fonksiyon içerisinde çağırdığımızda sorun çıkmasın. Daha sonra, istemciden gelen mesajları tutması için bir char buf tanımlıyoruz. Her ihtimale karşı içeriğini sıfırlıyoruz, çünkü içi dolu olabilir veya boş olabilir; işimizi garantiye almış olduk. Bir verial int değişkeni tanımlayıp recv fonksiyonu ile bufdan gelen verileri veriala yazdık.
Kodda önemli olan diğer bir parametre ise for döngüsü içerisinde yer alan auto anahtar kelimesidir. Hatırlarsanız önceki konumuzda istemcilerin tutulduğu clients adında bir soketimiz vardı. Bağlanan tüm istemciler, clients soketinde depolanıyordu. Bu işlem, vector sayesinde olmaktaydı. Clients soketimizde yer alan tüm istemcilere, yani istemcilerimize gelen mesaj paketimizi iletiyoruz. Auto anahtar kelimesi, clients içerisindeki istemcinin adres bilgisini clienta yazar. Daha sonra, if koşuluna girer; eğer istemci ile gönderen aynı değilse o istemciye paket ulaştırılır, aksi halde geçilir. Bu sayede gönderen hariç herkese mesaj gider."
Eğer client yukarıdaki socket_error koşuluna yada verial == 0 koşuluna girerse break durumuna düşer ve soketi kapatıp istemciyi silmemiz gerekir.
Şimdi bu fonksiyonu bağlantıların sürekli dinlendiği while döngümüze dahil edelim fakat burada bir çakışma söz konusu olacaktır bu neden thread ile yeni bir iş parçacığı oluşturup bu fonksiyonu bağımsız halde çalıştırmamız gerekecek bu sayede while döngüsü ile hem istemci bağlantımız dinlenecek hem de gelen mesajları sürekli olarak dinleyebileceğiz.
Görüldüğü üzere iş parçacığımızı oluşturduk ve clientSocket üzerinden gelen bilgileri client_bekleyici fonksiyonuna aktarım işlemi yapmak için socketimizi belirttik. Eğer bu durum else durumuna düşerse yani kullanıcı bilgiler fonksiyonundan false cevap alırsa soketi kapatıyoruz ve clienti serverdan dışarı atıyoruz. Server.cpp dosyamızdaki işlemler bu kadar.
Şimdi gelelim client.cpp dosyamıza bu dosyamızda iki fonksiyon tanımlamamız gerekiyor. İlk fonksiyon sunucudan gelen mesajı dinlemek aslında diğer clientlerden gelen mesajı almak için yazıyoruz çünkü server tarafından iletilmekteydi. İkinci fonksiyonumuz ise mesaj gönderme fonksiyonumuz olacaktır.
İşlemler görüldüğü üzere aynı. Bu aşamaları anlatmıştık zaten burayı geçiyorum ve mesaj yazma kısmına geliyorum.
"Burada önemli olan parametremiz, send fonksiyonunun içerisindeki parametrelerdir. sock parametresi, soketimizin adıdır yani istemci ile bağlantı kurduğumuz soket ismidir. userInput.c_str() parametresi ile userInput'dan alınan verileri send fonksiyonunun anlayacağı şekle çeviriyoruz. userInput.size() + 1 parametresi ise input'tan girilen bilgilerin tamamının alındığını garantiye almak için +1 ifadesini ekliyoruz. Yani, gönderilen mesajın sonunda \0 (null terminator) karakteri de bulunur, bu da cümlenin sonunu belirtir. 0 parametresi ise flag olarak adlandırılır ve genellikle varsayılan olarak 0 girilir.
İşlemler tamam. Bu fonksiyonumuzu da main fonksiyonumuza ekleyelim. Daha önceki konumuzda client için thread oluşturmuştuk. Burada iki fonksiyonumuz için thread oluşturacağız: biri sunucudan mesaj almak için, diğeri ise mesaj göndermek için. Çünkü ikisi de while döngüsüne sokuldu ve paralel çalışmaya tabi tutulmazsa birisi çalışır, diğeri bekler."
Evet anlattığım üzere artık sorunsuz şekilde iki fonksiyonda aynı anda çalışacak. Şimdi serverimizi ve clientimizi compiler edip başlatıp giriş yapıp konuşmaya çalışalım.
Görüldüğü üzere başarılı şekilde çalışıyor. Local ağdan çıkartıp port açarak yada ngrok üzerinden tünelleme kurarak global şekilde sohbet edebilirsiniz. Bir önceki konumda bu aşamayıda göstermiştim. Bir konumuzun daha sonuna geldik umarım eğlenmişsinizdir.

