Veri Paketlemesi Hazretleri
Önceki İleri Teknikler Sonraki
Veri Paketlemesi Hazretleri
Veri paketlemesi de ne demek oluyor? En basit anlamına bakacak olursak bu şu anlama gelir: Verinin başına ya onu tanımlayan bir başlık bilgisi koyacaksınız ya uzunluk bilgisi ya da her ikisi birden.
Başlık deyip durduğumuz şey neye benzer? Bu tamamen size kalmış ve projeden projeye değişebilen bir şeydir.
Ama! Bu açıklamalar biraz havada kalmadı mı?
Tamam. Mesela çok kullanıcılı bir chat programı geliştirdiğinizi ve bu programın da SOCK_STREAM türünden soketler kullandığını var sayalım. Kullanıcılardan biri bir şey dediğinde sunucuya gitmesi gereken iki bilgi parçası vardır: Kim dedi ve ne dedi?
Buraya kadar güzel. "Problem ne?" diye soruyor olabilirsiniz.
Problem şu: Gelip giden mesajların uzunluğu değişken. "Ali" isimli biri "Selam" diyebilir ve sonra da "Veli" isimli biri "naber moruk?" diyebilir.
Bu durumda siz de send() ile gelen verileri tüm istemcilere yollarsınız öyle değil mi ve yolladığınız veri de şu şekilde olur:
A l i S e l a m V e l i N a b e r m o ru k ?
Ve böyle sürüp gider. Peki istemci bir paketin bitip diğerinin başladığını nasıl anlayacak? Eğer isterseniz tüm paketlerin boyunu sabit yapabilirsiniz ve sonra da sendall() işlevini çağırırsınız ( yukarıda bir örneği var). Fakat bu bant genişliğini boş yere harcamak demektir! Yani herhalde send() ile sadece Ali'nin "Selam" demesi için 1024 byte göndermek istemeyiz değil mi?
İşte bu yüzden veriyi ufak tefek bir başlık ve paket yapısı ile paketleriz. Hem sunucu hem de istemci verinin nasıl paketleneceğini (marshal) ve bu paketin nasıl açılacağın (unmarshal) bilir. Sıkı durun çünkü tam da şu andan itibaren bir sunucu ile istemcinin hangi kurallara göre iletişim kuracağını belirleyen bir protokol tanımlamaya başlıyoruz!
Şimdi var sayalım ki kullanıcı adı en fazla 8 karakter boyunda olabiliyor ve eğer daha kısa ise geriye kalan bölge '\0' ile dolduruluyor. Ve sonra varsayalım ki mesaj uzunluğu değişken olabiliyor ve maksimum 128 karaktere izin veriyoruz. Buna göre örnek bir pakete bakalım:
  1. len (1 byte, unsigned) -- Paketin toplam boyu, 8 baytlık kullanıcı ismi ve chat mesajı dahil.
  2. name (8 byte) -- Kullanıcı ismi, gerekirse sonu NULL ile doldurulmuş.
  3. chatdata (n-byte) -- Gönderilecek mesajın kendisi. En fazla 128 byte olabilir. Paket boyu bu verinin boyu artı 8 (kullanıcı ismi bölümünün uzunluğu) olarak hesaplanabilir.
Neden söz konusu veri alanları için sırasi ile 8 byte ve 128 byte sınırlarını seçtim? Tamamen keyfime kalmış, bana yeterince uzun göründü. Ama belki de sizin ihtiyaçlarınız için 8 byte çok az gelebilir ve siz de 30 byte uzunluğunda bir isim alanı kullanabilirsiniz. Size kalmış.
Yukarıdaki paket tanımlamasını kullanarak ilk paketimizin görüntüsü şuna benzeyecektir (onaltılık ve ASCII):
      0D     41 6C 69 00 00 00 00 00      53 65 6C 61 6D
  (uzunluk)  A  l  i     (dolgu)          S  e  l  a  m
Ve ikincisi de benzer şekilde:
      13     56 65 6C 69 00 00 00 00      4E 61 62 65 72 6D 6F 72 75 6B 3F
  (uzunluk)  V  e  l  i    (dolgu)        N  a  b  e  r  m  o  r  u  k  ?
