Ne daha iyi? Çok küçük TCP paketleri mi yoksa uzun bir tane mi? [kapalı]


16

Yaptığım bir oyun için sunucuya ve sunucudan biraz veri gönderiyorum.

Şu anda böyle konum verileri gönderiyorum:

sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));

Açıkça ilgili X, Y ve Z değerlerini gönderiyor.

Böyle bir veri göndermek daha verimli olur mu?

sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));

2
Sınırlı tecrübemde paket kaybı genellikle% 5'in altında.
mucaho

5
SendToClient aslında bir paket gönderiyor mu? Eğer öyleyse, bunu nasıl yaptınız?
user253751

1
mucaho Hiç bir zaman kendim ya da başka bir şey ölçmedim, ama TCP'nin kenarlarda o kadar kaba olduğuna şaşırdım. % 0.5 veya daha az gibi bir şey umuyordum.
Panzercrisis

1
@Panzercrisis Seninle aynı fikirdeyim. Şahsen% 5'lik bir kaybın kabul edilemez olacağını hissediyorum. Diyelim ki, benim gibi oyunda yeni bir gemi gönderdiğini düşünürseniz, bu paketin alınma olasılığının% 1'i bile felaket olurdu, çünkü görünmez gemiler alırdım.
Ocak

2
adamlar ucube etmeyin, ben bir üst sınır olarak% 5 demekti :) gerçekte diğer yorumlarla belirtildiği gibi, çok daha iyi.
mucaho

Yanıtlar:


28

Bir TCP segmentinin oldukça fazla yükü vardır. Bir TCP paketiyle 10 baytlık bir ileti gönderdiğinizde, aslında şunları gönderirsiniz:

  • 16 bayt IPv4 başlığı (IPv6 yaygınlaştığında 40 bayta yükselir)
  • 16 bayt TCP başlığı
  • 10 bayt yük
  • kullanılan veri bağlantısı ve fiziksel katman protokolleri için ek yük

10 bayt veri taşımak için 42 bayt trafik elde edilir. Böylece, kullanılabilir bant genişliğinizin yalnızca% 25'inden daha azını kullanırsınız. Ve bu, Ethernet veya PPPoE gibi alt düzey protokollerin tükettiği ek yükü henüz açıklamamaktadır (ancak çok fazla alternatif olduğu için bunların tahmin edilmesi zordur).

Ayrıca, birçok küçük paket yönlendiricilere, güvenlik duvarlarına, anahtarlara ve diğer ağ altyapı ekipmanlarına daha fazla yük bindirir, bu nedenle siz, servis sağlayıcınız ve kullanıcılarınız yüksek kaliteli donanıma yatırım yapmadığınızda, bu başka bir darboğaz haline gelebilir.

Bu nedenle elinizdeki tüm verileri bir kerede bir TCP segmentinde göndermeye çalışmalısınız.

Paket kaybını ele alma hakkında : TCP kullandığınızda bunun için endişelenmenize gerek yoktur. Protokolün kendisi, kaybolan paketlerin yeniden gönderilmesini ve paketlerin sırayla işlenmesini sağlar, böylece gönderdiğiniz tüm paketlerin diğer tarafa ulaşacağını ve gönderdiğiniz sıraya ulaşacağını varsayabilirsiniz. Bunun bedeli, paket kaybı olduğunda, oynatıcınız önemli bir gecikme yaşayacaktır, çünkü bırakılan bir paket, yeniden talep edilip alınana kadar tüm veri akışını durduracaktır.

Bu bir sorun olduğunda, her zaman UDP kullanabilirsiniz. Ama sonra kaybetti ve dışı sipariş mesajlar için kendi çözüm bulmak gerekir (o azından garantiler de mesajlar olduğunu do gelmesi, tam ve hasarsız gelmesi).


1
TCP'nin paket kaybını geri kazanma biçiminin getirdiği yük, paketlerin boyutlarına bağlı olarak değişebilir mi?
Panzercrisis

@Panzercrisis Sadece yeniden gönderilmesi gereken daha büyük bir paket olduğu sürece.
Philipp

8
İşletim sisteminin neredeyse kesinlikle Nagles Algorithm en.wikipedia.org/wiki/Nagle%27s_algorithm'i giden verilere uygulayacağını , uygulamada ayrı yazarlar veya bunları birleştirmeniz önemli olmadığı anlamına gelir. TCP aracılığıyla dağıtmadan önce.
Vality

