บัฟเฟอร์ recv ของฉันควรใหญ่แค่ไหนเมื่อเรียก recv ในไลบรารีซ็อกเก็ต


129

ฉันมีคำถามสองสามข้อเกี่ยวกับซ็อกเก็ตไลบรารี่ในซีนี่คือตัวอย่างของโค้ดที่ฉันจะอ้างถึงในคำถามของฉัน

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. ฉันจะตัดสินใจได้อย่างไรว่าจะสร้าง recv_buffer ขนาดใหญ่ได้อย่างไร ฉันใช้ 3000 แต่มันไม่มีข้อ จำกัด
  2. จะเกิดอะไรขึ้นหากrecv()ได้รับแพ็คเก็ตที่ใหญ่กว่าบัฟเฟอร์ของฉัน
  3. ฉันจะรู้ได้อย่างไรว่าฉันได้รับข้อความทั้งหมดโดยไม่ต้องโทร recv อีกครั้งและรอมันตลอดไปเมื่อไม่มีอะไรจะได้รับ?
  4. มีวิธีที่ฉันสามารถสร้างบัฟเฟอร์ไม่ได้มีพื้นที่จำนวนคงที่เพื่อให้ฉันสามารถเพิ่มไปเรื่อย ๆ โดยไม่ต้องกลัวที่จะหมดพื้นที่? อาจใช้strcatเพื่อเชื่อมการrecv()ตอบสนองล่าสุดกับบัฟเฟอร์หรือไม่

ฉันรู้ว่ามันเป็นคำถามจำนวนมากในหนึ่งเดียว แต่ฉันจะขอบคุณคำตอบอย่างมาก

คำตอบ:


230

คำตอบสำหรับคำถามเหล่านี้จะแตกต่างกันไปขึ้นอยู่กับว่าคุณกำลังใช้ซ็อกเก็ตสตรีม ( SOCK_STREAM) หรือซ็อกเก็ตดาตาแกรม ( SOCK_DGRAM) - ภายใน TCP / IP, อดีตสอดคล้องกับ TCP และหลังเป็น UDP

คุณรู้ได้อย่างไรว่าขนาดใหญ่ที่จะทำให้บัฟเฟอร์ส่งผ่านไปยังrecv()?

  • SOCK_STREAM: มันไม่สำคัญมากนัก หากโปรโตคอลของคุณเป็นธุรกรรม / โต้ตอบเพียงแค่เลือกขนาดที่สามารถเก็บข้อความ / คำสั่งที่ใหญ่ที่สุดที่คุณคาดหวังอย่างสมเหตุสมผล (3000 น่าจะดี) หากโปรโตคอลของคุณกำลังถ่ายโอนข้อมูลจำนวนมากบัฟเฟอร์ที่ใหญ่กว่านั้นจะมีประสิทธิภาพมากกว่า - กฎง่ายๆคือรอบ ๆ เดียวกับที่เคอร์เนลได้รับขนาดบัฟเฟอร์ของซ็อกเก็ต

  • SOCK_DGRAM: ใช้บัฟเฟอร์ใหญ่พอที่จะเก็บแพ็กเก็ตที่ใหญ่ที่สุดที่โปรโตคอลระดับแอปพลิเคชันของคุณเคยส่ง หากคุณใช้ UDP โดยทั่วไปแล้วโปรโตคอลระดับแอปพลิเคชันของคุณไม่ควรส่งแพ็คเก็ตที่มีขนาดใหญ่กว่าประมาณ 1,400 ไบต์เพราะแน่นอนว่าพวกเขาจะต้องแยกส่วนและประกอบใหม่

จะเกิดอะไรขึ้นถ้าrecvแพ็กเก็ตใหญ่กว่าบัฟเฟอร์

  • SOCK_STREAM: คำถามไม่สมเหตุสมผลเท่าที่ควรเพราะซ็อกเก็ตสตรีมไม่มีแนวคิดของแพ็กเก็ต - มันเป็นเพียงกระแสไบต์ต่อเนื่อง หากมีมากขึ้นไบต์สามารถอ่านกว่าบัฟเฟอร์ของคุณมีห้องพักสำหรับแล้วพวกเขาก็จะได้รับการจัดคิวโดย OS recvและพร้อมสำหรับการโทรไปของคุณไป

  • SOCK_DGRAM: ไบต์ส่วนเกินถูกยกเลิก

