socket网络信息查询API 学习《Linux高性能服务器编程》第五章Linux网络编程基础API,为了印象深刻一些,多动手多实践,所以记下这个笔记。这一篇主要记录Linux中socket网络信息查询API,包括gethostbyname和gethostbyaddr、getservbyname和getservbyport、getaddrinfo、getnameinfo。
socket当中两要素:IP和端口号,都是用数值表示的。但是有时候我们可以使用主机名代替IP,使用服务名代替端口号。
1 2 telnet 127.0.0.1 80 telnet localhost www
这个功能就是使用网络信息API实现的。
gethostbyname和gethostbyaddr gethostbyname
函数根据主机名称获取主机的完整信息,gethostbyaddr
函数根据IP地址获取主机的完整信息。gethostbyname
函数通常先在本地的/etc/hosts
配置文件中查找主机,如果没有找到,再去访问DNS服务器。这两个函数的定义如下:
1 2 3 4 5 6 7 #include <netdb.h> extern int h_errno;struct hostent *gethostbyname (const char *name) ;#include <sys/socket.h> struct hostent *gethostbyaddr (const void *addr, socklen_t len, int type) ;
name
参数表示目标主机的主机名。
addr
参数指定目标主机的IP地址,len
参数指定addr
的所指定IP的长度
type
参数指定IP地址的类型,比如AF_INET
等
其中hostent
定义如下:
1 2 3 4 5 6 7 8 9 #include <netdb.h> struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; }
参数介绍
h_name
:主机名h_aliases
:主机别名列表,可能有多个h_addrtype
:地址类型(地址族)h_length
:地址长度h_addr_list
:按网络字节序列出的主机IP地址列表
从网上找了个图显示了一下
gethostbyname
举例
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 #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> int main (int argc, char **argv) { if (argc != 2 ) { printf ("Use example: %s www.baidu.com\n" , *argv); return -1 ; } char *name = argv[1 ]; struct hostent *hptr ; hptr = gethostbyname(name); if (hptr == NULL ) { printf ("gethostbyname error for host: %s: %s\n" , name, hstrerror(h_errno)); return -1 ; } printf ("\tofficial: %s\n" , hptr->h_name); char **pptr; char str[INET_ADDRSTRLEN]; for (pptr = hptr->h_aliases; *pptr != NULL ; pptr++) { printf ("\talias: %s\n" , *pptr); } switch (hptr->h_addrtype) { case AF_INET: pptr = hptr->h_addr_list; for (; *pptr != NULL ; pptr++) { printf ("\taddress: %s\n" , inet_ntop(hptr->h_addrtype, *pptr, str, sizeof (str))); } break ; default : printf ("unknown address type\n" ); break ; } return 0 ; }
gethostbyaddr
举例
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 #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> int main (int argc, char **argv) { if (argc != 2 ) { printf ("Use example: %s 127.0.0.1\n" , *argv); return -1 ; } char *ip = argv[1 ]; struct in_addr addr ; inet_pton(AF_INET, ip, &addr); struct hostent *hptr ; hptr = gethostbyaddr(&addr, sizeof (addr), AF_INET); if (hptr == NULL ) { printf ("gethostbyaddr error for host: %s: %s\n" , ip, hstrerror(h_errno)); return -1 ; } printf ("\tofficial: %s\n" , hptr->h_name); char **pptr; char str[INET_ADDRSTRLEN]; for (pptr = hptr->h_aliases; *pptr != NULL ; pptr++) { printf ("\talias: %s\n" , *pptr); } switch (hptr->h_addrtype) { case AF_INET: pptr = hptr->h_addr_list; for (; *pptr != NULL ; pptr++) { printf ("\taddress: %s\n" , inet_ntop(hptr->h_addrtype, *pptr, str, sizeof (str))); } break ; default : printf ("unknown address type\n" ); break ; } return 0 ; }
getservbyname和getservbyport getservbyname
函数根据名称获取某个服务的完整信息,getservbyport
函数根据端口号获取某个服务的完整信息。它们实际上都是通过读取/etc/services
文件来获取服务的信息的。这两个函数的定义如下:
1 2 3 4 #include <netdb.h> struct servent *getservbyname (const char *name, const char *proto) ;struct servent *getservbyport (int port, const char *proto) ;
name
参数指定目标服务的名字。
port
参数指定目标服务对应的端口号。
proto
参数指定服务类型,给它传递“tcp”表示获取流服务,给它传递“udp”表示获取数据报服务,给它传递NULL则表示获取所有类型的服务。
函数返回的servent
的定义如下:
1 2 3 4 5 6 7 #include <netdb.h> struct servent { char *s_name; char **s_aliases; int s_port; char *s_proto; }
s_name
:服务名称
s_aliases
:服务别名列表,可能有多个
s_port
:端口号
s_proto
:服务类型,通常是tcp或者udp
getservbyname
举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <assert.h> int main (int argc, char const *argv[]) { struct servent *servinfo = getservbyname("ssh" , "tcp" ); assert(servinfo); printf ("name is %s\n" , servinfo->s_name); char **pptr; for (pptr = servinfo->s_aliases; *pptr != NULL ; pptr++) { printf ("alias: %s\n" , *pptr); } printf ("port is %d\n" , ntohs(servinfo->s_port)); printf ("protocol is %s\n" , servinfo->s_proto); return 0 ; }
getservbyport
举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <assert.h> int main (int argc, char const *argv[]) { int port = 80 ; struct servent *servinfo = getservbyport(htons(port), "tcp" ); assert(servinfo); printf ("name is %s\n" , servinfo->s_name); char **pptr; for (pptr = servinfo->s_aliases; *pptr != NULL ; pptr++) { printf ("alias: %s\n" , *pptr); } printf ("port is %d\n" , ntohs(servinfo->s_port)); printf ("protocol is %s\n" , servinfo->s_proto); return 0 ; }
需要指出的是,上面讨论的4个函数都是不可重入的,即非线程安全的。不过netdb.h
头文件给出了它们的可重入版本。正如Linux下所有其他函数的可重入版本的命名规则那样,这些函数的函数名是在原函数名尾部加上_r (re-entrant)
。
getaddrinfo getaddrinfo
函数既能通过主机名获得IP地址(内部使用的是gethostbyname
函数),也能通过服务名获得端口号(内部使用的是getservbyname
函数)。它是否可重人取决于其内部调用的gethostbyname
和getservbyname
函数是否是它们的可重入版本。该函数的定义如下:
1 2 3 4 5 #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) ;
node
参数可以接收主机名,也可以接收字符串表示的IP地址,用点分十分制。
service
参数可以接收服务名,也可以接收字符串表示的十进制端口。
hints
参数是给getaddrinfo
的一个提示,以对getaddrinfo
的输出进行更精确的控制。hints
参数可以设置为NULL,表示允许getaddrinfo
反馈任何可用的结果。
res
参数返回一个链表,这个链表用于存储getaddrinfo
反馈的结果。
除此之外,在我们调用完getaddrinfo
之后,需要使用freeaddrinfo
对res进行内存释放。
1 2 3 4 #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> void freeaddrinfo (struct addrinfo *res) ;
addrinfo
的定义如下
1 2 3 4 5 6 7 8 9 10 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 ; };
ai_family
:地址族,比如:AF_INET
ai_socktype
:服务类型,比如:SOCK_STREAM
ai_protocol
:指具体的网络协议
ai_addrlen
:地址ai_addr
的长度
ai_addr
:指向socket的地址
ai_canonname
:主机的别名
ai_next
:链表的下一个对象
ai_flags
可以取下表中标志
当我们使用hints参数的时候,可以设置其ai_flags
,ai_family
,ai_socktype
和ai_protocol
四个字段,其他字段则必须被设置为NULL。
根据主机名获取IP地址:
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 #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <assert.h> int main (int argc, char **argv) { if (argc != 2 ) { printf ("Use example: %s www.baidu.com\n" , *argv); return -1 ; } char *name = argv[1 ]; struct addrinfo hints ; struct addrinfo *res , *cur ; int ret; struct sockaddr_in *addr ; char ipbuf[16 ]; memset (&hints, 0 , sizeof (struct addrinfo)); hints.ai_family = AF_INET; hints.ai_flags = AI_PASSIVE; hints.ai_protocol = 0 ; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(name, NULL , &hints, &res); assert(ret >= 0 ); for (cur = res; cur != NULL ; cur = cur->ai_next) { addr = (struct sockaddr_in *)cur->ai_addr; printf ("ip: %s\n" , inet_ntop(AF_INET, &addr->sin_addr, ipbuf, cur->ai_addrlen)); printf ("alias: %s\n" , cur->ai_canonname); } freeaddrinfo(res); return 0 ; }
不过不知道为啥别名为null
根据主机名和端口号获取地址信息:
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 #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <assert.h> int main (int argc, char **argv) { if (argc != 2 ) { printf ("Use example: %s 80\n" , *argv); return -1 ; } char *port = argv[1 ]; char *hostname = "localhost" ; struct addrinfo hints ; struct addrinfo *res , *cur ; int ret; struct sockaddr_in *addr ; char ipbuf[16 ]; memset (&hints, 0 , sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE; hints.ai_protocol = 0 ; hints.ai_socktype = 0 ; ret = getaddrinfo(hostname, port, &hints, &res); assert(ret >= 0 ); for (cur = res; cur != NULL ; cur = cur->ai_next) { addr = (struct sockaddr_in *)cur->ai_addr; printf ("ip: %s\n" , inet_ntop(AF_INET, &addr->sin_addr, ipbuf, cur->ai_addrlen)); printf ("port: %d\n" , ntohs(addr->sin_port)); printf ("alias: %s\n" , cur->ai_canonname); } freeaddrinfo(res); return 0 ; }
不过不知道为啥别名为null
getnameinfo getnameinfo
函数能通过socket地址同时获得以字符串表示的主机名 (内部使用的是gethostbyaddr
函数)和服务名 (内部使用的是getservbyport
函数)。它是否可重入取决于其内部调用的gethostbyaddr和 getservbyport函数是否是它们的可重入版本。该函数的定义如下:
1 2 3 4 5 #include <sys/socket.h> #include <netdb.h> int getnameinfo (const struct sockaddr *addr, socklen_t addrlen,char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) ;
getnameinfo
将返回的主机名存储在host参数指向的缓存中,将服务名存储在serv参数指向的缓存中,hostlen
和 servlen
参数分别指定这两块缓存的长度。flags参数控制getnameinfo
的行为,它可以接收下表中的选项。
getaddrinfo
和 getnameinfo
函数成功时返回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 #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <assert.h> int main (int argc, char **argv) { if (argc != 3 ) { printf ("Use example: %s 127.0.0.1 80\n" , *argv); return -1 ; } char *ip = argv[1 ]; int port = atoi(argv[2 ]); char hostname[128 ] = {0 }; char servername[128 ] = {0 }; struct sockaddr_in addr_dst ; memset (&addr_dst, 0 , sizeof (addr_dst)); addr_dst.sin_family = AF_INET; addr_dst.sin_addr.s_addr = inet_addr(ip); addr_dst.sin_port = htons(port); int ret = getnameinfo((struct sockaddr *)&addr_dst, sizeof (addr_dst), hostname, sizeof (hostname), servername, sizeof (servername), 0 ); assert(ret == 0 ); printf ("hostname IP: %s \n" , hostname); printf ("servername : %s \n" , servername); return 0 ; }