1
@Vality Kullandığım çoğu soket API'si, her soket için nagle'ı etkinleştirmeye veya devre dışı bırakmaya izin veriyor. Çoğu oyun için, devre dışı bırakmanızı öneririm, çünkü düşük gecikme genellikle bant genişliğini korumaktan daha önemlidir.
Philipp

4
Nagle'ın algoritması birdir, ancak gönderen tarafta verilerin arabelleğe alınmasının tek nedeni değildir. Orada hiçbir şekilde güvenilir olarak kuvvet veri gönderir. Ayrıca, tamponlama / parçalanma, NAT'lar, yönlendiriciler, proxy'ler ve hatta alıcı tarafta veri gönderildikten sonra herhangi bir yerde oluşabilir. TCP, veri aldığınız boyut ve zamanlama ile ilgili herhangi bir garanti vermez, sadece düzenli ve güvenilir bir şekilde ulaşacağını garanti eder. Boyut garantilerine ihtiyacınız varsa UDP kullanın. TCP'nin anlaşılması daha kolay göründüğü gerçeği, onu tüm sorunlar için en iyi araç yapmaz!
Panda Pijama

10

Bir büyük olan (akıl içinde) daha iyidir.

Dediğiniz gibi, paket kaybı ana nedendir. Paketler genellikle sabit boyutlu çerçevelerde gönderilir, bu nedenle 10 küçük çerçeveli 10 çerçeveden daha büyük bir mesajla bir kare almak daha iyidir.

Ancak standart TCP'de, devre dışı bırakmadıkça bu gerçekten bir sorun değildir. ( Nagle algoritması olarak adlandırılır ve oyunlar için devre dışı bırakmalısınız.) TCP sabit bir zaman aşımı süresi veya paket "dolana kadar" bekleyecektir. "Dolu" olduğunda, kısmen çerçeve boyutuna göre belirlenen biraz sihirli bir sayı olur.


Nagle'ın algoritmasını duydum, ancak devre dışı bırakmak gerçekten iyi bir fikir mi? Ben sadece birisi daha verimli (ve belirgin nedenlerle verimlilik istiyorum) dedi nerede bir StackOverflow cevap geldi.
joehot200

6
@ joehot200 Bunun tek doğru cevabı "duruma bağlıdır". Çok fazla veri göndermek için daha etkilidir, evet, ancak oyunların ihtiyaç duyduğu gerçek zamanlı akış için değil.
D-side

4
@ joehot200: Nagle'ın algoritması, bazı TCP uygulamalarının bazen kullandığı gecikmeli bir onay algoritması ile zayıf etkileşime girer. Bazı TCP uygulamaları, kısa bir süre sonra daha fazla verinin izlenmesini beklerse bazı verileri aldıktan sonra bir ACK göndermeyi geciktirir (çünkü daha sonraki paketi kabul etmek, öncekini de dolaylı olarak onaylayacaktır). Nagle'ın algoritması, bir birimin bazı veriler gönderdiyse ancak bir bildirim duymamışsa kısmi bir paket göndermemesi gerektiğini söylüyor. Bazen iki yaklaşım kötü etkileşir, her iki taraf da diğerinin bir şeyler yapmasını bekler, ...
Supercat

2
... bir "akıl sağlığı zamanlayıcısı" devreye girer ve durumu çözer (bir saniye içinde).
Supercat

2
Ne yazık ki, Nagle algoritmasını devre dışı bırakmak, diğer ana bilgisayar tarafında arabelleğe almayı önlemek için hiçbir şey yapmaz. Nagle'ın algoritmasını devre dışı bırakmak recv(), her send()çağrı için bir çağrı almanızı sağlamaz , bu da çoğu insanın aradığı şeydir. UDP gibi bunu garanti eden bir protokol kullanmak. "Sahip olduğunuz tek şey TCP olduğunda, her şey bir akıntıya benziyor"
Panda Pijama

6

Önceki tüm cevaplar yanlış. Uygulamada, bir uzun send()veya birkaç küçük send()arama yapıp yapmadığınız önemli değildir .

Phillip'in belirttiği gibi, bir TCP segmentinin bir miktar ek yükü vardır, ancak bir uygulama programcısı olarak, segmentlerin nasıl oluşturulduğu üzerinde hiçbir kontrolünüz yoktur. Basit bir ifadeyle:

