你有一个固定大小的消息,所以你可以使用这样的东西:
#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 提供一个太大的值,如果它不验证它会使接收器崩溃(或更糟)。 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 的大小而不是结构的大小。这更有用。
另一种方法是使用哨兵值。这是一个告诉读者消息结束的值。这就是终止 C 字符串的 NUL。这更复杂,因为您不知道提前阅读多少。逐字节读取成本太高,因此通常使用缓冲区。
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();
}
}
使用标记值的好处是不需要提前知道消息的长度(因此发送者可以在消息完全创建之前开始发送。)
缺点与 C 字符串相同:消息不能包含标记值,除非使用某种形式的转义机制。在这和阅读器的复杂性之间,您可以看到为什么长度前缀通常比哨兵值更受欢迎。 :)
最后,对于要在完全创建之前开始发送的大消息的标记值,有一个比标记值更好的解决方案:以长度为前缀的块序列。一个人继续读取块,直到遇到大小为 0 的块,表示结束。
HTTP 支持以长度为前缀的消息(以Content-Length: <length> 标头的形式)和这种方法(以Transfer-Encoding: chunked header 的形式)。