C 中的多播:使用 addrinfo 时不接收二进制文件

Multicasting in C: Binary does not receive when using addrinfo

我在两个几乎相同的程序中遇到了这个有趣的小问题。我想做的是在多播套接字上发送一些数据并接收它。现在,如果发件人收到消息我没问题(我会设置选项稍后不接收)。

我有两个实施案例。在第一种方法中,我使用传统方法初始化 sockaddr 结构,然后绑定到同一套接字上的多播组。然而,这是 IPv4/IPv6 依赖的,为了避免这种情况,我尝试在程序的第二个变体中使用 addrinfo 结构。两个程序都在下面给出。

问题是,在第一个用例中正在接收消息,我使用的是常规 sockaddr,而在第二个用例中没有设置 received/socket 描述符的消息.有人可以帮我解释一下为什么会这样吗?

变体 1(sockaddr

#include<stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>       /* for nonblocking */
#include <netinet/tcp.h>

fd_set hm_tprt_conn_set;

main()
{
    struct ip_mreq mreq;
    struct sockaddr_in mc_addr;
    int sock_fd ;
    int val;
    int reuse = 1;

    struct sockaddr_in ip;
    struct sockaddr_in src_addr;

    int total_bytes_rcvd=0;
    unsigned int length;
    unsigned char buf[50]; 
    int op_complete = 0;
    int os_error;

    struct timeval select_timeout;
    fd_set read_set;
    int32_t nready; //Number of ready descriptors

    time_t time_val;

    length = sizeof (src_addr);

    sock_fd = socket(AF_INET, SOCK_DGRAM,0);
    if(sock_fd == -1)
    {
        printf("\n Error Opening UDP MCAST socket");
        perror("\n Cause is ");
        exit(0);
    }

    printf("\n Setting the socket to non-blocking mode");
    val = fcntl(sock_fd, F_GETFL , 0);
    val = fcntl(sock_fd, F_SETFL, val | O_NONBLOCK);

    if (val == -1)
    {
            printf("\n Error while setting socket to non-blocking mode");
            perror("Cause is ");
            sock_fd = -1;
            exit(0);
    } //end if val == -1

    if (setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        fprintf(stderr, "setsockopt: %d\n", errno);
        perror("Cause is ");
            exit(0);
    }

    FD_SET(sock_fd, &hm_tprt_conn_set);

    printf("\n Construct a mcast address structure");
    /* construct a multicast address structure */

    memset(&mc_addr, 0, sizeof(mc_addr));
    mc_addr.sin_family = AF_INET;
    mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    mc_addr.sin_port = htons(4936);

    memset(&ip, 0, sizeof(ip));
    ip.sin_family = AF_INET;
    ip.sin_addr.s_addr = inet_addr("224.0.0.203")/*htonl(INADDR_ANY)*/;
    ip.sin_port = htons(4936);

    printf("\n Bind the multicast address structure and port to the recieving socket ");
    if (bind( sock_fd, (struct sockaddr*) &mc_addr, sizeof(mc_addr)) == -1)
    {
        fprintf(stderr, "bind: %d\n", errno);
            perror("\n Cause is ");
        exit(0);
     }

    mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.203");
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

        if(setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq)) == -1)
    {
        fprintf(stderr, "setsockopt: %d\n", errno);
        perror("\n Cause is ");
            exit(0);
    }

    printf("\nCreated Recv Socket: %d", sock_fd);   


    fflush(stdout);
    memset(&src_addr, 0, sizeof(mc_addr));
    while(1){
    /* Send a multicast */  
    time_val = time(NULL);
    snprintf(buf, sizeof(buf), "Hello: %s", ctime(&time_val));
        total_bytes_rcvd = sendto(sock_fd,
                        buf,
                        sizeof(buf),
                           0,
                          (struct sockaddr *)&ip,
                         length );
    printf("\n%d bytes sent.", total_bytes_rcvd);

    /* perform select */                         
        select_timeout.tv_sec = 0;
        select_timeout.tv_usec = 5000000;

        read_set = hm_tprt_conn_set;

        nready = select(sock_fd+1, &read_set, NULL, NULL, &select_timeout);
        if(nready == 0)
        {
            /***************************************************************************/
            /* No descriptors are ready                                                */
            /***************************************************************************/
            continue;
        }
        else if(nready == -1)
        {
            perror("Error Occurred on select() call.");
            continue;
        }

    if(FD_ISSET(sock_fd, &read_set))
        {
        printf("\n Recv the data"); 
        total_bytes_rcvd = recvfrom(sock_fd,
                                         buf,
                                      sizeof(buf),
                                       0,
                                  (struct sockaddr *)&src_addr,
                                     &length );

        printf("%s: message = \" %s \"\n", inet_ntoa(src_addr.sin_addr), buf);
        printf("\n total byte recieved %d", total_bytes_rcvd);

            /***************************************************************************/
            /* If select returned 1, and it was a listen socket, it makes sense to poll*/
            /* again by breaking out and use select again.                             */
            /***************************************************************************/
            if(--nready <=0)
            {
                printf("\nNo more incoming requests.");
                continue;
            }
        }//end select on listenfd
    }
}

