手把手教你在netty中使用TCP协议请求DNS服务器( 二 )


QUESTION,ANSWER,AUTHORITY,ADDITIONAL;每个section里面又包含了多个DnsRecord, DnsRecord代表的就是Resource record,简称为RR , RR中有一个CLASS字段 , 下面是DnsRecord中CLASS字段的定义:
int CLASS_IN = 1;int CLASS_CSNET = 2;int CLASS_CHAOS = 3;int CLASS_HESIOD = 4;int CLASS_NONE = 254;int CLASS_ANY = 255;DnsMessage是DNS消息的统一表示 , 对于查询来说 , netty中提供了一个专门的查询类叫做DefaultDnsQuery 。
先来看下DefaultDnsQuery的定义和构造函数:
public class DefaultDnsQuery extends AbstractDnsMessage implements DnsQuery {public DefaultDnsQuery(int id) {super(id);}public DefaultDnsQuery(int id, DnsOpCode opCode) {super(id, opCode);}DefaultDnsQuery的构造函数需要传入id和opCode 。
我们可以这样定义一个DNS查询:
int randomID = (int) (System.currentTimeMillis() / 1000);DnsQuery query = new DefaultDnsQuery(randomID, DnsOpCode.QUERY)既然是QEURY,那么还需要设置4个sections中的查询section:
query.setRecord(DnsSection.QUESTION, new DefaultDnsQuestion(queryDomain, DnsRecordType.A));这里调用的是setRecord方法向section中插入RR数据 。
这里的RR数据使用的是DefaultDnsQuestion 。DefaultDnsQuestion的构造函数有两个 , 一个是要查询的domain name , 这里就是”www.flydean.com”,另外一个参数是dns记录的类型 。
dns记录的类型有很多种 , 在netty中有一个专门的类DnsRecordType表示,DnsRecordType中定义了很多个类型 , 如下所示:
public class DnsRecordType implements Comparable<DnsRecordType> {public static final DnsRecordType A = new DnsRecordType(1, "A");public static final DnsRecordType NS = new DnsRecordType(2, "NS");public static final DnsRecordType CNAME = new DnsRecordType(5, "CNAME");public static final DnsRecordType SOA = new DnsRecordType(6, "SOA");public static final DnsRecordType PTR = new DnsRecordType(12, "PTR");public static final DnsRecordType MX = new DnsRecordType(15, "MX");public static final DnsRecordType TXT = new DnsRecordType(16, "TXT");...因为类型比较多 , 我们挑选几个常用的进行讲解 。

  • A类型 , 是address的缩写 , 用来指定主机名或者域名对应的ip地址.
  • NS类型 , 是name server的缩写 , 是域名服务器记录 , 用来指定域名由哪个DNS服务器来进行解析 。
  • MX类型,是mail exchanger的缩写 , 是一个邮件交换记录 , 用来根据邮箱的后缀来定位邮件服务器 。
  • CNAME类型 , 是canonical name的缩写 , 可以将多个名字映射到同一个主机.
  • TXT类型 , 用来表示主机或者域名的说明信息 。
以上几个是我们经常会用到的dns record类型 。
这里我们选择使用A , 用来查询域名对应的主机IP地址 。
构建好query之后 , 我们就可以使用netty client发送query指令到dns服务器了 , 具体的代码如下:
DnsQuery query = new DefaultDnsQuery(randomID, DnsOpCode.QUERY).setRecord(DnsSection.QUESTION, new DefaultDnsQuestion(queryDomain, DnsRecordType.A));ch.writeAndFlush(query).sync();DNS查询的消息处理DNS的查询消息我们已经发送出去了 , 接下来就是对消息的处理和解析了 。
还记得我们自定义的Do53ChannelInitializer吗?看一下它的实现:
class Do53ChannelInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();p.addLast(new TcpDnsQueryEncoder()).addLast(new TcpDnsResponseDecoder()).addLast(new Do53ChannelInboundHandler());}}我们向pipline中添加了两个netty自带的编码解码器TcpDnsQueryEncoder和TcpDnsResponseDecoder , 还有一个自定义用来做消息解析的Do53ChannelInboundHandler 。
因为我们向channel中写入的是DnsQuery,所以需要一个encoder将DnsQuery编码为ByteBuf,这里使用的是netty提供的TcpDnsQueryEncoder:
public final class TcpDnsQueryEncoder extends MessageToByteEncoder<DnsQuery> TcpDnsQueryEncoder继承自MessageToByteEncoder , 表示将DnsQuery编码为ByteBuf 。
看下他的encode方法:
protected void encode(ChannelHandlerContext ctx, DnsQuery msg, ByteBuf out) throws Exception {out.writerIndex(out.writerIndex() + 2);this.encoder.encode(msg, out);out.setShort(0, out.readableBytes() - 2);}可以看到TcpDnsQueryEncoder在msg编码之前存储了msg的长度信息 , 所以是一个基于长度的对象编码器 。


推荐阅读