ฉันจะรู้ได้อย่างไรว่าฉันได้รับข้อความทั้งหมด?

  • SOCK_STREAM: คุณต้องสร้างวิธีการกำหนดจุดสิ้นสุดข้อความในโปรโตคอลระดับแอปพลิเคชันของคุณ โดยทั่วไปนี่คือส่วนนำหน้าความยาว (เริ่มต้นแต่ละข้อความด้วยความยาวของข้อความ) หรือตัวคั่นสิ้นสุดข้อความ (ซึ่งอาจเป็นบรรทัดใหม่ในโปรโตคอลแบบข้อความเป็นต้น) ตัวเลือกที่สามใช้น้อยกว่าคือการกำหนดขนาดคงที่สำหรับแต่ละข้อความ การรวมกันของตัวเลือกเหล่านี้ก็เป็นไปได้เช่นกันตัวอย่างเช่นส่วนหัวที่มีขนาดคงที่ที่มีค่าความยาว

  • SOCK_DGRAM: การrecvโทรครั้งเดียวจะส่งคืนดาตาแกรมเดียวเสมอ

มีวิธีที่ฉันสามารถสร้างบัฟเฟอร์ไม่ได้มีพื้นที่จำนวนคงที่เพื่อให้ฉันสามารถเพิ่มไปเรื่อย ๆ โดยไม่ต้องกลัวที่จะหมดพื้นที่?

ไม่ได้อย่างไรก็ตามคุณสามารถลองปรับขนาดบัฟเฟอร์โดยใช้realloc()(หากมีการจัดสรรไว้ตั้งแต่แรกmalloc()หรือcalloc()นั่นคือ)


1
ฉันมี "/ r / n / r / n" ที่ส่วนท้ายของข้อความในโปรโตคอลที่ฉันใช้ และฉันต้องทำในขณะที่วงในฉันเรียก recv ฉันวางข้อความที่จุดเริ่มต้นของ recv_buffer และคำสั่ง while ของฉันมีลักษณะเช่นนี้ while (! (strstr (recv_buffer, "\ r \ n \ r \ n")); คำถามของฉันคือมันเป็นไปได้หรือไม่ที่ recv หนึ่งจะได้รับ "\ r \ n" และใน ต่อไป recv รับ "\ r \ n" เพื่อให้สภาพในขณะที่ฉันไม่เคยมาจริงหรือไม่?
adhanlon

3
ใช่แล้ว. คุณสามารถแก้ปัญหานี้ได้โดยวนลูปถ้าคุณไม่มีข้อความที่สมบูรณ์และการบรรจุไบต์จากถัดไปrecvลงในบัฟเฟอร์ตามข้อความบางส่วน คุณไม่ควรใช้strstr()กับบัฟเฟอร์ดิบที่เต็มไปด้วยrecv()- ไม่มีการรับประกันว่ามันมี nul-terminator ดังนั้นมันอาจทำให้เกิดstrstr()ปัญหาได้
คาเฟ่

3
ในกรณีของ UDP ไม่มีอะไรผิดปกติกับการส่งแพ็กเก็ต UDP ที่สูงกว่า 1,400 ไบต์ การแยกส่วนเป็นกฎหมายอย่างสมบูรณ์และเป็นส่วนพื้นฐานของโปรโตคอล IP (แม้ใน IPv6 แต่มีผู้ส่งเริ่มต้นเสมอต้องดำเนินการกระจายตัว) สำหรับ UDP คุณจะถูกบันทึกไว้เสมอหากคุณใช้บัฟเฟอร์ 64 KB เนื่องจากไม่มีแพ็กเก็ต IP (v4 หรือ v6) ที่มีขนาดใหญ่กว่า 64 KB (ไม่ใช่แม้กระทั่งตอนที่แยกส่วน) และสิ่งนี้รวมถึงส่วนหัว IIRC ดังนั้นข้อมูลจะเป็น ต่ำกว่า 64 KB
Mecki

1
@caf คุณต้องล้างข้อมูลบัฟเฟอร์ในแต่ละการเรียกไปยัง recv () หรือไม่ ฉันเคยเห็นโค้ดลูปและรวบรวมข้อมูลและวนซ้ำอีกครั้งซึ่งควรรวบรวมข้อมูลเพิ่มเติม แต่ถ้าบัฟเฟอร์เคยเต็มคุณไม่จำเป็นต้องล้างมันเพื่อหลีกเลี่ยงการละเมิดหน่วยความจำเนื่องจากการเขียนผ่านจำนวนหน่วยความจำที่จัดสรรสำหรับบัฟเฟอร์หรือไม่
Alex_Nabu

1
@Alex_Nabu: คุณไม่จำเป็นต้องล้างมันตราบเท่าที่ยังมีพื้นที่เหลืออยู่ในนั้นและคุณไม่ได้บอกrecv()ให้เขียนไบต์มากกว่าที่จะมีพื้นที่เหลืออยู่
caf

16

สำหรับการสตรีมโปรโตคอลเช่น TCP คุณสามารถกำหนดบัฟเฟอร์ของคุณให้มีขนาดใดก็ได้ ที่กล่าวว่าควรใช้ค่าทั่วไปที่มีอำนาจเป็น 2 เช่น 4096 หรือ 8192

หากมีข้อมูลที่มากขึ้นแล้วสิ่งที่บัฟเฟอร์ของคุณก็จะก็จะถูกบันทึกไว้ใน kernel recvสำหรับการโทรต่อไปยัง

ใช่คุณสามารถเติบโตบัฟเฟอร์ของคุณ คุณสามารถทำ recv ไปที่กลางบัฟเฟอร์เริ่มต้นที่ offset idxคุณจะทำ:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);

