Bộ đệm recv của tôi nên lớn đến mức nào khi gọi recv trong thư viện socket


129

Tôi có một vài câu hỏi về thư viện socket trong C. Đây là một đoạn mã tôi sẽ đề cập đến trong các câu hỏi của mình.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. Làm cách nào để tôi quyết định mức độ lớn để tạo recv_buffer? Tôi đang sử dụng 3000, nhưng nó tùy ý.
  2. Điều gì xảy ra nếu recv()nhận được một gói lớn hơn bộ đệm của tôi?
  3. Làm thế nào tôi có thể biết nếu tôi đã nhận được toàn bộ tin nhắn mà không gọi lại recv và nó sẽ đợi mãi khi không có gì để nhận?
  4. Có cách nào để tôi có thể làm cho bộ đệm không có một khoảng trống cố định, để tôi có thể tiếp tục thêm vào nó mà không sợ hết dung lượng không? có thể sử dụng strcatđể nối các recv()phản ứng mới nhất với bộ đệm?

Tôi biết đó là rất nhiều câu hỏi trong một, nhưng tôi sẽ đánh giá rất cao bất kỳ câu trả lời nào.

Câu trả lời:


230

Các câu trả lời cho những câu hỏi này khác nhau tùy thuộc vào việc bạn đang sử dụng socket socket ( SOCK_STREAM) hay socket datagram ( SOCK_DGRAM) - trong TCP / IP, cái trước tương ứng với TCP và cái sau với UDP.

Làm thế nào để bạn biết làm thế nào lớn để làm cho bộ đệm truyền đến recv()?

  • SOCK_STREAM: Nó không thực sự quá quan trọng. Nếu giao thức của bạn là giao dịch / tương tác, chỉ cần chọn một kích thước có thể chứa thông điệp / lệnh riêng lẻ lớn nhất mà bạn mong đợi một cách hợp lý (3000 có thể sẽ ổn). Nếu giao thức của bạn đang truyền dữ liệu số lượng lớn, thì bộ đệm lớn hơn có thể hiệu quả hơn - một quy tắc tốt là xung quanh giống như nhân nhận kích thước bộ đệm của ổ cắm (thường là khoảng 256kB).

  • SOCK_DGRAM: Sử dụng bộ đệm đủ lớn để chứa gói lớn nhất mà giao thức cấp ứng dụng của bạn từng gửi. Nếu bạn đang sử dụng UDP, thì nói chung, giao thức cấp ứng dụng của bạn không nên gửi các gói lớn hơn khoảng 1400 byte, bởi vì chúng chắc chắn sẽ cần phải được phân mảnh và lắp lại.

Điều gì xảy ra nếu recvnhận được một gói lớn hơn bộ đệm?

  • SOCK_STREAM: Câu hỏi không thực sự có ý nghĩa như đặt, bởi vì các ổ cắm luồng không có khái niệm về gói - chúng chỉ là một luồng byte liên tục. Nếu có nhiều byte có sẵn để đọc hơn bộ đệm của bạn có chỗ, thì chúng sẽ được HĐH xếp hàng và sẵn sàng cho cuộc gọi tiếp theo của bạn recv.

  • SOCK_DGRAM: Các byte thừa được loại bỏ.

Làm thế nào tôi có thể biết nếu tôi đã nhận được toàn bộ tin nhắn?

  • SOCK_STREAM: Bạn cần xây dựng một số cách xác định tin nhắn cuối vào giao thức cấp ứng dụng của mình. Thông thường, đây là tiền tố độ dài (bắt đầu mỗi tin nhắn có độ dài của tin nhắn) hoặc dấu phân cách cuối tin nhắn (ví dụ có thể chỉ là một dòng mới trong giao thức dựa trên văn bản). Tùy chọn thứ ba, ít sử dụng hơn là bắt buộc kích thước cố định cho mỗi tin nhắn. Kết hợp các tùy chọn này cũng có thể - ví dụ: tiêu đề có kích thước cố định bao gồm giá trị độ dài.

  • SOCK_DGRAM: Một recvcuộc gọi luôn trả về một datagram duy nhất.

Có cách nào để tôi có thể làm cho bộ đệm không có một khoảng trống cố định, để tôi có thể tiếp tục thêm vào nó mà không sợ hết dung lượng không?

Tuy nhiên, bạn có thể thử thay đổi kích thước bộ đệm bằng cách sử dụng realloc()(nếu ban đầu nó được phân bổ bằng malloc()hoặc calloc(), nghĩa là).


