Apa yang lebih baik Banyak paket TCP kecil, atau yang panjang? [Tutup]

16

Saya mengirim sedikit data ke dan dari server, untuk game yang saya buat.

Saat ini saya mengirim data lokasi seperti ini:

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

Jelas itu mengirimkan masing-masing nilai X, Y, dan Z.

Akankah lebih efisien mengirim data seperti ini?

sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));
joehot200
sumber
2
Paket loss biasanya di bawah 5%, dalam pengalaman saya yang terbatas.
mucaho
5
Apakah sendToClient benar-benar mengirim paket? Jika demikian, bagaimana Anda membuatnya melakukannya?
user253751
1
@mucaho Saya tidak pernah mengukurnya sendiri atau apa pun, tapi saya terkejut TCP itu terlalu kasar. Saya akan berharap untuk sesuatu yang lebih seperti 0,5% atau kurang.
Panzercrisis
1
@ Panzercrisis Saya harus setuju dengan Anda. Saya pribadi merasa bahwa kehilangan 5% tidak dapat diterima. Jika Anda berpikir tentang sesuatu seperti saya mengirim, katakanlah, sebuah kapal baru menelurkan dalam permainan, bahkan kemungkinan 1% dari paket yang tidak diterima akan menjadi bencana, karena saya akan mendapatkan kapal yang tidak terlihat.
joehot200
2
jangan panik guys, maksudku 5% sebagai batas atas :) pada kenyataannya itu jauh lebih baik, seperti dicatat oleh komentar lain.
mucaho

Jawaban:

28

Segmen TCP memiliki banyak overhead. Ketika Anda mengirim pesan 10 byte dengan satu paket TCP, Anda sebenarnya mengirim:

  • 16 byte header IPv4 (akan meningkat menjadi 40 byte saat IPv6 menjadi umum)
  • 16 byte header TCP
  • 10 byte payload
  • overhead tambahan untuk data-link dan protokol lapisan fisik yang digunakan

menghasilkan 42 byte traffic untuk mengangkut 10 byte data. Jadi, Anda hanya menggunakan kurang dari 25% dari bandwidth yang tersedia. Dan itu belum memperhitungkan overhead yang dikonsumsi protokol tingkat rendah seperti Ethernet atau PPPoE (tetapi ini sulit untuk diperkirakan karena ada begitu banyak alternatif).

Selain itu, banyak paket kecil memberikan tekanan lebih besar pada router, firewall, sakelar, dan peralatan infrastruktur jaringan lainnya, jadi ketika Anda, penyedia layanan dan pengguna Anda tidak berinvestasi pada perangkat keras berkualitas tinggi, ini dapat berubah menjadi hambatan lain.

Untuk alasan itu Anda harus mencoba mengirim semua data yang Anda miliki sekaligus dalam satu segmen TCP.

Mengenai penanganan kehilangan paket : Saat Anda menggunakan TCP, Anda tidak perlu khawatir tentang hal itu. Protokol itu sendiri memastikan bahwa setiap paket yang hilang dikirim ulang dan paket diproses secara berurutan, sehingga Anda dapat mengasumsikan bahwa semua paket yang Anda kirim akan tiba di sisi lain, dan mereka akan tiba dalam urutan yang Anda kirim. Harga untuk ini adalah ketika ada paket yang hilang, pemain Anda akan mengalami kelambatan yang cukup besar, karena satu paket yang dijatuhkan akan menghentikan seluruh aliran data sampai diminta kembali dan diterima.

Ketika ini merupakan masalah, Anda selalu dapat menggunakan UDP. Tapi kemudian Anda harus mencari solusi sendiri untuk hilang dan out-of-order pesan (setidaknya jaminan bahwa pesan yang tidak tiba, tiba lengkap dan tidak rusak).

