根据connect(2)手册页
如果套接字sockfd的类型为SOCK_DGRAM,则serv_addr是默认情况下发送数据报的地址,和接收数据报的唯一地址。如果套接字的类型为SOCK_STREAM或SOCK_SEQPACKET,则此调用将尝试连接到绑定到serv_addr指定的地址的套接字。
我正在尝试过滤来自同一端口上广播的两个不同多播组的数据包,我认为connect()已经完成了这项工作,但我无法使其工作。事实上,当我将它添加到我的程序时,我没有收到任何数据包。有关此thread的更多信息。
这是我设置连接参数的方式:
memset(&mc_addr, 0, sizeof(mc_addr));
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr(multicast_addr);
mc_addr.sin_port = htons(multicast_port);
printf("Connecting...\n");
if( connect(sd, (struct sockaddr*)&mc_addr, sizeof(mc_addr)) < 0 ) {
perror("connect");
return -1;
}
printf("Receiving...\n");
while( (len = recv(sd, msg_buf, sizeof(msg_buf), 0)) > 0 )
printf("Received %d bytes\n", len);
答案 0 :(得分:12)
您的程序(可能)存在以下问题:
这是一个接收多播的示例程序。它使用recvfrom(),而不是recv(),但它是相同的,除了你还获得每个接收数据包的源地址。
要从多个多播组接收,您有三个选项。
第一个选项:为每个多播组使用单独的套接字,并将每个套接字bind()绑定到多播地址。这是最简单的选择。
第二个选项:为每个组播组使用单独的套接字,bind()每个套接字INADDR_ANY,并使用套接字过滤器过滤掉除一个组播组之外的所有组播组。
因为您已绑定到INADDR_ANY,您可能仍会获得其他多播组的数据包。可以使用内核的套接字过滤器对它们进行过滤:
#include <stdint.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/filter.h>
/**
* Adds a Linux socket filter to a socket so that only IP
* packets with the given destination IP address will pass.
* dst_addr is in network byte order.
*/
int add_ip_dst_filter (int fd, uint32_t dst_addr)
{
uint16_t hi = ntohl(dst_addr) >> 16;
uint16_t lo = ntohl(dst_addr) & 0xFFFF;
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, SKF_NET_OFF + 16), // A <- IP dst high
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, hi, 0, 3), // if A != hi, goto ignore
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, SKF_NET_OFF + 18), // A <- IP dst low
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, lo, 0, 1), // if A != lo, goto ignore
BPF_STMT(BPF_RET + BPF_K, 65535), // accept
BPF_STMT(BPF_RET + BPF_K, 0) // ignore
};
struct sock_fprog fprog = {
.len = sizeof(filter) / sizeof(filter[0]),
.filter = filter
};
return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
}
第三个选项:使用单个套接字接收所有组播组的多播。
在这种情况下,您应该为每个组执行IP_ADD_MEMBERSHIP。这样,您就可以在单个套接字上获取所有数据包。
但是,您需要额外的代码来确定接收到的数据包发往哪个组播组。要做到这一点,你必须:
您需要做的确切事情取决于IP协议版本和操作系统。我是这样做的(IPv6代码没有经过测试):enabling PKTINFO和reading the option。
这是一个接收多播的简单程序,它演示了第一个选项(绑定到多播地址)。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXBUFSIZE 65536
int main (int argc, char **argv)
{
if (argc != 4) {
printf("Usage: %s <group address> <port> <interface address>\n", argv[0]);
return 1;
}
int sock, status, socklen;
char buffer[MAXBUFSIZE+1];
struct sockaddr_in saddr;
struct ip_mreq imreq;
// set content of struct saddr and imreq to zero
memset(&saddr, 0, sizeof(struct sockaddr_in));
memset(&imreq, 0, sizeof(struct ip_mreq));
// open a UDP socket
sock = socket(PF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket failed!");
return 1;
}
// join group
imreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
imreq.imr_interface.s_addr = inet_addr(argv[3]);
status = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(const void *)&imreq, sizeof(struct ip_mreq));
saddr.sin_family = PF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
status = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
if (status < 0) {
perror("bind failed!");
return 1;
}
// receive packets from socket
while (1) {
socklen = sizeof(saddr);
status = recvfrom(sock, buffer, MAXBUFSIZE, 0, (struct sockaddr *)&saddr, &socklen);
if (status < 0) {
printf("recvfrom failed!\n");
return 1;
}
buffer[status] = '\0';
printf("Received: '%s'\n", buffer);
}
}
答案 1 :(得分:1)
首先要注意的是,多播数据包是发送到多播地址,而不是来自多播地址。 connect()将允许(或不允许)从指定地址接收的数据包。
要配置套接字以接收多播数据包,您需要使用以下两种套接字选项之一:
前者允许您指定多播地址,后者允许您指定发送者的多播地址和源地址。
可以使用以下内容完成此操作:
struct ip_mreq groupJoinStruct;
unsigned long groupAddr = inet_addr("239.255.0.1");
groupJoinStruct.imr_multiaddr.s_addr = groupAddr;
groupJoinStruct.imr_interface.s_addr = INADDR_ANY; // or the address of a specific network interface
setsockopt( yourSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &groupJoinStruct );
(为简洁省略了错误处理)
要停止接收此组地址的多播数据包,请使用套接字选项:
请注意,套接字可以具有多个多播成员资格。但是,由于多播地址是数据包的目标地址,因此您需要能够获取数据包的目标地址,以便能够区分不同多播地址的数据包。
要获取数据包的目标地址,您需要使用recvmsg()
而不是recv()
或recvfrom()
。目标地址包含在IPSTROTO_IP消息级别中,类型为DSTADDR_SOCKOPT。
正如@Ambroz Bizjak所述,您需要设置IP_PKTINFO
套接字选项才能读取此信息。
其他要检查的事项是:
ifconfig
时,请检查列出的“MULTICAST”ifconfig eth0 promisc
答案 2 :(得分:0)
只要所有SENDING套接字都绑定到bind
的相关多播地址,就应该可以正常工作。您在connect
中指定的地址与收到的数据包的SOURCE地址进行匹配,因此您需要确保所有数据包都具有相同的(多播)源和目的地。
答案 3 :(得分:0)
bind(2)
每个套接字到相应多播组和端口的地址而不是INADDR_ANY
。那会为你做过滤。