使用recv()确定数据包大小的最佳方法是什么?
对socket编程和C语言来说是非常新的。我正在尝试编写一个基本程序,在两台机器之间发送和接收数据。我知道,使用recv()确定数据包大小的最佳方法是什么?,c,sockets,send,recv,C,Sockets,Send,Recv,对socket编程和C语言来说是非常新的。我正在尝试编写一个基本程序,在两台机器之间发送和接收数据。我知道,recv不会一次获取所有数据——您必须循环它,直到它读取整个消息 我在客户端创建了一个简单的消息结构,而不只是在两台机器上设置限制: struct Message { size_t length; char contents[1024 - sizeof(size_t)]; } message; message.length = sizeof(struct Message)
recv
不会一次获取所有数据——您必须循环它,直到它读取整个消息
我在客户端创建了一个简单的消息
结构,而不只是在两台机器上设置限制:
struct Message {
size_t length;
char contents[1024 - sizeof(size_t)];
} message;
message.length = sizeof(struct Message);
message.contents = information_i_want_to_send;
当它到达服务器时,我将recv
读入一个缓冲区:received=recv(ioSock,&buffer,1024,0)
(它恰好与我的消息结构大小相同,但假设它不是…)
然后我从缓冲区中提取Message.length
,如下所示:
size_t messagelength;
messagelength = *((size_t *) &buffer);
#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);
}
然后我将recv
循环到缓冲区,同时接收
。
这是可行的,但我忍不住觉得它真的很难看,而且很粗糙。(特别是如果第一个
recv
调用读取的值小于sizeof(size\u t)
或者机器是不同的位体系结构,在这种情况下,size\u t cast将无法工作。)。有更好的方法吗?有两种方法
1.)
使用二进制同步协议。(使用STX-文本开始和ETX-文本结束)标识文本开始和结束
(二)
附加在数据开头发送的数据字节数。套接字将读取这些字节数,并获取从套接字接收的字节数。然后读取所有数据并获得所需的数据量
嗯。。。看起来很难。。。??让我给你举个例子
需要发送的实际数据:ABCDEFGHIJ
新数据格式:0010ABCDEFGHIJ
服务器端需要的数据:ABCDE
recv函数将读取前4个字节,以获得实际数据的字节数(在循环中,直到获得4个字节):
根据上述情况,“recvbuf”将被0010转换为整数,其值为“10”,可存储在某个整数变量中。因此,我们:
int toReadVal = 10
现在我们只需要在下一次recv呼叫中读取以下10位数字:
int received= recv(ioSock, recvbuf1, toReadVal, 0);
最后,我们得到recvbuf1作为ABCDEFGHIG的值。现在,您可以根据需要截断值。有两种方法可以做到这一点 1.) 使用二进制同步协议。(使用STX-文本开始和ETX-文本结束)标识文本开始和结束 (二) 附加在数据开头发送的数据字节数。套接字将读取这些字节数,并获取从套接字接收的字节数。然后读取所有数据并获得所需的数据量 嗯。。。看起来很难。。。??让我给你举个例子 需要发送的实际数据:ABCDEFGHIJ 新数据格式:0010ABCDEFGHIJ 服务器端需要的数据:ABCDE recv函数将读取前4个字节,以获得实际数据的字节数(在循环中,直到获得4个字节): 根据上述情况,“recvbuf”将被0010转换为整数,其值为“10”,可存储在某个整数变量中。因此,我们:
int toReadVal = 10
现在我们只需要在下一次recv呼叫中读取以下10位数字:
int received= recv(ioSock, recvbuf1, toReadVal, 0);
最后,我们得到recvbuf1作为ABCDEFGHIG的值。现在,您可以根据需要截断该值。您有一条固定大小的消息,因此可以使用如下内容:
size_t messagelength;
messagelength = *((size_t *) &buffer);
#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);
}
注:
包含message->length
的大小,而不是结构的大小。这是非常有用的message->contents
另一种方法是使用sentinel值。这是一个告诉读者消息结束的值。这就是终止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();
}
}
使用sentinel值的优点是不需要事先知道消息的长度(这样发送者就可以在消息完全创建之前开始发送消息)
缺点与C字符串相同:除非使用某种形式的转义机制,否则消息不能包含sentinel值。在这一点和读取器的复杂性之间,您可以看出为什么长度前缀通常比sentinel值更受欢迎。:)
最后,对于要在完全创建之前开始发送的大型消息,有一个比sentinel值更好的解决方案:一系列长度前缀块。一个人一直在读块,直到遇到大小为0的块,表示结束
HTTP支持长度前缀消息(以
内容长度:
标题的形式)和此方法(以的形式)。您有一个固定大小的消息,因此您可以使用如下内容:
size_t messagelength;
messagelength = *((size_t *) &buffer);
#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);
}
注:
包含message->length
的大小,而不是结构的大小。这是非常有用的message->contents
另一种方法是使用sentinel值。这是一个告诉读者消息结束的值。这就是终止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();
}
}
使用sentinel值的优点是不需要事先知道消息的长度(这样发送者就可以在消息完全创建之前开始发送消息)
缺点与C字符串相同:除非使用某种形式的转义机制,否则消息不能包含sentinel值。在这一点和读取器的复杂性之间,您可以看出为什么长度前缀通常比sentinel值更受欢迎。:)
最后,对于要在完全创建之前开始发送的大型消息,有一个比sentinel值更好的解决方案:一系列长度前缀块。一个人一直在读c