Seberapa besar seharusnya buffer recv saya ketika memanggil recv di perpustakaan socket

129

Saya punya beberapa pertanyaan tentang perpustakaan soket di C. Ini adalah potongan kode yang akan saya rujuk dalam pertanyaan saya.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. Bagaimana cara saya memutuskan seberapa besar untuk membuat recv_buffer? Saya menggunakan 3000, tetapi arbitrer.
  2. apa yang terjadi jika recv()menerima paket yang lebih besar dari buffer saya?
  3. bagaimana saya bisa tahu jika saya telah menerima seluruh pesan tanpa memanggil recv lagi dan membuatnya menunggu selamanya ketika tidak ada yang diterima?
  4. apakah ada cara saya bisa membuat buffer tidak memiliki jumlah ruang yang tetap, sehingga saya bisa terus menambahkannya tanpa takut kehabisan ruang? mungkin menggunakan strcatuntuk menggabungkan recv()respons terbaru ke buffer?

Saya tahu ada banyak pertanyaan dalam satu pertanyaan, tetapi saya akan sangat menghargai tanggapan apa pun.

adhanlon
sumber

Jawaban:

230

Jawaban atas pertanyaan-pertanyaan ini bervariasi tergantung pada apakah Anda menggunakan soket aliran ( SOCK_STREAM) atau soket datagram ( SOCK_DGRAM) - dalam TCP / IP, yang pertama sesuai dengan TCP dan yang terakhir dengan UDP.

Bagaimana Anda tahu seberapa besar untuk membuat buffer diteruskan recv()?

  • SOCK_STREAM: Tidak terlalu penting. Jika protokol Anda bersifat transaksional / interaktif, pilih saja ukuran yang dapat menampung pesan / perintah individu terbesar yang Anda harapkan (3000 kemungkinan baik-baik saja). Jika protokol Anda mentransfer data massal, maka buffer yang lebih besar bisa lebih efisien - aturan praktis yang baik hampir sama dengan kernel menerima ukuran buffer dari socket (seringkali sekitar 256kB).

  • SOCK_DGRAM: Gunakan penyangga yang cukup besar untuk menampung paket terbesar yang pernah dikirim protokol tingkat aplikasi Anda. Jika Anda menggunakan UDP, maka secara umum protokol tingkat aplikasi Anda seharusnya tidak mengirim paket yang lebih besar dari sekitar 1400 byte, karena mereka pasti perlu difragmentasi dan disusun kembali.

Apa yang terjadi jika recvmendapat paket yang lebih besar dari buffer?

  • SOCK_STREAM: Pertanyaannya tidak masuk akal, karena soket aliran tidak memiliki konsep paket - mereka hanya aliran byte yang berkelanjutan. Jika ada lebih banyak byte yang tersedia untuk dibaca daripada ruang buffer Anda, maka mereka akan diantrekan oleh OS dan tersedia untuk panggilan Anda selanjutnya recv.

  • SOCK_DGRAM: Bytes yang berlebihan dibuang.

Bagaimana saya bisa tahu jika saya telah menerima seluruh pesan?

  • SOCK_STREAM: Anda perlu membangun beberapa cara untuk menentukan end-of-message ke dalam protokol tingkat aplikasi Anda. Biasanya ini adalah awalan panjang (mulai setiap pesan dengan panjang pesan) atau pembatas akhir pesan (yang mungkin hanya berupa baris baru dalam protokol berbasis teks, misalnya). Opsi ketiga, yang kurang digunakan, adalah untuk mengamanatkan ukuran tetap untuk setiap pesan. Kombinasi opsi ini juga dimungkinkan - misalnya, header ukuran tetap yang menyertakan nilai panjang.

  • SOCK_DGRAM: recvPanggilan tunggal selalu mengembalikan datagram tunggal.

Apakah ada cara saya dapat membuat buffer tidak memiliki jumlah ruang yang tetap, sehingga saya dapat terus menambahkannya tanpa takut kehabisan ruang?

Tidak. Namun, Anda dapat mencoba mengubah ukuran buffer menggunakan realloc()(jika awalnya dialokasikan dengan malloc()atau calloc(), yaitu).

