У вас есть сообщение фиксированного размера, поэтому вы можете использовать что-то вроде этого:
#include <errno.h>
#include <limits.h>
// Returns the number of bytes read.
// EOF was reached if the number of bytes read is less than requested.
// On error, returns -1 and sets errno.
ssize_t recv_fixed_amount(int sockfd, char *buf, size_t size) {
if (size > SSIZE_MAX) {
errno = EINVAL;
return -1;
}
ssize_t bytes_read = 0;
while (size > 0) {
ssize_t rv = recv(sockfd, buf, size, 0);
if (rv < 0)
return -1;
if (rv == 0)
return bytes_read;
size -= rv;
bytes_read += rv;
buf += rv;
}
return bytes_read;
}
Было бы использовано что-то вроде этого:
typedef struct {
uint32_t length;
char contents[1020];
} Message;
Message message;
ssize_t bytes_read = recv_fixed_amount(sockfd, &(message.length), sizeof(message.length));
if (bytes_read == 0) {
printf("EOF reached\n");
exit(EXIT_SUCCESS);
}
if (bytes_read < 0) {
perror("recv");
exit(EXIT_FAILURE);
}
if (bytes_read != sizeof(message.length)) {
fprintf(stderr, "recv: Premature EOF.\n");
exit(EXIT_FAILURE);
}
bytes_read = recv_fixed_amount(sockfd, &(message.content), sizeof(message.content));
if (bytes_read < 0) {
perror("recv");
exit(EXIT_FAILURE);
}
if (bytes_read != msg_size) {
fprintf(stderr, "recv: Premature EOF.\n");
exit(EXIT_FAILURE);
}
Примечания:
size_t
не будет везде одинаковым, поэтому я переключился на uint32_t
.
Я читаю поля независимо, потому что заполнение внутри структуры может варьироваться между реализациями. Их также нужно будет отправлять таким образом.
Получатель заполняет message.length
информацией из потока, но фактически не использует ее.
Злонамеренный или ошибочный отправитель может предоставить значение для message.length
, которое слишком велико и может sh получатель (или хуже), если он не проверяет его. То же самое касается contents
. Он может не заканчиваться NUL, если это ожидается.
Но что, если длина не была фиксированной? Тогда отправителю нужно как-то сообщить, сколько нужно прочитать читателю. Обычный подход - префикс длины.
typedef struct {
uint32_t length;
char contents[];
} Message;
uint32_t contents_size;
ssize_t bytes_read = recv_fixed_amount(sockfd, &contents_size, sizeof(contents_size));
if (bytes_read == 0) {
printf("EOF reached\n");
exit(EXIT_SUCCESS);
}
if (bytes_read < 0) {
perror("recv");
exit(EXIT_FAILURE);
}
if (bytes_read != sizeof(contents_size)) {
fprintf(stderr, "recv: Premature EOF.\n");
exit(EXIT_FAILURE);
}
Message *message = malloc(sizeof(Message)+contents_size);
if (!message) {
perror("malloc");
exit(EXIT_FAILURE);
}
message->length = contents_size;
bytes_read = recv_fixed_amount(sockfd, &(message->contents), contents_size);
if (bytes_read < 0) {
perror("recv");
exit(EXIT_FAILURE);
}
if (bytes_read != contents_size) {
fprintf(stderr, "recv: Premature EOF.\n");
exit(EXIT_FAILURE);
}
Примечания:
message->length
содержит размер message->contents
вместо размера структуры. Это гораздо более полезно.
Другой подход заключается в использовании значения часового. Это значение, которое сообщает читателю, что сообщение окончено. Это то, что NUL, который завершает строки C. Это сложнее, потому что вы не знаете, сколько читать заранее. Чтение за байтом слишком дорого, поэтому обычно используется буфер.
while (1) {
extend_buffer_if_necessary();
recv_into_buffer();
while (buffer_contains_a_sentinel()) {
// This also shifts the remainder of the buffer's contents.
extract_contents_of_buffer_up_to_sentinel();
process_extracted_message();
}
}
Преимущество использования значения Sentinel заключается в том, что ему не нужно заранее знать длину сообщения ( поэтому отправитель может начать отправлять его до того, как он будет полностью создан.)
Недостаток тот же, что и для строк C: сообщение не может содержать значение sentinel, если не используется какой-либо механизм экранирования. Между этим и сложностью читателя вы можете понять, почему префикс длины обычно предпочтительнее значения часового. :)
Наконец, есть лучшее решение, чем значения часового для больших сообщений, которые вы хотите начать отправлять до того, как они будут полностью созданы: последовательность фрагментов с префиксом длины. Чтение фрагментов продолжается до тех пор, пока не будет обнаружен фрагмент размером 0, сигнализирующий об окончании.
HTTP поддерживает как сообщения с префиксом длины (в форме заголовка Content-Length: <length>
), так и этот подход (в форме Transfer-Encoding: chunked
header ).