“网络编程”示例中的代码如何工作?

时间:2011-02-18 13:36:42

标签: c sockets network-programming

我正在阅读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是什么有一个概括,但结果是*resstruct *pvoid *addr和{{ 1}}。

3 个答案:

答案 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_ntopipstr)和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非常好。我不知道你在运行什么平台,但在其他平台上应该没有太大的不同。