(Uzunluk bilgisi de elbette Ağ Byte Sıralamasına uygun depolanmalıdır. Ancak mevcut örnekte tek bir byte olduğu için çok önemli değil. En genel durumda ise sakın unutmayın ki tüm ikili düzendeki tamsayılarınız Ağ Byte Sıralamasına göre dizilmiş olmalıdır.)
Bu veriyi yollarken güvenli bir şekilde, mesela sendall() gibi bir işlevle yollamalısınız. Böylece birçok kez send() işlevini çağırmış olmak gerekse de gittiğinden emin olabilirsiniz.
Benzer şekilde bu veriyi aldığınızda çözmek için biraz iş yapmanız gerekir. En genel durumda verinin ancak bir kısmının gelmiş olabileceğini göz önünde bulundurmalısınız (misal sadece "00 13 56 65 6C" size ulaşmış olabilir Veli isimli kullanıcıdan. Bu ilk parça gördüğünüz gibi eksiktir ve verinin tamamına erişene dek birkaç kez daha recv() işlevini çağırmanız gerekebilir.
İyi de nasıl? Paketin en başına paket uzunluğu konmuş olduğundan en baştan itibaren bir paketin orjinal uzunluğunu biliriz. Aynı zamanda biliriz ki azami paket boyu da 1+8+128 olarak hesaplanabilir yani toplam olara 137 byte (çünkü biz böyle tanımladık).
Bu durumda yapabileceğiniz şeylerden biri iki paketi barındırabilecek büyüklükte bir dizi tanımlamaktır. Burası sizin çalışma diziniz olacak ve paketler buraya vardıkça onları buradan alıp düzenleyeceksiniz.
recv() işlevini her çağırışınızda gelen veriyi çalışma dizisine aktarırsınız ve paketin tamamlanıp tamamlanmadığına bakarsınız. Yani dizi içindeki byte miktarının paket boyuna eşit mi yoksa ondan büyük mü olduğunu kontrol edersiniz (+1 çünkü başlıktaki uzunluk kendisi için kullanılan 1 byte'lık uzunluğu içermez). Eğer tampondaki byte sayısı 1'den az ise o zaman paket tam değildir açık olarak. Bu durumu özel olarak ele almalısınız, çünkü bu ilk byte çöplüktür ve paket boyu için ona güvenemezsiniz.
Paket tamamlandığında artık onun üzerinde istediğiniz işlemi gerçekleştirebilirsiniz. Onu kullanabilir, çalışma dizinizden çıkarabilirsiniz.
Nasıl? Kafanızda evirip çevirmeye başladınız mı? Sıkı durun şimdi ikinci darbe geliyor: bir paketi sonuna kadar okumakla kalmayıp aynı esnada bir sonraki paketin de bir kısmını son recv() çağrısında okumuş olabilirsiniz. Yani çalışma bölgenizde bir tam paket ve bir de ardından gelen bir kısmi paket vardır! (İşte çalışma tamponunu biraz geniş tutun dememin sebebi bu idi. Yani iki paketi tutabilecek kadar -- işte şimdi o durum gerçekleşti!)
İlk paketin boyunu bildiğinize ve gelen byte'ları da takip ettiğinize göre bu sayıları kullanarak çalışma tamponunuzdaki baytlardan hangisinin ilk pakete hangisinin de kısmi pakete ait olduğunu tespit edebilirsiniz. Birincisi ile uğraşmayı bitirince onu çalışma dizisinden çıkarıp bir sonraki paketin parçasını tampon başlangıcına taşıyabilirsiniz ve böylece tampon da bir sonraki recv() çağrısı için hazır hale gelir.
(Bazı okuyucular belki fark etmiştir, kısmi olarak gelen ikinci paketi tampon başına taşımak biraz vakit alabilecek bir iştir ve program buna gerek duymaması için döngüsel tampon (circular buffer) kullanacak şekilde kodlanabilir. Maalesef döngüsel tamponla ilgili tartışma bu makalenin alanı dışına düşmektedir. Gerçekten merak ettiyseniz Veri Yapıları ile ilgili güzel bir kitap alıp ilgili bölümü okuyun)
Kolay olduğunu söylememiştim. Şey, aslında söylemiştim ve öyleydi. Öyledir, yani eğer pratik yapmaya başlarsanız gerisi çorap söküğü gibi gelecektir. Excalibur üzerine yemin ederim öyle olacağına!
Önceki Üst Ana Başlık Sonraki
Sorunlu send() Durumları Başlangıç Diğer Kaynaklar
Bir Linux Kitaplığı Sayfası