Net Kütüphanesi Kullanımı
Net Kütüphanesi Nedir?
Net kütüphanesi, istemci ve sunucu arasında veri göndermenin birkaç yolundan biridir.
Net kütüphanesinin en büyük avantajları, usermessage ve konsol komutlarına kıyasla mesaj başına 65533 bayt (yaklaşık 64KiB) gibi büyük bir boyut sınırına sahip olması ve veriyi geriye doğru, yani istemciden sunucuya gönderebilmesidir.
Net Kütüphanesini Kullanma
Mesajları Önbelleğe Alma
Bir net mesajı gönderdiğinizde, büyük olasılıkla kısaltılmış bir URL içeren bir hata alırsınız. Bunun nedeni, mesajın önbelleğe alınmamış olmasıdır. Bir mesajı önbelleğe almak için, sunucu tarafında bir kez bunu çağırmanız yeterlidir:
Mesajları Alma
Net kütüphanesi ile veri gönderebilmek için, veri alındığında onu işleyecek bir şey olmalıdır. Bunun için iki argüman alan net.Receive fonksiyonunu kullanırız: net mesajının adı ve mesaj alındığında çalıştırılacak olan geri çağırma (callback) fonksiyonu.
Aşağıda bir net mesajı almanın bir örneği bulunmaktadır:
Net mesajımız "MyMessage" gönderildiğinde, alıcı fonksiyon çağrılır. Alıcı fonksiyonun ilk argümanı mesajın uzunluğudur (hata ayıklama veya ağ koruması için kullanılabilir), ikincisi ise onu gönderen oyuncudur; bu yalnızca net.SendToServer çağrıldığında kullanılır (bkz. aşağısı).
Mesajları Gönderme
Artık mesajı alacak bir fonksiyonumuz olduğuna göre, mesajı göndermemiz gerekiyor.
Bir net mesajı göndermeden önce, mesajın dize (string) adının sunucu tarafında bir kez util.AddNetworkString ile önbelleğe alınması gerekir.
Daha sonra bir net mesajı göndermeye başlamak için, mesajın adıyla net.Start fonksiyonunu çağırın. Sonra,
- Sunucu tarafı: Tek bir argüman alan net.Send fonksiyonunu kullanın: bir Player veya bir oyuncu tablosu. Tüm oyunculara gönderiyorsanız, net.Broadcast kullanın.
- İstemci tarafı: net.SendToServer kullanın.
Örneğin, myMessage adlı net mesajını sunucudaki ilk oyuncuya göndermek isteseydiniz, şunu yapardınız:
Boş bir net mesajı gönderiyoruz, şimdi net mesajlarında veri göndermeye bakacağız. Genellikle boş net mesajları, sunucuda gerçekleşen ve sunucu onlara bildirmedikçe istemcinin haberdar olmadığı bir şeyi oyuncuya bildirmenin basit bir yolu olarak kullanılır. Örneğin, sunucuda belirli bir süre geçtikten sonra, istemciye bir menünün açılması gerektiğini bildirmek için boş bir net mesajı gönderilebilir.
Veri Aktarımı
Veri göndermek için, net.Start fonksiyonunu çağırdıktan sonra, her biri kendi veri türü için olan aşağıdaki fonksiyonları kullanabilirsiniz:
- net.WriteBool
- net.WriteString
- net.WriteInt
- net.WriteUInt
- net.WriteFloat
- net.WriteDouble
- net.WriteAngle
- net.WriteVector
- net.WriteNormal
- net.WriteColor
- net.WriteTable
- net.WriteEntity
- net.WriteData
- net.WriteBit
- net.WriteType
Mesajı göndermek için sunucuda net.Send, istemcide ise net.SendToServer kullanın. Ayrıca sunucu tarafında net.Broadcast kullanarak bağlı olan her oyuncuya bir mesaj gönderebilirsiniz.
Veri okumak için, yukarıdaki her yazılabilir veri türü için genellikle net.Read(Tür) şeklinde fonksiyonlar mevcuttur.
Örnekler
Bunları denemeden önce hook'lar hakkında temel bir anlayışa sahip olmanız önerilir.
Örnek 1: İstemciye bir oyuncunun öldüğünü bildirme.
GM:PlayerDeath, yalnızca sunucu tarafında çağrılan bir hook'tur. Her oyuncuya bir oyuncunun öldüğünü bildirmek istersek, net kütüphanesini kullanabiliriz.
Sunucu tarafı:
İstemci tarafı:
Açıklama:
chat.AddText, yalnızca istemcide kullanabileceğimiz bir fonksiyon olduğundan, istemciye bir oyuncunun öldüğünü bildirmenin bir yolunu bulmamız gerekiyor. Çözüm basit, GM:PlayerDeath hook'unda, entity'yi istemciye göndermek için net.WriteEntity kullanın, entity'yi okuyun ve ardından istediğimiz renklerle chat.AddText'i çalıştırın.
Meydan Okumalar (Bunları yapmadan ilerlemeyin!):
- Herkesin, oyuncunun kim tarafından öldürüldüğünü görmesini sağlayabilir misiniz?
- Sadece saldıran oyuncuya bir oyuncu öldürdüğünü bildirecek şekilde düzenleyebilir misiniz?
Örnek 2: İstemciden sunucuya bilgi verme.
Oyuncunun kaç yaşında olduğunu bilmek istiyoruz ve aynı zamanda sunucudaki herkesin bunu bilmesini istiyoruz. Sorun şu ki, VGUI fonksiyonları yalnızca istemci tarafında çağrılabilir, ama endişelenmeyin, net kütüphanesi burada!
İstemci tarafı:
net.WriteEntity kullanarak sunucuya kim olduğumuzu bildirmediğimize dikkat edin. Bunun nedeni, sunucu tarafında net.Receive'ın ikinci argümanının mesajı gönderen oyuncu olmasıdır. Bu örnekten sonra net mesajı güvenliği hakkında daha derine ineceğiz.
Sunucu tarafı:
Meydan Okumalar (Bunları yapmadan devam etmeyin!): Yaş mesajının renkli görünmesini sağlayabilir misiniz? Mesajın aynı zamanda oyuncunun en sevdiği yemeği de göstermesini sağlayabilir misiniz?
Güvenlik
İstemciden sunucuya bir net mesajı gönderirken, büyük bir güvenlik riski oluşturursunuz. Bir anlığına birinin sunucu tarafında şu kodu yazdığını farz edelim:
Bu koda bir göz atın. Bu kod gerçekten bir sunucuda kullanılsaydı ne olabileceğini anlayabiliyor musunuz?
Hadi parçalara ayıralım. Bu net mesajı, BanPlayer net mesajı sunucuya gönderildiğinde bir oyuncuyu yasaklar. Ancak, oyuncunun gerçekten bir admin olup olmadığını kontrol eden hiçbir denetim yoktur. Bu, insanların kendi net mesajlarını, oluşturulduğu betiğin dışından göndererek sunucunuzdaki herhangi bir oyuncuyu yasaklama erişimi kazanabileceği anlamına gelir. Bunu önlemek için 1 basit kuralı izleyin:
İSTEMCİYE GÜVENMEYİN
Kullanıcı girdisi üzerindeki kontrolleri istemci tarafında yapıp sonra sunucu tarafında bunların sorunsuz olduğunu varsaymayın. Tembel olmayın ve veriyi sadece kolayca manipüle edilebildiği alanda kontrol etmeyin. Negatif sayılara, çok hızlı net mesajı gönderenlere, asla yazılmaması gereken entity'leri yazmaya çalışanlara ve daha birçok şeye karşı dikkatli olmalısınız. Kodunuzu yayınlamadan önce, onu kırmak için elinizden gelenin en iyisini yapın. Kullanıcıdan girdi istemeye devam edebilirsiniz, endişe bu girdinin geçerliliğindedir.
net.WriteEntity(LocalPlayer()) ve sunucu tarafında local caller = net.ReadEntity() yaptığınızda sık karşılaşılan bir sorundur. Bunu yapmayın, bu sunucu kodunuzu açığa çıkarır ve istismar edilebilir!Net fonksiyonlarınızı güvence altına almanın kolay bir yolu:
İyileştirme
Net iletişiminizi tasarlarken, onu olabildiğince etkili hale getirmek önemlidir.
İşte kontrol edebileceğiniz bazı bölümler:
Basitleştirme
Aynı verimlilikle ne kadar az veri gönderebilirseniz o kadar iyidir.
Her iki taraf için de zaten bilinebilecek verileri bulun.
Player:SendLua ve BroadcastLua kullanmaktan kaçınmaya çalışın.
Her seferinde tüm derma menüsünü istemciye göndermenize gerek yok. Bu menüyü açan bir istemci taraflı fonksiyon oluşturun ve sunucu tarafından boş bir net mesajı gönderin.
Önceki net maliyeti: mesaj başına (kodun uzunluğu + 1) * 8 bit.
Sonraki net maliyeti: mesaj başına 0 bit.
Int ve UInt'ler için uygun bit miktarını bulun
Yeni başlayan biri olarak net.WriteUInt ve net.WriteInt'in ikinci parametresinde 32 bit kullanmanıza izin verilir, ancak küçük sayılar gönderiyorsanız bu çok fazladır. net.WriteUInt ve net.WriteInt sayfalarında ihtiyacınız olan bit miktarını bulmak için kullanabileceğiniz tablolar bulunur.
Örneğin oyuncu seçtiği bir sayıyı gönderir, sayının 0 ile 64 arasında olması gerektiğini bilirsiniz. Bu sayı, etkilenecek oyuncu miktarını temsil eder (sunucuda 64 yuva vardır). Negatif değerlerimiz yok, bu yüzden net.WriteUInt kullanıyoruz. 65 sayıyı (0-64) sığdırmak için 7 bite ihtiyacımız var.
7 bit kesinlikle 32'den daha iyidir, ancak burada mantığımızı kullanabiliriz: eğer bu bir oyuncu tarafından gönderiliyorsa, en az bir oyuncu var demektir, bu yüzden 0 olamaz, oyuncunun 1'den 64'e kadar seçim yapmasını sağlayabiliriz. Bu hala 7 bit, ancak gönderirken 1 çıkarıp (ve okurken 1 ekleyerek) 6 bit ile yapabiliriz.
Önceki net maliyeti: mesaj başına 32 bit.
Sonraki net maliyeti: mesaj başına 6 bit.
net.WriteTable yerine statik bir yapı göndermeyi deneyin
net.WriteTable çok başlangıç dostu bir fonksiyondur ancak aşırı maliyetlidir. Diyelim ki sunucuya bir SunInfo yapısı göndermek istiyorsunuz. Bu yapının 2 alanı vardır: bir vektör ve bir sayı.
net.WriteTable'ın sorunu, sayınızın ne kadar büyük olabileceğini bilmemesidir, vektörleri normalleştirilmiş vektörlerden ayırmaz. net.WriteTable anahtar türünü, anahtar verisini, değer türünü, değer verisini gönderir. Türü yazmak 8 bit maliyetlidir. Vektör maliyetleri değişken bit miktarıdır. net.WriteTable tüm sayıları 64 bit olan double olarak yazar. net.WriteTable tüm vektörleri (normalleştirilmiş ve değil) vektör olarak yazar.
Yani SunInfo yapısını net.WriteTable ile gönderirseniz şunu gönderecektir:
| Yazılan Bit | Değer Türü | Değer | Açıklama |
|---|---|---|---|
| 8 | number |
TYPE_STRING |
Anahtar türü (UInt) |
| 80 | string |
direction |
Anahtar |
| 8 | number |
TYPE_VECTOR |
Değer türü (UInt) |
| 3-69 | Vector |
util.GetSunInfo().direction |
Değer |
| 8 | number |
TYPE_STRING |
Anahtar türü (UInt) |
| 96 | string |
obstruction |
Anahtar |
| 8 | number |
TYPE_NUMBER |
Değer türü (UInt) |
| 64 | number |
util.GetSunInfo().obstruction |
Değer |
| 8 | number |
TYPE_NIL |
Tablonun sonu |
Toplam: mesaj başına minimum 283 bit. mesaj başına maksimum ~349 bit.
Bu çok maliyetlidir, tablodaki alanlarımızı bildiğimiz için anahtarlardan kurtulabiliriz (283-349'dan 91-157'ye).
Ayrıca değerlerimizi de biliyoruz, bu yüzden türlerini göndermemize gerek yok.
Bu noktada net.WriteTable kullanmayı bırakmalısınız.
Normalleştirilmiş vektör için net.WriteNormal (daha az bit maliyeti ve çok fazla hassasiyet kaybı olmaz) ve normalleştirilmiş sayı için net.WriteFloat kullanalım (tam hassasiyet gerekiyorsa net.WriteDouble kullanın, ama burada o kadar önemli değil):
| Yazılan Bit | Değer Türü | Değer |
|---|---|---|
| 3-27 | Vector |
suninfo.direction |
| 32 | number |
suninfo.obstruction |
Toplam: mesaj başına minimum 35 bit. mesaj başına maksimum 59 bit.
Bence fark oldukça açık.
Sıkıştırma
Büyük miktarda veri göndermeniz gerekiyorsa, onu sıkıştırabilirsiniz.
Bu yöntem net bant genişliğinden tasarruf sağlar ancak CPU süresini artırabilir.
Sıkıştırma için kullanılan fonksiyonlar şunlardır: util.Compress, util.Decompress, net.WriteData, net.ReadData, net.WriteUInt ve net.ReadUInt.
Gönderme:
Önce dizeyi sıkıştırmamız ve net.WriteData ile göndermemiz gerekir. Ancak net.ReadData uzunluğu gerektirir, bu yüzden önce onu ve sonra verinin kendisini göndermemiz gerekir.
Okuma:
Önce sıkıştırılmış dizenin uzunluğunu almalı, sonra onu okuyup sıkıştırmayı açmalıyız.
len argümanını kullanabilir ve local len = net.ReadUInt( 16 ) yerine onu kullanabilirsiniz.Dengeleme
Çok fazla mesaj veya çok fazla veri gönderilirse net kanalı taşabilir. Bunun için yapılabilecekler şunlardır:
Net mesajlarınız için en uygun oranı bulun
Net mesajlarının zamanı ve boyutu arasında bir denge bulabilirsiniz.
Eğer gecikme (lag) yaşadıysanız, anında göndermeniz gerekmiyorsa, muhtemelen net mesajını parçalara ayırmalısınız.
Mesajları göndermek için en uygun gecikmeyi bulun. Onları saniyede bir yerine dakikada bir gönderebilir misiniz?
Sıkıştırma
Büyük veri göndermeniz gerekiyorsa ve onu geciktiremiyor veya bölemiyorsanız, yukarıda bahsedilen sıkıştırma yöntemini kullanın.
Güvenilmez mod (Unreliable mode)
net.Start'ın ikinci bir parametresi vardır: unreliable. Bu, mesajı bir ağ paketindeki “güvenilmez” katman üzerinden gönderilecek şekilde ayarlar.
Adının akla getirebileceğinin aksine, bu, net mesajının kaybolabileceği veya bozulabileceği anlamına gelmez; hala TCP onaylı bir paket üzerinde ilerlemektedir.
Çok büyük olan güvenilir (reliable) net mesajları, ağ arabelleğinin taşmasına neden olarak istemcinin bağlantısını keser. “Overflowed reliable channel” hatası bu anlama gelir.
Buna karşı koymak için net mesajlarınızda güvenilmez modu ayarlayabilirsiniz. Güvenilmez mesajlar, güvenilir katmana eklenir. Bir istemci, güvenilmez veriler içeren büyük bir paket aldığında, güvenilmez veriler önce atılır ve oyun onsuz devam eder.
Bu, net mesajlarınızın taşmasını önlemeye yardımcı olmalıdır, ancak istemciye ulaşmaları garanti değildir. Bu, kaybolması durumunda hiçbir şeyi bozmayacak bilgileri göndermek için kullanılabilir.
Unutmayın: tüm net mesajlarının güvenilmez olması gerekmez. Bunu yapsaydınız, hepsi “ya hep ya hiç” şeklinde bırakılır ve istemci hiç veri almazdı. Verinin ortasında bir kesilme olmaz; güvenilir/güvenilmez ayrımı, alınan veri için tek ayırıcıdır.
Tüm sunucular · GmodTR Launcher · Rehber · GMod Wiki · GmodTR nedir? · Sunucu ekle · Discord