kaf
sumber
1
Saya memiliki "/ r / n / r / n" di akhir pesan dalam protokol yang saya gunakan. Dan saya memiliki loop do do, di dalam saya menelepon recv saya menempatkan pesan di awal recv_buffer. dan pernyataan while saya terlihat seperti ini while ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); Pertanyaan saya adalah, apakah mungkin satu recv untuk mendapatkan "\ r \ n" dan di recv berikutnya dapatkan "\ r \ n", sehingga kondisi sementara saya tidak pernah menjadi kenyataan?
adhanlon
3
Ya itu. Anda dapat mengatasi masalah itu dengan memutar-mutar jika Anda tidak memiliki pesan lengkap dan memasukkan byte dari yang berikutnya recvke buffer mengikuti pesan parsial. Anda tidak boleh menggunakan strstr()buffer mentah yang diisi oleh recv()- tidak ada jaminan bahwa itu berisi nul-terminator, jadi itu bisa menyebabkan strstr()crash.
caf
3
Dalam hal UDP, tidak ada yang salah dengan mengirim paket UDP di atas 1400 byte. Fragmentasi adalah legal dan merupakan bagian mendasar dari protokol IP (bahkan dalam IPv6, namun selalu ada pengirim awal yang harus melakukan fragmentasi). Untuk UDP Anda selalu menyimpan jika Anda menggunakan buffer 64 KB, karena tidak ada paket IP (v4 atau v6) yang dapat berukuran di atas 64 KB (bahkan ketika terfragmentasi) dan ini bahkan termasuk header IIRC, sehingga data akan selalu di bawah 64 KB pasti.
Mecki
1
@caf Anda perlu mengosongkan buffer pada setiap panggilan ke recv ()? Saya telah melihat kode loop dan mengumpulkan data dan loop lagi yang seharusnya mengumpulkan lebih banyak data. Tetapi jika buffer pernah penuh tidak Anda perlu mengosongkannya untuk menghindari pelanggaran memori karena menulis melewati jumlah memori yang dialokasikan untuk buffer?
Alex_Nabu
1
@Alex_Nabu: Anda tidak perlu mengosongkannya selama masih ada ruang di dalamnya, dan Anda tidak meminta recv()untuk menulis lebih banyak byte daripada ada ruang yang tersisa.
caf
16

Untuk protokol streaming seperti TCP, Anda dapat mengatur buffer dengan ukuran apa pun. Yang mengatakan, nilai-nilai umum yang merupakan kekuatan 2 seperti 4096 atau 8192 direkomendasikan.

Jika ada lebih banyak data maka apa buffer Anda, itu hanya akan disimpan dalam kernel untuk panggilan Anda selanjutnya recv.

Ya, Anda dapat terus menumbuhkan buffer Anda. Anda dapat melakukan recv ke tengah buffer mulai dari offset idx, Anda akan melakukan:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);
R Samuel Klatchko
sumber
6
Kekuatan dua bisa lebih efisien dalam berbagai cara, dan sangat disarankan.
Yann Ramin
3
menguraikan @theatrus, efisiensi yang menonjol adalah bahwa operator modulo dapat diganti dengan bitwise dan dengan mask (mis. x% 1024 == x & 1023), dan pembagian integer dapat diganti dengan operasi shift kanan (mis. x / 1024 = = x / 2 ^ 10 == x >> 10)
vicatcu
15

Jika Anda memiliki SOCK_STREAMsoket, recvdapatkan "hingga 3000 byte pertama" dari stream. Tidak ada panduan yang jelas tentang seberapa besar membuat buffer: satu-satunya waktu Anda tahu seberapa besar aliran, adalah ketika semuanya selesai ;-).

Jika Anda memiliki SOCK_DGRAMsoket, dan datagram lebih besar dari buffer, recvisi buffer dengan bagian pertama datagram, mengembalikan -1, dan set errno ke EMSGSIZE. Sayangnya, jika protokolnya adalah UDP, ini berarti seluruh datagram hilang - bagian dari alasan mengapa UDP disebut sebagai protokol yang tidak dapat diandalkan (saya tahu bahwa ada protokol datagram yang dapat diandalkan tetapi mereka tidak terlalu populer - saya tidak bisa nama satu dalam keluarga TCP / IP, meskipun mengetahui yang terakhir dengan cukup baik ;-).

Untuk menumbuhkan buffer secara dinamis, alokasikan awalnya dengan mallocdan gunakan reallocsesuai kebutuhan. Tapi itu tidak akan membantu Anda recvdari sumber UDP, sayangnya.

