Linux C/C++实现DNS客户端请求域名IP的示例代码

 更新时间:2024年03月05日 09:24:27   作者:1776323096  
DNS全称:Domain Name System,域名解析系统,是互联网的一项服务,本文主要介绍了C/C++如何实现DNS客户端请求域名IP,感兴趣的可以了解下

一、DNS介绍

全称:Domain Name System,域名解析系统。是互联网的一项服务。它实质上是一个 域名 和 IP 相互映射的分布式数据库,有了它,我们就可以通过域名更方便的访问互联网。

**功能:**每个IP地址都可以有一个主机名,主机名由一个或多个字符串组成,字符串之间用小数点隔开。有了主机名,就不要死记硬背每台IP设备的IP地址,只要记住相对直观有意义的主机名就行了。这就是DNS协议所要完成的功能。

主机名到IP地址的映射有两种方式:

静态映射,每台设备上都配置主机到IP地址的映射,各设备独立维护自己的映射表,而且只供本设备使用;

动态映射,建立一套域名解析系统(DNS),只在专门的DNS服务器上配置.主机到IP地址的映射,网络上需要使用主机名通信的设备,首先需要到DNS服务器查询主机所对应的IP地址。

通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。在解析域名时,可以首先采用静态域名解析的方法,如果静态域名解析不成功,再采用动态域名解析的方法。可以将一些常用的域名放入静态域名解析表中,这样可以大大提高域名解析效率。

DNS 有以下特点:

  • 分布式的
  • 协议支持 TCP 和 UDP,常用端口是 53
  • 每一级域名的长度限制是 63
  • 域名总长度限制是 253

二、DNS协议

1、头部

TransactionID(事务ID):DNS 报文的 ID 标识。对于请求报文和其对应的应答报文,该字段的值是相同的,通过它可以区分 DNS 应答报文是对哪个请求进行响应的。

Flags(标志):DNS 报文中的标志字段。

第15位:QR(Response),查询请求/响应的标志信息。0为请求(query) 1为响应(response)。

第14-11位:Opcode, 操作码。0 表示标准查询;1 表示反向查询;2 表示服务器状态请求。

第10位:AA(Authoritative),授权应答,该字段在响应报文中有效。值为 1 时,表示名称服务器是权威服务器;值为 0 时,表示不是权威服务器。

第9位:TC(Truncated):表示是否被截断。值为 1 时,表示响应已超过 512 字节并已被截断(一个UDP报文为512字节),只返回前 512 个字节。

第8位:RD(Recursion Desired):是否请求递归(这个比特位被请求设置,应答的时候使用的相同的值返回)。该字段能在一个查询中设置,并在响应中返回。如果该位为 1,告诉名称服务器必须处理这个查询,这种方式被称为一个递归查询。如果该位为 0,且被请求的名称服务器没有一个授权回答,它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询。

第7位:RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为 1 时,表示DNS服务器支持递归查询。

第6-4位:Z:保留字段,在所有的请求和应答报文中,它的值必须为 0。

第3-0位:rcode(Reply code):返回码字段,表示响应的差错状态。

  • 当值为 0 时,表示 没有错误;
  • 当值为 1 时,表示 报文格式错误(Format error),服务器不能理解请求的报文;
  • 当值为 2 时,表示 域名服务器失败(Server failure),因为服务器的原因导致没办法处理这个请求;
  • 当值为 3 时,表示 名字错误(Name Error),只有对授权域名解析服务器有意义,指出解析的域名不存在;
  • 当值为 4 时,表示 查询类型不支持(Not Implemented),即域名服务器不支持查询类型;
  • 当值为 5 时,表示 拒绝(Refused),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答。
  • 当值为 6-15 时:保留值

Questions(问题计数):DNS查询请求的数目

Answer RRs(回答资源记录数):DNS响应的数目

Authority RRs(权威名称服务器计数):权威名称服务器的数目

Additional RRs(附加资源记录数):额外的记录数目(权威名称服务器对应 IP 地址的数目)

2、问题部分

问题部分指的是报文格式中查询问题区域(Queries)部分。该部分是用来显示 DNS 查询请求的问题,通常只有一个问题。该部分包含正在进行的查询信息,包含查询名(被查询主机名字)、查询类型、查询类。