1
Tôi có "/ r / n / r / n" ở cuối tin nhắn trong giao thức tôi đang sử dụng. Và tôi có một vòng lặp do while, bên trong tôi đang gọi recv Tôi đặt tin nhắn ở đầu recv_buffer. và câu lệnh while của tôi trông như thế này trong khi ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); recv tiếp theo nhận "\ r \ n", để điều kiện trong khi của tôi không bao giờ thành hiện thực?
adhanlon

3
Vâng, đúng vậy. Bạn có thể giải quyết vấn đề đó bằng cách lặp đi lặp lại nếu bạn không có một thông báo hoàn chỉnh và nhét các byte từ phần tiếp theo recvvào bộ đệm theo thông điệp một phần. Bạn không nên sử dụng strstr()trên bộ đệm thô được lấp đầy bởi recv()- không có gì đảm bảo rằng nó có chứa bộ kết thúc không, vì vậy nó có thể gây ra sự strstr()cố.
phê

3
Trong trường hợp UDP, không có gì sai khi gửi các gói UDP trên 1400 byte. Phân mảnh là hoàn toàn hợp pháp và là một phần cơ bản của giao thức IP (ngay cả trong IPv6, tuy nhiên luôn có người gửi ban đầu phải thực hiện phân mảnh). Đối với UDP, bạn luôn được lưu nếu bạn sử dụng bộ đệm 64 KB, vì không có gói IP nào (v4 hoặc v6) có thể có kích thước trên 64 KB (ngay cả khi bị phân mảnh) và điều này thậm chí bao gồm cả IIRC tiêu đề, vì vậy dữ liệu sẽ luôn luôn chắc chắn dưới 64 KB.
Mecki

1
@caf bạn có cần làm trống bộ đệm trên mỗi lệnh gọi tới recv () không? Tôi đã thấy vòng lặp mã và thu thập dữ liệu và lặp lại nó sẽ thu thập thêm dữ liệu. Nhưng nếu bộ đệm đã đầy, bạn không cần làm trống nó để tránh vi phạm bộ nhớ do ghi vượt qua số lượng bộ nhớ được phân bổ cho bộ đệm?
Alex_Nabu

1
@Alex_Nabu: Bạn không cần làm trống nó miễn là còn một khoảng trống trong đó và bạn không được yêu recv()cầu viết nhiều byte hơn khoảng trống còn lại.
phê

16

Đối với các giao thức phát trực tuyến như TCP, bạn có thể thiết lập bộ đệm của mình thành bất kỳ kích thước nào. Điều đó nói rằng, các giá trị phổ biến là lũy thừa của 2 như 4096 hoặc 8192 được khuyến nghị.

Nếu có nhiều dữ liệu hơn thì bộ đệm của bạn là gì, nó sẽ chỉ được lưu trong kernel cho cuộc gọi tiếp theo của bạn recv.

Có, bạn có thể tiếp tục phát triển bộ đệm của bạn. Bạn có thể thực hiện một recv vào giữa bộ đệm bắt đầu từ offset idx, bạn sẽ làm:

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

6
Sức mạnh của hai có thể hiệu quả hơn theo nhiều cách, và được đề xuất mạnh mẽ.
Yann Ramin

3
xây dựng trên @theatrus, một hiệu quả đáng chú ý là toán tử modulo có thể được thay thế bằng bitwise và bằng mặt nạ (ví dụ x% 1024 == x & 1023) và phép chia số nguyên có thể được thay thế bằng thao tác quyền thay đổi (ví dụ x / 1024 = = x / 2 ^ 10 == x >> 10)
Abbeyatcu

15

Nếu bạn có một SOCK_STREAMổ cắm, recvchỉ cần nhận "tối đa 3000 byte đầu tiên" từ luồng. Không có hướng dẫn rõ ràng về mức độ lớn để tạo bộ đệm: lần duy nhất bạn biết luồng lớn như thế nào, là khi tất cả đã hoàn thành ;-).

Nếu bạn có một SOCK_DGRAMổ cắm và datagram lớn hơn bộ đệm, hãy recvlấp đầy bộ đệm với phần đầu tiên của datagram, trả về -1 và đặt errno thành EMSGSIZE. Thật không may, nếu giao thức là UDP, điều này có nghĩa là phần còn lại của datagram bị mất - một phần lý do tại sao UDP được gọi là không đáng tin cậy giao thức (tôi biết rằng có các giao thức datagram đáng tin cậy nhưng chúng không phổ biến - tôi không thể đặt tên một trong họ TCP / IP, mặc dù biết cái sau khá rõ ;-).

Để phát triển bộ đệm một cách linh hoạt, hãy phân bổ ban đầu mallocvà sử dụng reallockhi cần thiết. Nhưng điều đó sẽ không giúp bạn với recvnguồn UDP, than ôi.


