CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章高频C++11重难点知识解析

  • 第2章Linux GDB高级调试指南

  • 第3章C++多线程编程从入门到进阶

  • 第4章C++网络编程重难点解析

    • 4.1 学习网络编程,你应该掌握哪些 socket 函数
    • 4.2 TCP 网络通信的基本流程
    • 4.3 设计跨平台网络通信库时需要注意的一些 socket 函数用法
    • 4.4 bind 函数重难点分析
    • 4.5 select 函数用法和原理
    • 4.6 socket 的阻塞模式和非阻塞模式
    • 4.7 发送 0 字节的数据是什么效果?
    • 4.8 connect 函数在阻塞和非阻塞模式下的行为
    • 4.9 连接时顺便接收第一组数据
    • 4.10 如何获取当前 socket 对应的接收缓冲区中有多少数据可读
    • 4.11 Linux EINTR 错误码
    • 4.12 Linux SIGPIPE 信号
    • 4.13 Linux poll 函数用法
    • 4.14 Linux epoll 模型
    • 4.15 高效的 readv 和 writev 函数
    • 4.16 主机字节序和网络字节序
    • 4.17 域名解析 API 介绍
  • 第5章网络通信故障排查常用命令

  • 第6章高性能网络通信协议设计精要

  • 第7章高性能服务结构设计

  • 第8章Redis 网络通信模块源码分析

  • 第9章后端服务重要模块设计探索

  • C++后端开发进阶
  • 第4章C++网络编程重难点解析
zhangxf
2023-04-05

4.17 域名解析 API 介绍

为了便于记忆,有时候我们需要我们的程序可以使用域名和端口号去连接服务,这种情况下,我们需要使用 socket API gethostbyname 函数先把域名转换成 ip 地址,再使用 connect 函数连接。在 Linux 系统上, gethostbyname 函数的签名如下:

#include <netdb.h>
       
struct hostent* gethostbyname(const char* name);
1
2
3

域名转换成 ip 时,转换结果存在一个 hostent 结构体中。转换成功后的 ip 地址存放在 hostent 最后一个字段中,hostent 结构体类型定义如下:

struct hostent 
{
    char*  h_name;            /* official name of host */
    char** h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char** h_addr_list;       /* list of addresses */
}

#define h_addr h_addr_list[0] /* for backward compatibility */
1
2
3
4
5
6
7
8
9
10
  • 字段 h_name: 地址的正式名称;
  • 字段 h_aliases: 地址的预备名称指针;
  • 字段 h_addrtype: 地址类型,通常是AF_INET;
  • 字段 h_length: 地址的长度,以字节数目为计量单位;
  • 字段 h_addr_list:主机网络地址指针,网络字节顺序。 其中,h_addr 是字段 h_addr_list 中的第一地址。

注意:虽然 h_addr_list[0] 看起来是一个 char* 类型,但实际上是一个 uint32_t,这是 ip 地址的 32 bit 整数表示形式,如果需要转换成十进制点分法字符串再调用 inet_ntoa() 函数即可。

我们来看一段示例代码:

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

//extern int h_errno;

bool connect_to_server(const char* server, short port)
{
    int hSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (hSocket == -1)
        return false;

    struct sockaddr_in addrSrv = { 0 };
    struct hostent* pHostent = NULL;
    //unsigned int addr = 0;

    //如果传入的参数 server 的值是 somesite.com 这种域名域名形式则 if 条件成立,
	//接着调用 gethostbyname 解析域名为 4 字节的 ip 地址(整型)
	if (addrSrv.sin_addr.s_addr = inet_addr(server) == INADDR_NONE)
    {       
		pHostent = gethostbyname(server);
        if (pHostent == NULL)      
            return false;
        
        //当使用 gethostbyname 解析域名时可能会得到多个 ip 地址,一般最常用的使用第一个 ip 地址
        addrSrv.sin_addr.s_addr = *((unsigned long*)pHostent->h_addr_list[0]);
    }

    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port);
    int ret = connect(hSocket, (struct sockaddr*)&addrSrv, sizeof(addrSrv));
    if (ret == -1)
        return false;

    return true;
}

int main()
{
	if (connect_to_server("baidu.com", 80))
		printf("connect successfully.\n");
	else
		printf("connect error.\n");
	
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

上述 connect_to_server 函数既可以支持直接传入域名,也可以传入 ip 地址:

connect_to_server("127.0.0.1", 8888);
connect_to_server("localhost", 8888);

connect_to_server("61.135.169.125", 80);
connect_to_server("baidu.com", 80);
1
2
3
4
5

实际在使用 gethostbyname 函数时需要注意以下:

  • gethostbyname 函数是不可重入函数,在 Linux 下建议使用 gethostbyname_r 函数替代;

  • gethostbyname 在解析域名时,会阻塞当前执行线程的,直到得到返回结果;

  • 在使用 gethostbyname 函数出错时,你不能使用 errno 获取错误码信息(因此也不能使用 perror() 函数打印错误信息),你应该使用 h_errno 错误码(也可以调用 herror() 打印错误信息),herror() 函数签名如下:

    void herror(const char *s);
    
    1

在新的 Linux 系统中,gethostbyname 和 gethostbyaddr 一样,已经被标记为废弃的,你应该使用新的函数 getaddrinfo 去替代它们,getaddrinfo 签名如下:

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char* node, 
                const char* service, 
                const struct addrinfo* hints, 
                struct addrinfo** res);