问题部分格式如图所示。

该部分中每个字段含义如下:

Name(查询的域名):不定长(例子:www.baidu.com 需写作:3www5baidu3com0,这里的3,5,3分别指的是后面域名的长度,最后的0是‘\0’,是字符串结尾标志),有时也会是 IP 地址,用于反向查询。

Type(查询类型):DNS 查询请求的资源类型。通常查询类型为 A 类型,表示由域名获取对应的 IP 地址。

Class(查询类):指定信息的协议组,通常为互联网地址(值为 1)。

//查询类型(查询的资源记录类型)
enum QueryType
{   
    A     = 0x01, //指定计算机 IP 地址。   
    NS    = 0x02, //指定用于命名区域的 DNS 名称服务器。   
    MD    = 0x03, //指定邮件接收站(此类型已经过时了,使用MX代替)   
    MF 	  = 0x04, //指定邮件中转站(此类型已经过时了,使用MX代替)   
    CNAME = 0x05, //指定用于别名的规范名称。   
    SOA   = 0x06, //指定用于 DNS 区域的“起始授权机构”。   
    MB    = 0x07, //指定邮箱域名。   
    MG 	  = 0x08, //指定邮件组成员。   
    MR 	  = 0x09, //指定邮件重命名域名。   
    NULL  = 0x0A, //指定空的(NULL)资源记录   
    WKS   = 0x0B, //描述已知服务。   
    PTR   = 0x0C, //如果查询是 IP 地址,则指定计算机名;否则指定指向其它信息的指针。   
    HINFO = 0x0D, //指定计算机 CPU 以及操作系统类型。   
    MINFO = 0x0E, //指定邮箱或邮件列表信息。   
    MX    = 0x0F, //指定邮件交换器。   
    TXT   = 0x10, //指定文本信息。   
    UINFO = 0x64, //指定用户信息。   
    UID   = 0x65, //指定用户标识符。   
    GID	  = 0x66, //指定组名的组标识符。   
    ANY   = 0xFF  //指定所有数据类型。   
};  

//查询类(指定信息的协议组)
enum QueryClass
{   
    IN     = 0x01, //指定 Internet 类别。   
    CSNET  = 0x02, //指定 CSNET 类别。(已过时)   
    CHAOS  = 0x03, //指定 Chaos 类别。   
    HESIOD = 0x04, //指定 MIT Athena Hesiod 类别。   
    ANY	   = 0xFF  //指定任何以前列出的通配符。   
}; 

3、资源记录部分

资源记录部分是指 DNS 报文格式中的最后三个字段,包括 回答问题区域字段、权威名称服务器区域字段、附加信息区域字段。这三个字段均采用一种称为资源记录的格式,格式如图所示。

回答区域(Answers): 响应的内容,可以有0-n条(请求时为空即可)

1、Name:域名(2字节或不定长)

它的格式和Queries区域的查询名字字段是一样的。

有一点不同就是,当报文中域名重复出现的时候,该字段使用2个字节 的偏移指针来表示。比如,在资源记录中,域名通常是查询问题部分的域名的重复,因此用2字节的指针来表示,具体格式是最前面的两个高位是 11,用于识别指针。其余的14位从DNS报文的开始处计数(从0开始),指出该报文中的相应字节数。

一个典型的例子,0xC00C(1100000000001100),12(00000000001100)正好是头部的长度,其正好指向Queries区域的查询名字字段)。

2、Type:2字节 响应的资源记录的类型

与问题部分中的查询类型值是一样的。

3、Class:2字节 地址类型

与问题部分中的查询类的值是一样的。

4、TTL:4字节 表示的是资源记录的生命周期(以秒为单位)

一般用于当地址解析程序取出资源记录后决定保存及使用缓存数据的时间,它同时也可以表明该资源记录的稳定程度,极为稳定的信息会被分配一个很大的值(比如86400,这是一天的秒数)。

5、Datalength:2字节,资源数据的长度

指接下来的data长度,单位为字节。

6、Address/CNAME:资源数

4字节地址/不定长域名

授权区域(Authoritativenameservers):

1、Name:域名(2字节或不定长),同上

