socket
库来
获取
域名的
端口信息。
在C语言中获取域名对应的端口,通常需要结合网络编程相关的函数和库来实现,以下是几种常见的方法:
1、使用getaddrinfo函数
原理:getaddrinfo
函数可以根据给定的域名(或IP地址)和服务(如http、https等),解析出相应的IP地址和端口号等信息,它通过填充一个struct addrinfo
结构体链表来返回结果,其中包含了各种可能的地址信息组合,包括IPv4、IPv6地址以及对应的端口号等。
示例代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> int main() { struct addrinfo hints, res; int status; char ipstr[INET6_ADDRSTRLEN]; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // 允许IPv4或IPv6 hints.ai_socktype = SOCK_STREAM; // 流式套接字 if ((status = getaddrinfo("www.example.com", "http", &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s ", gai_strerror(status)); return 2; } // 遍历所有返回的结果,打印出每个地址的IP和端口信息 for (struct addrinfo p = res; p != NULL; p = p->ai_next) { void addr; char ipver; // 获取指针指向正确的字段,根据地址类型选择IPv4或IPv6 if (p->ai_family == AF_INET) { // IPv4 struct sockaddr_in ipv4 = (struct sockaddr_in )p->ai_addr; addr = &(ipv4->sin_addr); ipver = "IPv4"; } else { // IPv6 struct sockaddr_in6 ipv6 = (struct sockaddr_in6 )p->ai_addr; addr = &(ipv6->sin6_addr); ipver = "IPv6"; } // 将IP转换为字符串并输出 inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); printf("%s: %s ", ipver, ipstr); // 如果是IPv4,可以直接访问端口号 if (p->ai_family == AF_INET) { struct sockaddr_in ipv4 = (struct sockaddr_in )p->ai_addr; printf("Port: %d ", ntohs(ipv4->sin_port)); } } freeaddrinfo(res); // 释放内存 return 0; }
解释:上述代码中,首先定义了hints
结构体来指定一些基本的参数,如地址族(允许IPv4或IPv6)和套接字类型(流式套接字),然后调用getaddrinfo
函数,传入要查询的域名“www.example.com”和服务类型“http”,函数返回后,通过遍历res
链表,可以获取到每个可能的地址信息,包括IP地址和端口号,对于IPv4地址,可以直接从sockaddr_in
结构体中获取端口号;对于IPv6地址,虽然上述代码中未直接打印端口号,但也可以通过类似的方式从sockaddr_in6
结构体中获取。
2、使用getnameinfo函数(基于已获取的套接字地址结构)
原理:如果已经有了一个套接字地址结构(比如通过其他方式获取到了一个sockaddr_in
或sockaddr_in6
结构体),可以使用getnameinfo
函数来获取与之相关的域名和端口号等信息,不过需要注意的是,这个函数主要用于将数值形式的地址转换为可读的字符串形式,而不是直接用于域名解析获取端口。
示例代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <unistd.h> int main() { struct sockaddr_in sa; char host[NI_MAXHOST], service[NI_MAXSERV]; int s; // 假设已经有一个套接字地址结构sa,这里只是示例初始化 memset(&sa, 0, sizeof(struct sockaddr_in)); sa.sin_family = AF_INET; sa.sin_port = htons(80); // 假设端口为80 inet_pton(AF_INET, "93.184.216.34", &sa.sin_addr); // 假设IP地址为百度的某个IP // 获取域名和端口号 s = getnameinfo((struct sockaddr )&sa, sizeof(sa), host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV); if (s != 0) { printf("getnameinfo: %s ", gai_strerror(s)); return 1; } printf("Host: %s, Service: %s ", host, service); return 0; }
解释:上述代码中,首先初始化了一个sockaddr_in
结构体sa
,并设置了其IP地址和端口号(这里以百度的一个IP地址和端口80为例),然后调用getnameinfo
函数,传入该套接字地址结构,尝试获取与之相关的域名和端口号,不过需要注意的是,由于这里是手动设置的IP地址,所以获取到的域名可能不是实际的域名,而是IP地址的反查结果(如果有的话)。
3、结合DNS查询和套接字编程
原理:可以先通过DNS查询获取域名对应的IP地址,然后创建一个套接字并连接到该IP地址的相应端口,最后通过套接字选项或其他方式获取已连接套接字的端口号,这种方法相对复杂,但在某些特定场景下可能需要使用。
示例代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <unistd.h> int main() { struct hostent he; struct sockaddr_in sa; int sockfd; char host[] = "www.example.com"; int port; // 通过DNS查询获取主机信息 if ((he = gethostbyname(host)) == NULL) { herror("gethostbyname"); return 1; } // 创建套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } // 设置套接字地址结构 memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(80); // 假设端口为80 memcpy(&sa.sin_addr, he->h_addr, he->h_length); // 连接到服务器 if (connect(sockfd, (struct sockaddr )&sa, sizeof(sa)) < 0) { perror("connect"); return 1; } // 获取已连接套接字的端口号 socklen_t len = sizeof(sa); if (getsockname(sockfd, (struct sockaddr )&sa, &len) == -1) { perror("getsockname"); return 1; } port = ntohs(sa.sin_port); printf("Connected to port: %d ", port); close(sockfd); return 0; }
解释:上述代码中,首先通过gethostbyname
函数进行DNS查询,获取域名对应的主机信息,然后创建一个套接字,并设置套接字地址结构的IP地址和端口号(这里假设端口为80),接着调用connect
函数连接到服务器,连接成功后,通过getsockname
函数获取已连接套接字的端口号,并将其转换为主机字节序后打印出来,最后关闭套接字。
1、问:在使用getaddrinfo函数时,如果只想获取IPv4地址对应的端口号,应该如何修改代码?
答:可以在调用getaddrinfo
函数之前,将hints.ai_family
设置为AF_INET
,这样就会只返回IPv4地址相关的结果,然后在遍历结果时,按照上述示例代码中的方法获取端口号即可。
hints.ai_family = AF_INET; // 只允许IPv4
这样修改后,getaddrinfo
函数只会尝试解析IPv4地址,并在返回的结果中只包含与IPv4相关的地址和端口信息。
2、问:为什么在获取域名端口时,有时需要将端口号从网络字节序转换为主机字节序?
答:在网络编程中,不同的操作系统和硬件平台可能使用不同的字节序来存储数据,为了确保数据在网络上的正确传输和解读,网络字节序(大端字节序)被广泛用于表示多字节数据,如IP地址和端口号等,而主机字节序则取决于具体的计算机架构,可能是大端字节序也可能是小端字节序,当在网络编程中获取到端口号等数据时,它们通常是以网络字节序存储的,但是在实际使用和显示这些数据时,我们需要将其转换为主机字节序,以便与我们本地系统的字节序保持一致,方便进行后续的处理和显示,在一些函数返回的端口号是网络字节序的,如果不进行转换直接使用或显示,可能会导致数据错误或不符合预期。