0%

socket网络信息查询API

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> /* for AF_INET */
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; /* 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 */
}

参数介绍

h_name:主机名
h_aliases:主机别名列表,可能有多个
h_addrtype:地址类型(地址族)
h_length:地址长度
h_addr_list:按网络字节序列出的主机IP地址列表

从网上找了个图显示了一下

img

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);
}

//输出ip地址
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;
}

image-20220814105118617

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);
}

//输出ip地址
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;
}

image-20220814105222471

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; /* official service name */
char **s_aliases; /* alias list */
int s_port; /* port number */
char *s_proto; /* protocol to use */
}

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;
}

image-20220814141312451

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;
}

image-20220814142922859

需要指出的是,上面讨论的4个函数都是不可重入的,即非线程安全的。不过netdb.h头文件给出了它们的可重入版本。正如Linux下所有其他函数的可重入版本的命名规则那样,这些函数的函数名是在原函数名尾部加上_r (re-entrant)

getaddrinfo

getaddrinfo函数既能通过主机名获得IP地址(内部使用的是gethostbyname函数),也能通过服务名获得端口号(内部使用的是getservbyname函数)。它是否可重人取决于其内部调用的gethostbynamegetservbyname函数是否是它们的可重入版本。该函数的定义如下:

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可以取下表中标志

image-20220814160248251

当我们使用hints参数的时候,可以设置其ai_flagsai_familyai_socktypeai_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; /* Allow IPv4 */
hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
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;
}

image-20220814165923059

不过不知道为啥别名为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; /* Allow IPv4 */
hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
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;
}

image-20220814170106763

不过不知道为啥别名为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参数指向的缓存中,hostlenservlen参数分别指定这两块缓存的长度。flags参数控制getnameinfo的行为,它可以接收下表中的选项。

image-20220814171842523

getaddrinfogetnameinfo函数成功时返回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;
}

image-20220814181037933