简介

在前面的文章中,我们提到了使用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. 【JDBC】学习路径7-转账-commit事务操作

    现在我们要做一个转账系统. 第一章:创建一张新的表格(用户银行账户表格) 在之前的JDBC_01数据库中新增一个表格,名字为:Account   其中,有id.银行卡号.银行密码.账户余额. 创建表格 ...

  2. Dapr中国社区活动之 分布式运行时开发者日 (2022.09.03)

    自2019年10月首次发布以来,Dapr(Distributed Application Runtime,分布式应用运行时)因其"更稳定"."更可靠".&quo ...

  3. Homework7

    问:了解java的反射机制. 答:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法.而对于任意一个对象,都能够调用它的任意一个方法.这种动态获取的信息以及动态调用对象的 ...

  4. 基于 Gitea 服务端渲染的 Jupyter Notebooks

    本指南将向您展示如何通过配置外部渲染器来使 Gitea 呈现 Jupyter Notebooks.当然,你还可以根据本指南来为你的 Gitea 实例配置其他类型的文档渲染器,甚至是二进制文件!相信Gi ...

  5. 使用ESP8266nodeMCU 向微信推送模板数据

    使用HTTPS协议向微信公众号推送消息,(使用ESP8266的低成本实现) 前几天被朋友问到这个东西的实现方式,花了一下午时间研究一下,特此记录.没有排版比较乱. 一丶前往微信公众平台注册微信微信公众 ...

  6. 4、StringBuilder类

    StringBuilder类 一个可变的字符序列,此类提供一个与StringBuffer 兼容的 API,但不保证同步(StringBuilder 不是线程安全). 该类被设计用作 StringBuf ...

  7. SQL注入篇——sqli-labs各关卡方法介绍|1-65

    主要是记下来了每关通过可以采用的注入方式,可能部分关卡的通关方式写的不全面,欢迎指出,具体的获取数据库信息请手动操作一下. 环境初始界面如下: sql注入流程语句: order by 3--+ #判断 ...

  8. Kibana:在Kibana 中定制 time picker 及 指标可视化显示格式

    文章转载自:https://blog.csdn.net/UbuntuTouch/article/details/107066779

  9. Beats: 使用 Filebeat 进行日志结构化

    文章转载自:https://blog.csdn.net/UbuntuTouch/article/details/106688240 生产一个叫做 json_logs 的文件: {"user_ ...

  10. MinIO Server配置指南

    MinIO server在默认情况下会将所有配置信息存到 ${HOME}/.minio/config.json 文件中. 以下部分提供每个字段的详细说明以及如何自定义它们. 配置目录 默认的配置目录是 ...