Net Kütüphanesi Kullanımı

Bu sayfayı okumadan önce, lütfen Lua'nın temellerini anladığınızdan emin olun, aksi takdirde takılıp kalırsınız!

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:

util.AddNetworkString( "YourMessageName" )
Bu net ismini kullanabilmeniz için bir süre geçmesi gerekir.

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.Receive( "MyMessage", function( len, ply ) print( "I got it!" ) end )

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.

util.AddNetworkString( "myMessage" )

Daha sonra bir net mesajı göndermeye başlamak için, mesajın adıyla net.Start fonksiyonunu çağırın. Sonra,

Örneğin, myMessage adlı net mesajını sunucudaki ilk oyuncuya göndermek isteseydiniz, şunu yapardınız:

net.Start( "myMessage" ) net.Send( Entity( 1 ) )

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:

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.

Veriyi gönderdiğiniz sırayla okumanız gerekir.

Ö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ı:

util.AddNetworkString( "PlayerDied" ) hook.Add( "PlayerDeath", "NotifyClient", function( ply, inf, att ) net.Start( "PlayerDied" ) net.WriteEntity( ply ) net.Broadcast() end )

İstemci tarafı:

net.Receive( "PlayerDied", function() local ply = net.ReadEntity() if not ply:IsValid() then return end -- Make sure the dead player exists chat.AddText( ply, Color( 255, 0, 0 ), " öldü!" ) end )

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ı:

local frame = vgui.Create( "DFrame" ) frame:SetSize( 400, 400 ) frame:SetTitle( "Yaşınız kaç?" ) frame:Center() local age = vgui.Create( "DNumberWang", frame ) age:Dock( TOP ) age:SetDecimals( 0 ) age:SetMin( 0 ) age:SetMax( 100 ) local send = vgui.Create( "DButton", frame ) send:Dock( TOP ) send:SetText( "Yaşımı gönder!" ) send.DoClick = function() net.Start( "SendAge" ) net.WriteUInt( age:GetValue(), 8 ) -- İkinci argüman 8'dir, çünkü yaş asla 255'in üzerinde olmayacaktır. Bunu yapmak bant genişliğinden tasarruf sağlar ve bant genişliğini azaltabilir. net.SendToServer() end

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ı:

util.AddNetworkString( "SendAge" ) net.Receive( "SendAge", function( len, ply ) -- len, net mesajının uzunluğudur ki bunu umursamıyoruz, ply ise mesajı gönderen oyuncudur. local age = net.ReadUInt( 8 ) -- ReadUInt'ın bir argüman gerektirdiğine dikkat edin. Bu, WriteUInt'ın ikinci argümanıdır ve bize sayının ne kadar büyük olduğunu söyler. PrintMessage( HUD_PRINTTALK, ply:Nick() .. " " .. age .. " yaşında!" ) end )

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:

util.AddNetworkString( "BanPlayer" ) net.Receive( "BanPlayer", function( len, ply ) local toBan = net.ReadEntity() local time = net.ReadUInt( 32 ) toBan:Ban( time, true ) end )

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.

Bu, mesajı kimin gönderdiğini bilmek istediğinizde ve net.Receive'daki ikinci argümanı kullanmak yerine istemci tarafında 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:

net.Receive("YourNetName", function( len, ply ) if !ply:IsSuperAdmin() then return end -- gönderen superadmin değilse fonksiyondan çıkar print( "This is a secured Net Message!" ) end)

İyileştirme

Bu bölüm, karmaşıklığı nedeniyle yeni başlayanlar için önerilmez.

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.

send.DoClick = function() net.Start( "SelectPlayers" ) net.WriteUInt( slider:GetValue() - 1, 6 ) net.SendToServer() end
net.Receive( "SelectPlayers", function() local amount = net.ReadUInt(6) + 1 print( "Amount:", amount ) end )

Ö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):

local suninfo = util.GetSunInfo() net.WriteNormal( suninfo.direction ) net.WriteFloat( suninfo.obstruction )
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.

Bu yöntem, istemciden sunucuya iletişim için kullanıldığında hash-DOS (hash çarpışması hizmet reddi) saldırısı riski nedeniyle JSON için önerilmez.

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:

local compressedString = util.Compress( largestring ) local len = #compressedString net.WriteUInt( len, 16 ) net.WriteData( compressedString, len )

Ö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:

local len = net.ReadUInt( 16 ) local compressedString = net.ReadData( len ) local largestring = util.Decompress( compressedString )

Önce sıkıştırılmış dizenin uzunluğunu almalı, sonra onu okuyup sıkıştırmayı açmalıyız.

Eğer net mesajınız yalnızca sıkıştırılmış bir dize içeriyorsa, net.Receive'dan gelen bir 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