Linux上的UDP connect()和recv()

Linux上的UDP connect()和recv(),c,linux,udp,C,Linux,Udp,根据connect(2)手册页 如果socket sockfd的类型为SOCK_DGRAM,则serv_addr是默认情况下数据报发送到的地址,是接收数据报的唯一地址。如果套接字的类型为SOCK_STREAM或SOCK_SEQPACKET,则此调用将尝试连接到绑定到serv_addr指定的地址的套接字 我正在尝试过滤来自两个不同多播组的数据包,这两个多播组正在同一端口上广播,我原以为connect()可以完成这项工作,但我无法让它工作。事实上,当我把它添加到我的程序中时,我没有收到任何数据包。更

根据connect(2)手册页

如果socket sockfd的类型为SOCK_DGRAM,则serv_addr是默认情况下数据报发送到的地址,是接收数据报的唯一地址。如果套接字的类型为SOCK_STREAM或SOCK_SEQPACKET,则此调用将尝试连接到绑定到serv_addr指定的地址的套接字

我正在尝试过滤来自两个不同多播组的数据包,这两个多播组正在同一端口上广播,我原以为connect()可以完成这项工作,但我无法让它工作。事实上,当我把它添加到我的程序中时,我没有收到任何数据包。更多信息在此

以下是我设置连接参数的方式:

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);
memset(&mc_addr,0,sizeof(mc_addr));
mc_addr.sin_family=AF_INET;
mc_addr.sin_addr.s_addr=inet_addr(多播地址);
mc_addr.sin_port=htons(多播_port);
printf(“连接…\n”);
if(connect(sd,(struct sockaddr*)和mc_addr,sizeof(mc_addr))<0){
perror(“连接”);
返回-1;
}
printf(“接收…\n”);
而((len=recv(sd,msg_buf,sizeof(msg_buf),0))>0)
printf(“接收到%d字节\n”,len);
您的程序(可能)存在以下问题:

  • 您应该使用bind()而不是connect(),并且
  • 您缺少setsockopt(…,IP_添加_成员身份…)
下面是一个接收多播的示例程序。它使用recvfrom(),而不是recv(),但它是相同的,只是您还可以获得每个接收到的数据包的源地址

要从多个多播组接收,您有三个选项

第一个选项:为每个多播组使用单独的套接字,并将每个套接字绑定到一个多播地址。这是最简单的选择

第二个选项:为每个多播组使用一个单独的套接字,将每个套接字绑定到任何套接字,并使用套接字筛选器筛选出除单个多播组以外的所有多播组

因为您已经绑定到INADR_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));
}    
#包括
#包括和

下面是一个接收多播的简单程序,它演示了第一个选项(绑定到多播地址)

#包括
#包括
#包括
#包括
#包括
#定义MAXBUFSIZE 65536
int main(int argc,字符**argv)
{
如果(argc!=4){
printf(“用法:%s\n”,argv[0]);
返回1;
}
int sock,status,socklen;
字符缓冲区[MAXBUFSIZE+1];
saddr中的结构sockaddr_;
结构ip_mreq imreq;
//将struct saddr和imreq的内容设置为零
memset(&saddr,0,sizeof(struct sockaddr_in));
memset(&imreq,0,sizeof(struct ip_mreq));
//打开UDP套接字
sock=插座(PF_INET,sock_DGRAM,0);
if(sock<0){
perror(“套接字失败!”);
返回1;
}
//加入组
imreq.imr_multiaddr.s_addr=inet_addr(argv[1]);
imreq.imr_interface.s_addr=inet_addr(argv[3]);
状态=setsockopt(sock、IPPROTO\u IP、IP\u添加\u成员资格、,
(const void*)和imreq,sizeof(struct ip_mreq));
saddr.sin_family=PF_INET;
saddr.sinu port=htons(atoi(argv[2]);
saddr.sin_addr.s_addr=inet_addr(argv[1]);
状态=绑定(sock,(struct sockaddr*)和saddr,sizeof(struct sockaddr_in));
如果(状态<0){
perror(“绑定失败!”);
返回1;
}
//从套接字接收数据包
而(1){
socklen=sizeof(SADD);
状态=recvfrom(sock、buffer、MAXBUFSIZE、0、(struct sockaddr*)和saddr以及socklen);
如果(状态<0){
printf(“recvfrom失败!\n”);
返回1;
}
缓冲区[状态]='\0';
printf(“收到:'%s'\n',缓冲区);
}
}

只要所有发送套接字都使用
bind
绑定到所讨论的多播地址,这项功能就可以工作。您在
connect
中指定的地址与接收到的数据包的源地址相匹配,因此您希望确保所有数据包具有相同的(多播)源和目标。

首先要注意的是,多播数据包被发送到多播地址,而不是从多播地址发送。 connect()将允许(或不允许)从指定地址接收数据包

要配置套接字以接收多播数据包,您需要使用以下两个套接字选项之一:

  • IP_添加_成员,或
  • IP\添加\源\成员资格
前者允许您指定多播地址,后者允许您指定发送方的多播地址和源地址

这可以通过以下方法实现:


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 );
(为了简洁起见,省略了错误处理)

要停止接收此组地址的多播数据包,请使用套接字选项:

  • IP_DROP_会员资格,或
  • IP_删除_源_成员资格
请注意,套接字可以具有多个多播成员身份。但是,由于多播地址是数据包的目标地址,因此您需要能够获取数据包的目标地址,以便能够区分不同多播地址的数据包

要获取数据包的目标地址,您需要使用
recvmsg()
而不是
recv()
recvfrom()
。目标地址包含在DSTADDR_SOCKOPT类型的IPPROTO_IP消息级别中。 正如@Ambroz Bizjak所述,您需要设置
IP_PKTINFO
socket选项才能读取此信息


其他需要检查的事项包括:

  • 您的内核支持多播吗?检查/proc/net/igmp是否存在,以确保它已启用
  • 您的网络接口上是否启用了多播?检查在界面上运行
    ifconfig
    时列出的“多播”
  • 您的网络接口支持多播吗?从历史上看,并非所有人都这样做。如果不是你
    
    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 );