2 Şubat 2016 Salı

Websocket protokolü ile binary veri transferi


HTML5 ile yapılan gerçek zamanlı uygulamalar gittikçe artıyor. Websocket protokolü bu uygulamalarda server-client iletişimini sağlayan vazgeçilmez bir araç. Veri akışının çok fazla olduğu bu tarz sistemlerde gecikmeyi ve bandwith'i asgariye indirmek için gönderilen verinin optimize edilmesi gerekir.

JSON

Böyle sistemlerde insanlar tarafından kolayca okunabilmek için tasarlanmış JSON gibi formatlar verimsiz kalacaktır. Bunun nedenini bir multiplayer oyun örneği ile açıklayayım, tüm kullanıcılara saniyede yaklaşık 30 kere yollanan genel durum bilgisi aşağıdaki gibi JSON formatında olsun:
[{"id":2,"point":500,"x":55.31234535,"y":32.312332},{"id":3,"point":3000,"x":3.34261345,"y":22.52112362}]
Üstteki JSON 106 Byte boyutunda. Şimdi, boyutunu düşürmek için detayları azaltarak biraz anlamsızlaştıralım.
[[2,500,55.31234535,32.312332],[3,3000,3.34261345,22.52112362]]
Parametre isimlerini yok sayarak boyutu 63 Byte'a düşürebildik. Ama 4 tam ve 4 ondalıklı sayıyı gönderebilmek için bu kadar yer harcamak hala büyük bir sıkıntı, çünkü muhtemelen pozisyon bilgisi saniyede en az 25 defa yollanacak ve kullanıcı sayısı 2'den çok daha fazla olacak.

Bilgiyi bu şekilde string tipinde yolladığımız zaman her karakter en az 1 byte yer kaplar. Bundan dolayı örneğin id'si 2 olan oyuncunun x pozisyonu  11 byte yer kaplıyor. Bu pozisyon bilgisini float32 tipinde yollayabilseydik sadece 4 byte yer kaplayacaktı.

Binary veri transferi

Pozisyon bilgilerini float32 tipi ile tutarsak kullanıcı başına x ve y toplam 8 byte yer kaplar. ID ve puanı da int16 tipi ile tutalım onlarda toplamda 4 byte'tır. Bu hesaba göre kişi başına 12 byte ile istediğimiz bilgileri gönderebileceğiz. Yani, üstteki örnekteki iki kişinin bilgisini 24 byte'a göndermiş olacağız. Peki, bunu Javascript ile nasıl başarırız?

Javascript'te binary dizilerini oluşturmak ve görüntülemek için işe yarar sınıflar var. Örneğin binary veri dizisini temsil etmek için ArrayBuffer nesnesi kullanılır. ArrayBuffer 'a veriler Typed Array'lar aracılığıyla eklenir. Typed Array'lar binary veri oluşturmayı ve binary veriye erişmeyi sağlarlar. Gelin bahsettiğim sınıfları kullanarak örnekteki durumu gerçekleştirelim:
var players = [
  {id: 2, point: 50, x:55.31234535, y: 32.312332},
  {id: 3, point: 100, x:3.34261345, y: 22.52112362}
];
var buf = new ArrayBuffer(12*players.length);
players.forEach(function(player, index){
    var info = new Uint16Array(buf, index*12, 2);
    info.set([player.id, player.point], 0);
    var position = new Float32Array(buf, index*12+4, 2);
    position.set([player.x, player.y]);
});
ws.send(buf);

Yukarıdaki kod bloğu ArrayBuffer nesnesini oluşturduktan sonra kullanıcıların id ve puan bilgisi int16, pozisyonları ise float32 Typed Array nesneleri yardımıyla ArrayBuffer'a ekliyor ve son olarak ArrayBuffer nesnesini websocket ile client'a gönderiyor.

Server binary veriyi gönderdi... Peki client nasıl bu veriyi okuyacak ? Javascript'te bunun için DataView adlı sınıf mevcut. ArrayBuffer'a veriyi nasıl koyduysak DataView ile aynı şekilde geri alıyoruz.

var ws = new WebSocket('wss://example.com/socket');
ws.binaryType = "arraybuffer";
ws.onmessage = function(msg) {
 var players = [];
  if(msg.data instanceof ArrayBuffer) {
       var dv = new DataView(msg.data);
       for(var i = 0; i < dv.byteLength / 12; i++) {
        var player = {};
          var player.id = dv.getUint16(0);
          var player.point = dv.getUint16(2);
          var player.positionX = dv.getFloat32(4);
          var player.positionY = dv.getFloat32(8);
        players[i] = player;
       }
  }
}

Yukarıda client, server'da bulunan players dizisini, server'dan aldığı bilgiyle tekrar oluşturdu.

Böylece gönderilen binary verilerin nasıl alınacağını da görmüş olduk. Bir dahaki yazımda websocket protokolündeki ping pong sinyallerinden(heartbeat messages) bahsetmeyi düşünüyorum. O zamana kadar hoşçakalın :)

Hiç yorum yok:

Yorum Gönder