6
พลังของสองสามารถมีประสิทธิภาพมากขึ้นในหลายวิธีและแนะนำอย่างยิ่ง
Yann Ramin

3
อย่างละเอียดใน @theatrus ประสิทธิภาพที่โดดเด่นคือผู้ประกอบการโมดูโลสามารถถูกแทนที่ด้วย bitwise และด้วยหน้ากาก (เช่น x% 1024 == x & 1023) และการหารจำนวนเต็มสามารถแทนที่ด้วยการดำเนินการเลื่อนขวา (เช่น x / 1024 = = x / 2 ^ 10 == x >> 10)
vicatcu

15

หากคุณมีSOCK_STREAMซ็อกเก็ตrecvเพียงรับ "มากถึง 3000 ไบต์แรก" จากสตรีม ไม่มีแนวทางที่ชัดเจนในการสร้างบัฟเฟอร์: เพียงครั้งเดียวที่คุณรู้ว่ากระแสข้อมูลคือเท่าใดเมื่อมันเสร็จสิ้น ;-)

หากคุณมี SOCK_DGRAMซ็อกเก็ตและดาตาแกรมมีขนาดใหญ่กว่าบัฟเฟอร์ให้recvเติมบัฟเฟอร์ด้วยส่วนแรกของดาตาแกรมส่งคืน -1 และตั้งค่า errno เป็น EMSGSIZE น่าเสียดายถ้าโปรโตคอลเป็น UDP นี่หมายความว่าดาตาแกรมที่เหลือหายไป - ส่วนหนึ่งของสาเหตุที่ UDP เรียกว่าโปรโตคอลที่ไม่น่าเชื่อถือ (ฉันรู้ว่ามีโปรโตคอลดาตาแกรมที่เชื่อถือได้ แต่พวกเขาไม่ได้รับความนิยมมาก - ชื่อหนึ่งในตระกูล TCP / IP แม้จะรู้หลังดี ;-)

หากต้องการขยายบัฟเฟอร์แบบไดนามิกให้จัดสรรและเริ่มต้นด้วยmallocและใช้reallocตามต้องการ แต่นั่นจะไม่ช่วยคุณrecvจากแหล่ง UDP เลย


7
เนื่องจาก UDP จะส่งคืนได้มากที่สุดหนึ่งแพ็คเก็ต UDP (แม้ว่าจะมีหลายอันอยู่ในซ็อกเก็ตบัฟเฟอร์) และไม่มีแพ็กเก็ต UDP ที่สูงกว่า 64 KB (แพ็คเก็ต IP อาจมากถึง 64 KB แม้เมื่อแยกส่วน) โดยใช้บัฟเฟอร์ 64 KB ปลอดภัยและรับประกันอย่างแน่นอนว่าคุณจะไม่สูญเสียข้อมูลใด ๆ ในระหว่าง recv บนซ็อกเก็ต UDP
Mecki

7

สำหรับSOCK_STREAMซ็อกเก็ตขนาดบัฟเฟอร์ไม่สำคัญเพราะคุณเพิ่งดึงไบต์ที่รอและคุณสามารถดึงข้อมูลเพิ่มเติมได้ในการโทรครั้งต่อไป เพียงเลือกขนาดบัฟเฟอร์ที่คุณสามารถจ่ายได้

สำหรับSOCK_DGRAMซ็อกเก็ตคุณจะได้รับส่วนที่เหมาะสมของข้อความที่รอและส่วนที่เหลือจะถูกทิ้ง คุณสามารถรับขนาดดาต้ารอด้วย ioctl ต่อไปนี้:

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

อีกทางหนึ่งคุณสามารถใช้MSG_PEEKและตั้งMSG_TRUNCค่าสถานะของการrecv()โทรเพื่อรับขนาดดาตาแกรมที่รอ

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