2、Type:2字节 同上,响应的资源记录类型。此处为2(NS),表示要获取该域名的权威名称服务器名称

3、Class:2字节 同上,地址类型,即查询类(指定信息的协议组)

4、TTL:4字节 同上

5、Datalength:2字节,资源数据的长度,指接下来的data长度,单位为字节。

6、nameserver:此处为6字节,表示该域名对应的权威名称服务器的名称。

附加区域(Additionalrecords):

1、Name:域名(2字节或不定长),同上,在这里指的是 权威名称服务器 的名称

2、Type:2字节 同上,响应的资源记录类型。

3、Class:2字节,同上,地址类型,即查询类(指定信息的协议组)

4、TTL:4字节 同上

5、Datalength:2字节,指接下来的data长度,单位为字节。

6、Address:此处为4字节地址,指的是 权威名称服务器 的IP地址

三、代码实现

实现了同步方式与异步方式。

#include <iostream>
#include <strings.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/epoll.h>

using namespace std;

//域名数组
const char* domainAddr[] = {
    "www.0voice.com",
	"draw.0voice.wang",
	"www.baidu.com",
	"tieba.baidu.com",
	"news.baidu.com",
	"zhidao.baidu.com",
	"music.baidu.com",
	"image.baidu.com",
	"v.baidu.com",
	"map.baidu.com",
	"baijiahao.baidu.com",
	"xueshu.baidu.com",
	"cloud.baidu.com",
	"www.163.com",
	"open.163.com",
	"auto.163.com",
	"gov.163.com",
	"money.163.com",
	"sports.163.com",
	"tech.163.com",
	"edu.163.com",
	"www.taobao.com",
	"q.taobao.com",
	"sf.taobao.com",
	"yun.taobao.com",
	"baoxian.taobao.com",
	"www.tmall.com",
	"suning.tmall.com",
	"www.tencent.com",
	"www.qq.com",
	"www.aliyun.com",
	"www.ctrip.com",
	"hotels.ctrip.com",
	"hotels.ctrip.com",
	"vacations.ctrip.com",
	"flights.ctrip.com",
	"trains.ctrip.com",
	"bus.ctrip.com",
	"car.ctrip.com",
	"piao.ctrip.com",
	"tuan.ctrip.com",
	"you.ctrip.com",
	"g.ctrip.com",
	"lipin.ctrip.com",
	"ct.ctrip.com"
};

//DNS头部
struct DNS_HEADER {
    uint16_t transID; //事务ID:DNS 报文的 ID 标识。
                      //对于请求报文和其对应的应答报文,该字段的值是相同的,
                      //通过它可以区分 DNS 应答报文是对哪个请求进行响应的。
    uint16_t flags; //标志:DNS 报文中的标志字段
	/*
	第15位:QR(Response),查询请求/响应的标志信息。0为请求(query) 1为响应(response)。

	第14-11位:Opcode, 操作码。0 表示标准查询;1 表示反向查询;2 表示服务器状态请求。

	第10位:AA(Authoritative),授权应答,该字段在响应报文中有效。值为 1 时,表示名称服务器是权威服务器;值为 0 时,表示不是权威服务器。

	第9位:TC(Truncated):表示是否被截断。值为 1 时,表示响应已超过 512 字节并已被截断(一个UDP报文为512字节),只返回前 512 个字节。

	第8位:RD(Recursion Desired):期望递归。该字段能在一个查询中设置,并在响应中返回。
								   如果该位为 1,告诉名称服务器必须处理这个查询,这种方式被称为一个递归查询。
								   如果该位为 0,且被请求的名称服务器没有一个授权回答,
								   				它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询。
								是否请求递归(这个比特位被请求设置,应答的时候使用的相同的值返回)。

	第7位:RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为 1 时,表示DNS服务器支持递归查询。

	第6-4位:Z:保留字段,在所有的请求和应答报文中,它的值必须为 0。

	第3-0位:rcode(Reply code):返回码字段,表示响应的差错状态。
								   当值为 0 时,表示 没有错误;
								   当值为 1 时,表示 报文格式错误(Format error),服务器不能理解请求的报文;
								   当值为 2 时,表示 域名服务器失败(Server failure),因为服务器的原因导致没办法处理这个请求;
								   当值为 3 时,表示 名字错误(Name Error),只有对授权域名解析服务器有意义,指出解析的域名不存在;
								   当值为 4 时,表示 查询类型不支持(Not Implemented),即域名服务器不支持查询类型;
								   当值为 5 时,表示 拒绝(Refused),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答。
								   当值为 6-15 时:保留值
	*/
    uint16_t questions; //问题计数:DNS查询请求的数目
    uint16_t answerRRs; //回答资源记录数:DNS响应的数目
    uint16_t authorityRRs; //权威名称服务器计数:权威名称服务器的数目
    uint16_t additionalRRs; //附加资源记录数:额外的记录数目(权威名称服务器对应 IP 地址的数目)
};

