背景
网络编程中常用getaddrinfo()函数,从DNS地址获取ip地址。但该函数会阻塞调用线程,是个租塞的调用。
使用Reactor网络库上采用非阻塞方式编写一个DNS客户端,获得ip地址。
设计与实现
一言蔽之,先实现DNS请求和DNS解析。再将DNS请求和DNS解析地逻辑放在libevent框架下即可实现非阻塞dns客户端。
DNS请求和DNS解析
前提知识:
对比报文格式写结构体
typedef struct //DNS message header
{
unsigned short id;
unsigned short flags;
unsigned short questNum;
unsigned short answerNum;
unsigned short authorNum;
unsigned short additionNum;
} DNSHDR, *pDNSHDR;
typedef struct //DNS message request recored
{
unsigned short type;
unsigned short queryclass;
} QUERYHDR, *pQUERYHDR;
typedef struct //DNS message response recored
{
unsigned short type;
unsigned short classes;
unsigned int ttl;
unsigned short length;
} RESPONSE, *pRESPONSE;
将域名放到请求报文中就不细讲了,仔细就能够做出来;在解析回复报文时,用char*指针指向回复内容,根据报文格式解析各个部分。其中有几个地方容易出错。
- 最好使用unsigned char*代替char*。使用char*打印数据时,有时会出现莫名其妙的数据,如0xc0会显示成0xfffffc0。
- 在解析完一个区域,挪动指针时。如解析完RESPONSE,pTraceResponse+=sizeof(RESPONSE),很容易出错。RESPONSE内部时3个short和一个int数据,加起来应该等于10,但是由于结构体对齐,sizeof(RESPONSE)返回12.因此在声明RESPONSE结构体时,可加上__attribute__ ((packed))
- 注意网络的大小端。在解析flag时,我没有使用htons函数,练了一下手,自己用wireshark抓包对比着写的解析函数。
//将DNS应答报文的“标志”字段右移15位即取最高位 , 0 为DNS查询报文,1为应答报文 //没有使用htons,由于网络字节序字节15变成7位 if (pDnsHdr->flags >> 7) // { //flags低位值为3,标识服务器没有与请求域名相应的记录 if (((pDnsHdr->flags >> 4) & 0x0007) != 0) { printf("No corresponding domain name entry . \n"); return; } if ((pDnsHdr->flags >> 2) & 0x0001) //查看标志位AA,看是否时权威应答 { printf("Authoritative anwser : \n"); } else { printf("None-authoritative anwser : \n"); }
Reactor网络库--libevent
前提知识:
- 同步异步IO:简述同步IO、异步IO、阻塞IO、非阻塞IO之间的联系与区别 - 大数据从业者FelixZh - 博客园
- Reactor处理机制:异步网络模型
前面实现了DNS报文的请求,实现了后面就简单了。只需要编译libevent,将自己的DNS请求交给libevent管理。
- 编译libevent:https://aceld.gitbooks.io/libevent/content/chapter1.html
- code完事儿
最重要的show me the code:GitHub - ltCodeW/Reactor_DNSclient: 使用Reactor框架libevent写的DNS客户端