如何使用套接字地址获取自己的IP地址?

9
我想获取程序所在计算机的IP地址,以便将其发送给客户端,但我总是得到0.0.0.1而不是真实的IP地址(例如127.0.0.1)。我目前能够获取端口,但无法获取IP地址。如何获取IP地址?最好的解决方案是使用sockaddr_in获取它。以下是我目前正在执行的操作:
int open_connection(char* ip, int* port)
{
  int                   sock;
  struct sockaddr_in    sin;
  socklen_t             len;
  int                   i;

  i = 0;
  len = sizeof(sin);
  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    return (-1);
  bzero(&sin, sizeof(struct sockaddr_in));
  sin.sin_family = AF_INET;
  if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0)
    perror("Error on bind");
  if (getsockname(sock, (struct sockaddr *)&sin, &len) != 0)
    perror("Error on getsockname");
  strcpy(ip, inet_ntoa(sin.sin_addr)); // IP = 0.0.0.0
  *port = sin.sin_port;
  return (sock);
}

编辑:我明白了,我的想法是错误的。所以我的问题是:获取自己的IP地址的最佳方法是什么?


1
如果客户端正在与您交流...他已经拥有了您的IP地址。我不明白为什么您需要显式地发送它。 - Pyrce
1
@Pyrce 我正在制作一个FTP服务器,当进入被动模式时,我需要发送服务器正在侦听的IP地址和端口...即使它是相同的IP地址... - Julien Fouilhé
2
也许这是一个老问题?https://dev59.com/c3VC5IYBdhLWcg3wsTVi 我认为绑定到0.0.0.0是一个具体的事情,这意味着绑定在所有IP地址上 - 我怀疑你会从绑定中得到其他东西,因为这就是你传入的内容。 - Rup
3
实际上,你仍然可以使用 getsockname() 函数,但是在客户端连接的套接字上调用它,而不是在绑定的套接字上调用。这将展示客户端正在使用的接口,我认为这将是他们需要用于辅助数据连接的相同接口。 - Rup
@Rup 好的,然后使用 ioctl 获取此接口的 IP 地址?您能将其发布为答案,以便我可以验证吗?谢谢。 - Julien Fouilhé
与此类似的问题在这里:http://stackoverflow.com/q/10956890/694576 - alk
4个回答

15
当你使用bind()将套接字绑定到0.0.0.0时,在调用getsockname()时,该套接字只有该IP可用。这意味着该套接字绑定了所有本地接口。要从套接字获取特定的IP地址,必须将其绑定到特定的IP地址。
使用套接字API获取计算机的本地IP是错误的方法。一个常见的错误是使用 gethostname()gethostbyname()getaddrinfo()来获取本地IP列表。通常可以工作,但它有一些隐藏的陷阱可能会导致虚假信息,但人们倾向于忽略这个事实,或者根本不知道它(我多年来都不知道,但后来我学到了更好的方法)。
相反,您真的应该使用特定于平台的API枚举本地网络接口。这将提供更可靠的信息。Windows具有GetAdaptersInfo()GetAdaptersAddresses()。其他平台具有getifaddrs()。这些将告诉您哪些本地IP可用。然后,您可以使用bind()将套接字绑定到0.0.0.0,以便在任何这些IP上接受客户端,或将其绑定到特定的IP以仅在该IP上接受客户端。

请问您能详细说明一下 getaddrinfo() 中的 "... hidden gotchas ..." 吗? - alk
gethostbyname()getaddrinfo()旨在使用DNS查找来将主机名解析为IP地址。它们不适用于检索有关本地主机的信息。用户可能会更改/破坏操作系统的DNS缓存(hosts文件等),并且PC的主机名可能被配置为根本没有IP或映射到多个IP,这些IP可能并非全部属于该PC(考虑负载平衡等)。在所有设置中,这些函数不能保证传递本地主机名时始终返回有效的本地IP。相反,GetAdaptersInfo()getifsaddrs()等函数是专为此目的设计的。 - Remy Lebeau
哦,是的,你基本上指的是涉及到的外部DNS。我同意这一点。谢谢澄清。 - alk
这个(getifaddrs())完美地运作了,而且实现起来也很容易,谢谢! - Julien Fouilhé
这对我非常有帮助。我在使用C++中的RakNet进行网络编程,他们的代码内部调用了gethostbyname()。在某些网络上,这个调用需要很长时间(约30秒)才能获取本地IP地址。看来这是你们的一个“坑点”。将他们的内部代码更改为使用getifaddrs()似乎解决了问题。非常感谢您发布这个答案! - aardvarkk
显示剩余2条评论

5
sockets API允许您枚举分配给网络接口的IP地址,但如果您是从路由器后面连接到互联网,则不会告诉您您的“真实IP”是什么。
唯一的方法是向外部询问。这就是像FileZilla FTP服务器这样的服务器所做的。它们指示您将URL配置为“ip.php”脚本,例如此脚本在服务器设置中,以便它可以询问Internet其公共IP地址,以在被动模式下使用。
您还可以考虑使用STUN,这是一种在VoIP中广泛使用的协议,用于发现公共IP。

1
如果 OP 想要更多选项,我个人喜欢 ifconfig.mecanhazip.com - jweyrich
谢谢,但这不适合我的现需求(因为这是一份学校项目,而且不需要在线工作)。我会看看是否有时间来实现它。 - Julien Fouilhé

1
你可以调用 ioctl(sock, SIOCGIFADDR, adr)。
请参阅 netdevice(7)。

0

根据 @Remy Lebeau 的回答,我编写了一个返回当前机器地址的函数。我只在 macOS High Sierra 上进行了测试。

interfaec 可以是 lo0en0 等任何东西。

ipVersion 可以是 AF_INETAF_INET6

long int getInternalAddress(char* interface, sa_family_t ipVersion)
{
  struct ifaddrs *ifaddrHead, *ifaddr;
  /* int_8 */
  sa_family_t family;
  int n;
  char *interfaceName;

  if (getifaddrs(&ifaddrHead) != 0)
  {
    fprintf(stderr, "ifaddrs error");
  }

  /* iterate through address list */
  for (ifaddr = ifaddrHead, n = 0; ifaddr != NULL; ifaddr = ifaddr->ifa_next, n++)
  {
    family = ifaddr->ifa_addr->sa_family;
    interfaceName = ifaddr->ifa_name;

    if (!family || family != ipVersion || strcmp(interfaceName, interface)) continue;

    struct sockaddr *addr = ifaddr->ifa_addr;
    struct sockaddr_in* addr_in = (struct sockaddr_in*) addr;
    long int address = addr_in->sin_addr.s_addr;

    freeifaddrs(ifaddrHead);

    return address;
  }

  freeifaddrs(ifaddrHead);

  return 0;
}

使用它,

int main()
{
  long int address = getInternalAddress((char*) &"en0", AF_INET);
  printf("%li\n", address);

  return 0;
}

我在C语言方面还是一个初学者,如果有任何错误请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接