//DNS查询问题区域
struct DNS_QUERIES {
	string   qName;  //查询的域名,不定长(例子:www.baidu.com 需写作:3www5baidu3com0)
	uint16_t qType;  //查询类型:查询的资源记录类型
	uint16_t qClass; //查询类:指定信息的协议组
};

//查询类型(查询的资源记录类型)
enum QueryType
{   
    A       = 0x01, //指定计算机 IP 地址。   
    NS      = 0x02, //指定用于命名区域的 DNS 名称服务器。   
    MD      = 0x03, //指定邮件接收站(此类型已经过时了,使用MX代替)   
    MF 	    = 0x04, //指定邮件中转站(此类型已经过时了,使用MX代替)   
    CNAME   = 0x05, //指定用于别名的规范名称。   
    SOA     = 0x06, //指定用于 DNS 区域的“起始授权机构”。   
    MB      = 0x07, //指定邮箱域名。   
    MG 	    = 0x08, //指定邮件组成员。   
    MR 	    = 0x09, //指定邮件重命名域名。   
    _NULL_  = 0x0A, //指定空的(NULL)资源记录   
    WKS     = 0x0B, //描述已知服务。   
    PTR     = 0x0C, //如果查询是 IP 地址,则指定计算机名;否则指定指向其它信息的指针。   
    HINFO   = 0x0D, //指定计算机 CPU 以及操作系统类型。   
    MINFO   = 0x0E, //指定邮箱或邮件列表信息。   
    MX      = 0x0F, //指定邮件交换器。   
    TXT     = 0x10, //指定文本信息。   
    UINFO   = 0x64, //指定用户信息。   
    UID     = 0x65, //指定用户标识符。   
    GID	    = 0x66, //指定组名的组标识符。   
    _ANY_   = 0xFF  //指定所有数据类型。   
};  

//查询类(指定信息的协议组)
enum QueryClass
{   
    IN     = 0x01, //指定 Internet 类别。   
    CSNET  = 0x02, //指定 CSNET 类别。(已过时)   
    CHAOS  = 0x03, //指定 Chaos 类别。   
    HESIOD = 0x04, //指定 MIT Athena Hesiod 类别。   
    ANY	   = 0xFF  //指定任何以前列出的通配符。   
}; 

