![](themes/DeepBlue/images/pixel.gif) |
Ana Menü | ![](themes/DeepBlue/images/pixel.gif) |
Forumlar | ![](themes/DeepBlue/images/pixel.gif) |
| ![](themes/DeepBlue/images/pixel.gif) |
Socket Programlama'ya Giriş
(2421 kelime) (612 okuma) ![Yazdırılabilir Sayfa](images/print.gif)
Socket Programlama'ya Giriş
Bu dökümanda ağ üzerinden oynanan çok
kullanıcılı (multiplayer) oyunları programlamak için kullanılan socket
iletişim mekanizmasının kullanımını örneklerle inceleyeceğiz. Ortam
olarak windows ve Visual C++ 6.0 kullanılacak. Fakat kod ufak
değişiklikler ile başka sistemlere de kolaylıkla aktarılabilir.
Multiplayer?
Günümüzde internet kullanımının yaygınlaşması ile beraber programlar
arasında haberleşme birçok uygulamanın vazgeçilmez parçası haline
geldi. Oyunlarda internet çılgınlığından nasibini aldı. Bir anda her
yandan MMORPG (Massive Multiplayer Online Role Playing Game : çok
oyunculu devasa rol yapma oyunları) 'ler fırlamaya başladı. Counter
Strike - Quake - Battlefield gibi oyunlar net cafelerde deliler gibi
oynanılır oldu.
Artık insanlar evlerinde kendi başlarına oyun oynamak istemiyorlar. En
azından eskisi kadar istemiyorlar. Kendileri kadar iyi oynayabilecek
-gerçek- rakipler ile oynamak çok daha çekici geliyor. Belli başlı oyun
firmaları da bu ihtiyacı görüp bir şekilde oyunlarına çok kişinin aynı
anda oynayabilmesi özelliğini (multiplayer) eklemeye çalışıyor. Çok
oyunculu ortam artık o kadar oyunların ayrılmaz bir özelliği olarak
görünmeye başlandı ki, hepimiz oyun yorumlarındaki "güzel bir oyun, ama
ne yazıkki çoklu oynama desteği yok" gibisinden lafları görmeye alışır
hale geldik. Multiplayer desteği grafik, müzik, senaryo kadar
oyunların bir parçası artık.
Multiplayer oyunların temelinde bilgisayarların haberleşmesi bulunuyor.
Her bilgisayarda çalışan oyun, oyuncunun kararları, hareketleri
doğrultusunda işleyişini sürdürürken, bir yandan da oyuncunun oyundaki
konumunu, yaptığı işi, yani oyuna etkisini diğer oyunculara,
oyuncuların bilgisayarlarına göndermek zorunda. Bu şekilde aynı oyunu
oynayan birçok kişi aslında o oyunun ortak parçası olabiliyor.
Günümüz oyunlarında genel olarak kullanılan haberleşme mantığı
istemci-sunucu (client-server) mimarisi olarak adlandırılır. Bu
yöntemde sunucu bilgisayar olarak adlandırılan bilgisayarda çalışan bir
program bulunur. İstemci bilgisayarlar ise teker teker oyuncu
bilgisayarlarıdır. Oyuncular oyunlarını bu bilgisayarlarda
çalıştırırlar ve multiplayer için sunucu bilgisayara bağlanırlar.
Oyuncular arasındaki tüm haberleşme sunucu bilgisayar üzerinden
gerçekleşir.
Örnek olarak multiplayer oyunların ilklerinden olan Quake oyununu ele
alalım. Her oyuncuda Quake oyunu kurulu durumda. Oyunun tüm verileri,
grafikler, sesler, harita dosyaları kişisel bilgisayarda bulunuyor.
İnternet üzerinde herhangi bir yerde (yada kişisel ağda -LAN- bir
yerlerde) ise Quake sunucu programı çalışmakta. Multiplayer oyun
oynamak isteyenler quake oyunu içerisinden sunucunun IP adresini yada
internet alan adını (domain name) girerek sunucuya bağlanırlar. Daha
sonra oyun başlar..
Oyuncu bilgisayarlarındaki quake oyununun görevi temel olarak
kullanıcının
tuşlara basışlarını, ve mouse hareketlerini almak, bu bilgiler
doğrultusunda bakış açısını ve varsa ilerleme yönünü belirlemek, daha
sonra bu bilgileri sunucu bilgisayara göndermek.. Sunucu bilgisayar tüm
oyunculardan gelen hareket bilgilerine göre oyuncu - oyuncu ve oyuncu -
harita etkileşimlerini hesaplar (çarpışmalar, patlamalar, enerji ve
bonus değişimleri..) ve güncellenmiş oyuncu konumlarını ve oyunun
durumunu tekrar tüm oyuncu bilgisayarlarına gönderir. Oyunun ana
mantığı ve karar verme mekanizması aslında sunucu bilgisayarda
işlemektedir. Kişisel bilgisayarlardaki quake programları ise grafik
gösterme ve kullanıcıdan girdi almada kullanılmaktadır.
Bizde bu haberleşme dünyasına bir giriş yapmak istiyoruz. Ama
nasıl?. Aslında çok da zor değil.. İlk amacımız bilgisayarların bir
şekilde haberleşmesini gerçekleştirmek olmalı. Bilgisayarlar arasındaki
haberleşmeyi programlarken "socket mekanizması" olarak adlandırılan
yapıyı kullanacağız.
İnternet ortamında iki bilgisayar arasında haberleşmek demek, bir
bilgisayardan çıkan veri bloklarının istenilen başka bir bilgisayara
ulaşması demektir. Veri her zaman bloklar halinde iletilir. Bu bloklara
ise genel literatürde paket adı verilir. Quake örneğinde bu paketler
içerisinde oyuncunun gidiş yönü, ateş edip etmediği gibi bilgiler
bulunmakta idi.. Her paketin bir çıkış bilgisayarı birde gidiş
bilgisayarı var. Peki bir paket hangi bilgisayara gideceğini nasıl
biliyor? Her bilgisayarın internet üzerinde diğer bilgisayarlardan
ayırdedilmesini sağlayan bir adresi var. Bu adrese IP (Internet
Protokol) adresi diyoruz. Oluşturulan paketlerin başına gideceği
bilgisayarın
IP adresi bilgisi de işlenir. Bu sayede paketin adresine ulaştırılması
kurulu internet - ağ altyapısı ile sağlanabilir. IP adresi yanında Port
numarası olarak adlandırılan bir adres daha var. Port ları basit olarak
nete bağlı bir bilgisayarın haberleşme kanalları olarak
düşünebilirsiniz. Her program haberleşmek için belirli bir portu
kullanmak zorundadır. Portlar numara:0 dan başlayıp numara 65537 ye
kadar devam ederler. Bir program örneğin 13456 numaralı portu
haberleşme için kullanıyor ise, başka bir program bu portu kullanamaz.
Bilgisayarınız bir apartman ise: portları kapı numaraları olarak
düşünebilirsiniz. Mahallenize gelen bir paket hangi apartmana
(bilgisayara) gideceğini IP numarası (posta kodu+mahalle+apartman nosu)
ile bulur.. Apartman içinde hangi daireye gidileceği ise port numarası
(kapı
nosu) ile belirlidir.. Bilgisayarınız bir apartman unutmayın :)..
Birde bağlantı türü denilen kavram var. Bu kavram aslında
paketlerinizin iletilmesinden sorumlu alt yapının (işletim
sistemi,ethernet kartı, driver programı) iletimi sağlama metodu
ile ilgili. İki tip var. Bağlantı tabanlı (connection oriented) ve
bağlantı tabanlı olmayan (connectionless).. İlk tip aynı zamanda TCP
ikinci tipse UDP olarak ta adlandırılır. Aslında iki tiptede bizim için
çok değişen bir şey yok. Sadece uygulamada ufak farklılıklar ile temel
birkaç işlevsel farklılık bulunmakta. İlk tipte sanal olarak iki
haberleşen bilgisayar arasında bir haberleşme köprüsü kurulumu
sağlanır. Buna bağlantı diyoruz. Bağlantı başarı ile sağlandığı zaman
artık iki bilgisayar arasında gönderilen paketler sistem tarafından
güzelcene sıra ile iletilir.. Sizde bağlantı kopmadığı sürece bu
paketlerin yerlerine gönderdiğiniz sıra ile gittiğinden emin olup
mutlu olabilirsiniz. Her hangi bir sebepten dolayı bir paket gideceği
yere iletilemediği zaman, iletişim sistemi otomatik olarak tekrar
göndermeyi deneme işlemini gerçekleştirir. İkinci yapıda ise, bağlantı
kurma diye bir şeyden
söz edilmez. Paketlerinizi adresi belli olan bir bilgisayara
gönderirsiniz. Sistem bu paketlerin o bilgisayara başarı ile ulaştığını
garanti edecek ve doğrulayacak bir mekanizmaya sahip değildir. Böyle
bir mekanizmayı sizin programınız ile kurmanınz gerekir. 1. yöntem daha
güvenli iken , ikinci yöntem ise daha hızlıdır. Oyunlarda genel olarak
2. yöntem kullanılır. Çünkü oyun haberleşmesinde hız çok ama çok
önemlidir.
Client (İstemci)
Bu kadar teori yeter.. Hadi uygulamaya geçelim.. İlk olarak basit bir
istemci yazılımı (quake?.. hayır daha değil.. :) ) programlayacağız.
Yaptığımız program ile
sunucu bilgisayara "merhaba" mesajını yollamak istiyoruz.
// İlk olarak winsock
kütüphanesini linker a belirtelim ve ana socket
header ını include edelim..
#pragma comment( lib,
"wsock32.lib" )
#include <stdio.h>
#include <winsock2.h>
int main(int argc,char **argv) {
// Şimdi de sistemdeki winsock alt
yapısını aktif hale getirmek için
aşağıdaki kodu yazalım..
WSAData wsaData;
if (WSAStartup(MAKEWORD(1, 1),
&wsaData) != 0) {
printf("socket
sistemini başlatmada hata! (WSAStartup...)");
}
// Bu işlemin %99.9 başarı ile
tamamlanması gerekir..
// Şimdi ise karşı haberleşmeye
geçeceğimiz bilgisayarın (karşı taraf)
// adresini belirmek için bir
yapıyı uygun şekilde dolduralım..
// dolduracağımız yapı bu
sockaddr_in
sinIgInterfaceSend_remote;
// bu parametre AF_INET olmalı
sinIgInterfaceSend_remote.sin_family
= AF_INET;
// bu parametreye karşı tarafın IP
adresi yada host adı gelecek..
// 127.0.0.1 IP adresi aslında
kendi bilgisayarımızı belirtir. Bu adresi
// kullanarak kendi
bilgisayarımızdaki bir sunucu ya paket gönderebiliriz.
// programlarımızı test ederken bu
IP adresi çok işimize yarayacak.
sinIgInterfaceSend_remote.sin_addr.s_addr
= inet_addr("127.0.0.1");
// karşı tarafın port numarası
sinIgInterfaceSend_remote.sin_port
= htons(19998);
// ve bloğun bir kısmını
sıfırlamamız gerekiyor..
memset(&sinIgInterfaceSend_remote.sin_zero,0,8);
// Şimdide iletişimde
kullanacığımız socket i oluşturalım..
// SOCK_DGRAM parametresi
haberleşme yöntemi olarak UDP kullanacağımızı
belirtiyor.
SOCKET socket_send =
socket(AF_INET,SOCK_DGRAM,0);
//Evet artık UDP haberleşmesi için
gerekli hazırlıkları tamamladık.
// Şimdi sıra karşı tarafa veri
göndermekte.. Bu iş için sendto(...)
foksiyonunu/ kullanacağız.
const char *mesaj = "merhaba";
int result =
sendto(socket_send,mesaj,strlen(mesaj),0,(struct sockaddr
*)&sinIgInterfaceSend_remote,sizeof(struct sockaddr));
if (result == SOCKET_ERROR)
printf("socket
hatası");
else
printf("veri
gönderildi: gönderilen byte:
%d",result);
return 0;
}
// sendto(...) fonksiyonunu biraz
açıklamamız gerekiyor.
// Bu fonksiyon ile bir socket
üzerinden istediğimiz veriyi
gönderebiliyoruz.
// Fonksiyonun aldığı
parametreleri sırayla açıklamak gerekirse:
// socket_send: haberleşmede
kullandığımız socket
// mesaj: göndermek istediğimiz
veriyi işaret eden bir char* tipinde
pointer
// strlen(mesaj): göndermek
istediğimiz mesajın uzunluğu..
// Burayı byte cinsinden doldurmak
gerekiyor..
// Ben kolaylık olsun diyerek
strlen(..) fonksiyonunu kullandım,
istersek direk 7 de yazabilirdik..
// 0: bu parametre flags olarak
geçer .. bizim durumumuzda 0 olması
gerekiyor.
// (struct sockaddr
*)&sinIgInterfaceSend_remote: veri
göndereceğimiz makineyi belirten adres yapısına pointer..
// sizeof(struct sockaddr): adres
yapısının boyutu..
// Göndermede bir hata durumu söz
konusu ise sendto(...) fonksiyonu
// SOCKET_ERROR hata kodunu
döndürür.
// İşlem başarılı ise gönderilen
byte sayısı geri döner (bizim
örneğimizde 7 geri dönecektir..)
İşte oldukça basit bir istemci uygulaması yazdık. Çok da zor olmadı
değil mi?.. Şimdi birde sunucu uygulaması yazalım.. Bu program da
istemciden aldığı mesajı ekrana yazdırsın..
Server (Sunucu)
// İstemcide yaptığımız bazı ön
işlemleri tekrar yapıyoruz..
#pragma comment( lib,
"wsock32.lib" )
#include <stdio.h>
#include <winsock2.h>
int main(int argc,char **argv) {
WSAData wsaData;
if (WSAStartup(MAKEWORD(1, 1),
&wsaData) != 0) {
printf("socket
sitemini başlatmada hata! (WSAStartup...)");
}
// Şimdide veri almak istediğimiz
port numarasını sisteme belirtmek
için bir yapıyı doldurmamız gerekiyor.
sockaddr_in
sinIgInterfaceRecv_local;
// Bu parametre AF_INET olmalı
sinIgInterfaceRecv_local.sin_family
= AF_INET;
// Bu paramtre de aynen korunacak.
sinIgInterfaceRecv_local.sin_addr.s_addr
= htonl(INADDR_ANY);
// Bu parametrede veriyi
okuyacağımız portu belirtiyoruz.
// Buraya istemci uygulamada
kullandığımız port numarasını aynen
yazmamız gerekiyor.
sinIgInterfaceRecv_local.sin_port
= htons(19998);
// Bu tarafıda sıfırlayalım..
memset(&sinIgInterfaceRecv_local.sin_zero,0,8);
// İstemcide olduğu gibi socket
imizi oluşturuyoruz..
SOCKET igsocket_recv =
socket(AF_INET,SOCK_DGRAM,0);
// Bu noktaya kadar istemi ile
sunucu kodları arasında pek bir fark yok.
// Sunucu uygulamasında veri
almaya başlamadan önce yapmamız gereken
// son bir adım daha kaldı. Bu
adım ile hazırladığımzı adres yapısını
socket e
// bağlıyoruz. Yani socket imizi
adreslendirmiş oluyoruz aslında.
if (bind(igsocket_recv, (struct
sockaddr*)
&sinIgInterfaceRecv_local, sizeof(struct sockaddr)) < 0) {
printf("socket
bağlama hatası!");
}
// bind(...) fonksiyonunun geri
dönüş değeri negatif ise bağlamada bir
hata oluşmuş demektir.
// bu tarz hatalar genelde
kullanımda olan bir porta bağlanmaya
çalıştığımızda
// ortaya çıkar..
// Ve şimdide son olarak sıra veri
okumaya geldi.. Veri okumak için
recvfrom(...) fonksiyonunu kullanacağız.
char mesaj[256];
int result =
recvfrom(igsocket_recv,mesaj,7,0,0,0);
if (result != SOCKET_ERROR)
printf("alınan
mesaj: %s",mesaj);
return 0;
}
// recvfrom(...) fonksiyonunun
kullanımı sendto(...) fonksiyonuna göre
daha kolay görünüyor.
// ilk parametre olarak
kullanacağımız socket i belirtiyoruz.. daha
sonra okunan verinin aktarılacağı
// bir bölge belirtmemiz
gerekiyor.. Sonraki parametre okumak
istediğimiz verinin boyutu..
// İstemcinin 7 byte uzunluğunda
bir veri gönderdiğini bildiğimiz için
direk olarak 7 sayısını verdik..
// Son 3 parametreyi 0 olarak
verebilirsiniz.
// recvfrom(...) fonksiyonu veriyi
başarı ile okuduğu zaman geriye kaç
byte okuduğu bilgisini
// döndürür. Bir hata durumunda
ise (örneğin bağlantının kopması
gibi..) SOCKET_ERROR
// dönecektir. Mesaj uzunluğuna
istediğiniz değeri girebilirsiniz.. 7
yerine 255 de girebilirdik..
// Bu durumda bize ulaşan veri her
zaman 7 byte olacağından 7 byte
okunup, mesaj
// alanına yazılacaktı.
Okuyacağınız verinin uzunluğunu bilmediğiniz
zamanlarda daha
// büyük bir alan ve sayı
belirtmek işinize yarayabilir..
Evet basitçene bir client - server uygulaması yazdık. Bu uygulamanın
özellikle sunucu kısmı ile ilgili açıklığa kavuşturulması gereken bir
kaç ayrıntısı var diye düşünüyorum. recvfrom(...) fonksiyonu önemli. Bu
fonksiyon socket den veri okumaya çalışıyor. Peki veri olmassa ne olur.
Bu durumda bu fonksiyon veriyi okuyana kadar beklemeye girecektir..
Buna bloklama (blocking..) diyoruz. Yazacağınız sunucu nun temel
döngüsü şu şekilde olacaktır..
while (oyun_devam_eder) {
// veriyi al
recvfrom(...)
// veriyi işle
// her bağlı
oyuncuya veri gönder..
sendto(...)
}
Bloklama mekanizmasını kullanmayarakta sunucumuzu oluşturabilirdik..
Socket imizi oluştururken kullanacağımız bikaç değişik parametre ile
bunu sağlayabiliriz. Ama ben açıkçası bloklamalı socketleri
kullanıyorum ve şimdiye kadar da bir zararlarını görmedim.. Sadece
dikkat etmeniz gereken nokta: veriyi almak için ayrı bir thread
kullanmak uygulamanızın türüne göre oldukça kullanışlı olabilir..
Thread kullanımı için sitemizde bir döküman var..
Örnek Kod
Bu dökümanda çok basit olarak windows altında UDP socket haberleşmesi
konusunu anlatmaya çalıştım. Olabildiğince yalın bir dille konunun
üzerinden geçtim. Birçok ayrıntıyı fazla kafa karıştırmamak için
esgeçmek zorunda kaldım. Konu hakkında uygulama yapmayı düşünenlere
önerim: kesinlikle bu makale ile yetinmeyin. Örnek proje üzerinde
uğraştıktan sonra mutlaka bi google çekip (www.google.com) başka makale
ve örneklere de göz atın. Aşağıdaki linkten örnek uygulamayı
indirerek üzerinde değişikler yapıp konuyu daha iyi kavrayabilirsiniz.
Kolay gelsin.
[
örnek client-server uygulaması VC++6 proje dosyaları ]
Deniz Aydınoğlu :: 2004 :: www.oyunyapimi.org
|
[ Geri Dön: Oyun Yapımı (Genel) | Bölümler İndeksi ] |
|
| ![](themes/DeepBlue/images/pixel.gif)
|