代码之家  ›  专栏  ›  技术社区  ›  Christopher

如何从udp套接字(C/C++)获取自己的(本地)IP地址

  •  11
  • Christopher  · 技术社区  · 18 年前
    1. 您有多个网络适配器。
    2. 将UDP套接字绑定到本地端口,而不指定地址。
    3. 在其中一个适配器上接收数据包。

    如何获取接收数据包的适配器的本地ip地址?

    问题是,“接收器适配器的ip地址是什么?”而不是我们在

    receive_from( ..., &senderAddr, ... );
    

    电话。

    7 回复  |  直到 18 年前
        1
  •  3
  •   Timbo    18 年前

    您可以枚举所有网络适配器,获取它们的IP地址,并将子网掩码覆盖的部分与发送方的地址进行比较。

    比如:

    IPAddress FindLocalIPAddressOfIncomingPacket( senderAddr )
    {
        foreach( adapter in EnumAllNetworkAdapters() )
        {
            adapterSubnet = adapter.subnetmask & adapter.ipaddress;
            senderSubnet = adapter.subnetmask & senderAddr;
            if( adapterSubnet == senderSubnet )
            {
                return adapter.ipaddress;
            }
        }
    }
    
        2
  •  3
  •   Rob Wells    18 年前

    解决方案由 timbo 假设地址范围是唯一的并且不重叠。虽然情况通常如此,但这不是一个通用的解决方案。

    Steven的书《Unix网络编程》(第20.2节)中提供了一个很好的函数实现,它完全符合你的要求 这是一个基于recvmsg()而不是recvfrom()的函数。如果您的套接字启用了IP_RECVIF选项,那么recvmsg()将返回接收数据包的接口的索引。然后,这可用于查找目标地址。

    源代码可用 here 。有问题的函数是“recvfrom_flags()”

        3
  •  3
  •   Community Mohan Dere    9 年前

    你好,

    我假设您已经使用INADDR_ANY指定了地址进行了绑定。

    如果是这样的话,那么INADDR_ANY的语义是在所有接口上指定的端口上创建UDP套接字。套接字将接收发送到指定端口上所有接口的所有数据包。

    使用此套接字发送时,将使用编号最低的接口。传出发送者的地址字段设置为所使用的第一个传出接口的IP地址。

    第一个输出接口定义为执行ifconfig-a时的序列。它可能是eth0。

    HTH。

    干杯, 抢

        4
  •  0
  •   Community Mohan Dere    9 年前

    遗憾的是,当与绑定到“AnyIP”的套接字一起使用时,sendto和recvfrom API调用从根本上被破坏,因为它们没有本地IP信息的字段。

    那么,你能做些什么呢?

    1. 您可以猜测(例如基于路由表)。
    2. 您可以获取本地地址列表,并为每个本地地址绑定一个单独的套接字。
    3. 您可以使用支持此信息的较新API。这有两个部分,首先,您必须使用相关套接字选项(IPv4为ip_recvif,ipv6为ipv6_recvif)告诉堆栈您需要此信息。然后,您必须使用不同的函数(在linux和其他几个类unix系统上为recvmsg,在windows上为WSArecvmsm)来接收数据包。

    这些选择都不好。猜测很明显总是会产生错误的答案。如果程序运行时本地地址列表发生变化,绑定单独的套接字会增加软件的复杂性并导致问题。较新的API是正确的技术解决方案,但可能会降低可移植性 (特别是看起来WSArecvmsg在windows XP上不可用) 并且可能需要修改您正在使用的套接字包装库。

    Edit看起来我错了,似乎MS文档具有误导性,WSArecvmsg在windows XP上可用。看见 https://stackoverflow.com/a/37334943/5083516

        5
  •  -3
  •   diciu    18 年前

    在Linux环境中,您可以使用 recvmsg 获取本地ip地址。

        //create socket and bind to local address:INADDR_ANY:
        int s = socket(PF_INET,SOCK_DGRAM,0);
        bind(s,(struct sockaddr *)&myAddr,sizeof(myAddr)) ;
        // set option
        int onFlag=1;
        int ret = setsockopt(s,IPPROTO_IP,IP_PKTINFO,&onFlag,sizeof(onFlag));
        // prepare buffers
        // receive data buffer
        char dataBuf[1024] ;
        struct iovec iov = {
            .iov_base=dataBuf,
            .iov_len=sizeof(dataBuf)
        } ;
        // control buffer
        char cBuf[1024] ;
        // message
        struct msghdr  msg = {
            .msg_name=NULL, // to receive peer addr with struct sockaddr_in
            .msg_namelen=0, // sizeof(struct sockaddr_in)
            .msg_iov=&iov,
            .msg_iovlen=1,
            .msg_control=cBuf,
            .msg_controllen=sizeof(cBuf)
        } ;
        while(1) {
            // reset buffers
            msg.msg_iov[0].iov_base = dataBuf ;
            msg.msg_iov[0].iov_len = sizeof(dataBuf) ;
            msg.msg_control = cBuf ;
            msg.msg_controllen = sizeof(cBuf) ;
            // receive
            recvmsg(s,&msg,0);
            for( struct cmsghdr* pcmsg=CMSG_FIRSTHDR(&msg);
                 pcmsg!=NULL; pcmsg=CMSG_NXTHDR(&msg,pcmsg) ) {
                if(pcmsg->cmsg_level==IPPROTO_IP && pcmsg->cmsg_type==IP_PKTINFO) {
                    struct in_pktinfo * pktinfo=(struct in_pktinfo *)CMSG_DATA(pcmsg);
                    printf("ifindex=%d ip=%s\n", pktinfo->ipi_ifindex, inet_ntoa(pktinfo->ipi_addr)) ;
                }
            }
        }
    

    以下内容在非对称路由环境中不起作用。

    您可以首先将SO_REUSEADDR设置为true

    BOOL bOptVal = 1;
    setsockopt(so, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));
    

    之后 receive_from( ..., &remoteAddr, ... ); 创建另一个套接字,然后连接回remoteAddr。然后调用getsockname可以获取ip地址。

    SOCKET skNew = socket( )
    // Same local address and port as that of your first socket 
    // INADDR_ANY
    bind(skNew, ,  )
    // set SO_REUSEADDR to true again
    setsockopt(skNew, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));
    
    // connect back  
    connect(skNew, remoteAddr)
    
    // get local address of the socket
    getsocketname(skNew, )
    
        6
  •  -3
  •   Rostyslav Dzinko Ankit    14 年前
    ssize_t
         recvfrom(int socket, void *restrict buffer, size_t length, int flags,
             struct sockaddr *restrict address, socklen_t *restrict address_len);
    
         ssize_t
         recvmsg(int socket, struct msghdr *message, int flags);
    
    [..]
         If address is not a null pointer and the socket is not connection-oriented, the
         source address of the message is filled in.
    

    实际代码:

    int nbytes = recvfrom(sock, buf, MAXBUFSIZE, MSG_WAITALL, (struct sockaddr *)&bindaddr, &addrlen);

    fprintf(stdout,“在本地地址%s\n上读取%d个字节”,nbytes,inet_ntoa(bindaddr.sin_addr.s_addr));

    希望这能有所帮助。

    推荐文章