简介

在前面的文章中,我们提到了使用netty构建tcp和udp的客户端向已经公布的DNS服务器进行域名请求服务。基本的流程是借助于netty本身的NIO通道,将要查询的信息封装成为DNSMessage,通过netty搭建的channel发送到服务器端,然后从服务器端接受返回数据,将其编码为DNSResponse,进行消息的处理。

那么DNS Server是否可以用netty实现呢?

答案当然是肯定的,但是之前也讲过了DNS中有很多DnsRecordType,所以如果想实现全部的支持类型可能并现实,这里我们就以最简单和最常用的A类型为例,用netty来实现一下DNS的TCP服务器。

搭建netty服务器

因为是TCP请求,所以这里使用基于NIO的netty server服务,也就是NioEventLoopGroup和NioServerSocketChannel,netty服务器的代码如下:

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap().group(bossGroup,
workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new Do53ServerChannelInitializer());
final Channel channel = bootstrap.bind(dnsServerPort).channel();
channel.closeFuture().sync();

因为是服务器,所以我们需要两个EventLoopGroup,一个是bossGroup,一个是workerGroup。

将这两个group传递给ServerBootstrap,并指定channel是NioServerSocketChannel,然后添加自定义的Do53ServerChannelInitializer即可。

Do53ServerChannelInitializer中包含了netty自带的tcp编码解码器和自定义的服务器端消息处理方式。

这里dnsServerPort=53,也是默认的DNS服务器的端口值。

DNS服务器的消息处理

Do53ServerChannelInitializer是我们自定义的initializer,里面为pipline添加了消息的处理handler:

class Do53ServerChannelInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(
new TcpDnsQueryDecoder(),
new TcpDnsResponseEncoder(),
new Do53ServerInboundHandler());
}
}

这里我们添加了两个netty自带的编码解码器,分别是TcpDnsQueryDecoder和TcpDnsResponseEncoder。

对于netty服务器来说,接收到的是ByteBuf消息,为了方便服务器端的消息读取,需要将ByteBuf解码为DnsQuery,这也就是TcpDnsQueryDecoder在做的事情。

public final class TcpDnsQueryDecoder extends LengthFieldBasedFrameDecoder

TcpDnsQueryDecoder继承自LengthFieldBasedFrameDecoder,也就是以字段长度来区分对象的起始位置。这和TCP查询传过来的数据结构是一致的。

下面是它的decode方法:

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf)super.decode(ctx, in);
return frame == null ? null : DnsMessageUtil.decodeDnsQuery(this.decoder, frame.slice(), new DnsQueryFactory() {
public DnsQuery newQuery(int id, DnsOpCode dnsOpCode) {
return new DefaultDnsQuery(id, dnsOpCode);
}
});
}

decode接受一个ByteBuf对象,首先调用LengthFieldBasedFrameDecoder的decode方法,将真正需要解析的内容解析出来,然后再调用DnsMessageUtil的decodeDnsQuery方法将真正的ByteBuf内容解码成为DnsQuery返回。

这样就可以在自定义的handler中处理DnsQuery消息了。

上面代码中,自定义的handler叫做Do53ServerInboundHandler:

class Do53ServerInboundHandler extends SimpleChannelInboundHandler<DnsQuery>

从定义看,Do53ServerInboundHandler要处理的消息就是DnsQuery。

看一下它的channelRead0方法:

    protected void channelRead0(ChannelHandlerContext ctx,
DnsQuery msg) throws Exception {
DnsQuestion question = msg.recordAt(DnsSection.QUESTION);
log.info("Query is: {}", question);
ctx.writeAndFlush(newResponse(msg, question, 1000, QUERY_RESULT));
}

我们从DnsQuery的QUESTION section中拿到DnsQuestion,然后解析DnsQuestion的内容,根据DnsQuestion的内容返回一个response给客户端。