คุณต้องMSG_PEEKมอง (ไม่ได้รับ) ข้อความที่รอ - recv ส่งคืนขนาดจริงไม่ใช่ขนาดที่ถูกตัดทอน และคุณต้องMSG_TRUNCไม่ล้นบัฟเฟอร์ปัจจุบันของคุณ

จากนั้นคุณสามารถเพียงmalloc(size)บัฟเฟอร์จริงและrecv()ดาต้า


MSG_PEEK | MSG_TRUNC ไม่มีเหตุผล
มาร์ควิสแห่ง Lorne

3
คุณต้องการให้ MSG_PEEK มอง (ไม่ได้รับ) ข้อความที่รอรับขนาด (recv ส่งกลับจริงไม่ใช่ขนาดที่ถูกตัดทอน) และคุณต้องการ MSG_TRUNC ที่จะไม่ล้นบัฟเฟอร์ปัจจุบันของคุณ เมื่อคุณได้รับขนาดที่คุณจัดสรรบัฟเฟอร์ที่ถูกต้องและรับ (ไม่มองไม่ตัดทอน) ข้อความที่รอ
smokku

@Alex Martelli พูดว่า 64KB เป็นขนาดสูงสุดของแพ็คเก็ต UDP ดังนั้นถ้าเราmalloc()ใช้บัฟเฟอร์ 64KB MSG_TRUNCมันไม่จำเป็นเหรอ?
mLstudent33

1
โปรโตคอล IP รองรับการกระจายตัวของข้อมูลดังนั้นดาตาแกรมอาจมีขนาดใหญ่กว่าแพ็คเก็ตเดียว - มันจะถูกแยกส่วนและส่งเป็นหลายแพ็คเก็ต ยังSOCK_DGRAMไม่ได้เป็นเพียง UDP
smokku

1

ไม่มีคำตอบที่แน่นอนสำหรับคำถามของคุณเพราะเทคโนโลยีมีความผูกพันกับการใช้งานเฉพาะเสมอ ฉันสมมติว่าคุณกำลังสื่อสารใน UDP เนื่องจากขนาดบัฟเฟอร์ขาเข้าไม่ได้ทำให้เกิดปัญหากับการสื่อสาร TCP

ตามRFC 768ขนาดแพ็กเก็ต (รวมส่วนหัว) สำหรับ UDP สามารถอยู่ในช่วง 8 ถึง 65 515 ไบต์ ดังนั้นขนาดที่ป้องกันความล้มเหลวสำหรับบัฟเฟอร์ที่เข้ามาคือ 65 507 ไบต์ (~ 64KB)

อย่างไรก็ตามแพ็คเก็ตขนาดใหญ่ไม่สามารถกำหนดเส้นทางได้อย่างถูกต้องโดยอุปกรณ์เครือข่ายอ้างอิงการสนทนาที่มีอยู่สำหรับข้อมูลเพิ่มเติม

ขนาดที่เหมาะสมของแพ็คเก็ต UDP สำหรับปริมาณงานสูงสุดคือเท่าไร
ขนาดแพ็คเก็ต UDP ที่ปลอดภัยที่ใหญ่ที่สุดบนอินเทอร์เน็ตคืออะไร


-4

16kb นั้นถูกต้อง หากคุณใช้กิกะบิตอีเธอร์เน็ตแต่ละแพ็คเก็ตอาจมีขนาด 9kb


3
TCP ซ็อกเก็ตเป็นสตรีมซึ่งหมายความว่า recv อาจส่งคืนข้อมูลที่สะสมจากหลาย ๆ แพ็คเก็ตดังนั้นขนาดแพ็กเก็ตนั้นไม่เกี่ยวข้องทั้งหมดสำหรับ TCP ในกรณีของ UDP การเรียก recv แต่ละครั้งจะส่งกลับได้มากที่สุดแพ็คเก็ต UDP เดี่ยวขนาดแพ็คเก็ตมีความเกี่ยวข้อง แต่ขนาดแพ็คเก็ตที่ถูกต้องคือประมาณ 64 KB เนื่องจากแพ็กเก็ต UDP อาจ (และมักจะ) กระจัดกระจายถ้าจำเป็น อย่างไรก็ตามไม่มีแพ็คเก็ต IP ที่สามารถสูงกว่า 64 KB แม้จะไม่มีการกระจายตัวดังนั้น recv บนซ็อกเก็ต UDP สามารถส่งคืนได้มากที่สุด 64 KB (และสิ่งที่ไม่ได้ส่งคืนจะถูกยกเลิกสำหรับแพ็กเก็ตปัจจุบัน!)
Mecki
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.