1. 前言

    Arduino for esp8266中有两个DNS服务相关的库:

  1. ESP8266mDNS库
  • 这个库是mDNS库,使用这个库的时候ESP8266可以在AP模式或是以STA模式接入局域网;
  • 局域网中的其他开启mDNS服务的设备就可以通过网址访问ESP8266;
  • 这个博主在之前的博文中有讲解过 —— ESP8266开发之旅 网络篇⑫ 域名服务——ESP8266mDNS库
  • 有个明显缺点,需要其他设备也开启mDNS服务,像window系统需要安装一个 Bonjour,同时域名为 xxx.local;
  1. DNSServer库
  • 这个库就是本文将用到的建立DNS服务的方式,使用该库时ESP8266必须工作在AP模式下;
  • 这个属于真正意义的精简版DNS服务器;
  • DNSServer运行于 UDP服务,请回顾:ESP8266开发之旅 网络篇⑩ UDP服务;

看看DNSServer具体工作原理:

  • 在这里,DNS服务器唯一的作用就是把域名转成对应映射的地址

2. DNS server库

    ESP8266使用DNS服务(一般和WebServer服务一起使用,WebServer请回顾 ESP8266开发之旅 网络篇⑪ WebServer——ESP8266WebServer库的使用),请在代码中加入以下头文件:

#include <DNSServer.h>

    讲解方法之前,先来看看博主总结的百度脑图:

常用方法非常简单,就4个方法,毕竟DNS服务器的功能比较单一。

2.1 start —— 启动DNS服务器

函数说明:

/**
 * 启动DNS服务器
 * @param port  端口号 DNS端口一般占用53
 * @param domainName 映射域名
 * @param resolvedIP 映射IP地址
 * @return  bool 是否启动成功
 */
bool start(const uint16_t &port,
           const String &domainName,
           const IPAddress &resolvedIP);

源码说明:

bool DNSServer::start(const uint16_t &port, const String &domainName,
                     const IPAddress &resolvedIP)
{
  _port = port;
  _buffer = NULL;
  _domainName = domainName;
  _resolvedIP[0] = resolvedIP[0];
  _resolvedIP[1] = resolvedIP[1];
  _resolvedIP[2] = resolvedIP[2];
  _resolvedIP[3] = resolvedIP[3];
  downcaseAndRemoveWwwPrefix(_domainName);
  //启动了UDP服务 监听客户端向DNS服务器查询域名
  return _udp.begin(_port) == 1;
}

2.2 stop —— 停止DNS服务器

函数说明:

/**
 * 停止DNS服务器
 */
void stop();

源码说明:

void DNSServer::stop()
{
  //停止udp服务
  _udp.stop();
  free(_buffer);
  _buffer = NULL;
}

2.3 setErrorReplyCode —— 设置错误响应码

函数说明:

/**
 * 设置错误响应码
 * @param  DNSReplyCode  错误响应码
 */
void setErrorReplyCode(const DNSReplyCode &replyCode);

DNSReplyCode 定义如下:

enum class DNSReplyCode
{
  NoError = 0,
  FormError = 1,
  ServerFailure = 2, //服务错误
  NonExistentDomain = 3,
  NotImplemented = 4,//未定义
  Refused = 5,//拒绝访问
  YXDomain = 6,
  YXRRSet = 7,
  NXRRSet = 8
};

2.4 processNextRequest —— 处理DNS请求服务

函数说明:

/**
 * 处理DNS请求服务
 */
void processNextRequest();

源码说明:

/**
 * 处理DNS请求服务
 */
void DNSServer::processNextRequest()
{
  //获取UDP请求内容
  _currentPacketSize = _udp.parsePacket();
  if (_currentPacketSize)
  {
    if (_buffer != NULL) free(_buffer);
    _buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char));
    if (_buffer == NULL) return;
    _udp.read(_buffer, _currentPacketSize);
    _dnsHeader = (DNSHeader*) _buffer;

    //判断请求是否查找域名映射的IP地址 *在这里有非常特殊作用 读者请注意
    if (_dnsHeader->QR == DNS_QR_QUERY &&
        _dnsHeader->OPCode == DNS_OPCODE_QUERY &&
        requestIncludesOnlyOneQuestion() &&
        (_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName)
       )
    {
      //返回IP地址
      replyWithIP();
    }
    else if (_dnsHeader->QR == DNS_QR_QUERY)
    {
      //响应错误码
      replyWithCustomCode();
    }

    free(_buffer);
    _buffer = NULL;
  }
}

/**
 * 响应域名对应的IP地址
 */