Philipp
sumber
1
Apakah overhead dari cara TCP memulihkan kehilangan paket bervariasi sesuai dengan ukuran paket?
Panzercrisis
@Panzercrisis Hanya sejauh ada paket yang lebih besar yang perlu dikirim ulang.
Philipp
8
Saya akan mencatat bahwa OS hampir pasti akan menerapkan Nagles Algorithm en.wikipedia.org/wiki/Nagle%27s_algorithm ke data keluar, ini berarti bahwa tidak masalah jika dalam aplikasi Anda menulis atau menggabungkannya secara terpisah, itu akan menggabungkannya sebelum benar-benar membagikannya melalui TCP.
Vality
1
@Vality Kebanyakan soket API yang saya gunakan memungkinkan untuk mengaktifkan atau menonaktifkan nagle untuk setiap soket. Untuk sebagian besar game, saya akan merekomendasikan untuk menonaktifkannya, karena latensi rendah biasanya lebih penting daripada menghemat bandwidth.
Philipp
4
Algoritma Nagle adalah satu, tetapi bukan satu-satunya alasan data dapat disangga di sisi pengirim. Tidak ada cara untuk memaksa pengiriman data secara andal. Juga, buffering / fragmentasi dapat terjadi di mana saja setelah data dikirim, baik di NAT, router, proxy, atau bahkan sisi penerima. TCP tidak membuat jaminan mengenai ukuran dan waktu di mana Anda menerima data, hanya bahwa itu akan tiba secara berurutan dan andal. Jika Anda membutuhkan jaminan ukuran, gunakan UDP. Fakta bahwa TCP tampaknya lebih mudah dipahami tidak menjadikannya alat terbaik untuk semua masalah!
Panda Pajama
10

Satu besar (masuk akal) lebih baik.

Seperti yang Anda katakan, packet loss adalah alasan utama. Paket umumnya dikirim dalam bingkai dengan ukuran tetap, jadi lebih baik mengambil satu bingkai dengan pesan besar daripada 10 bingkai dengan 10 kecil.

Namun dengan TCP standar, ini bukan masalah, kecuali jika Anda menonaktifkannya. (Ini disebut algoritme Nagle , dan untuk gim Anda harus menonaktifkannya.) TCP akan menunggu batas waktu yang tetap atau hingga paketnya "penuh". Di mana "penuh" adalah beberapa angka ajaib, sebagian ditentukan oleh ukuran bingkai.

Elva
sumber
Saya pernah mendengar tentang algoritma Nagle, tetapi apakah itu benar-benar ide yang bagus untuk menonaktifkannya? Saya baru saja datang langsung dari jawaban StackOverflow di mana seseorang mengatakan bahwa itu lebih efisien (dan untuk alasan yang jelas saya ingin efisiensi).
joehot200
6
@ joehot200 Satu-satunya jawaban yang benar untuk itu adalah "itu tergantung". Memang lebih efisien untuk mengirim banyak data, ya, tetapi tidak untuk streaming real-time yang cenderung dibutuhkan game.
Sisi D
4
@ joehot200: Algoritma Nagle berinteraksi buruk dengan algoritma keterlambatan-pengakuan yang kadang-kadang digunakan oleh implementasi TCP tertentu. Beberapa implementasi TCP akan menunda pengiriman ACK setelah mereka mendapatkan beberapa data jika mereka mengharapkan lebih banyak data untuk mengikuti segera setelah (karena mengakui paket kemudian akan secara implisit mengakui yang sebelumnya juga). Algoritma Nagle mengatakan bahwa suatu unit tidak boleh mengirim paket parsial jika telah mengirim beberapa data tetapi tidak mendengar pengakuan. Kadang-kadang kedua pendekatan berinteraksi buruk, dengan masing-masing pihak menunggu yang lain untuk melakukan sesuatu, sampai ...
supercat
2
... "timer kewarasan" menendang dan menyelesaikan situasi (atas perintah sedetik).
supercat
2
Sayangnya, menonaktifkan algoritme Nagle tidak akan melakukan apa pun untuk mencegah buffering di sisi host lain. Menonaktifkan algoritme Nagle tidak memastikan Anda mendapatkan satu recv()panggilan untuk setiap send()panggilan, yang merupakan hal yang dicari kebanyakan orang. Namun, menggunakan protokol yang menjamin hal ini, seperti halnya UDP. "Ketika semua yang Anda miliki adalah TCP, semuanya tampak seperti aliran"
Panda Pajama
6

Semua jawaban sebelumnya salah. Dalam praktiknya, tidak masalah apakah Anda mengeluarkan satu send()panggilan lama atau beberapa send()panggilan kecil .

Seperti yang dinyatakan Phillip, segmen TCP memiliki beberapa overhead, tetapi sebagai pemrogram aplikasi, Anda tidak memiliki kendali atas bagaimana segmen dihasilkan. Secara sederhana:

