接收多播数据包的用户缓冲区大小?
User buffer size to receive multicast packets?
以下代码来自Git。它加入多播组并接收数据包。
这里我们循环并在一个名为 msgbuf
:
的缓冲区中接收数据
while (1)
{
char msgbuf[MSGBUFSIZE];
const int addrlen = sizeof(addr);
const int nbytes = recvfrom(fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &addr, &addrlen);
如何选择缓冲区的大小 msgBuf
? 是否必须是最大数据包大小?或者我是否需要在处理第一个数据包时存储多个数据包?
完整代码:
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Command line args should be multicast group and port\n");
printf("(e.g. for SSDP, `listener 239.255.255.250 1900`)\n");
return 1;
}
char* group = argv[1]; // e.g. 239.255.255.250 for SSDP
int port = atoi(argv[2]); // 0 if error, which is an invalid port
if(port <= 0)
{
perror("Invalid port");
return 1;
}
// create what looks like an ordinary UDP socket
//
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
{
perror("socket");
return 1;
}
// allow multiple sockets to use the same PORT number
//
u_int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes)) < 0)
{
perror("Reusing ADDR failed");
return 1;
}
// set up destination address
//
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // differs from sender
addr.sin_port = htons(port);
// bind to receive address
//
if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
{
perror("bind");
return 1;
}
// use setsockopt() to request that the kernel join a multicast group
//
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(group);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof(mreq)) < 0)
{
perror("setsockopt");
return 1;
}
// now just enter a read-print loop
//
while (1)
{
char msgbuf[MSGBUFSIZE];
const int addrlen = sizeof(addr);
const int nbytes = recvfrom(fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &addr, &addrlen);
if (nbytes < 0)
{
perror("recvfrom");
return 1;
}
msgbuf[nbytes] = '[=11=]';
puts(msgbuf);
}
return 0;
}
如@Ingo 所述,在此代码中您应该使用:
char msgbuf[MSGBUFSIZE + 1];
+ 1
是因为recvfrom
最多可以向数组中写入MSGBUFSIZE
个字节,然后你在末尾写入另一个NUL字节。
至于为 MSGBUFSIZE
选择一个值,这将取决于协议规范。考虑到大多数物理网络在没有碎片的情况下发送超过 1500 个字节将很困难,像 2048 这样的值可能是一个合理的值。您还可以检查 nbytes == MSGBUFSIZE
(也可能使用 MSG_TRUNC
)并报告 "packet truncated" 警告,但这对于通过 public 互联网路由的数据包基本上不会发生
回应:
do I need to store multiple packets whilst I process the first?
您通常会让网络堆栈来处理这个问题。 recv
保持 packet/datagram 边界,因此将始终在提供的缓冲区地址处开始写入下一个数据包。同样,这取决于您如何检测和处理错误的协议,例如丢失或乱序的数据包,以及超时
与将数据包组合成流的 TCP 不同,UDP 尊重数据包边界,因此 recvfrom
一次只获取一个数据包。
所以MSGBUFSIZE
只需要和一个数据包一样大。如果您不使用巨型数据包,则为 1500,否则为 9000。
以下代码来自Git。它加入多播组并接收数据包。
这里我们循环并在一个名为 msgbuf
:
while (1)
{
char msgbuf[MSGBUFSIZE];
const int addrlen = sizeof(addr);
const int nbytes = recvfrom(fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &addr, &addrlen);
如何选择缓冲区的大小 msgBuf
? 是否必须是最大数据包大小?或者我是否需要在处理第一个数据包时存储多个数据包?
完整代码:
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Command line args should be multicast group and port\n");
printf("(e.g. for SSDP, `listener 239.255.255.250 1900`)\n");
return 1;
}
char* group = argv[1]; // e.g. 239.255.255.250 for SSDP
int port = atoi(argv[2]); // 0 if error, which is an invalid port
if(port <= 0)
{
perror("Invalid port");
return 1;
}
// create what looks like an ordinary UDP socket
//
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
{
perror("socket");
return 1;
}
// allow multiple sockets to use the same PORT number
//
u_int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes)) < 0)
{
perror("Reusing ADDR failed");
return 1;
}
// set up destination address
//
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // differs from sender
addr.sin_port = htons(port);
// bind to receive address
//
if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
{
perror("bind");
return 1;
}
// use setsockopt() to request that the kernel join a multicast group
//
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(group);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof(mreq)) < 0)
{
perror("setsockopt");
return 1;
}
// now just enter a read-print loop
//
while (1)
{
char msgbuf[MSGBUFSIZE];
const int addrlen = sizeof(addr);
const int nbytes = recvfrom(fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &addr, &addrlen);
if (nbytes < 0)
{
perror("recvfrom");
return 1;
}
msgbuf[nbytes] = '[=11=]';
puts(msgbuf);
}
return 0;
}
如@Ingo 所述,在此代码中您应该使用:
char msgbuf[MSGBUFSIZE + 1];
+ 1
是因为recvfrom
最多可以向数组中写入MSGBUFSIZE
个字节,然后你在末尾写入另一个NUL字节。
至于为 MSGBUFSIZE
选择一个值,这将取决于协议规范。考虑到大多数物理网络在没有碎片的情况下发送超过 1500 个字节将很困难,像 2048 这样的值可能是一个合理的值。您还可以检查 nbytes == MSGBUFSIZE
(也可能使用 MSG_TRUNC
)并报告 "packet truncated" 警告,但这对于通过 public 互联网路由的数据包基本上不会发生
回应:
do I need to store multiple packets whilst I process the first?
您通常会让网络堆栈来处理这个问题。 recv
保持 packet/datagram 边界,因此将始终在提供的缓冲区地址处开始写入下一个数据包。同样,这取决于您如何检测和处理错误的协议,例如丢失或乱序的数据包,以及超时
与将数据包组合成流的 TCP 不同,UDP 尊重数据包边界,因此 recvfrom
一次只获取一个数据包。
所以MSGBUFSIZE
只需要和一个数据包一样大。如果您不使用巨型数据包,则为 1500,否则为 9000。