我正在阅读Beej的“Guide to network programming”。
在他的一个介绍示例中,他谈到获取主机名的IP地址(例如google.com或yahoo.com)。 这是代码。
/*
** showip.c -- show IP addresses for a host given on the command line
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
struct addrinfo hints, *res, *p;
int status;
char ipstr[INET6_ADDRSTRLEN];
if (argc != 2) {
fprintf(stderr,"usage: showip hostname\n");
return 1;
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
hints.ai_socktype = SOCK_STREAM;
if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
return 2;
}
printf("IP addresses for %s:\n\n", argv[1]);
for(p = res; p != NULL; p = p->ai_next) {
void *addr;
char *ipver;
// get the pointer to the address itself,
// different fields in IPv4 and IPv6:
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
// convert the IP to a string and print it:
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf(" %s: %s\n", ipver, ipstr);
}
freeaddrinfo(res); // free the linked list
return 0;
}
令我困惑的部分是for循环。
for(p = res; p != NULL; p = p->ai_next) {
void *addr;
char *ipver;
// get the pointer to the address itself,
// different fields in IPv4 and IPv6:
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
// convert the IP to a string and print it:
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf(" %s: %s\n", ipver, ipstr);
}
有人会介意一步一步地进行伪步,或者这些事情是什么?它是否在链接列表中进行迭代?我对struct addrinfo
是什么有一个概括,但结果是*res
和struct *p
或void *addr
和{{ 1}}。
答案 0 :(得分:5)
首先,你知道linked list是什么吗?如果您了解这一点,您将会认识到for循环的内容。 p
是指向结构的指针,该结构也引用( links )列表中的下一个结构。所以你循环遍历那些addrinfo
结构的结构列表。 4
现在,您需要了解的有关网络数据包的事情是它们由标头组成。特别是Ethernet frame。这是硬件到硬件协议。它让你在一个物理的,有界的网络上解决问题,但对物理网络边界的路由一无所知。
接下来是tcp或者可能是另一个传输层协议,它位于两个级别之间的某个位置。 TCP与UDP与X的关系是关于如何管理数据包 - 例如TCP要求按顺序重新组装数据包,而UDP则是“广播”类型协议。
最后,您拥有互联网协议套件(IPv4,IPv6)。这些是更高级别的协议,可以控制更广泛的路由感,因此他们了解整个互联网,但更少了解到达目的地所需的步骤。
对此的一个很好的解释是这个page的方便图表。为了完成图片,BGP是路由器知道如何移动内容的方式。
tcp / udp通过成为(有关的)协议的一部分(例如IPv4)来适应这张图片
因此,以太网帧包含其他协议,尤其是IPv4,其中包含路由器需要通过互联网(跨多个物理网络)获取信息的信息。 internet 协议指定您要去的地方,从您所在的位置开始。因此,典型的IPv4主体在整个传输过程中保持不变,但每次遍历物理网络时,它都会被包含在不同的以太网数据包中。
现在,在以太网标题中有一个字段用于查找“以太网主体”包含的内容。这一行:
if (p->ai_family == AF_INET) {
一样。 AF_INET
是一个常量,它与tcp用于将数据包主体标识为IPv4的值相匹配。因此,如果您正在查看IPv4标头,则此循环将继续读取该信息。
else子句技术上错误,因为IPv4不会自动使其成为IPv6。您可以将其更改为测试IPv6,如下所示:
else if (p->ai_family == AF_INET6) {
您可能想要这样做,以防万一你拿起别的东西。
现在值得解释这一点魔力:
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
这基本上采用数据的网络或原始形式,其显示为字节序列,并将其(转换为)转换为结构中的字段。因为你知道这些领域有多大,所以这是一种非常快速简便的方法来提取你需要的东西。
最后需要解释的是:
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
还有其他方法可以实现这一目标,特别是ntohs()
。
基本上,网络数据以大端编码方式传输,为了读取它,您需要(可能)将数据转换为系统的编码。它可能是大端,或者可能很小,这在很大程度上取决于你的系统。阅读endianness上的维基百科文章。
总结:您在这里看到的是计算机科学结构,网络如何工作和C代码的组合。
答案 1 :(得分:1)
嗯,这并不复杂。 getaddrinfo
会在联机帮助页中返回addrinfo
结构(struct addrinfo **res
)的链接列表,其中每个结构都包含有关给定界面可用的一个地址的信息(联构帮助页中为const char *node
)。
现在,正在检查每个结构,并打印出有关结构的信息。要打印 IPv4 或 IPv6 ,相应地设置变量ipver
。在打印出信息之前,必须将地址从二进制形式转换为字符串。这是由inet_ntop
( * n * umber 到 * p * ointer)完成的。
结果字符串inet_ntop
(ipstr
)和ipver
现在打印到控制台。但是,打印ipver
不是必需的,因为您可以从ipstr
识别地址类型:IPv4地址(我们都知道)写入192.168.1.10
而IPv6地址使用冒号分隔地址元素:2001:0db8:85a3:0000:0000:8a2e:0370:7334
。
答案 2 :(得分:0)
是的,res
指向代表主机的不同IP地址的addrinfo
结构的链接列表。 MSDN documentation on the getaddrinfo function非常好。我不知道你在运行什么平台,但在其他平台上应该没有太大的不同。