Linux 网络编程 gethostbyname, getaddrinfo,IPv4 与 IPv6 tcp server

Part 01 gethostbyname

1. 各种情况下的示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

extern int h_errno;

int main()
{
    char* hosts[] = {
        // 百度
        "www.baidu.com",
        // localhost
        "localhost",
        // 本机局域网ip地址
        "192.168.0.101",
        // 回环
        "127.0.0.1",
        // 本机 hostname 返回值
        "frodo-K45DR",
        NULL
    };
    
    int j = 0;
    for (; ; j++)
    {
        char* host = hosts[j];
        if (host == NULL)
        {
            break;
        }
        printf("-------------------------------------------\n"
            "hostname: %s\n", host);
        struct hostent * h = gethostbyname(host);
        if (h == NULL)
        {
            printf("Error: %s\n", hstrerror(h_errno));
            return 0;
        }
        printf("h_name: %s, type: %s\n", h->h_name, h->h_addrtype == AF_INET ? "IPv4" : "IPv6");
        int i = 0;
        char str[32] = {};
        for (i = 0; ; i++)
        {
            if (h->h_addr_list[i] == NULL)
            {
                break;
            }
            printf("IP: %s\n", inet_ntop(h->h_addrtype, h->h_addr_list[i], str, 31));
        }
    }

    return 0;
}

结果:

------------------------------------------
hostname: www.baidu.com
h_name: www.a.shifen.com, type: IPv4
IP: 14.215.177.39
IP: 14.215.177.38
-------------------------------------------
hostname: localhost
h_name: localhost, type: IPv4
IP: 127.0.0.1
-------------------------------------------
hostname: 192.168.0.101
h_name: 192.168.0.101, type: IPv4
IP: 192.168.0.101
-------------------------------------------
hostname: 127.0.0.1
h_name: 127.0.0.1, type: IPv4
IP: 127.0.0.1
-------------------------------------------
hostname: frodo-K45DR
h_name: frodo-K45DR, type: IPv4
IP: 127.0.1.1

2. gethostname底层到底做了什么?

精简一下示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

extern int h_errno;

int main()
{
    char* hosts[] = {
        // 百度
        "www.baidu.com",
#if 0
        // localhost
        "localhost",
        // 本机局域网ip地址
        "192.168.0.101",
        // 回环
        "127.0.0.1",
        // 本机 hostname 返回值
        "frodo-K45DR",
#endif
        NULL
    };
    
    int j = 0;
    for (; ; j++)
    {
        char* host = hosts[j];
        if (host == NULL)
        {
            break;
        }
        #if 0
        printf("-------------------------------------------\n"
            "hostname: %s\n", host);
        #endif
        struct hostent * h = gethostbyname(host);
        if (h == NULL)
        {
            printf("Error: %s\n", hstrerror(h_errno));
            return 0;
        }
        #if 0
        printf("h_name: %s, type: %s\n", h->h_name, h->h_addrtype == AF_INET ? "IPv4" : "IPv6");
        int i = 0;
        char str[32] = {};
        for (i = 0; ; i++)
        {
            if (h->h_addr_list[i] == NULL)
            {
                break;
            }
            printf("IP: %s\n", inet_ntop(h->h_addrtype, h->h_addr_list[i], str, 31));
        }
        #endif
    }

    return 0;
}

j精简后的代码,只是查看 hostname 为 www.baidu.com 的情况,并且,只是一次简单的调用,以及错误判断,其他都不处理。

我们采用 strace 工具查看其都做了什么?

执行命令:

strace  -o  gethostbyname.txt  ./gethostbyname_demo

如下:

