为了让事情更清晰一些:
- 服务器显然会有"localhost": 127.0.0.1
- 服务器将拥有一个内部(管理)IP地址:172.16.x.x
- 服务器将拥有一个外部(公共)IP地址:80.190.x.x
在OS X上,我发现ioctl解决方案存在问题(OS X符合POSIX标准,因此应该类似于Linux)。但是,getifaddress()可以让您轻松地完成相同的操作,在我的OS X 10.5上运行良好,下面的操作应该也是相同的。
我做了一个简单的示例,它将打印出机器的所有IPv4地址(您还应该检查getifaddrs是否成功,即返回0)。
我已更新它以显示IPv6地址。
#include <stdio.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
int main (int argc, const char * argv[]) {
struct ifaddrs * ifAddrStruct=NULL;
struct ifaddrs * ifa=NULL;
void * tmpAddrPtr=NULL;
getifaddrs(&ifAddrStruct);
for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr) {
continue;
}
if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
// is a valid IP4 Address
tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
char addressBuffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
} else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
// is a valid IP6 Address
tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
char addressBuffer[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
}
}
if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct);
return 0;
}
tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
这样的方式。 - Andreyioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);
阅读 /usr/include/linux/if.h
了解有关ifconf
和ifreq
结构的信息。这应该可以给您提供系统上每个接口的IP地址。还要阅读 /usr/include/linux/sockios.h
获取其他的ioctl命令。
我喜欢jjvainio的答案。正如Zan Lnyx所说,它使用本地路由表来找到将用于与特定外部主机连接的以太网接口的IP地址。通过使用已连接的UDP套接字,您可以获取信息而不实际发送任何数据包。该方法要求您选择一个特定的外部主机。大多数时候,任何知名的公共IP地址都可以胜任。我喜欢Google的公共DNS服务器地址8.8.8.8用于此目的,但有时您可能想选择不同的外部主机IP。以下是说明完整方法的一些代码。
void GetPrimaryIp(char* buffer, size_t buflen)
{
assert(buflen >= 16);
int sock = socket(AF_INET, SOCK_DGRAM, 0);
assert(sock != -1);
const char* kGoogleDnsIp = "8.8.8.8";
uint16_t kDnsPort = 53;
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_addr.s_addr = inet_addr(kGoogleDnsIp);
serv.sin_port = htons(kDnsPort);
int err = connect(sock, (const sockaddr*) &serv, sizeof(serv));
assert(err != -1);
sockaddr_in name;
socklen_t namelen = sizeof(name);
err = getsockname(sock, (sockaddr*) &name, &namelen);
assert(err != -1);
const char* p = inet_ntop(AF_INET, &name.sin_addr, buffer, buflen);
assert(p);
close(sock);
}
您已经发现了没有单一的“本地IP地址”。以下是如何找到可以发送到特定主机的本地地址。
这种方法的优点是可以在许多Unix版本上运行...并且您可以轻松修改它以在任何操作系统上工作。以上所有解决方案都会在不同阶段出现编译器错误。一旦有了一个好的POSIX方式来解决,就不要使用此方法(在撰写本文时还没有这种方法)。
// ifconfig | perl -ne 'print "$1\n" if /inet addr:([\d.]+)/'
#include <stdlib.h>
int main() {
setenv("LANG","C",1);
FILE * fp = popen("ifconfig", "r");
if (fp) {
char *p=NULL, *e; size_t n;
while ((getline(&p, &n, fp) > 0) && p) {
if (p = strstr(p, "inet ")) {
p+=5;
if (p = strchr(p, ':')) {
++p;
if (e = strchr(p, ' ')) {
*e='\0';
printf("%s\n", p);
}
}
}
}
}
pclose(fp);
return 0;
}
/sbin/ifconfig eth1 | grep \"inet addr\" | cut -d':' -f2 | cut -d' ' -f1
。当然,这只是将“解析”移到了控制台,语言问题仍然存在。 - Martin Meeserifconfig
一样会过时。现在应该使用 ip
命令。生成语言中立输出的正确方法是将 LANG
环境变量设置为 C
,例如:LANG=C ls -l
。 - Anders我认为你的问题并没有一个明确的正确答案。相反,有很多方法可以接近你想要的结果。因此,我将提供一些提示来完成它。
如果机器有超过2个接口(lo
算一个),你将难以轻松地自动检测到正确的接口。以下是如何解决此问题的一些方法。
例如,问题在于,如果主机位于NAT防火墙后面的DMZ中,该防火墙将公共IP更改为某些私有IP并转发请求。您的机器可能有10个接口,但只有一个对应于公共接口。
即使自动检测也无法在双重NAT的情况下工作,其中您的防火墙甚至将源IP转换为完全不同的内容。因此,您甚至不能确定默认路由是否通向具有公共接口的接口。
这是我推荐的自动检测方式
类似 ip r get 1.1.1.1
的命令通常会告诉您具有默认路由的接口。
如果您想在喜欢的脚本/编程语言中重新创建此操作,请使用 strace ip r get 1.1.1.1
并遵循黄砖路。
/etc/hosts
进行设置如果您想保持控制,这是我的建议
您可以在 /etc/hosts
中创建一个条目,例如:
80.190.1.3 publicinterfaceip
然后,您可以使用别名publicinterfaceip
来引用您的公共接口。
不幸的是,
haproxy
无法理解IPv6的这个技巧。
如果您不是
root
用户,则环境变量是/etc/hosts
的一个很好的解决方法。
与/etc/hosts
相同,但使用环境变量。 您可以尝试/etc/profile
或~/.profile
。
因此,如果您的程序需要一个变量MYPUBLICIP
,那么您可以包含以下代码(这是C语言,请随意从中创建C ++):
#define MYPUBLICIPENVVAR "MYPUBLICIP"
const char *mypublicip = getenv(MYPUBLICIPENVVAR);
if (!mypublicip) { fprintf(stderr, "please set environment variable %s\n", MYPUBLICIPENVVAR); exit(3); }
所以你可以这样调用你的脚本/程序/path/to/your/script
MYPUBLICIP=80.190.1.3 /path/to/your/script
这甚至在crontab
中也有效。
如果您不能使用
ip
,那么绝望的方法
如果您知道您不想要什么,您可以枚举所有接口并忽略所有错误的接口。
这里似乎已经有一个答案https://dev59.com/c3VC5IYBdhLWcg3wsTVi#265978支持这种方法。
喝醉了试图在酒精中淹死自己的方法
您可以尝试枚举网络上的所有UPnP网关,以此找到某些“外部”事物的合适路线。 这甚至可能在您的默认路由未指向的路径上。
更多信息,请参见https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol
这将为您提供良好的印象,哪个是真正的公共接口,即使您的默认路线指向其他位置。
山与先知相遇的地方
IPv6路由器会广告自己,以便为您提供正确的IPv6前缀。 查看前缀会让您了解它是否具有某些内部IP或全局IP。
您可以监听IGMP或IBGP帧,以找出一些合适的网关。
不到2 ^ 32个IP地址。 因此,在LAN上仅对它们进行ping不需要长时间。 这为您提供了有关互联网从您的角度来看位于何处的统计提示。 但是,您应该比著名的https://de.wikipedia.org/wiki/SQL_Slammer更加明智。
ICMP甚至ARP是网络旁路信息的良好来源。 它也可能对您有所帮助。
您可以使用以太网广播地址与所有网络基础设施设备通信,这通常会有所帮助,例如DHCP(甚至DHCPv6)等。
这个附加列表可能是无限的,并且总是不完整的,因为每个网络设备制造商都在忙于发明新的安全漏洞,以自动检测自己的设备。 这通常对如何检测不应该有公共接口的地方有很大帮助。
说得够多了。 出门。
除了Steve Baker所说的内容外,您可以在netdevice(7)手册页中找到有关SIOCGIFCONF ioctl的描述。
一旦您获得主机上所有IP地址的列表,您将不得不使用特定于应用程序的逻辑来过滤掉您不想要的地址,并希望仅剩下一个IP地址。
不要硬编码:这种东西是会变化的。许多程序通过读取配置文件来确定绑定什么,并执行文件中指定的操作。这样,如果您的程序将来需要绑定到非公共 IP 的内容,它可以轻松完成。
你可以只使用 stdio.h
头文件来进行编程吗?(不涉及网络头文件)
以下是完整的 C++ 代码:
(如果你使用家庭调制解调器,则此代码适用于本地 IP)
#include <stdio.h>
int main()
{
static char ip[32];
FILE *f = popen("ip a | grep 'scope global' | grep -v ':' | awk '{print $2}' | cut -d '/' -f1", "r");
int c, i = 0;
while ((c = getc(f)) != EOF) i += sprintf(ip+i, "%c", c);
pclose(f);
printf(ip);
}
BONUS: 你也可以使用它的 NDK C++ 编译器将其交叉编译到 Android 上
JUST SAYING: 我有一个 VPS,在那里我运行了这个程序并获取了外部 IP
ifconfig
、route
等命令已被弃用,现在应使用ip
命令代替它们。请使用ip
命令来进行相关操作。 - Anders