Alex Martelli
sumber
7
Karena UDP selalu mengembalikan paling banyak satu paket UDP (bahkan jika banyak berada dalam buffer socket) dan tidak ada paket UDP bisa di atas 64 KB (paket IP paling banyak 64 KB, bahkan ketika terfragmentasi), menggunakan buffer 64 KB benar-benar aman dan terjamin, bahwa Anda tidak pernah kehilangan data apa pun selama pemulihan pada soket UDP.
Mecki
7

Untuk SOCK_STREAMsoket, ukuran buffer tidak terlalu penting, karena Anda hanya menarik beberapa byte yang menunggu dan Anda dapat mengambil lebih banyak di panggilan berikutnya. Pilih saja ukuran buffer apa pun yang Anda mampu.

Untuk SOCK_DGRAMsoket, Anda akan mendapatkan bagian yang pas dari pesan tunggu dan sisanya akan dibuang. Anda bisa mendapatkan ukuran datagram tunggu dengan ioctl berikut:

#include <sys/ioctl.h>
int size;
ioctl(sockfd, FIONREAD, &size);

Atau Anda dapat menggunakan MSG_PEEKdan MSG_TRUNCmenandai recv()panggilan untuk mendapatkan ukuran datagram yang menunggu.

ssize_t size = recv(sockfd, buf, len, MSG_PEEK | MSG_TRUNC);

Anda perlu MSG_PEEKmengintip (tidak menerima) pesan tunggu - recv mengembalikan ukuran sebenarnya, bukan terpotong; dan Anda tidak perlu MSG_TRUNCmeluap buffer Anda saat ini.

Maka Anda bisa saja malloc(size)buffer nyata dan recv()datagram.

smokku
sumber
MSG_PEEK | MSG_TRUNC tidak masuk akal.
Marquis of Lorne
3
Anda ingin MSG_PEEK mengintip (tidak menerima) pesan tunggu, untuk mendapatkan ukurannya (recv mengembalikan ukuran sebenarnya, bukan terpotong) dan Anda perlu MSG_TRUNC untuk tidak meluap buffer Anda saat ini. Setelah Anda mendapatkan ukuran Anda mengalokasikan buffer yang benar dan menerima (tidak mengintip, tidak memotong) pesan tunggu.
smokku
@Alex Martelli mengatakan 64KB adalah ukuran maksimal dari paket UDP jadi jika kita malloc()untuk buffer 64KB maka MSG_TRUNCtidak perlu?
mLstudent33
1
Protokol IP mendukung fragmentasi, sehingga datagram mungkin lebih besar dari satu paket - itu akan terfragmentasi dan dikirim dalam beberapa paket. Juga SOCK_DGRAMbukan hanya UDP.
smokku
1

Tidak ada jawaban mutlak untuk pertanyaan Anda, karena teknologi selalu terikat pada implementasi spesifik. Saya berasumsi Anda berkomunikasi dalam UDP karena ukuran buffer yang masuk tidak membawa masalah pada komunikasi TCP.

Menurut RFC 768 , ukuran paket (termasuk header) untuk UDP dapat berkisar dari 8 hingga 65.515 byte. Jadi ukuran gagal-bukti untuk buffer yang masuk adalah 65 507 byte (~ 64KB)

Namun, tidak semua paket besar dapat diarahkan dengan benar oleh perangkat jaringan, lihat diskusi yang ada untuk informasi lebih lanjut:

Berapa ukuran optimal paket UDP untuk throughput maksimum?
Apa Ukuran Paket UDP Aman terbesar di Internet

YeenFei
sumber
-4

16kb hampir benar; jika Anda menggunakan gigabit ethernet, setiap paket bisa berukuran 9 kb.

Andrew McGregor
sumber
3
Soket TCP adalah stream, yang berarti recv dapat mengembalikan data yang terakumulasi dari beberapa paket, sehingga ukuran paket sama sekali tidak relevan untuk TCP. Dalam kasus UDP, masing-masing panggilan balik mengembalikan paling banyak paket UDP tunggal, di sini ukuran paket relevan tetapi ukuran paket yang benar adalah sekitar 64 KB, karena paket UDP dapat (dan sering akan) terfragmentasi jika diperlukan. Namun, tidak ada paket IP yang dapat di atas 64 KB, bahkan tidak dengan fragmentasi, sehingga memulihkan pada soket UDP paling banyak dapat mengembalikan 64 KB (dan apa yang tidak dikembalikan dibuang untuk paket saat ini!)
Mecki