execve("./gethostbyname_demo", ["./gethostbyname_demo"], 0x7ffc928b93c0 /* 36 vars */) = 0
brk(NULL)                               = 0x5569e7a84000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=115268, ...}) = 0
mmap(NULL, 115268, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f638ed50000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f638ed77000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f638e750000
mprotect(0x7f638e937000, 2097152, PROT_NONE) = 0
mmap(0x7f638eb37000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f638eb37000
mmap(0x7f638eb3d000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f638eb3d000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f638ed784c0) = 0
mprotect(0x7f638eb37000, 16384, PROT_READ) = 0
mprotect(0x5569e5b22000, 4096, PROT_READ) = 0
mprotect(0x7f638ed6f000, 4096, PROT_READ) = 0
munmap(0x7f638ed50000, 115268)          = 0
brk(NULL)                               = 0x5569e7a84000
brk(0x5569e7aa5000)                     = 0x5569e7aa5000
stat("/etc/resolv.conf", {st_mode=S_IFREG|0644, st_size=304, ...}) = 0
openat(AT_FDCWD, "/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=92, ...}) = 0
read(3, "# The \"order\" line is only used "..., 4096) = 92
read(3, "", 4096)                       = 0
close(3)                                = 0
openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=304, ...}) = 0
read(3, "# Dynamic resolv.conf(5) file fo"..., 4096) = 304
read(3, "", 4096)                       = 0
close(3)                                = 0
uname({sysname="Linux", nodename="frodo-K45DR", ...}) = 0
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3)                                = 0
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3)                                = 0
openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=556, ...}) = 0
read(3, "# /etc/nsswitch.conf\n#\n# Example"..., 4096) = 556
read(3, "", 4096)                       = 0
close(3)                                = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=115268, ...}) = 0
mmap(NULL, 115268, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f638ed50000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P#\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=47568, ...}) = 0
mmap(NULL, 2168632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f638e538000
mprotect(0x7f638e543000, 2093056, PROT_NONE) = 0
mmap(0x7f638e742000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa000) = 0x7f638e742000
mmap(0x7f638e744000, 22328, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f638e744000
close(3)                                = 0
mprotect(0x7f638e742000, 4096, PROT_READ) = 0
munmap(0x7f638ed50000, 115268)          = 0
openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=226, ...}) = 0
read(3, "127.0.0.1\tlocalhost\n127.0.1.1\tfr"..., 4096) = 226
read(3, "", 4096)                       = 0
close(3)                                = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=115268, ...}) = 0
mmap(NULL, 115268, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f638ed50000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_mdns4_minimal.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\v\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=10160, ...}) = 0
mmap(NULL, 2105360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f638e330000
mprotect(0x7f638e332000, 2093056, PROT_NONE) = 0
mmap(0x7f638e531000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7f638e531000
close(3)                                = 0
mprotect(0x7f638e531000, 4096, PROT_READ) = 0
munmap(0x7f638ed50000, 115268)          = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=115268, ...}) = 0
mmap(NULL, 115268, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f638ed50000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\17\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=26936, ...}) = 0
mmap(NULL, 2121952, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f638e128000
mprotect(0x7f638e12d000, 2097152, PROT_NONE) = 0
mmap(0x7f638e32d000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5000) = 0x7f638e32d000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\00008\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=101168, ...}) = 0
mmap(NULL, 2206336, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f638df08000
mprotect(0x7f638df1f000, 2097152, PROT_NONE) = 0
mmap(0x7f638e11f000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7f638e11f000
mmap(0x7f638e121000, 6784, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f638e121000
close(3)                                = 0
mprotect(0x7f638e11f000, 4096, PROT_READ) = 0
mprotect(0x7f638e32d000, 4096, PROT_READ) = 0
munmap(0x7f638ed50000, 115268)          = 0
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0

poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
sendto(3, "y\370\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
poll([{fd=3, events=POLLIN}], 1, 5000)  = 1 ([{fd=3, revents=POLLIN}])
ioctl(3, FIONREAD, [90])                = 0
recvfrom(3, "y\370\201\200\0\1\0\3\0\0\0\0\3www\5baidu\3com\0\0\1\0\1\300"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [28->16]) = 90
close(3)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

gethostbyname 函数,底层打开检索了一堆文件,/etc/host, /etc/host.conf 等等,检索不到,最终打开 /etc/resov.conf, 找到 nameserver 127.0.0.53,启socket 发送UDP数据包到该 ip的53端口,进行检索。

Part 02 getaddrinfo

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

int main()
{
    char* hosts[] =
    {
        "www.baidu.com",
#if 0
        "0.0.0.0",
        "192.168.0.101",
        "127.0.0.1"
#endif
        NULL
    };
    int rv = -1;
    char * _port = NULL;

    int i = 0;
    for (; hosts[i] != NULL; i++)
    {
        char* host = hosts[i];
        struct addrinfo hints, *serverinfo, *p;

        memset(&hints, 0, sizeof(hints));

        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;
        rv = getaddrinfo(host, _port, &hints, &serverinfo);
        if (rv != 0)
        {
            printf("error: %s\n", gai_strerror(rv));
            return 0;
        }
        freeaddrinfo(serverinfo);
    }
    return 0;
}

可以采用同样的方式进行strace跟踪,此处略。

重点介绍一下该两个函数的区别

1. gethostbyname 只支持ipv4,不支持ipv6,Linux网络编程中,示例给出了对 addrtype 的判断,但是实际AF_INET6分支根本走不进去,直接回返回错误。hstrerror 返回的错误为“Unknown host”, 但是 getaddrinfo 既支持IPv4也支持IPv6.

2. getaddrinfo 可以获取更加精确完整的 sockaddr 结构。

这两个函数,编程层面,可以帮助我们获取一些域名下的ip地址。对于linux后端server端编程,getaddrinfo函数可以方便的启动IPv4或者IPv6函数的server端。

本机ip作为hostname,指定端口启动TCPserver 端示例如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>


int main()
{
    char* hosts[] =
    {
        "0.0.0.0",
#if 0   // IPv6
        "00::00",
#endif
        NULL
    };
    int s = -1, rv = -1;

    int port = 8888;
    char _port[6] = {};
    snprintf(_port, 5, "%d", port);

    int i = 0;
    struct sockaddr_in * sa = NULL;
    struct sockaddr_in6 * sa6 = NULL;
    for (; hosts[i] != NULL; i++)
    {
        char* host = hosts[i];
        struct addrinfo hints, *serverinfo, *p;

        memset(&hints, 0, sizeof(hints));
        // hints.ai_family = AF_INET;
        // hints.ai_family = AF_INET6;
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        // hints.ai_socktype = SOCK_DGRAM;
        hints.ai_flags = AI_PASSIVE;
        // hints.ai_flags = AI_NUMERICHOST;

        rv = getaddrinfo(host, _port, &hints, &serverinfo);
        if (rv != 0)
        {
            printf("error: %s\n", gai_strerror(rv));
            return 0;
        }
        for (p = serverinfo; p != NULL; p = p->ai_next)
        {
            char ipbuf[46] = {};
            size_t ipbuf_len = 46;
            if (p->ai_family == AF_INET)
            {
                sa = (struct sockaddr_in*)(p->ai_addr);
                inet_ntop(AF_INET, &(sa->sin_addr), ipbuf, ipbuf_len);
            }
            else
            {
                sa6 = (struct sockaddr_in6*)p->ai_addr;
                inet_ntop(AF_INET6, &(sa6->sin6_addr), ipbuf, ipbuf_len);
            }
            printf("host: %s, family: %s, type: %s, ip: %s\n",
                host,
                p->ai_family == AF_INET ? "AF_INET" : "AF_INET6",
                p->ai_socktype == SOCK_STREAM ? "SOCK_STREAM" : "SOCK_DGRAM",
                ipbuf);
            
            // 
            int sockfd = socket(p->ai_family, p->ai_socktype, 0);
            if (sockfd == -1)
            {
                perror("socket");
                return 0;
            }
            int ret = bind(sockfd, (struct sockaddr*)p->ai_addr, p->ai_addrlen);
            if (ret == -1)
            {
                perror("bind");
                return 0;
            }
            ret = listen(sockfd, 128);
            if (ret == -1)
            {
                perror("listen");
                return 0;
            }
            // 兼容 IPv4 与 IPv6 的地址存储结构
            struct sockaddr_storage addr;
            socklen_t len = sizeof(addr);
            int cfd = -1;
            char bf[1024];
            while (1)
            {
                cfd = accept(sockfd, (struct sockaddr*)&addr, &len);
                if (cfd == -1)
                {
                    perror("accept");
                    continue;
                }
                while (1)
                {
                    ret = read(cfd, bf, sizeof(bf) - 1);
                    if (ret == -1)
                    {
                        perror("read");
                        break;
                    }
                    else if (ret == 0)
                    {
                        close(cfd);
                        printf("client closed\n");
                        break;
                    }
                    else if (ret >= sizeof(bf)-1)
                    {
                        bf[sizeof(bf)-1] = 0;
                    }
                    else
                    {
                        bf[ret] = 0;
                    }
                    printf("message from client: %s\n", bf);
                    ret = write(cfd,"hello", 5);
                    if (ret == -1)
                    {
                        perror("write");
                        break;;
                    }
                }
                close(cfd);
            }
        }

        freeaddrinfo(serverinfo);
    }

    return 0;
}


getaddrinfo 函数可以根据给定的ip地址,自动的去启动 IPv4 或者 IPv6协议的tcp server端。

当然,经过试验,无论启动的是IPv4或者是IPv6的监听8888端口的TCP server端,IPv4的tcp client 端都是可以连接进行通信的

当 host 为 0.0.0.0 时,启动之后的 netstat 查看结果如下:

当 host 为 00::00 时,启动之后 netstat 查看结果如下:

tcp/udp server的启动方式,更多情况下,采用的是这种方式,准备地址的过程,采用 getaddrinfo 进行准备。

如果随便给一个ip地址,getaddrinfo 会报错吗?

当给定的hostname是ip地址格式,并且格式没有问题,getaddrinfo并不会报错。

当给定的hostname是无效的,getaddrinfo就会报错,其实 getaddrinfo 并不能起到真正有效的验证ip地址的作用。

这种方式,并不是完全的兼容IPv4与IPv6,毕竟 V4与 V6的sockopt 的选项略有不同,有些时候是需要进行 addrtype 是AF_INET 或者 AF_INET6进行判断的。

兼容格式的 struct sockaddr_storage 怎么处理?

参见如下示例即可。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值