C语言网络编程(4)— 通过DNS连接到百度-优化

C语言网络编程(4)— 通过DNS连接到百度-优化

一、gethostbyname和getaddrinfo

之前我们使用gethostbyname()函数完成了主机名到地址的解析,但这个函数仅仅支持IPv4,且不允许调用者指定所需地址类型的任何信息,返回的结构只包含了用于存储IPv4地址的空间。
IPv6中引入了新的API getaddrinfo(),它是协议无关的,既可用于IPv4也可用于IPv6。
getaddrinfo() 函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个 struct addrinfo 的结构体(列表)指针而不是一个地址清单。这些 struct addrinfo 结构体随后可由套接口函数直接使用。
如此一来,getaddrinfo()函数把协议相关性安全隐藏在这个库函数内部。应用程序只要处理由getaddrinfo()函数填写的套接口地址结构。
参考自:getaddrinfo

二、getaddrinfo()函数说明

从man手册中查得其函数原型:

int getaddrinfo(const char *node, const char *service,const struct addrinfo *hints,struct addrinfo **res);
  1. 参数node:hostname 或者IP地址,主机名(“www.baidu.com”)或者是数字化的地址字符串(IPv4的点分十进制串(“192.168.1.100”)或者IPv6的16进制串(“2000::1:2345:6789:abcd”));
    如果在后面的参数hints中的ai_flags 中设置了AI_NUMERICHOST 标志,那么node参数只能是数字化的地址字符串,不能是域名,该标志的作用就是阻止进行域名解析;

  2. 参数service:服务名,可以是十进制的端口号(“8080”)字符串,也可以是已定义的服务名称,如"ftp"、"http"等,详细请查看/etc/services 文件,最后翻译成对应服务的端口号;如果此参数设置为NULL,那么返回的socket地址中的端口号不会被设置。
    如果在后面的参数hints中的ai_flags 中设置了AI_NUMERICSERV标志并且该参数未设置为NULL,那么该service必须是一个指向10进制的端口号字符串,不能设定成服务名,该标志就是用来阻止服务名解析。
    nodeservice可以设置为NULL,但是同时只能有一个为NUL。

  3. 参数hints:该参数指向用户设定的 struct addrinfo 结构体,只能设定该结构体中 ai_family、ai_socktype、ai_protocol 和 ai_flags 四个域,其他域必须设置为0 或者 NULL, 通常是申请 结构体变量后使用memset()初始化再设定指定的四个域。该参数可以设置为NULL,等价于 ai_socktype = 0, ai_protocol = 0,ai_family = AF_UNSPEC, ai_flags = 0 (在GNU Linux中ai_flag = AI_V4MAPPED | AI_ADDRCONFIG,可以看作是GNU的改进)。
    ① ai_family:指定返回地址的协议簇,取值范围:AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNSPEC(IPv4 and IPv6)
    ② ai_socktype:用于设定返回地址的socket类型,常用的有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW, 设置为0表示所有类型都可以。
    ③ ai_protocol:常用的有 IPPROTO_TCP、IPPROTO_UDP 等,设置为0表示所有协议。
    ④ ai_flags:附加选项,多个选项可以使用或操作进行结合。

  4. 参数res:该参数获取一个指向存储结果的 struct addrinfo 结构体列表;使用完成后要调用 freeaddrinfo() 释放存储结果空间。

  5. 返回值:函数执行成功,返回值为 0 ,其他情况返回值表示错误种别。使用函数gai_strerror() 可以获取可读性的错误信息,用法用strerror()相同;

参考自:getaddrinfo详解

三、编写程序获取IP地址

首先,包含我们需要用到的头文件

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