1
2
3
4
5
6
7
8

getaddrinfo 函数调用成功返回 0,失败返回非 0 值,调用成功后结果存储在参数 res 中。addrinfo 结构体定义如下:

struct addrinfo
{
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr* ai_addr;
    char*		     ai_canonname;
    struct addrinfo* ai_next;
};
1
2
3
4
5
6
7
8
9
10
11

如果你不再需要 res 这个变量,记得使用 freeaddrinfo 函数将其指向的资源释放:

void freeaddrinfo(struct addrinfo* res);
1

getaddrinfo 使用示例如下:

struct addrinfo hints = {0}; 
hints.ai_flags = AI_CANONNAME;
hints.ai_family = family;
hints.ai_socktype = socktype;

struct addrinfo* res;
int n = getaddrinfo(host, service, &hints, &res);
if(n == 0)
{
    //调用成功,使用 res
    
    //释放 res 资源
    freeaddr(res);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

getaddrinfo 函数不仅支持 ipv4,同时也支持 ipv6 的解析,redis-server 的源码中就使用了这个函数去解析 ipv4 和 ipv6 的地址(位于 net.c 文件中):

static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
                                   const struct timeval *timeout,
                                   const char *source_addr) {
    int s, rv, n;
    char _port[6];  /* strlen("65535"); */
    struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
    int blocking = (c->flags & REDIS_BLOCK);
    int reuseaddr = (c->flags & REDIS_REUSEADDR);
    int reuses = 0;
    long timeout_msec = -1;

    servinfo = NULL;
    c->connection_type = REDIS_CONN_TCP;
    c->tcp.port = port;

    /* We need to take possession of the passed parameters
     * to make them reusable for a reconnect.
     * We also carefully check we don't free data we already own,
     * as in the case of the reconnect method.
     *
     * This is a bit ugly, but atleast it works and doesn't leak memory.
     **/
    if (c->tcp.host != addr) {
        if (c->tcp.host)
            free(c->tcp.host);

        c->tcp.host = strdup(addr);
    }

    if (timeout) {
        if (c->timeout != timeout) {
            if (c->timeout == NULL)
                c->timeout = malloc(sizeof(struct timeval));

            memcpy(c->timeout, timeout, sizeof(struct timeval));
        }
    } else {
        if (c->timeout)
            free(c->timeout);
        c->timeout = NULL;
    }

    if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
        __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
        goto error;
    }

    if (source_addr == NULL) {
        free(c->tcp.source_addr);
        c->tcp.source_addr = NULL;
    } else if (c->tcp.source_addr != source_addr) {
        free(c->tcp.source_addr);
        c->tcp.source_addr = strdup(source_addr);
    }

    snprintf(_port, 6, "%d", port);
    memset(&hints,0,sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    /* Try with IPv6 if no IPv4 address was found. We do it in this order since
     * in a Redis client you can't afford to test if you have IPv6 connectivity
     * as this would add latency to every connect. Otherwise a more sensible
     * route could be: Use IPv6 if both addresses are available and there is IPv6
     * connectivity. */
    if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
         hints.ai_family = AF_INET6;
         if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
            __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
            return REDIS_ERR;
        }
    }
    for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry:
        if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
            continue;

        c->fd = s;
        if (redisSetBlocking(c,0) != REDIS_OK)
            goto error;
        if (c->tcp.source_addr) {
            int bound = 0;
            /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
            if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
                char buf[128];
                snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
                __redisSetError(c,REDIS_ERR_OTHER,buf);
                goto error;
            }

            if (reuseaddr) {
                n = 1;
                if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
                               sizeof(n)) < 0) {
                    goto error;
                }
            }

            for (b = bservinfo; b != NULL; b = b->ai_next) {
                if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
                    bound = 1;
                    break;
                }
            }
            freeaddrinfo(bservinfo);
            if (!bound) {
                char buf[128];
                snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
                __redisSetError(c,REDIS_ERR_OTHER,buf);
                goto error;
            }
        }
        if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
            if (errno == EHOSTUNREACH) {
                redisContextCloseFd(c);
                continue;
            } else if (errno == EINPROGRESS && !blocking) {
                /* This is ok. */
            } else if (errno == EADDRNOTAVAIL && reuseaddr) {
                if (++reuses >= REDIS_CONNECT_RETRIES) {
                    goto error;
                } else {
                    redisContextCloseFd(c);
                    goto addrretry;
                }
            } else {
                if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
                    goto error;
            }
        }
        if (blocking && redisSetBlocking(c,1) != REDIS_OK)
            goto error;
        if (redisSetTcpNoDelay(c) != REDIS_OK)
            goto error;

        c->flags |= REDIS_CONNECTED;
        rv = REDIS_OK;
        goto end;
    }
    if (p == NULL) {
        char buf[128];
        snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
        __redisSetError(c,REDIS_ERR_OTHER,buf);
        goto error;
    }

error:
    rv = REDIS_ERR;
end:
    freeaddrinfo(servinfo);
    return rv;  // Need to return REDIS_OK if alright
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

本节完。

上次更新: 2025/04/01, 20:53:14
4.16 主机字节序和网络字节序
5.1 ifconfig

← 4.16 主机字节序和网络字节序 5.1 ifconfig→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式