这里的respone是我们自定义的:

    private DefaultDnsResponse newResponse(DnsQuery query,
DnsQuestion question,
long ttl, byte[]... addresses) {
DefaultDnsResponse response = new DefaultDnsResponse(query.id());
response.addRecord(DnsSection.QUESTION, question); for (byte[] address : addresses) {
DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord(
question.name(),
DnsRecordType.A, ttl, Unpooled.wrappedBuffer(address));
response.addRecord(DnsSection.ANSWER, queryAnswer);
}
return response;
}

上面的代码封装了一个新的DefaultDnsResponse对象,并使用query的id作为DefaultDnsResponse的id。并将question作为response的QUESEION section。

除了QUESTION section,response中还需要ANSWER section,这个ANSWER section需要填充一个DnsRecord。

这里构造了一个DefaultDnsRawRecord,传入了record的name,type,ttl和具体内容。

最后将构建好的DefaultDnsResponse返回。

因为客户端查询的是A address,按道理我们需要通过QUESTION中传入的domain名字,然后根据DNS服务器中存储的记录进行查找,最终返回对应域名的IP地址。

但是因为我们只是模拟的DNS服务器,所以并没有真实的域名IP记录,所以这里我们伪造了一个ip地址:

    private static final byte[] QUERY_RESULT = new byte[]{46, 53, 107, 110};

然后调用Unpooled的wrappedBuffer方法,将byte数组转换成为ByteBuf,传入DefaultDnsRawRecord的构造函数中。

这样我们的DNS服务器就搭建好了。

DNS客户端消息请求

上面我们搭建好了DNS服务器,接下来就可以使用DNS客户端来请求DNS服务器了。

这里我们使用之前创建好的netty DNS客户端,只不过进行少许改动,将DNS服务器的域名和IP地址替换成下面的值:

        Do53TcpClient client = new Do53TcpClient();
final String dnsServer = "127.0.0.1";
final int dnsPort = 53;
final String queryDomain ="www.flydean.com";
client.startDnsClient(dnsServer,dnsPort,queryDomain);

dnsServer就填本机的IP地址,dnsPort就是我们刚刚创建的默认端口53。

首先运行DNS服务器:

INFO  i.n.handler.logging.LoggingHandler - [id: 0x021762f2] REGISTERED
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2] BIND: 0.0.0.0/0.0.0.0:53
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2, L:/0:0:0:0:0:0:0:0:53] ACTIVE

可以看到DNS服务器已经准备好了,绑定的端口是53。

然后运行上面的客户端,在客户端可以得到下面的结果:

INFO  c.f.d.Do53TcpChannelInboundHandler - question is :DefaultDnsQuestion(www.flydean.com. IN A)
INFO c.f.d.Do53TcpChannelInboundHandler - ip address is: 46.53.107.110

可以看到DNS查询成功,并且返回了我们在服务器中预设的值。

然后再看一下服务器端的输出:

INFO  i.n.handler.logging.LoggingHandler - [id: 0x021762f2, L:/0:0:0:0:0:0:0:0:53] READ: [id: 0x44d4c761, L:/127.0.0.1:53 - R:/127.0.0.1:65471]
INFO i.n.handler.logging.LoggingHandler - [id: 0x021762f2, L:/0:0:0:0:0:0:0:0:53] READ COMPLETE
INFO c.f.d.Do53ServerInboundHandler - Query is: DefaultDnsQuestion(www.flydean.com. IN A)

可以看到服务器端成功和客户端建立了连接,并成功接收到了客户端的查询请求。

总结

以上就是使用netty默认DNS服务器端的实现原理和例子。因为篇幅有限,这里只是默认了type为A address的情况,对其他type感兴趣的朋友可以自行探索。

本文的代码,大家可以参考:

learn-netty4