Satu send()panggilan tidak harus diterjemahkan ke satu segmen TCP.

OS sepenuhnya bebas untuk buffer semua data Anda dan mengirimkannya dalam satu segmen, atau mengambil yang panjang dan memecahnya menjadi beberapa segmen kecil.

Ini memiliki beberapa implikasi, tetapi yang paling penting adalah:

Satu send()panggilan, atau satu segmen TCP tidak harus diterjemahkan ke satu recv()panggilan yang berhasil di ujung lainnya

Alasan di balik ini adalah TCP adalah protokol stream . TCP memperlakukan data Anda sebagai aliran byte yang panjang, dan sama sekali tidak memiliki konsep "paket". Dengan send()Anda menambahkan byte ke aliran itu, dan dengan recv()Anda mendapatkan byte dari sisi lain. TCP akan secara agresif menyangga dan membagi data Anda di mana pun ia mau untuk memastikan data Anda sampai ke pihak lain secepat mungkin.

Jika Anda ingin mengirim dan menerima "paket" dengan TCP, Anda harus mengimplementasikan penanda mulai paket, penanda panjang dan sebagainya. Bagaimana kalau menggunakan protokol berorientasi pesan seperti UDP saja? UDP menjamin satu send()panggilan diterjemahkan ke satu datagram terkirim dan ke satu recv()panggilan!

Ketika semua yang Anda miliki adalah TCP, semuanya tampak seperti aliran

Piyama Panda
sumber
1
Awalan setiap pesan dengan panjang pesan tidak terlalu rumit.
ysdx
Anda memiliki satu sakelar untuk dibalik ketika datang ke agregasi paket, apakah Algoritma Nagle sedang berlaku atau tidak. Bukan hal yang aneh jika tidak aktif dalam jaringan game untuk memastikan pengiriman paket yang kurang terisi dengan cepat.
Lars Viklund
Ini sepenuhnya OS atau bahkan perpustakaan khusus. Anda juga memiliki banyak kontrol - jika Anda mau. Memang benar Anda tidak memiliki kontrol total, TCP selalu diperbolehkan untuk menggabungkan dua pesan, atau membelah satu jika tidak cocok dengan MTU, tetapi Anda masih bisa mengisinya dengan arah yang benar. Pengaturan berbagai pengaturan konfigurasi, pengiriman pesan secara manual terpisah 1 detik atau buffering data dan kirim dalam satu kesempatan.
Dorus
@ysdx: tidak, tidak di sisi pengirim, tapi ya di sisi penerima. Karena Anda tidak memiliki jaminan di mana tepatnya Anda akan mendapatkan data recv(), Anda perlu membuat buffering sendiri untuk mengimbanginya. Saya akan memeringkatnya dalam kesulitan yang sama dengan menerapkan keandalan di atas UDP.
Panda Pajama
@ Piagnda Piyama: Implementasi naif dari sisi penerima adalah: while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }(menghilangkan penanganan kesalahan dan membaca sebagian untuk singkatnya tetapi tidak banyak berubah). Melakukan ini adalah selecttidak menambah kerumitan dan jika Anda menggunakan TCP Anda mungkin perlu buffer sebagian pesan. Menerapkan keandalan yang kuat di atas UDP jauh lebih rumit dari itu.
ysdx
3

Banyak paket kecil baik-baik saja. Bahkan, jika Anda khawatir tentang overhead TCP, cukup masukkan bufferstreamyang mengumpulkan hingga 1500 karakter (atau apa pun MTU TCP Anda, paling baik untuk memintanya secara dinamis), dan atasi masalah di satu tempat. Melakukan hal itu membuat Anda kekurangan ~ 40 byte untuk setiap paket tambahan yang seharusnya Anda buat.

Yang mengatakan, masih lebih baik untuk mengirim lebih sedikit data, dan membangun objek yang lebih besar membantu di sana. Tentu saja lebih kecil untuk dikirim "UID:10|1|2|3daripada mengirim UID:10;x:1UID:10;y:2UID:10;z:3. Bahkan, juga pada titik ini Anda tidak harus menciptakan kembali roda, gunakan perpustakaan seperti protobuf yang dapat mengurangi data seperti itu ke string 10 byte atau kurang.