/*
回答区域:
Answers:查询响应内容,可以有0-n条(请求时为空即可)
	Name:域名(2字节或不定长):
		它的格式和Queries区域的查询名字字段是一样的。
		有一点不同就是,当报文中域名重复出现的时候,该字段使用2个字节的偏移指针来表示。
		比如,在资源记录中,域名通常是查询问题部分的域名的重复,
		因此用2字节的指针来表示,具体格式是最前面的两个高位是 11,用于识别指针。
		其余的14位从DNS报文的开始处计数(从0开始),指出该报文中的相应字节数。
		一个典型的例子,0xC00C(1100000000001100),12(00000000001100)正好是头部的长度,其正好指向Queries区域的查询名字字段)。
	Type:2字节 响应的资源记录的类型,与问题部分中的查询类型值是一样的。
	Class:2字节 地址类型,与问题部分中的查询类的值是一样的。
	TTL:4字节 
		以秒为单位,表示的是资源记录的生命周期,
		一般用于当地址解析程序取出资源记录后决定保存及使用缓存数据的时间,
		它同时也可以表明该资源记录的稳定程度,极为稳定的信息会被分配一个很大的值(比如86400,这是一天的秒数)。
	Datalength:2字节,资源数据的长度,指接下来的data长度,单位为字节。
	Address/CNAME:资源数据,4字节地址/不定长域名

授权区域
Authoritativenameservers:
	Name:域名(2字节或不定长),同上
	Type:2字节 同上,响应的资源记录类型。此处为2(NS),表示要获取该域名的权威名称服务器名称
	Class:2字节 同上,地址类型,即查询类(指定信息的协议组)
	TTL:4字节 同上
	Datalength:2字节,资源数据的长度,指接下来的data长度,单位为字节。
	nameserver:此处为6字节,表示该域名对应的权威名称服务器的名称。

//附加区域
Additionalrecords:
	Name:域名(2字节或不定长),同上,在这里指的是 权威名称服务器 的名称
	Type:2字节 同上,响应的资源记录类型。
	Class:2字节,同上,地址类型,即查询类(指定信息的协议组)
	TTL:4字节 同上
	Datalength:2字节,指接下来的data长度,单位为字节。
	Address:此处为4字节地址,指的是 权威名称服务器 的IP地址
*/

//构建DNS头部
DNS_HEADER create_dns_header() {
    DNS_HEADER header;
    bzero(&header, sizeof(header));

    srandom(time(NULL));

    header.transID = random();
    header.flags = htons(0x0100);
    header.questions = htons(0x0001);

    return move(header);
}

//构建DNS查询问题区域
DNS_QUERIES create_dns_queries(const string& qName, QueryType qType, QueryClass qClass) {
	DNS_QUERIES queries;

	char* tmpName = strdup(qName.c_str());
	char* token = strtok(tmpName, ".");
	while (token != NULL) {
		size_t len = strlen(token);

		queries.qName.append((char*)(&len), 1);
		queries.qName.append(token, len);

		token = strtok(NULL, ".");
	}
	//queries.qName.append("\0");

	free(tmpName);

	queries.qType  = htons(qType);
	queries.qClass = htons(qClass);

	return std::move(queries);
}

//构建一个DNS请求包
string create_dns_request(const DNS_HEADER& header, const DNS_QUERIES& queries) {
	string request = "";
	request.append((char*)&header, sizeof(header));

	request.append(queries.qName.data(), queries.qName.length() + 1);
	request.append((char*)&queries.qType, sizeof(queries.qType));
	request.append((char*)&queries.qClass, sizeof(queries.qClass));

	return std::move(request);
}

//解析域名(3www5baidu3com0 ---> www.baidu.com)
string parse_name(const string& name) {
	if (name.empty()) {
		return "";
	}

	string domain = "";
	int pos = 0;

	while (pos < name.length() - 1) {
		int len = name.at(pos);

		pos += 1;
		domain.append(name.data() + pos, len);
		pos += len;

		if (pos >= name.length()) {
			break;
		}

		domain.append(".");
	}

	return std::move(domain);
}

//解析收到的DNS响应
void parse_dns_response(const char* data, uint16_t len) {
	if (len == 0) {
		return;
	}

	const char* ptr = data;
	ptr += 4;

	//问题数
	int questions = ntohs(*((uint16_t*)ptr));
	ptr += 2;

	//回答资源记录数:DNS响应的数目
	int answerRRs = ntohs(*((uint16_t*)ptr));
	printf("\nanswerRRs:%d\n", answerRRs);
	ptr += 2;

	//跳过查询问题区域
	ptr += 4;
	for (int i = 0; i < questions; i++) {
		while(1) {
			int flag = *ptr;

			ptr += (1 + flag);

			if (flag == 0) {
				break;
			}
		}
		ptr += 4;
	}

	//解析回答区域
	for (int i = 0; i < answerRRs; i++) {
		//解析域名
		string domain = "", ip = "";
		uint8_t flag = *ptr;
		
		//该字段使用是的2个字节的偏移指针来表示
		if ((flag & 0xC0) == 0xC0) {
			int offsetBytes = ntohs(*(uint16_t*)ptr) & (~0xC000);
			const char* pName = data + offsetBytes;
			domain = parse_name(pName);
			ptr += 2;
		}else {
			domain = parse_name(ptr);
			ptr += (strlen(ptr) + 1);
		}

		//响应的资源记录的类型
		uint16_t type = ntohs(*(uint16_t*)ptr);
		ptr += 4;

		//资源记录的生命周期
		uint32_t ttl = ntohs(*(uint32_t*)ptr);
		ptr += 4;

		//资源数据的长度
		uint16_t dataLength = ntohs(*(uint16_t*)ptr);
		ptr += 2;

		//资源数据
		//不定长域名
		if (type == QueryType::CNAME) {
			ip = parse_name(ptr);
		}
		//4字节地址
		else if (type == QueryType::A) {
			if (dataLength == 4) {
				in_addr addr = {.s_addr = *(uint32_t*)ptr};
				ip = inet_ntoa(addr);
				ptr += 4;
			}
		}

		cout << domain << " : " << ip << endl;
	}
}