定义一个hints结构体,用来设置函数的getaddrinfo()的使用方式:

    // 1、定义一个hints结构体,用来设置函数的getaddrinfo()的使用方式
    struct addrinfo hints;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* 指定返回地址的协议簇,IPv4和IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* 设定返回地址的socket类型,流式套接字 */
    hints.ai_flags = AI_ALL;            /* 附加选项,查询IPv4和IPv6地址 */
    hints.ai_protocol = IPPROTO_TCP;    /* 指定协议类型,TCP协议 */

然后就可以使用getaddrinfo()开始解析了

    // 2、使用getaddrinfo()开始解析,定义一个struct addrinfo结构体指针,用来获取解析结果
    struct addrinfo *result;
    int err;
    err = getaddrinfo("www.baidu.com", "http", &hints, &result);
    if(err != 0)        /* 返回值不为0,函数执行失败*/
        printf("getaddrinfo err: %s",gai_strerror(err));

然后我们将获取到的信息打印出来

	// 3、将获取到的信息打印出来
    struct addrinfo *rp;        /* 因为解析结果是一个链表,定义一个结构体获取当前节点*/
    char buf[100];              /* 用来存储IP地址字符串 */
    int ipv4_cnt=0,ipv6_cnt=0;  /* 记录ip地址个数 */
    struct sockaddr_in  *ipv4;  /* IPv4地址结构体指针 */
    struct sockaddr_in6 *ipv6;  /* IPv6地址结构体指针 */
    for (rp = result; rp != NULL; rp = rp->ai_next) 
    {
        if(rp->ai_family == AF_INET) 
        {
            ipv4_cnt++;
            ipv4 = (struct sockaddr_in *)rp->ai_addr;
            inet_ntop(rp->ai_family, &ipv4->sin_addr, buf, sizeof(buf));
            printf("[IPv4-%d]%s [port]%d \n",ipv4_cnt,buf,ntohs(ipv4->sin_port));
        }
        else if(rp->ai_family == AF_INET6) 
        {
            ipv6_cnt++;
            ipv6 = (struct sockaddr_in6 *)rp->ai_addr;
            inet_ntop(rp->ai_family, &ipv6->sin6_addr, buf, sizeof(buf));
            printf("[IPv6-%d]%s [port]%d \n",ipv6_cnt,buf,ntohs(ipv6->sin6_port));
        }
    }

使用完我们需要释放addrinfo 内存

    // 4、释放addrinfo 内存
    freeaddrinfo(result);     

编译,运行,结果如下,可以看到,获取到两个IPv4地址
在这里插入图片描述
我们尝试将域名换成“www.163.com”,即网易的域名,可以看到,获取到了IPv6地址
在这里插入图片描述

四、连接百度服务器

尝试连接到百度服务器

    // 4、使用socket()函数获取一个TCP客户端socket文件描述符
	int tcp_client = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == tcp_client)
	{
		perror("socket");
		return -1;
	}

    // 5、链接到服务器
    err = connect(tcp_client, (const struct sockaddr *)ipv4, sizeof(*ipv4));
    if (err < 0)
		perror("connect err");
    else
	    printf("connect success, ret = %d.\n", err);

可以看到,连接成功
在这里插入图片描述
我们尝试发送一个GET请求,

    // 4、使用socket()函数获取一个TCP客户端socket文件描述符
	int tcp_client = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == tcp_client)
	{
		perror("socket");
		return -1;
	}

    // 5、链接到服务器
    err = connect(tcp_client, (const struct sockaddr *)ipv4, sizeof(*ipv4));
    if (err < 0)
		perror("connect err");
    else
	    printf("connect success, ret = %d.\n", err);
    
    // 6. 发送GET请求
	char sendbuf[]={"GET / HTTP/1.1\n\n"};
	err = send(tcp_client, sendbuf, strlen(sendbuf),0);
    if (err < 0)
		perror("send err");
    else
        printf("send size %d \n",err);
    

    // 7、等待接收服务端发送过来的数据,最大接收100个字节
    char recvbuf[100] = {0};
    err = recv(tcp_client, recvbuf, sizeof(recvbuf), 0);
    if (err < 0)
		perror("recv err");
    else
        printf("recv size %d \n",err);

    // 8、将接收到的数据打印出来
    printf("Recvdate: \n%s \n",recvbuf);

    // 9、关闭套接字
    close(tcp_client);

