Linux 上的 UDP connect() 和 recv()

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8481873/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 03:37:22  来源:igfitidea点击:

UDP connect() and recv() on Linux

clinuxudp

提问by Robert Kubrick

According to the connect(2) man pages

根据 connect(2) 手册页

If the socket sockfd is of type SOCK_DGRAM then serv_addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection to the socket that is bound to the address specified by serv_addr.

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

I am trying to filter packets from two different multicast groups that are being broadcasted on the same port and I thought connect() would have done the job but I can't make it work. In facts when I add it to my program I don't receive any packet. More info in this thread.

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

This is how I set the connect parameters:

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

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);

采纳答案by Ambroz Bizjak

Your program (probably) has the following problems:

您的程序(可能)存在以下问题:

  • you should be using bind() instead of connect(), and
  • you're missing setsockopt(..., IP_ADD_MEMBERSHIP, ...).
  • 您应该使用 bind() 而不是 connect(),并且
  • 你错过了 setsockopt(..., IP_ADD_MEMBERSHIP, ...)。

Here's an example program that receives multicasts. It uses recvfrom(), not recv(), but it's the same except you also get the source address for each received packet.

这是一个接收多播的示例程序。它使用 recvfrom(),而不是 recv(),但它是相同的,除了您还可以获得每个接收到的数据包的源地址。

To receive from multiple multicast groups, you have threeoptions.

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

First option: Use a separate socket for each multicast group, and bind() each socket to a multicast address. This is the simplest option.

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

Second option: Use a separate socket for each multicast group, bind() each socket INADDR_ANY, and use a socket filter to filter out all but a single multicast group.

第二个选项:为每个多播组使用单独的套接字,bind() 每个套接字 INADDR_ANY,并使用套接字过滤器过滤掉除单个多播组之外的所有内容。

Because you've bound to INADDR_ANY, you may still get packets for other multicast groups. It is possible to filter them out using the kernel's socket filters:

因为您已绑定到 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));
}    

Third option: use a single socket to receive multicasts for all multicast groups.

第三个选项:使用单个套接字接收所有多播组的多播。

In that case, you should do an IP_ADD_MEMBERSHIP for each of the groups. This way you get all packets on a single socket.

在这种情况下,您应该为每个组执行 IP_ADD_MEMBERSHIP。这样您就可以在单个套接字上获取所有数据包。

However, you need extra code to determine which multicast group a received packet was addressed to. To do that, you have to:

但是,您需要额外的代码来确定接收到的数据包的地址是哪个多播组。为此,您必须:

  • receive packets with recvmsg()and read the IP_PKTINFO or equivalent ancillary data message. However, to make recvmsg() give you this message, you first have to
  • enable reception of IP_PKTINFO ancillary data messages with setsockopt().
  • 使用recvmsg()接收数据包并读取 IP_PKTINFO 或等效的辅助数据消息。但是,要使 recvmsg() 向您提供此消息,您首先必须
  • 使用setsockopt() 启用IP_PKTINFO 辅助数据消息的接收。

The exact thing you need to do depends on IP protocol version and OS. Here's how I did it (IPv6 code not tested): enabling PKTINFOand reading the option.

您需要做的确切事情取决于 IP 协议版本和操作系统。这是我的做法(未测试 IPv6 代码):启用 PKTINFO阅读选项

Here's a simple program that receives multicasts, which demonstrates the first option (bind to multicast address).

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

#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] = '

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 );
'; printf("Received: '%s'\n", buffer); } }

回答by Chris Dodd

This should work as long as all the SENDING sockets are bound to the multicast address in question with bind. The address you specify in connectis matched against the SOURCE address of received packets, so you want to ensure that all packets have the same (multicast) SOURCE AND DESTINATION.

只要所有 SENDING 套接字都绑定到有问题的多播地址,这应该可以工作bind。您指定connect的地址与接收到的数据包的 SOURCE 地址相匹配,因此您要确保所有数据包都具有相同的(多播)SOURCE 和 DESTINATION。

回答by Andrew Edgecombe

The first thing to note is that multicast packets are sent toa multicast address, not froma multicast address. connect() will allow (or not) packets received from a nominated address.

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

To configure your socket to receive multicast packets you need to use one of two socket options:

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

  • IP_ADD_MEMBERSHIP, or
  • IP_ADD_SOURCE_MEMBERSHIP
  • IP_ADD_MEMBERSHIP,或
  • IP_ADD_SOURCE_MEMBERSHIP

The former allows you to specify a multicast address, the latter allows you to specify a multicast address and source address of the sender.

前者允许您指定多播地址,后者允许您指定发送者的多播地址和源地址。

This can be done using something like the following:

这可以使用以下内容来完成:

##代码##

(error handling omitted for brevity)

(为简洁起见省略了错误处理)

To stop receiving multicast packets for this group address, use the socket options:

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

  • IP_DROP_MEMBERSHIP, or
  • IP_DROP_SOURCE_MEMBERSHIP
  • IP_DROP_MEMBERSHIP,或
  • IP_DROP_SOURCE_MEMBERSHIP

Note that a socket can have multiple multicast memberships. But, as the multicast address is the destination address of the packet, you need to be able to grab the destination address of the packet to be able to distinguish between packets for different multicast addresses.

请注意,一个套接字可以有多个多播成员。但是,由于组播地址是数据包的目的地址,因此您需要能够抓取数据包的目的地址才能区分不同组播地址的数据包。

To grab the destination address of the packet you'll need to use recvmsg()instead of recv()or recvfrom(). The destination address is contained within the IPPROTO_IP message level, of type DSTADDR_SOCKOPT. As @Ambroz Bizjak has stated, you'll need to set the IP_PKTINFOsocket option to be able to read this information.

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



Other things to check are:

其他需要检查的事项是:

  • Is multicast supported in your kernel? Check for the existence of /proc/net/igmp to ensure it's been enabled.
  • Has multicast been enable on your network interface? Check for "MULTICAST" listed when you run ifconfigon your interface
  • Does your network interface support multicast? Historically not all have. If not you may be able to get around this by setting your interface to promiscuous mode. e.g. ifconfig eth0 promisc
  • 您的内核是否支持多播?检查 /proc/net/igmp 是否存在以确保它已启用。
  • 您的网络接口是否启用了多播?检查ifconfig在您的界面上运行时列出的“MULTICAST”
  • 您的网络接口是否支持多播?历史上并非所有人都有。如果没有,您可以通过将界面设置为混杂模式来解决此问题。例如ifconfig eth0 promisc

回答by Nikolai Fetissov

bind(2)each socket to the address of the respective multicast group and port instead of INADDR_ANY. That would do the filtering for you.

bind(2)每个套接字到相应多播组和端口的地址而不是INADDR_ANY. 那会为你做过滤。