DNS的全称domain name system,既然是一个系统就有客户端和服务器之分。一般情况来说我们并不需要感知这个DNS客户端的存在,因为我们在浏览器访问某个域名的时候,浏览器作为客户端已经实现了这个工作。,但是有时候我们没有使用浏览器,比如在netty环境中,如何构建一个DNS请求呢?,在RFC的规范中,DNS传输协议有很多种,如下所示:,这些协议都有对应的实现方式,我们先来看下Do53/TCP,也就是使用TCP进行DNS协议传输。,先来考虑一下如何在netty中使用Do53/TCP协议,进行DNS查询。,因为DNS是客户端和服务器的模式,我们需要做的是构建一个DNS客户端,向已知的DNS服务器端进行查询。,已知的DNS服务器地址有哪些呢?,除了13个root DNS IP地址以外,还出现了很多免费的公共DNS服务器地址,比如我们常用的阿里DNS,同时提供了IPv4/IPv6 DNS和DoT/DoH服务。,再比如百度DNS,提供了一组IPv4和IPv6的地址:,还有114DNS:,当然还有很多其他的公共免费DNS,这里我选择使用阿里的IPv4:223.5.5.5为例。,有了IP地址,我们还需要指定netty的连接端口号,这里默认的是53。,然后就是我们要查询的域名了,这里以www.flydean.com为例。,你也可以使用你系统中配置的DNS解析地址,以mac为例,可以通过nslookup进行查看本地的DNS地址:,有了DNS Server的IP地址,接下来我们需要做的就是搭建netty client,然后向DNS server端发送DNS查询消息。,因为我们进行的是TCP连接,所以可以借助于netty中的NIO操作来实现,也就是说我们需要使用NioEventLoopGroup和NioSocketChannel来搭建netty客户端:,netty中的NIO Socket底层使用的就是TCP协议,所以我们只需要像常用的netty客户端服务一样构建客户端即可。,然后调用Bootstrap的connect方法连接到DNS服务器,就建立好了channel连接。,这里我们在handler中传入了自定义的Do53ChannelInitializer,我们知道handler的作用是对消息进行编码、解码和对消息进行读取。因为目前我们并不知道客户端查询的消息格式,所以Do53ChannelInitializer的实现我们在后面再进行详细讲解。,netty提供了DNS消息的封装,所有的DNS消息,包括查询和响应都是DnsMessage的子类。,每个DnsMessage都有一个唯一标记的ID,还有代表这个message类型的DnsOpCode。,对于DNS来说,opCode有下面这几种:,因为每个DnsMessage都可能包含4个sections,每个section都以DnsSection来表示。因为有4个section,所以在DnsSection定义了4个section类型:,每个section里面又包含了多个DnsRecord, DnsRecord代表的就是Resource record,简称为RR,RR中有一个CLASS字段,下面是DnsRecord中CLASS字段的定义:,DnsMessage是DNS消息的统一表示,对于查询来说,netty中提供了一个专门的查询类叫做DefaultDnsQuery。,先来看下DefaultDnsQuery的定义和构造函数:,DefaultDnsQuery的构造函数需要传入id和opCode。,我们可以这样定义一个DNS查询:,既然是QEURY,那么还需要设置4个sections中的查询section:,这里调用的是setRecord方法向section中插入RR数据。,这里的RR数据使用的是DefaultDnsQuestion。DefaultDnsQuestion的构造函数有两个,一个是要查询的domain name,这里就是"www.flydean.com",另外一个参数是dns记录的类型。,dns记录的类型有很多种,在netty中有一个专门的类DnsRecordType表示,DnsRecordType中定义了很多个类型,如下所示:,因为类型比较多,我们挑选几个常用的进行讲解。,以上几个是我们经常会用到的dns record类型。,这里我们选择使用A,用来查询域名对应的主机IP地址。,构建好query之后,我们就可以使用netty client发送query指令到dns服务器了,具体的代码如下:,DNS的查询消息我们已经发送出去了,接下来就是对消息的处理和解析了。,还记得我们自定义的Do53ChannelInitializer吗?看一下它的实现:,我们向pipline中添加了两个netty自带的编码解码器TcpDnsQueryEncoder和TcpDnsResponseDecoder,还有一个自定义用来做消息解析的Do53ChannelInboundHandler。,因为我们向channel中写入的是DnsQuery,所以需要一个encoder将DnsQuery编码为ByteBuf,这里使用的是netty提供的TcpDnsQueryEncoder:,TcpDnsQueryEncoder继承自MessageToByteEncoder,表示将DnsQuery编码为ByteBuf。,看下他的encode方法:,可以看到TcpDnsQueryEncoder在msg编码之前存储了msg的长度信息,所以是一个基于长度的对象编码器。,这里的encoder是一个DnsQueryEncoder对象。,看一下它的encoder方法:,DnsQueryEncoder会依次编码header、questions和records。,完成编码之后,我们还需要从DNS server的返回中decode出DnsResponse,这里使用的是netty自带的TcpDnsResponseDecoder:,TcpDnsResponseDecoder继承自LengthFieldBasedFrameDecoder,表示数据是以字段长度来进行分割的,这和我们刚刚将的encoder的格式类似。,来看下他的decode方法:,decode方法先调用LengthFieldBasedFrameDecoder的decode方法将要解码的内容提取出来,然后调用responseDecoder的decode方法,最终返回DnsResponse。,这里的responseDecoder是一个DnsResponseDecoder。具体decoder的细节这里就不过多阐述了。感兴趣的同学可以自行查阅代码文档。,最后,我们得到了DnsResponse对象。,接下来就是自定义的InboundHandler对消息进行解析了:,在它的channelRead0方法中,我们调用了readMsg方法对消息进行处理:,DefaultDnsResponse是DnsResponse的一个实现,首先判断msg中的QUESTION个数是否大于零。,如果大于零,则打印出question的信息。,然后再解析出msg中的ANSWER并打印出来。,最后,我们可能得到这样的输出:,以上就是使用netty创建DNS client进行TCP查询的讲解。,本文的代码,大家可以参考:,learn-netty4