- Soru
Şu başlık dosyalarını nereden edinebilirim?
- Yanıt
Eğer sisteminizde bunlar mevcut değilse, muhtemelen zaten
ihtiyacınız olmadığı içindir. Üzerinde çalıştığınız platformun
belgelerini inceleyin. Eğer MS Windows ortamında derlemeye
çalışıyor iseniz tek ihtiyacınız olan
#include <winsock.h>.
- Soru
bind() "Address already in use" veya
"Adres zaten kullanımda" mesajı verdiğinde ne yapmalıyım?
- Yanıt
- Soru
Sistemdeki açık soketlerin listesini nasıl alabilirim?
- Yanıt
netstat komutunu kullanın. Bu komutla ilgili
ayrıntılar için ilgili man sayfasını okuyun. Basit bir örnek
vermem gerekirse sadece:
yazarak bile epey mantıklı bir çıktı alabilirsiniz. Buradaki asıl
mesele hangi soketin hangi program ile ilişkili olduğunu
bulmak
[141].
- Soru
Yönlendirme tablosunu nasıl görüntüleyebilirim?
- Yanıt
Kısaca: route komutunu çalıştırın
(Linux için bu komutun yeri genellikle /sbin
dizinidir) veya netstat -r komutunu deneyin.
- Soru
Eğer tek bir bilgisayarım varsa istemci-sunucu türünde programları
nasıl çalıştırabilirim? Bir ağ programı yazabilmek için bir ağa
ihtiyacım yok mu?
- Yanıt
Şanslısınız çünkü hemen hemen tüm işletim sistemleri bir tür
geridönüş (loopback) aygıtı denilen sanal bir ağ aygıtı kullanır.
Bu aygıt işletim sistemi çekirdeğine gömülü bir mekanizmadır ve
tıpkı fiziksel bir ağ kartı gibi davranır böylece aynı makinada
hem sunucu hem de istemci yazılımlar çalışabilir.
(Bu sanal aygıt yönlendirme tablosunda "lo"
olarak listelenir.)
Bir örnek vermek gerekirse "goat" isimli bir
bilgisayara giriş yaptığınızı varsayalım. Bir pencerede istemciyi
diğerinde de suncuyu çalıştırabilirsiniz. Ya da sunucuyu şuna
benzer bir komut ile arka planda çalıştırın:
"server &" ve sonra aynı yerde istemciyi
çalıştırın. Geridönüş aygıtı dediğimiz sanal aygıt sisteminizde
mevcut ise (%99.9 olasılıkla mevcuttur) client goat
veya client goat gibi bir komutla
("localhost"un sizdeki /etc/hosts
dosyasında tanımlı olduğunu varsayıyorum) istemci yazılımın sunucu
yazılım ile aynı makina üzerinde konuştuğunu gözlemleyebilirsiniz.
Bir ağa ihtiyaç duymadan!
Kısaca söylemek gerekirse ağa bağlı olmayan bir bilgisayarda
yukarıda verilmiş olan örnek kodları çalıştırabilmek için
herhangi bir değişiklik yapmanıza gerek yoktur. Yaşasınnn!
- Soru
Diğer tarafın bağlantıyı kestiğini nasıl tespit edebilirim?
- Yanıt
recv() işlevi öyle bir durumda 0
değerini döndürür.
- Soru
Kendi "ping" programımı nasıl yazabilirim? ICMP nedir?
Raw soketler ve SOCK_RAW ile daha ayrıntılı
bilgiyi nerede bulabilirim?
- Yanıt
Raw soketler ile ilgili tüm sorularınızın cevabını W. Richard Stevens'ın
"UNIX Network Programming" kitaplarında bulabilirsiniz. Lütfen
kılavuzun
kitaplar bölümüne bakın.
- Soru
Bu programları MS Windows üzerinde nasıl derlerim?
- Yanıt
- Soru
Bu programları Solaris/SunOS üzerinde nasıl derlerim?
Derlemeye çalıştığımda linker sürekli hata veriyor!
- Yanıt
- Soru
select() neden sinyal alır almaz düşüyor?
- Yanıt
Sinyaller genellikle bloklu sistem çağrılarının -1
değerini döndürmesine ve errno değişkeninin de
EINTR değerini almasına yol açar. Sinyal
işlevi sigaction() ile ayarladığınızda,
SA_RESTART kullanabilirsiniz. Böylece sistem
çağrısı kesildiğinde onu yeniden başlatabilme ihtimali olur.
Doğal olarak bu her zaman işe yaramaz.
Bunun için önereceğim en iyi çözüm bir goto
deyimine dayanır. Bildiğiniz gibi bu size yapısal programlama
dersi veren profesörün tüylerinin diken diken olmasına yeter de
artar bile, o halde neden kullanmayasınız!
select_restart:
if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
if (errno == EINTR) {
// bir sinyal kesildi o halde yeniden baslat
goto select_restart;
}
// gerçek hata ile burada ugras:
perror("select");
}
Tabii ki burada goto kullanmanız şart
değil; kontrol için başka yapıları da kullanabilirsiniz,
ancak ben özel olarak bu iş için goto deyiminin
daha temiz olduğunu düşünüyorum.
- Soru
recv() işlevi için bir zamanaşımı mekanizmasını
nasıl kurabilirim?
- Yanıt
select() işlevini
kullanın! Bu işlev okuyacağınız soket tanımlayıcıları için bir
zamanaşımı parametresi belirlemenize izin verir. Ya da bu işlevselliğin
tamamını tek bir işlev içine şu şekilde gömmelisiniz:
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
int recvtimeout(int s, char *buf, int len, int timeout)
{
fd_set fds;
int n;
struct timeval tv;
// dosya tanımlayıcı listesini ayarla
FD_ZERO(&fds);
FD_SET(s, &fds);
// zamanaşımı için struct timeval türündeki değişkeni ayarla
tv.tv_sec = timeout;
tv.tv_usec = 0;
// veri gelene veya zamanaşımı dolana dek bekle
n = select(s+1, &fds, NULL, NULL, &tv);
if (n == 0) return -2; // zamanaşımı!
if (n == -1) return -1; // hata
// veri burada olmalı, o halde normal şekilde recv() çağır
return recv(s, buf, len, 0);
}
// recvtimeout() örneği:
.
.
n = recvtimeout(s, buf, sizeof(buf), 10); // zamanaşımı 10 saniye
if (n == -1) {
// hata olustu
perror("recvtimeout");
}
else if (n == -2) {
// zamanaşımı!
} else {
// buf içine veri geldi
}
.
.
| Dikkat edin |
---|
recvtimeout() işlevi zamanaşımı durumunda
-2 değerini döndürür. Neden 0
değil? Hatırlayacak olursanız 0 değeri
recv() işlevi söz konusu olduğunda karşı
tarafın bağlantıyı kestiği anlamına geliyordu. Bu değer
kullanılmış olduğu için ve -1 de hata
anlamına geldiğinden zamanaşımı durumunu göstermesi için
-2 değerini seçtim.
|
- Soru
Veriyi soket üzerinden göndermeden önce nasıl şifreleyip sıkıştırabilirim?
- Yanıt
Şifrelemeyi gerçekleştirmenin kolay yollarından biri SSL
(secure sockets layer - güvenlik soketleri katmanı) kullanmaktır
ancak bu konu bu kılavuzun kapsamı dışındadır.
Ancak eğer sıkıştırma ve şifreleme algoritmalarınızı kullanmak
istiyorsanız verinin her iki uçta da adım adım işlendiğini
düşünmek işinizi kolaylaştıracaktır. Her adım veriyi belli bir
şekilde dönüştürür.
sunucu veriyi bir dosyadan (veya bir yerlerden) okur,
sunucu veriyi şifreler (bu kısmı siz ekleyeceksiniz),
sunucu şifrelenmiş veriyi send() ile yollar.
Şimdi de öteki tarafa bakalım:
istemci recv() ile kendisine yollanan
şifreli veriyi alır,
istemci şifreli veriyi çözer yani deşifre eder (bu kısmı siz
ekleyeceksiniz).
Yukarıda şifreleme/deşifreleme yaptığınız aşamada sıkıştırma/açma
işlemleri de yapabilirsiniz. Ya da hem şifreleme hem de sıkıştırma
yapabilirsiniz! Yalnız aklınızda bulunsun, bir veriyi eğer
şifreleyecekseniz önce sıkıştırın sonra şifreleyin
[142] :)
Yani istemci, sunucunun uyguladığı dönüşümlerin tersini
uygulayabildiği sürece araya istediğiniz kadar dönüşüm, işlem,
işlev, vs. sokabilirsiniz.
Tek yapmanız gereken verdiğim örnek kodda verinin gönderildiği
ve alındığı kısımları tespit bunların öncesine şifreleme
işlemini yerleştirmek.
- Soru
Bir sürü yerde gördüğüm şu "PF_INET" de nedir?
AF_INET ile bir bağlantısı var mıdır?
- Yanıt
- Soru
İstemciden kabuk komutlarını kabul edip onları çalıştıran bir
sunucu yazılımını nasıl geliştirebilirim?
- Yanıt
Basit olsun diye varsayalım ki istemci connect()
ile bağlanıyor, send() ile veriyi gönderiyor ve
close() ile bağlantıyı kesiyor (yani istemci
tekrar bağlanana dek bir sistem çağrısı söz konusu olmuyor).
İstemcinin izlediği süreç şöyledir:
connect() ile sunucuya bağlan,
send("/sbin/ls > /tmp/client.out"),
close() ile bağlantıyı kes.
Bu arada sunucu gelen veri ile muhatap olur ve onu çalıştırır:
accept() ile bağlantıyı kabul et,
recv(str) ile komut dizisini al,
close() ile bağlantıyı kes,
system(komut) ile komutu çalıştır.
| Dikkat |
---|
İstemcinin sunucuya ne yapacağını söylemesi uzaktan kabuk erişimi vermek
demektir. Böyle bir yetkiye sahip olan bir insan kötü şeyler yapabilir.
Mesela yukarıdaki gibi bir programda istemci
"rm -rf ~" gibi bir komut gönderirse ne olur?
Sizinle ilişkili alandakı tüm dosyalar silinir, işte budur
olacağı!
|
Bu yüzden akıllı olun ve kesinlikle güvenli olduğunuz programlar
haricinde uzaktaki istemcinin sizin sunucunuzda hiçbir şey
çalıştırmasına izin vermeyin, örneğin foobar
komutu gibi:
if (!strcmp(str, "foobar")) {
sprintf(sysstr, "%s > /tmp/server.out", str);
system(sysstr);
}
foobar güvenilir ve sorunsuz bir komut
olabilir ama gene de şüpheci olmalısınız ya istemci
"foobar; rm -rf ~" gibi bir komut dizisi
yollarsa? En güvenli yöntemlerden biri komuta verilen
parametrelerdeki alfanümerik olmayan her karakterin (boşluk
dahil) başına önceleme karakteri ("\")
koyan bir işlev yazmaktır.
Gördüğünüz gibi sunucu tarafında istemcinin gönderdiği komutları
çalıştırma gibi bir durum söz konusu olunca güvenlik çok önemli
bir mesele haline gelmektedir.
- Soru
Bir yığın veriyi bir seferde yollamaya çalışıyorum ama diğer
taraftan recv() ile okumaya kalktığımda
sadece 536 veya 1460 byte geldiğini görüyorum. Ancak aynı
denemeyi kendi makinamın üzerinde iki farklı pencere açıp
yaptığımda sorunsuz olarak veri yollayıp alabiliyorum.
Bunun sebebi nedir?
- Yanıt
MTU sınırını -- fiziksel ortamın bir seferde kaldırabileceği
azami yük miktarını aşıyorsunuz. Makinanızdaki sanal geridönüş
aygıtı sürücüsü 8K ya da daha fazlasını bir seferde sorunsuz
olarak iletebilir. Ancak Ethernet bir seferde başlık bilgisi ile
birlikte en fazla 1500 byte taşıyabilir ve siz de bu limiti
aşmış durumdasınız. Modem üzerinden veri yollamaya kalktığınızda
576 byte sınırı vardır ve yine bunu tek seferde geçerseniz
sorun yaşarsınız.
Öncelikli olarak tüm veriyi yolladığınızdan emin olmalısınız.
(Bunun için lütfen
sendall()
ile ilgili ayrıntılı açıklamalara bakın.) Bundan eminseniz buna
ek olarak verinin tamamını okuyana dek bir döngü içinde
recv() işlevini çağırmanız gerekir.
recv() işlevini defalarca çağırarak verinin
tamamını alma ile ilgili ayrıntılı açıklamalar için lütfen
Veri Paketlemesi Hazretleri bölümünü okuyun.
- Soru
Bilgisayarımda MS Windows çalışıyor ve elimde fork()
gibi bir sistem çağrısı olmadığı gibi struct sigaction
şeklinde bir yapı da yok. Ne yapabilirim?
- Yanıt
Eğer varsalar POSIX kitaplıkları içindedirler ve bunlar da
derleyiciniz ile gelmiş olabilir. Ben Windows kullanmıyorum bu
yüzden size kesin cevap veremeyeceğim. Fakat hatırladığım kadarı
ile Microsoft'un da kullandığı bir POSIX uyumluluk katmanı vardı
ve işte aradığınız fork() olsa olsa oradadır.
(Hatta belki sigaction bile.)
VC++ ile gelen belgeleri "fork" veya "POSIX" için bir tarayın ve
size hangi ipuçlarını verdiğine bir bakın.
Eğer mantıklı bir şey çıkamazsa fork()/sigaction
ikilisini bırakın ve şunu deneyin: CreateProcess().
Açıkçası ben CreateProcess() işlevi tam olarak
nasıl kullanılır bilmiyorum -- milyarlarca argüman alıyor olmalı ama
VC++ belgelerinde hepsi açıklanıyor olmalı.
- Soru
TCP/IP üzerinde veriyi güvenli ve şifreli bir şekilde nasıl
iletebilirim?
- Yanıt
- Soru
Bir güvenlik duvarının arkasındayım -- diğer taraftaki insanların
benim IP adresimi öğrenip benim sistemime bağlanmalarını nasıl
sağlarım?
- Yanıt
Maalesef bir güvenlik duvarının amacı tam da dışarıdaki
insanların doğrudan sizim makinanıza bağlanmasını engellemektir.
Bu şekilde içeri bağlanmaları çoğu durumda bir güvenlik açığı
olarak kabul edilir.
Bu tabii ki talebinizin imkânsız olduğu anlamına gelmez yani
gene de güvenlik duvarı üzerinde connect()
yapabilirsiniz tabii bir "maskeleme" ya da "NAT" veya benzer
bir şey söz konusu ise. Programlarınızı daima bağlantıyı sizin
başlatacağınız şekilde tasarlayın, böylece işiniz kolay olur.
Eğer bu sizi tatmin etmiyorsa sistem yöneticilerinize size özel
bir delik açmalarını söyleyebilirsiniz. Güvenlik duvarı,
üzerindeki NAT yazılımı ile ya da vekil tarzı bir şey ile bunu
gerçekleştirebilir.
Ancak lütfen unutmayın ki güvenlik duvarı üzerindeki bir delik
hafife alınabilecek bir durum değildir. Kötü niyetli kişilere
erişim hakkı vermediğinizden emin olmalısınız. Eğer bu konularda
henüz acemiyseniz bana inanın ki güvenli programlar yazmak hayal
edebileceğinizden çok daha zordur.
Sistem yöneticilerinizin benden nefret etmesine yol açmayın.
;-)