通过网络发送不同大小数据的最佳实践
Best practice of sending data of different sizes over the network
我想通过 UDP 发送不同大小的数据。要发送的数据大小不固定。我有以下情况:
unsigned char buffer[BUFFERSIZE];
int bytes = fill_buffer(buffer, sizeof(buffer)): // Returns number of filled bytes.
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))
在上面的例子中,接收方不知道要接收多少字节。我也想到了先发送接收的字节数再发送数据。但那样的话,我不知道如果数据包乱序到达会发生什么。
发送方将是
sendto(socket, &bytes, sizeof(bytes), 0, (struct sockaddr *)&server, sizeof(server))
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))
接收方将是
recvfrom(socket, &bytes, sizeof(bytes), 0, NULL, NULL)
recvfrom(socket, buffer, bytes, 0, NULL, NULL)
但是会不会是发送的数据乱码了?
我 认为 如果添加消息 header.[=23=,您可以在单个数据报中发送 两者 ]
发件人仅发送其拥有的负载数据量。
接收方总是请求 最大 负载大小,但会检查 recvfrom
的 header 和 return 以确定实际长度。
这里有一些粗略的代码可以说明我的想法:
struct header {
u32 magic_number;
u32 seq_no;
u32 msg_type;
u32 payload_length;
} __attribute__((__packed__));
#define MAXPAYLOAD 1024
struct message {
struct header info;
unsigned char payload[MAXPAYLOAD];
} __attribute__((__packed__));
void
sendone(int sockfd,const void *buf,size_t buflen)
{
struct message msg;
static u32 seqno = 0;
memcpy(&msg.payload[0],buf,buflen);
msg.info.magic_number = 0xDEADADDE;
msg.info.seq_no = seqno++;
msg.info.payload_length = buflen;
sendto(sockfd,&msg,sizeof(struct header) + buflen,...);
}
ssize_t
getone(int sockfd,void *buf,size_t buflen)
{
struct message msg;
ssize_t rawlen;
ssize_t paylen;
static u32 seqno = 0;
rawlen = recvfrom(sockfd,&msg,sizeof(struct header) + MAXPAYLOAD,...);
paylen = msg.info.payload_length;
if (rawlen != (sizeof(struct header) + paylen))
// error ...
memcpy(buf,&msg.payload[0],paylen);
return paylen;
}
接收方可以检查幻数和序列号以查找损坏或 missing/dropped 数据包等
事实上,使用 sendmsg
和 recvmsg
可能会提高效率,因为它们允许您使用 scatter/gather 列表发送单个消息。 (即)不必使用消息结构中的 memcpy
复制数据 in/out [您只需要 struct header
],因此更接近于零复制缓冲。
另一种选择可能是将 MSG_PEEK
标志与 recvfrom/recvmsg
一起使用。我自己从来没有用过这个,但它会是这样的:
- 做
recvmsg
长度为 sizeof(struct header)
带有 MSG_PEEK
标志
- 做第二个
recvmsg
,长度为sizeof(struct header) + msg.info.payload_length
这只是不必总是提供最大大小的缓冲区的好处。由于它涉及 两个 系统调用,因此它可能会慢一点。但是,它可能允许一些技巧,根据消息的类型 and/or length
从池中选择有效负载缓冲区
与基于流的协议 TCP 不同,这意味着对 recv
的调用并不完全对应于对发送的调用,UDP 是基于数据包的,这意味着每个 recvfrom
都完全匹配一个 sendto
。这也意味着您需要注意发送的每条消息的大小。
如果您发送的 UDP 数据报大于 IP 数据包中可以包含的数据报,则 UDP 消息将被分成多个 UDP 数据包,从而增加数据丢失的可能性。这是你想要避免的事情。此外,如果您使用的是 IPv6,则在尝试发送时会收到错误消息,因为 IPv6 不支持分段。
相对于您正在做的事情,这意味着什么?这意味着,粗略地说,您的消息不应大于 1450 字节左右,因此您可以将该值用作输入缓冲区的大小。然后可以用recvfrom
的return值,看看实际读取了多少字节。如果您的邮件比这大,您应该将它们分成多封邮件。
与任何基于 UDP 的协议一样,您需要考虑消息丢失并需要重新传输的情况,或者消息出现乱序的情况。
其实这个问题的答案很简单。
鉴于:
unsigned char buffer[BUFFERSIZE];
int bytes = fill_buffer(buffer, sizeof(buffer)): // Returns number of filled bytes.
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))
recvfrom
的 return 值告诉我们接收了多少字节,尽管我们进行了全读,
int bytesReceived = recvfrom(socket, buffer, sizeof(buffer), 0, NULL, NULL);
// Process bytesReceived number of bytes in the buffer
我想通过 UDP 发送不同大小的数据。要发送的数据大小不固定。我有以下情况:
unsigned char buffer[BUFFERSIZE];
int bytes = fill_buffer(buffer, sizeof(buffer)): // Returns number of filled bytes.
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))
在上面的例子中,接收方不知道要接收多少字节。我也想到了先发送接收的字节数再发送数据。但那样的话,我不知道如果数据包乱序到达会发生什么。
发送方将是
sendto(socket, &bytes, sizeof(bytes), 0, (struct sockaddr *)&server, sizeof(server))
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))
接收方将是
recvfrom(socket, &bytes, sizeof(bytes), 0, NULL, NULL)
recvfrom(socket, buffer, bytes, 0, NULL, NULL)
但是会不会是发送的数据乱码了?
我 认为 如果添加消息 header.[=23=,您可以在单个数据报中发送 两者 ]
发件人仅发送其拥有的负载数据量。
接收方总是请求 最大 负载大小,但会检查 recvfrom
的 header 和 return 以确定实际长度。
这里有一些粗略的代码可以说明我的想法:
struct header {
u32 magic_number;
u32 seq_no;
u32 msg_type;
u32 payload_length;
} __attribute__((__packed__));
#define MAXPAYLOAD 1024
struct message {
struct header info;
unsigned char payload[MAXPAYLOAD];
} __attribute__((__packed__));
void
sendone(int sockfd,const void *buf,size_t buflen)
{
struct message msg;
static u32 seqno = 0;
memcpy(&msg.payload[0],buf,buflen);
msg.info.magic_number = 0xDEADADDE;
msg.info.seq_no = seqno++;
msg.info.payload_length = buflen;
sendto(sockfd,&msg,sizeof(struct header) + buflen,...);
}
ssize_t
getone(int sockfd,void *buf,size_t buflen)
{
struct message msg;
ssize_t rawlen;
ssize_t paylen;
static u32 seqno = 0;
rawlen = recvfrom(sockfd,&msg,sizeof(struct header) + MAXPAYLOAD,...);
paylen = msg.info.payload_length;
if (rawlen != (sizeof(struct header) + paylen))
// error ...
memcpy(buf,&msg.payload[0],paylen);
return paylen;
}
接收方可以检查幻数和序列号以查找损坏或 missing/dropped 数据包等
事实上,使用 sendmsg
和 recvmsg
可能会提高效率,因为它们允许您使用 scatter/gather 列表发送单个消息。 (即)不必使用消息结构中的 memcpy
复制数据 in/out [您只需要 struct header
],因此更接近于零复制缓冲。
另一种选择可能是将 MSG_PEEK
标志与 recvfrom/recvmsg
一起使用。我自己从来没有用过这个,但它会是这样的:
- 做
recvmsg
长度为sizeof(struct header)
带有MSG_PEEK
标志 - 做第二个
recvmsg
,长度为sizeof(struct header) + msg.info.payload_length
这只是不必总是提供最大大小的缓冲区的好处。由于它涉及 两个 系统调用,因此它可能会慢一点。但是,它可能允许一些技巧,根据消息的类型 and/or length
从池中选择有效负载缓冲区与基于流的协议 TCP 不同,这意味着对 recv
的调用并不完全对应于对发送的调用,UDP 是基于数据包的,这意味着每个 recvfrom
都完全匹配一个 sendto
。这也意味着您需要注意发送的每条消息的大小。
如果您发送的 UDP 数据报大于 IP 数据包中可以包含的数据报,则 UDP 消息将被分成多个 UDP 数据包,从而增加数据丢失的可能性。这是你想要避免的事情。此外,如果您使用的是 IPv6,则在尝试发送时会收到错误消息,因为 IPv6 不支持分段。
相对于您正在做的事情,这意味着什么?这意味着,粗略地说,您的消息不应大于 1450 字节左右,因此您可以将该值用作输入缓冲区的大小。然后可以用recvfrom
的return值,看看实际读取了多少字节。如果您的邮件比这大,您应该将它们分成多封邮件。
与任何基于 UDP 的协议一样,您需要考虑消息丢失并需要重新传输的情况,或者消息出现乱序的情况。
其实这个问题的答案很简单。
鉴于:
unsigned char buffer[BUFFERSIZE];
int bytes = fill_buffer(buffer, sizeof(buffer)): // Returns number of filled bytes.
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))
recvfrom
的 return 值告诉我们接收了多少字节,尽管我们进行了全读,
int bytesReceived = recvfrom(socket, buffer, sizeof(buffer), 0, NULL, NULL);
// Process bytesReceived number of bytes in the buffer