Bir send()çağrı mutlaka bir TCP segmentine dönüşmez.

İşletim sistemi, tüm verilerinizi arabelleğe almak ve bir segmentte göndermek veya uzun olanı almak ve birkaç küçük segmente bölmek için tamamen ücretsizdir .

Bunun birkaç anlamı vardır, ancak en önemlisi:

Bir send()çağrı veya bir TCP segmenti recv(), diğer uçtaki başarılı bir çağrıya mutlaka dönüşmez

Bunun sebebi TCP'nin bir akış protokolü olmasıdır. TCP, verilerinizi uzun bir bayt akışı olarak görür ve kesinlikle "paket" kavramına sahip değildir. İle send()bu akışa bayt eklemek ve ile recv()diğer tarafa kapalı bayt olsun. TCP, verilerinizin mümkün olduğunca hızlı bir şekilde diğer tarafa ulaştığından emin olmak için verilerinizi uygun gördüğü yerlerde agresif bir şekilde arabelleğe alır ve böler.

TCP ile "paketler" göndermek ve almak istiyorsanız, paket başlangıç ​​işaretleyicilerini, uzunluk işaretleyicilerini vb. Uygulamanız gerekir. Bunun yerine UDP gibi mesaj odaklı bir protokol kullanmaya ne dersiniz? UDP, bir send()çağrının gönderilen bir datagrama ve bir çağrıyı çevirdiğini garanti eder recv()!

Sahip olduğunuz tek şey TCP olduğunda, her şey bir akışa benziyor


1
Her iletinin bir ileti uzunluğuyla ön eklenmesi zor değildir.
ysdx

Paket toplama söz konusu olduğunda, Nagle'ın Algoritması etkin olsun ya da olmasın, çevirmek için bir anahtarınız var. Yetersiz doldurulmuş paketlerin hızlı bir şekilde teslim edilmesini sağlamak için oyun ağında kapalı olması nadir değildir.
Lars Viklund

Bu tamamen işletim sistemine ve hatta kütüphaneye özgüdür. Ayrıca çok fazla kontrole sahipsiniz - isterseniz. Tam kontrole sahip olmadığınız doğrudur, TCP'nin her zaman iki mesajı birleştirmesine izin verilir veya MTU'ya uymuyorsa bir tanesini bölebilirsiniz, ancak yine de doğru yönde ipucu verebilirsiniz. Çeşitli yapılandırma ayarlarını yapma, mesajları 1 saniye aralıklarla manuel gönderme veya verileri arabelleğe alma ve tek çekimde gönderme.
Dorus

@ysdx: hayır, gönderen tarafta değil, evet alıcı tarafta. Tam olarak nerede veri alacağınız konusunda hiçbir garantiniz olmadığı için recv(), bunu telafi etmek için kendi tamponunuzu yapmanız gerekir. Bunu UDP üzerinden güvenilirlik uygulamakla aynı zorlukta sıralıyorum.
Panda Pijama

@Pagnda Pijama: Alıcı tarafın naif bir uygulaması: while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }(hata işleme ve kısalık okumalarını atlamak, ancak çok fazla değişmez). Bunu yapmak selectçok daha karmaşıklık katmaz ve TCP kullanıyorsanız muhtemelen kısmi mesajları arabelleğe almanız gerekir. UDP üzerinde sağlam güvenilirlik uygulamak bundan çok daha karmaşıktır.
ysdx

3