//设置socket为非阻塞
void setSocketNonBlock(int socketfd, bool nonBlock) {
	if (socketfd < 0) {
		return;
	}

	int flags = fcntl(socketfd, F_GETFL, 0);
	if (flags < 0) {
		return;
	}

	if (nonBlock) {
		flags |= O_NONBLOCK;
	}else {
		flags &= ~O_NONBLOCK;
	}

	fcntl(socketfd, F_SETFL, flags);
}

//向DNS服务端提交DNS请求(同步方式)
void dns_client_commit_sync() {
	//创建socket
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (fd < 0) {
		perror("socket");
		exit(-1);
	}

	setSocketNonBlock(fd, true);

	sockaddr_in destAddr;
	destAddr.sin_family = AF_INET;
	destAddr.sin_port = htons(53);
	destAddr.sin_addr.s_addr = inet_addr("114.114.114.114"); //DNS服务器地址

	//连接
	connect(fd, (sockaddr*)&destAddr, sizeof(destAddr));

	for (int i = 0; i < sizeof(domainAddr) / sizeof(domainAddr[0]); i++) {
		//构建DNS头部
    	DNS_HEADER header = create_dns_header();
    
		//构建DNS查询问题区域
    	DNS_QUERIES queries = create_dns_queries(domainAddr[i], QueryType::A, QueryClass::IN);

		//构建一个DNS请求包
		string req = create_dns_request(header, queries);

	    sendto(fd, req.data(), req.length(), 0, (sockaddr*)&destAddr, sizeof(destAddr));

		char buffer[2048] = {0};
		sockaddr_in addr;
		socklen_t addrLen = sizeof(addr);

		while (1) {
			int recvLen = recvfrom(fd, buffer, sizeof(buffer), 0, (sockaddr*)&addr, &addrLen);
			if (recvLen <= 0) {
				continue;
			}

			//解析收到的DNS响应
			parse_dns_response(buffer, recvLen);

			break;
		}
	}

	close(fd);
}

//处理DNS响应的线程(异步方式)
void *thread_process_async_response(void * arg) {
	int epfd = *(int*)arg;
	epoll_event events[1024] = {0};

	while (1) {
		int nready = epoll_wait(epfd, events, 1024, -1);

		if (nready < 0) {
			if (errno == EINTR || errno == EAGAIN) {
				continue;
			}else {
				break;
			}
		}else if (nready == 0) {
			continue;
		}

		for (int i = 0; i < nready; i++) {
			int fd = events[i].data.fd;
			char recvBuf[1024] = {0};

			if (events[i].events & EPOLLIN) {
				sockaddr_in srcAddr;
				socklen_t addrLen;

				int recvSize = recvfrom(fd, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&srcAddr, &addrLen);

				if (recvSize <= 0) {
					continue;
				}

				parse_dns_response(recvBuf, recvSize);

				epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
				close(fd);
			}
		}
	}

	return NULL;
}