Satu-satunya hal yang tidak boleh Anda lupakan adalah memasukkan Flushperintah pada stream Anda di lokasi yang relevan, karena begitu Anda berhenti menambahkan data ke stream Anda, itu mungkin menunggu tanpa batas sebelum mengirim apa pun. Benar-benar bermasalah ketika klien Anda menunggu data itu, dan server Anda tidak akan mengirim sesuatu yang baru sampai klien mengirim perintah berikutnya.

Paket loss adalah sesuatu yang bisa Anda pengaruhi di sini, secara marginal. Setiap byte yang Anda kirim berpotensi rusak, dan TCP akan secara otomatis meminta pengiriman ulang. Paket yang lebih kecil berarti peluang yang lebih rendah untuk setiap paket yang rusak, tetapi karena mereka bertambah pada biaya overhead, Anda mengirim lebih banyak byte, meningkatkan kemungkinan paket yang hilang lebih banyak. Ketika sebuah paket hilang, TCP akan melakukan buffer semua data yang berhasil sampai paket yang hilang dikirim ulang dan diterima. Ini menghasilkan penundaan besar (ping). Sementara total kerugian dalam bandwidth karena paket loss bisa diabaikan, ping yang lebih tinggi tidak diinginkan untuk gim.

Intinya: Kirim sesedikit mungkin data, kirim paket besar, dan jangan menulis metode tingkat rendah Anda sendiri untuk melakukannya, tetapi mengandalkan perpustakaan terkenal dan metode seperti bufferstreamdan protobuf untuk menangani angkat berat.

Dorus
sumber
Sebenarnya untuk hal-hal sederhana seperti ini mudah untuk menggulung sendiri. Jauh lebih mudah daripada melalui dokumentasi 50 halaman untuk menggunakan perpustakaan orang lain, dan kemudian setelah itu Anda masih harus berurusan dengan bug dan gotcha mereka.
Pacerier
Benar, menulis sendiri bufferstreamitu sepele, itu sebabnya saya menyebutnya metode. Anda masih ingin menanganinya di satu tempat, dan tidak mengintegrasikan logika buffer Anda dengan kode pesan Anda. Adapun Obyek Serialisasi, saya sangat ragu Anda mendapatkan sesuatu yang lebih baik daripada ribuan orang-jam yang dimasukkan ke sana, bahkan jika Anda DO mencoba, saya sangat menyarankan Anda membandingkan solusi Anda dengan implementasi yang dikenal.
Dorus
2

Walaupun saya adalah orang baru dalam pemrograman jaringan, saya ingin berbagi pengalaman terbatas saya dengan menambahkan beberapa poin:

  • TCP memang menyiratkan overhead - Anda harus mengukur statistik yang relevan
  • UDP adalah solusi de facto untuk skenario gaming jaringan, tetapi semua implementasi yang bergantung padanya memiliki algoritma sisi-CPU tambahan untuk memperhitungkan paket yang hilang atau dikirim tidak sesuai pesanan

Mengenai pengukuran, metrik yang harus dipertimbangkan adalah:

Seperti disebutkan, jika Anda mengetahui bahwa Anda tidak terbatas dalam arti tertentu dan dapat menggunakan UDP, lakukan itu. Ada beberapa implementasi berbasis UDP di luar sana, jadi Anda tidak perlu menemukan kembali roda atau bekerja melawan pengalaman bertahun-tahun dan pengalaman yang terbukti. Implementasi yang layak disebutkan adalah:

Kesimpulan: karena implementasi UDP dapat mengungguli (dengan faktor 3x) yang TCP, masuk akal untuk mempertimbangkannya, setelah Anda mengidentifikasi skenario Anda ramah UDP. Diperingatkan! Menerapkan tumpukan TCP lengkap di atas UDP selalu merupakan ide yang buruk.

teko teh
sumber
Saya menggunakan UDP. Saya baru saja beralih ke TCP. Kehilangan paket UDP tidak dapat diterima untuk data penting yang dibutuhkan klien. Saya dapat mengirim data perpindahan melalui UDP.
joehot200
Jadi, hal terbaik yang dapat Anda lakukan adalah: memang, gunakan TCP hanya untuk operasi penting ATAU gunakan implementasi protokol perangkat lunak berbasis UDP (dengan Enet menjadi sederhana dan UDT sedang diuji dengan baik). Tetapi pertama-tama, ukur kerugiannya dan putuskan apakah UDT akan memberi Anda keunggulan.
teodron