void DNSServer::replyWithIP()
{
  if (_buffer == NULL) return;
  _dnsHeader->QR = DNS_QR_RESPONSE;
  _dnsHeader->ANCount = _dnsHeader->QDCount;
  _dnsHeader->QDCount = _dnsHeader->QDCount;
  //_dnsHeader->RA = 1;  

  _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
  _udp.write(_buffer, _currentPacketSize);

  _udp.write((uint8_t)192); //  answer name is a pointer
  _udp.write((uint8_t)12);  // pointer to offset at 0x00c

  _udp.write((uint8_t)0);   // 0x0001  answer is type A query (host address)
  _udp.write((uint8_t)1);

  _udp.write((uint8_t)0);   //0x0001 answer is class IN (internet address)
  _udp.write((uint8_t)1);

  _udp.write((unsigned char*)&_ttl, 4);

  // Length of RData is 4 bytes (because, in this case, RData is IPv4)
  _udp.write((uint8_t)0);
  _udp.write((uint8_t)4);
  _udp.write(_resolvedIP, sizeof(_resolvedIP));
  _udp.endPacket();

  #ifdef DEBUG_ESP_DNS
    DEBUG_ESP_PORT.printf("DNS responds: %s for %s\n",
            IPAddress(_resolvedIP).toString().c_str(), getDomainNameWithoutWwwPrefix().c_str() );
  #endif
}

/**
 * 响应错误码
 */
void DNSServer::replyWithCustomCode()
{
  if (_buffer == NULL) return;
  _dnsHeader->QR = DNS_QR_RESPONSE;
  _dnsHeader->RCode = (unsigned char)_errorReplyCode;
  _dnsHeader->QDCount = 0;

  _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
  _udp.write(_buffer, sizeof(DNSHeader));
  _udp.endPacket();
}

注意点:

  • ESP8266 DNSServer 运行于UDP协议之上
  • ESP8266 DNSServer只能支持一个域名映射
  • 当ESP8266设置的域名为“*”,意味着所有请求都会被链接到该IP地址,我们可以利用这一点做一些特殊操作;

3. 实例

3.1 访问主机名

实验说明

在手机浏览器访问 "www.danpianji.com"会显示“Hello World”

实验源码

/**
 * 功能描述:在手机浏览器访问 "www.danpianji.com"会显示“Hello World”
 */

#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 1, 1);
DNSServer dnsServer;
ESP8266WebServer webServer(80);

void setup() {

  DebugBegin(115200);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  WiFi.softAP("DNSServer example");

  // modify TTL associated  with the domain name (in seconds)
  // default is 60 seconds
  dnsServer.setTTL(300);
  // set which return code will be used for all other domains (e.g. sending
  // ServerFailure instead of NonExistentDomain will reduce number of queries
  // sent by clients)
  // default is DNSReplyCode::NonExistentDomain
  dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);

  // 启动DNS server,映射主机名为 www.danpianji.com
  bool status = dnsServer.start(DNS_PORT, "www.danpianji.com", apIP);

  if(status){
      DebugPrintln("start dnsserver success.");
  }else{
     DebugPrintln("start dnsserver failed.");
  }

  // simple HTTP server to see that DNS server is working
  webServer.onNotFound([]() {
    String message = "Hello World!\n\n";
    message += "URI: ";
    message += webServer.uri();

    webServer.send(200, "text/plain", message);
  });
  webServer.begin();
}

void loop() {
  dnsServer.processNextRequest();
  webServer.handleClient();
}

实验结果

会看到一个DNSServer example开放式AP热点,连接上:

然后在手机浏览器访问 www.danpianji.com

3.2 Portal 认证

实验说明

通常,当我们连上一些wifi热点,只要没有认证手机号码信息,无论访问哪个页面都会弹出一个web认证页面(这就是商家用来收集手机用户信息的一种手段,慎重),这就是 Portal 认证。

Portal服务器也就是接收Portal客户端认证请求的服务器端系统,其主要作用是提供免费的门户服务和基于Web认证的界面,以及接入设备交互认证客户端的认证信息。其中的Web认证方案首先需要给用户分配一个地址,用于访问门户网站。

Portal 基于浏览器,采用的是B/S构架, 对不同权限的用户下发不同的VLAN 访问不同的服务器资源,当通过认证后才能访问internet资源,Portal认证方式不需要安装认证客户端, 减少了客户端的维护工作量,便于运营。

可以在Portal页面上开展业务拓展,如展示商家广告, 联系方式等基本信息。Portal广泛应用于运营商、学校等网络。

通过DNSServer,我们也可以使用到Portal认证

实验源码

/**
 * 功能描述:portal认证
 */

#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 1, 1);
DNSServer dnsServer;
ESP8266WebServer webServer(80);

String responseHTML = ""
                      "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>"
                      "<h1>Hello World!</h1><p>This is a captive portal example. All requests will "
                      "be redirected here.</p></body></html>";