//向DNS服务端提交DNS请求(异步方式)
void dns_client_commit_async() {
	int epfd = epoll_create(1);
	
	//创建一个线程来处理接收到的数据
	pthread_t threadID;
	pthread_create(&threadID, NULL, thread_process_async_response, &epfd);

	for (int i = 0; i < sizeof(domainAddr) / sizeof(domainAddr[0]); i++) {
		int fd = socket(AF_INET, SOCK_DGRAM, 0);
		if (fd < 0) {
			perror("socket");
			exit(-1);
		}

		setSocketNonBlock(fd, true);

		//将此fd加入epoll
		epoll_event event;
		event.events = EPOLL_EVENTS::EPOLLIN;
		event.data.fd = fd;
		epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);

		sockaddr_in destAddr;
		bzero(&destAddr, sizeof(destAddr));
		destAddr.sin_family = AF_INET;
		destAddr.sin_addr.s_addr = inet_addr("114.114.114.114");
		destAddr.sin_port = htons(53);

		connect(fd, (sockaddr*)&destAddr, sizeof(destAddr));

		//构建DNS头部
    	DNS_HEADER header = create_dns_header();
    
		//构建DNS查询问题区域
    	DNS_QUERIES queries = create_dns_queries(domainAddr[i], QueryType::A, QueryClass::IN);

		//构建一个DNS请求包
		string req = create_dns_request(header, queries);

	    sendto(fd, req.data(), req.length(), 0, (sockaddr*)&destAddr, sizeof(destAddr));
	}
}

int main(int argc, char* argv[]) {
#if 1
	dns_client_commit_sync(); //同步实现
#else
	dns_client_commit_async(); //异步实现
	getchar();
#endif

    return 0;
}

以上就是Linux C/C++实现DNS客户端请求域名IP的示例代码的详细内容,更多关于C++ DNS客户端请求域名IP的资料请关注脚本之家其它相关文章!

相关文章

  • C++使用动态内存分配的原因解说

    C++使用动态内存分配的原因解说

    这篇文章主要介绍了C++使用动态内存分配的原因解说,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • C++中std::is_object的具体使用

    C++中std::is_object的具体使用

    std::is_object是一种C++类型特性,其用途是判断一个类型是否是一个对象类型,本文主要介绍了C++中std::is_object的具体使用,感兴趣的可以了解一下
    2024-01-01
  • C语言版学生信息管理系统

    C语言版学生信息管理系统

    这篇文章主要为大家详细介绍了C语言版学生信息管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • 利用QDir实现删除选定文件目录下的空文件夹

    利用QDir实现删除选定文件目录下的空文件夹

    这篇文章主要为大家详细介绍了如何利用QDir实现删除选定文件目录下的空文件夹功能,文中的示例代码讲解详细,感兴趣的小伙伴可以动手尝试一下
    2022-08-08
  • 关于C语言操作符的那些事(超级全)

    关于C语言操作符的那些事(超级全)

    这篇文章主要给大家介绍了关于C语言操作符的那些事儿,c语言的操作符有很多,包括算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员,需要的朋友可以参考下
    2021-08-08
  • Qt数据库应用之实现数据打印到纸张

    Qt数据库应用之实现数据打印到纸张

    关于Qt打印内容到纸张,网上的办法非常多,比如有些直接用painter绘制,逐步控制分页打印。本文介绍的方法则是将内容作为html设置到文档对象,再调用文档对象的print方法传入QPrinter对象打印,感兴趣的同学可以了解一下
    2022-01-01
  • C++实现屏幕截图

    C++实现屏幕截图

    这篇文章主要为大家详细介绍了C++实现屏幕截图功能,截图自动保存为png格式文件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • C++枚举类型enum与enum class的使用

    C++枚举类型enum与enum class的使用

    这篇文章主要介绍了C++枚举类型enum与enum class的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • C++中strlen函数的三种实现方法

    C++中strlen函数的三种实现方法

    在C语言中我们要获取字符串的长度,可以使用strlen 函数,strlen 函数计算字符串的长度时,直到空结束字符,但不包括空结束字符,因为strlen函数时不包含最后的结束字符的,因此一般使用strlen函数计算的字符串的长度会比使用sizeof计算的字符串的字节数要小
    2022-05-05
  • Qt中QStringList与QString的常用方法总结

    Qt中QStringList与QString的常用方法总结

    这篇文章主要为大家总结了Qt中QString 与 (QStringList | QByteArray)之间的转换,以及QString、QStringList的一些常用方法,感兴趣的可以收藏一下
    2022-12-12

最新评论