如何从 udp-socket (C/C++) 获取您自己的(本地)IP 地址

Posted

技术标签:

【中文标题】如何从 udp-socket (C/C++) 获取您自己的(本地)IP 地址【英文标题】:How to get your own (local) IP-Address from an udp-socket (C/C++) 【发布时间】:2010-09-06 23:47:27 【问题描述】:
    您有多个网络适配器。 将 UDP 套接字绑定到本地端口,无需指定地址。 在其中一个适配器上接收数据包。

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

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

中获得的发件人地址
receive_from( ..., &senderAddr, ... );

打电话。

【问题讨论】:

【参考方案1】:

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

喜欢:

IPAddress FindLocalIPAddressOfIncomingPacket( senderAddr )

    foreach( adapter in EnumAllNetworkAdapters() )
    
        adapterSubnet = adapter.subnetmask & adapter.ipaddress;
        senderSubnet = adapter.subnetmask & senderAddr;
        if( adapterSubnet == senderSubnet )
        
            return adapter.ipaddress;
        
    

【讨论】:

仅当数据包源自适配器的子网时。如果它被路由到那里就不会...... 是的,Len 是对的。如果数据包通过本地路由器上的 NAT 到达,您只会看到本地 IP(类似于 10.0.0.2) 是不是广播没有路由(只在本地发送)?因此,IIRC 该方法应该可以工作。 它适用于广播,但广播远非服务器可能接收的唯一数据包。它不适用于具有多个 IP 地址的服务器的一般情况,其中任何一个都可能从 Internet 上的任何地方接收数据包。【参考方案2】:

timbo 提供的解决方案假定地址范围是唯一的且不重叠。虽然通常是这种情况,但它不是通用解决方案。

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

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

【讨论】:

我将对此进行调查。也许这是一个更好的解决方案。【参考方案3】:

生日,

我假设您已使用 INADDR_ANY 完成绑定以指定地址。

如果是这种情况,那么 INADDR_ANY 的语义就是在所有接口上指定的端口上创建一个 UDP 套接字。套接字将获取发送到指定端口上所有接口的所有数据包。

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

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

HTH。

干杯, 抢

【讨论】:

【参考方案4】:

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

那么你能做些什么呢?

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

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

编辑看起来我错了,似乎 MS 文档具有误导性,并且 WSarecvmsg 在 Windows XP 上可用。见https://***.com/a/37334943/5083516

【讨论】:

【参考方案5】:

您可以先将 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】: 大小_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len); 大小_t recvmsg(int socket, struct msghdr *message, int flags); [..] 如果地址不是空指针并且套接字不是面向连接的,则 填写消息的源地址。

实际代码:

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

希望这会有所帮助。

【讨论】:

源地址为远程地址【参考方案7】:

试试这个:

gethostbyname("localhost");

【讨论】:

这只会得到 localhost 被配置为解析的内容(应该是 127.0.0.1).. 而不是 UDP 数据包的接收端。

以上是关于如何从 udp-socket (C/C++) 获取您自己的(本地)IP 地址的主要内容,如果未能解决你的问题,请参考以下文章

如何从两个版本之间的 Perforce diff 中获取 C/C++ 函数名

如何从 c/c++ 源代码获取适用于 Windows 的 python .pyd? (更新:如果你想要的话,现在在 Python 中很活跃)

如何从 QML 旋转框中获取数据

如何使用C / C ++套接字从HTTP读取二进制文件

将参数从 C/C++ JNI 传递到 Java 并获取修改后的值

从 int 获取单个数字以在 C/C++ 中进行基数排序的最佳方法