7
Vì UDP luôn trả về tối đa một gói UDP (ngay cả khi nhiều gói nằm trong bộ đệm ổ cắm) và không có gói UDP nào có thể trên 64 KB (một gói IP có thể nhiều nhất là 64 KB, ngay cả khi bị phân mảnh), sử dụng bộ đệm 64 KB hoàn toàn an toàn và đảm bảo, rằng bạn không bao giờ mất bất kỳ dữ liệu nào trong quá trình recv trên ổ cắm UDP.
Mecki

7

Đối với SOCK_STREAMổ cắm, kích thước bộ đệm không thực sự quan trọng, bởi vì bạn chỉ cần kéo một số byte đang chờ và bạn có thể truy xuất thêm trong một cuộc gọi tiếp theo. Chỉ cần chọn bất kỳ kích thước bộ đệm bạn có thể đủ khả năng.

Đối với SOCK_DGRAMổ cắm, bạn sẽ nhận được phần phù hợp của tin nhắn chờ và phần còn lại sẽ bị loại bỏ. Bạn có thể nhận được kích thước datagram chờ với ioctl sau:

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

Ngoài ra, bạn có thể sử dụng MSG_PEEKvà gắn MSG_TRUNCcờ của recv()cuộc gọi để có được kích thước datagram đang chờ.

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

Bạn cần MSG_PEEKxem lén (không nhận) tin nhắn đang chờ - recv trả về kích thước thật, không bị cắt ngắn; và bạn cần MSG_TRUNCkhông tràn bộ đệm hiện tại của bạn.

Sau đó, bạn có thể chỉ cần malloc(size)bộ đệm thực và recv()datagram.


MSG_PEEK | MSG_TRUNC không có ý nghĩa.
Hầu tước Lorne

3
Bạn muốn MSG_PEEK nhìn trộm (không nhận) tin nhắn đang chờ, để có được kích thước của nó (recv trả về kích thước thật, không bị cắt ngắn) và bạn cần MSG_TRUNC để không tràn bộ đệm hiện tại của bạn. Khi bạn nhận được kích thước, bạn phân bổ bộ đệm chính xác và nhận (không nhìn trộm, không cắt ngắn) thông báo chờ.
smokku

@Alex Martelli nói 64KB là kích thước tối đa của gói UDP, vậy nếu chúng ta malloc()cho bộ đệm 64KB thì MSG_TRUNCcó cần thiết không?
mLstudent33

1
Giao thức IP hỗ trợ phân mảnh, do đó datagram có thể lớn hơn một gói - nó sẽ bị phân mảnh và truyền trong nhiều gói. Cũng SOCK_DGRAMkhông chỉ UDP.
smokku

1

Không có câu trả lời tuyệt đối cho câu hỏi của bạn, bởi vì công nghệ luôn bị ràng buộc phải cụ thể. Tôi giả sử bạn đang giao tiếp trong UDP vì kích thước bộ đệm đến không mang lại vấn đề gì cho giao tiếp TCP.

Theo RFC 768 , kích thước gói (bao gồm tiêu đề) cho UDP có thể dao động từ 8 đến 65 515 byte. Vì vậy, kích thước không chứng minh cho bộ đệm đến là 65 507 byte (~ 64KB)

Tuy nhiên, không phải tất cả các gói lớn đều có thể được định tuyến chính xác bởi các thiết bị mạng, hãy tham khảo thảo luận hiện có để biết thêm thông tin:

Kích thước tối ưu của gói UDP cho thông lượng tối đa là bao nhiêu?
Kích thước gói UDP an toàn lớn nhất trên Internet là bao nhiêu


-4

16kb là đúng; nếu bạn đang sử dụng ethernet gigabit, mỗi gói có thể có kích thước 9kb.


3
Ổ cắm TCP là luồng, có nghĩa là một recv có thể trả về dữ liệu được tích lũy từ nhiều gói, vì vậy kích thước gói hoàn toàn không liên quan đến TCP. Trong trường hợp UDP, mỗi cuộc gọi recv trả về tối đa một gói UDP, ở đây kích thước gói có liên quan nhưng kích thước gói chính xác là khoảng 64 KB, vì một gói UDP có thể (và thường sẽ) bị phân mảnh nếu được yêu cầu. Tuy nhiên, không có gói IP nào có thể vượt quá 64 KB, thậm chí không bị phân mảnh, do đó, recv trên ổ cắm UDP có thể trả lại tối đa 64 KB (và những gì không được trả lại sẽ bị loại bỏ cho gói hiện tại!)
Mecki 18/12/12
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.