简介

在前面的文章中,我们提到了使用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. 浅析websocket的基本应用spring boot + vue +C# + WPF

    1.基本概念 首先websocket是基于H5的一种通信.在网页中如果定时获取服务器端的实时数据,我们常采用long poll 和ajax轮询的方式.但是在轮询过程中,由于根本没有新数据的改变,而造成 ...

  2. Apache DolphinScheduler 简单任务定义及复杂的跨节点传参

    ​ 点亮 ️ Star · 照亮开源之路 GitHub:https://github.com/apache/dolphinscheduler Apache DolphinScheduler是一款非常不 ...

  3. AtCoder Beginner Contest 255(E-F)

    Aising Programming Contest 2022(AtCoder Beginner Contest 255) - AtCoder E - Lucky Numbers 题意: 给两个数组a ...

  4. OpenStack云计算平台部署(单节点)

    环境配置 虚拟机(centos7 .内存8G.硬盘300G.处理器4核并开启intel vt-x,网络模式设置为NAT,虚拟机网络一定要设置好,并可以ping通baidu,不然有中途掉IP的情况发生) ...

  5. docker容器数据卷的使用

    什么是容器数据卷 docker的理念回顾 将应用和运行的环境打包形成容器运行,运行可以伴随着容器,但是我们对于数据的要求,是希望能够持久化的! 就好比,你安装一个MySQL,结果你把容器删了,就相当于 ...

  6. 如何在Windows中批量创建VMware的虚拟机

    在最近的工作中,需要创建一批类似的机器.在VMware中创建了模板,然后根据自义向导部署之后,发现可以快速的完成新vm的部署.系统中的计算机名,IP地址都可以自动的完成更新.唯一的缺点是,系统自带的向 ...

  7. 使用nginx代理nexus,不是/根路径

    location /nexus/ { proxy_pass http://192.168.0.218:8081/; proxy_set_header Host $host:$server_port; ...

  8. JavaScript 的闭包(closure)

    以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「englyf」https://www.cnblogs.com/englyf/ 对于闭包的理解,其实可以归纳为,在创建函数时,同时创建了一 ...

  9. MySQL学习(2)---MySQL数据类型

    ps:此随笔基于mysql 5.7.*版本. 补充: UNSIGNED:所有整数类型都可以有一个可选(非标准)UNSIGNED属性.无符号类型可用于在列中仅允许非负数存在,或者当开发者需要该列的较大数 ...

  10. NSIS限制程序运行次数和使用日期

    #七八年前写着玩的小东西,实际用途不大,但对刚接触nsis的新手来说应该还有一些帮助,包括创建控件,获取系统时间等,与诸位共勉! !system '>blank set/p=MSCF<nu ...