Birçok küçük paket iyi. Aslında, TCP ek yükü konusunda endişeleriniz varsa, bufferstream1500 karaktere kadar (veya TCP MTU'larınız ne olursa olsun, dinamik olarak talep etmek en iyisi) toplayan bir etiket ekleyin ve sorunu tek bir yerde ele alın. Bunu yaptığınızda, aksi takdirde yaratacağınız her ekstra paket için ~ 40 bayt yükü size kalır.

Bununla birlikte, daha az veri göndermek ve orada daha büyük nesneler oluşturmak daha iyidir. Tabii ki göndermek, göndermekten "UID:10|1|2|3daha küçük UID:10;x:1UID:10;y:2UID:10;z:3. Aslında, bu noktada tekerleği yeniden icat etmemelisiniz, protobuf gibi bir kütüphaneyi 10 bayt veya daha az bir diziye indirgeyen bir kitaplık kullanmalısınız .

Unutmamanız gereken tek şey Flush, akışınıza ilgili konumlara bir komut eklemektir, çünkü akışınıza veri eklemeyi durdurur durdurmaz, herhangi bir şey göndermeden önce sonsuz bekleyebilir. Müşteriniz bu verileri beklerken gerçekten sorunludur ve istemciniz bir sonraki komutu gönderene kadar sunucunuz yeni bir şey göndermez.

Paket kaybı, burada marjinal olarak etkileyebileceğiniz bir şeydir. Gönderdiğiniz her bayt bozulmuş olabilir ve TCP otomatik olarak bir yeniden iletim ister. Daha küçük paketler, her bir paketin bozulması için daha düşük bir şans anlamına gelir, ancak ek yüke katıldıkları için, daha fazla bayt gönderirsiniz, kaybedilen bir paketin olasılığını daha da artırırsınız. Bir paket kaybolduğunda, TCP, eksik paket yeniden gönderilip alınana kadar sonraki tüm verileri arabelleğe alır. Bu büyük bir gecikmeye (ping) neden olur. Paket kaybı nedeniyle bant genişliğindeki toplam kayıp ihmal edilebilir olsa da, oyunlar için daha yüksek ping istenmez.

Alt satır: Mümkün olduğunca az veri gönderin, büyük paketler gönderin ve bunu yapmak için kendi düşük düzeyli yöntemlerinizi yazmayın, ancak bufferstreamağır kaldırmayı işlemek için iyi bilinen kütüphanelere ve protobuf gibi yöntemlere güvenin.


Aslında böyle basit şeyler için kendi rulolarınızı almak kolaydır. Başka bir kişinin kütüphanesini kullanmak için 50 sayfalık bir dokümantasyondan çok daha kolay ve bundan sonra hala hataları ve gotcha'ları ile uğraşmanız gerekiyor.
Pacerier

Doğru, kendiniz yazmak bufferstreamönemsiz, bu yüzden ona bir yöntem dedim. Hala tek bir yerde işlemek istiyorsunuz ve tampon mantığınızı mesaj kodunuzla entegre etmiyorsunuz. Nesne Serileştirme konusunda, başkalarının oraya koyduğu binlerce adam-saatten daha iyi bir şey aldığınızdan şüpheliyim, deneseniz bile, bilinen uygulamalara karşı çözümünüzü karşılaştırmanızı şiddetle tavsiye ederim.
Dorus

2

Ağ programlamasında bir neofit olmasına rağmen, sınırlı deneyimimi birkaç nokta ekleyerek paylaşmak istiyorum:

  • TCP ek yük anlamına gelir - ilgili istatistikleri ölçmelisiniz
  • UDP, ağa bağlı oyun senaryoları için fiili bir çözümdür, ancak buna dayanan tüm uygulamalarda, paketlerin kaybedildiğini veya sipariş dışı bırakıldığını hesaba katan ekstra bir CPU tarafı algoritması vardır

Ölçümlerle ilgili olarak, dikkate alınması gereken metrikler şunlardır:

Belirtildiği gibi, bir anlamda sınırlı olmadığınızı ve UDP kullanabileceğinizi öğrenirseniz, bunun için gidin. Orada UDP tabanlı bazı uygulamalar var, bu yüzden tekerleği yeniden icat etmek veya yılların tecrübesi ve kanıtlanmış deneyime karşı çalışmak zorunda değilsiniz. Bahsetmeye değer bu tür uygulamalar şunlardır:

Sonuç: Bir UDP uygulaması bir TCP uygulamasından (3 kat daha fazla) daha iyi performans gösterebileceğinden, senaryonuzu UDP dostu olarak belirledikten sonra bunu dikkate almak mantıklıdır. Dikkatli olun! Tam TCP yığınını UDP'nin üstüne uygulamak her zaman kötü bir fikirdir.


UDP kullanıyordum. Sadece TCP'ye geçtim. UDP'nin paket kaybı, müşterinin ihtiyaç duyduğu önemli veriler için kabul edilemezdi. Hareket verilerini UDP ile gönderebilirim.
joehot200

Yani, yapabileceğiniz en iyi şeyler: gerçekten, sadece önemli işlemler için TCP kullanın VEYA UDP tabanlı bir yazılım protokolü uygulaması kullanın (Enet basit ve UDT iyi test edilir). Ama önce, kaybı ölçün ve UDT'nin size bir avantaj getirip getirmeyeceğine karar verin.
teodron
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.