void setup() {
  DebugBegin(115200);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  WiFi.softAP("DNSServer CaptivePortal example");

  // 所有请求都映射到一个具体地址
  dnsServer.start(DNS_PORT, "*", apIP);

  // replay to all requests with same HTML
  webServer.onNotFound([]() {
    DebugPrintln("webServer handle.");
    webServer.send(200, "text/html", responseHTML);
  });
  webServer.begin();
}

void loop() {
  dnsServer.processNextRequest();
  webServer.handleClient();
}

实验结果
会看到一个 DNSServer CaptivePortal example 开放式AP热点,连接上:

然后在手机浏览器访问 www.danpianji.com

4. 总结

DNSServer也是相对来说非常重要的一章,特别对于web配网,需要使用到它,请读者认真理解使用。

ESP8266开发之旅 网络篇⑮ DNSServer——真正的域名服务的更多相关文章

  1. ESP8266开发之旅 网络篇⑯ 无线更新——OTA固件更新

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  2. ESP8266开发之旅 网络篇⑦ TCP Server & TCP Client

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  3. ESP8266开发之旅 网络篇⑧ SmartConfig——一键配网

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  4. ESP8266开发之旅 网络篇⑨ HttpClient——ESP8266HTTPClient库的使用

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  5. ESP8266开发之旅 网络篇⑩ UDP服务

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  6. ESP8266开发之旅 网络篇⑪ WebServer——ESP8266WebServer库的使用

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  7. ESP8266开发之旅 网络篇⑬ SPIFFS——ESP8266 SPIFFS文件系统

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  8. ESP8266开发之旅 网络篇⑭ web配网

    1. 前言     目前,市面上流行多种配网方式: WIFI模块的智能配网(SmartConfig以及微信AirKiss配网) SmartConfig 配网方式 请参考博主之前的博文 ESP8266开 ...

  9. ESP8266开发之旅 网络篇③ Soft-AP——ESP8266WiFiAP库的使用

    1. 前言     在前面的篇章中,博主给大家讲解了ESP8266的软硬件配置以及基本功能使用,目的就是想让大家有个初步认识.并且,博主一直重点强调 ESP8266 WiFi模块有三种工作模式: St ...

随机推荐

  1. 解决Android数据库异步操作的大问题

    前言 相信大家在开发过程中,也遇到过下面的这种异常: java.lang.IllegalStateException: attempt to re-open an already-closed obj ...

  2. [Leetcode] 第148题 排序链表

    一.题目描述 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3 输出: 1->2->3->4 示 ...

  3. at org.apache.jsp.WEB_002dINF.pages.login_jsp._jspInit( login_jsp.java:22)

    SEVERE: Servlet.service() for servlet jsp threw exception java.lang.NullPointerException at org.apac ...

  4. [转载 ]五种常见的 PHP 设计模式

    五种常见的 PHP 设计模式 策略模式 策略模式是对象的行为模式,用意是对一组算法的封装.动态的选择需要的算法并使用. 策略模式指的是程序中涉及决策控制的一种模式.策略模式功能非常强大,因为这个设计模 ...

  5. 动态set mybatis与ibatis的写法

    mybatis: <set> <if test="obj.buyerId != null"> buyerId = #{obj.buyerId}, </ ...

  6. pyinstaller程序打包工具

    PyInstaller是一个能将Python程序转换成单个可执行文件的程序, 操作系统支持Windows, Linux, Mac OS X, Solaris和AIX.并且很多包都支持开箱即用,不依赖环 ...

  7. Golang 接口与反射知识要点

    目录 Golang 接口与反射知识要点 1. 接口类型变量 2. 类型断言 3. 鸭子类型 4. 反射机制 5. reflect 包 TypeOf().ValueOf() Type().Kind() ...

  8. .net core 3.0 Signalr - 05 使用jwt将用户跟signalr关联

    Signalr是以Group.Connect为核心来进行推送,比如,给某个组.某个连接来推送,但实际场景中,核心应该是某个组.某个人:然而一个人可以对应多个连接(浏览器多个tab页):本节就来介绍下自 ...

  9. 微信小程序 实现多行文字 超出部分省略号显示

    在开发小程序: 澳买 的 时候 遇到一个棘手的问题: 当搜索澳洲产品,获取产品列表的时候,有时候产品的名称翻译成中文特别长 我们不能全部在有限的列表里面把产品名都显示出来,这样格式不好控制,显示 出来 ...

  10. vue-cli 脚手架安装

    1.安装node;选择适合自己系统的文件,下载一路next , a安装成功后,打开运行输入cmd 进入命令行: 在命令行工具中输入 npm -v  检查版本号 如果出现 则安装成功:(npm为node ...