变体 2(addrinfo

#include<stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>       /* for nonblocking */
#include <netinet/tcp.h>
#include <netdb.h>      /* AI_PASSIVE and other Macros for getaddrinfo() */

fd_set hm_tprt_conn_set;

main()
{

  struct addrinfo hints, *res, *ressave;
  char target[128] = "127.0.0.1";
  char service[128] = "4936";

    struct ip_mreq mreq;

    int sock_fd ;
    int val;
    int reuse = 1;

    struct sockaddr_in ip;
    struct sockaddr_in src_addr;

    int total_bytes_rcvd=0;
    unsigned int length;
    unsigned char buf[50]; 
    int op_complete = 0;
    int os_error;

    struct timeval select_timeout;
    fd_set read_set;
    int32_t nready; //Number of ready descriptors

    time_t time_val;

    length = sizeof (src_addr);

  sock_fd = socket(AF_INET, SOCK_DGRAM,0);
  if(sock_fd == -1)
  {
      printf("\n Error Opening UDP MCAST socket");
      perror("\n Cause is ");
      exit(0);
  }

  printf("\n Setting the socket to non-blocking mode");
  val = fcntl(sock_fd, F_GETFL , 0);
  val = fcntl(sock_fd, F_SETFL, val | O_NONBLOCK);

  if (val == -1)
  {
          printf("\n Error while setting socket to non-blocking mode");
          perror("Cause is ");
          sock_fd = -1;
          exit(0);
  } //end if val == -1

  if (setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
  {
      fprintf(stderr, "setsockopt: %d\n", errno);
        perror("Cause is ");
            exit(0);
    }

  FD_SET(sock_fd, &hm_tprt_conn_set);

    printf("\n Construct a mcast address structure");
    /* construct a multicast address structure */

  hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

  if((os_error = getaddrinfo(target, service, &hints, &res)) !=0)
  {
      printf("\n%s",gai_strerror(os_error));
      exit(0);
  }

  ressave = res;

  if(bind(sock_fd, res->ai_addr, res->ai_addrlen) != 0)
    {
        perror("Error binding to port");
        close(sock_fd);
        sock_fd = -1;
    }

    mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.203");
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

    if(setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq)) == -1)
    {
        fprintf(stderr, "setsockopt: %d\n", errno);
        perror("Cause is ");
        exit(0);
    }

  /* Set Destination address */
    memset(&ip, 0, sizeof(ip));
    ip.sin_family = AF_INET;
    ip.sin_addr.s_addr = inet_addr("224.0.0.203")/*htonl(INADDR_ANY)*/;
    ip.sin_port = htons(4936);

    /* Set to zero address where addresses of sender will be received */
    memset(&src_addr, 0, sizeof(src_addr));

    while(1){

    /* Send a multicast */  
    time_val = time(NULL);
    snprintf(buf, sizeof(buf), "Hello: %s", ctime(&time_val));
        total_bytes_rcvd = sendto(sock_fd,
                        buf,
                        sizeof(buf),
                          0,
                          (struct sockaddr *)&ip,
                        length );

    printf("\n%d bytes sent.", total_bytes_rcvd);

    /* perform select */                         
        select_timeout.tv_sec = 0;
        select_timeout.tv_usec = 5000000;

        read_set = hm_tprt_conn_set;

        nready = select(sock_fd+1, &read_set, NULL, NULL, &select_timeout);
        if(nready == 0)
        {
            /***************************************************************************/
            /* No descriptors are ready                                                */
            /***************************************************************************/
            continue;
        }
        else if(nready == -1)
        {
            perror("Error Occurred on select() call.");
            continue;
        }

    if(FD_ISSET(sock_fd, &read_set))
        {
        printf("\n Recv the data"); 
        total_bytes_rcvd = recvfrom(sock_fd,
                                  buf,
                                  sizeof(buf),
                                    0,
                                  (struct sockaddr *)&src_addr,
                                  &length );

        printf("%s: message = \" %s \"\n", inet_ntoa(src_addr.sin_addr), buf);
        printf("\n total byte recieved %d", total_bytes_rcvd);

            /***************************************************************************/
            /* If select returned 1, and it was a listen socket, it makes sense to poll*/
            /* again by breaking out and use select again.                             */
            /***************************************************************************/
            if(--nready <=0)
            {
                printf("\nNo more incoming requests.");
                continue;
            }
        }//end select on listenfd
    }
}

在您可以 bind() 之前,您需要有一个可用的插座。您将需要循环浏览所有结果。这是您的代码中缺少的内容。

ressave = res;

sock = socket(ressave->ai_family, ressave->ai_socktype, ressave->ai_protocol);
while(ressave != NULL && (sock < 0 || connect(sock, ressave->ai_addr, ressave->ai_addrlen) < 0)) {
    close(sock);
    if((ressave = ressave->ai_next) != NULL)
        sock = socket(ressave->ai_family, ressave->ai_socktype, ressave->ai_protocol);
}

此时您已经找到了一个可用的套接字 sock 或没有。当 ressave 不为 NULL 时,套接字 sock 的值有效。

不同之处在于,在第一个变体中,您绑定到 INADDR_ANY,而在第二个变体中,您绑定到 127.0.0.1。未能绑定到 INADDR_ANY 意味着您将不会收到任何多播数据。

您可以使用以下方法解决此问题:

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;

if((os_error = getaddrinfo(NULL, service, &hints, &res)) !=0)
{
   printf("\n%s",gai_strerror(os_error));
   exit(0);
}

来自有关 AI_PASSIVE 的 getaddrinfo 手册页:

If node is NULL, the network address in each socket structure is initialized according to the AI_PASSIVE flag, which is set in hints.ai_flags. The network address in each socket structure will be left unspecified if AI_PASSIVE flag is set. This is used by server applications, which intend to accept client connections on any network address. The network address will be set to the loopback interface address if the AI_PASSIVE flag is not set. This is used by client applications, which intend to connect to a server running on the same network host.

虽然在这种情况下您发送到同一台主机,但默认情况下多播数据不会从本地主机接口发出。您需要使用 IP_MULTICAST_IF 选项调用 setsockopt 来设置传出多播接口。

通过此更改,我能够使用第二个变体发送和接收。