连接成功,不过好像没有得到有效的回应
在这里插入图片描述
我们把https换成http,连接成功,且GET请求也得到了有效的回复
在这里插入图片描述

五、代码

最后贴上完整代码

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    // 1、定义一个hints结构体,用来设置函数的getaddrinfo()的使用方式
    struct addrinfo hints;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* 指定返回地址的协议簇,IPv4和IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* 设定返回地址的socket类型,流式套接字 */
    hints.ai_protocol = IPPROTO_TCP;    /* 指定协议类型,TCP协议 */
    hints.ai_flags = AI_ALL;            /* 控制选项,查询IPv4和IPv6地址 */

    // 2、使用getaddrinfo()开始解析,定义一个struct addrinfo结构体指针,用来获取解析结果
    struct addrinfo *result;
    int err;
    err = getaddrinfo("www.baidu.com", "http", &hints, &result);
    if(err != 0)        /* 返回值不为0,函数执行失败*/
        printf("getaddrinfo err: %s \n",gai_strerror(err));

    // 3、将获取到的信息打印出来
    struct addrinfo *rp;        /* 因为解析结果是一个链表,定义一个结构体获取当前节点*/
    char buf[100];              /* 用来存储IP地址字符串 */
    int ipv4_cnt=0,ipv6_cnt=0;  /* 记录ip地址个数 */
    struct sockaddr_in  *ipv4;  /* IPv4地址结构体指针 */
    struct sockaddr_in6 *ipv6;  /* IPv6地址结构体指针 */
    for (rp = result; rp != NULL; rp = rp->ai_next) 
    {
        if(rp->ai_family == AF_INET) 
        {
            ipv4_cnt++;
            ipv4 = (struct sockaddr_in *)rp->ai_addr;
            inet_ntop(rp->ai_family, &ipv4->sin_addr, buf, sizeof(buf));
            printf("[IPv4-%d]%s [port]%d \n",ipv4_cnt,buf,ntohs(ipv4->sin_port));
        }
        else if(rp->ai_family == AF_INET6) 
        {
            ipv6_cnt++;
            ipv6 = (struct sockaddr_in6 *)rp->ai_addr;
            inet_ntop(rp->ai_family, &ipv6->sin6_addr, buf, sizeof(buf));
            printf("[IPv6-%d]%s [port]%d \n",ipv6_cnt,buf,ntohs(ipv6->sin6_port));
        }
    }

    // 4、使用socket()函数获取一个TCP客户端socket文件描述符
	int tcp_client = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == tcp_client)
	{
		perror("socket");
		return -1;
	}

    // 5、链接到服务器
    err = connect(tcp_client, (const struct sockaddr *)ipv4, sizeof(*ipv4));
    if (err < 0)
		perror("connect err");
    else
	    printf("connect success, ret = %d.\n", err);
    
    // 6. 发送GET请求
	char sendbuf[]={"GET / HTTP/1.1\n\n"};
	err = send(tcp_client, sendbuf, strlen(sendbuf),0);
    if (err < 0)
		perror("send err");
    else
        printf("send size %d \n",err);
    

    // 7、等待接收服务端发送过来的数据,最大接收100个字节
    char recvbuf[100] = {0};
    err = recv(tcp_client, recvbuf, sizeof(recvbuf), 0);
    if (err < 0)
		perror("recv err");
    else
        printf("recv size %d \n",err);

    // 8、将接收到的数据打印出来
    printf("Recvdate: \n%s \n",recvbuf);

    // 9、关闭套接字
    close(tcp_client);

    // 10、释放addrinfo 内存
    freeaddrinfo(result);           

    exit(EXIT_SUCCESS);
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值