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 怎么处理?
参见如下示例即可。