netty系列之:来,手把手教你使用netty搭建一个DNS tcp服务器的更多相关文章

  1. [手把手教你] 用Swoft 搭建微服务(TCP RPC)

    序言 Swoft Framework 基于 Swoole 原生协程的新时代 PHP 全栈式协程框架 Swoft 是什么? Swoft 框架是首个基于Swoole 原生协程的新时代 PHP高性能协程全栈 ...

  2. 手把手教你使用FineUI开发一个b/s结构的取送货管理信息系统系列博文索引

    近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座,来讲解如何利用FineUI快速开 ...

  3. SpringCloud学习之手把手教你用IDEA搭建入门项目(一)

    SpringCloud简单搭建 jdk:1.8开发工具:IDEA注:需要了解springcloud 1.创建最简单的Maven项目 1)开始创建一个新的项目 ​ 2)创建一个空模板的maven项目,用 ...

  4. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  5. 手把手教你认识并搭建Nginx

    手把手教你认识并搭建Nginx Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器. Nginx 是由 Igor ...

  6. 手把手教你用vue-cli搭建vue项目

    手把手教你用vue-cli搭建vue项目 本篇主要是利用vue-cli来搭建vue项目,其中前提是node和npm已经安装好,文章结尾将会简单提到一个简单的例子.使用vue-cli搭建项目最开始我也是 ...

  7. 沉淀,再出发——手把手教你使用VirtualBox搭建含有三个虚拟节点的Hadoop集群

    手把手教你使用VirtualBox搭建含有三个虚拟节点的Hadoop集群 一.准备,再出发 在项目启动之前,让我们看一下前面所做的工作.首先我们掌握了一些Linux的基本命令和重要的文件,其次我们学会 ...

  8. 手把手教你用webpack3搭建react项目(开发环境和生产环境)(一)

    开发环境和生产环境整个配置源码在github上,源码地址:github-webpack-react 如果觉得有帮助,点个Star谢谢!! (一)是开发环境,(二)是生产环境. 一.首先创建packag ...

  9. 手把手教你使用 VuePress 搭建个人博客

    手把手教你使用 VuePress 搭建个人博客 有阅读障碍的同学,可以跳过第一至四节,下载我写好的工具包: git clone https://github.com/zhangyunchencc/vu ...

随机推荐

  1. 【Java】学习路径59-多个连接的服务器端

    ServerSocket可以对接多个Socket对象,利用这点,就可以实现:一台服务器对多个客户端. import java.io.IOException; import java.net.*; pu ...

  2. RabbitMQ 入门系列:8、扩展内容:接收信息时:可否根据RoutingKey过滤监听信息,答案是不能。

    系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...

  3. 第七十篇:Vue组件的使用

    好家伙, 1.vue的组件化开发 1.1.什么是组件? 组件是对UI结构的复用, vue是一个支持组件化开发的前端框架, vue中规定:组件的后缀名是.vue 例如:App.vue文件本质上就是一个v ...

  4. Helm安装ingress-nginx-4.2.0

    Application version 1.3.0 Chart version 4.2.0 获取chart包 helm fetch ingress-nginx/ingress-nginx --vers ...

  5. 闭包 与 js内存管理

    参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management            https://blog ...

  6. KingbaseES 多列分区的方法与性能

    前言 对于多列分区,可以选择单级多列的范围分区,也可以选择范围加子分区的方式.但二者在不同场景下对于性能是有差异的,这里的性能差异主要是分区裁剪引起的差异. 例子 创建两张分区表,采取不同的分区策略: ...

  7. Java 多线程:锁(二)

    Java 多线程:锁(二) 作者:Grey 原文地址: 博客园:Java 多线程:锁(二) CSDN:Java 多线程:锁(二) AtomicLong VS LongAddr VS Synchroni ...

  8. js中的对象和数组的创建

    <!DOCTYPE html><html><head> <title>获取控制</title> <meta charset=" ...

  9. 题解 P2471 【[SCOI2007]降雨量】

    原题传送门 前置芝士 离散化 ST表和RMQ问题 二分 正文 首先我们来分析一下题意. 题目会给出两个大小为 \(n\) 的数组,\(y\) 和 \(r\) ,其中 \(y_i\) 表示第 \(i\) ...

  10. Grafana Loki 学习之踩坑记

    转发自:https://mp.weixin.qq.com/s/zfXNEkdDC9Vqd9lh1ptC1g Grafana 出品的 loki 日志框架完美地与 kubernetes 的 label 理 ...