ESP8266开发之旅 网络篇⑫ 域名服务——ESP8266mDNS库

时间:2023-03-09 00:20:24
ESP8266开发之旅 网络篇⑫ 域名服务——ESP8266mDNS库

1. 前言

    前面的博文中,无论是作为client端还是server端,它们之间的通信都是通过具体的IP地址来寻址。通过IP地址来寻址,本身就是一个弊端,用户怎么会去记住这些魔法数字呢?那么有没有办法可以通过其他方式来映射到IP地址,我们只需要记住有意义的名字呢?
    一般来说,我们遇到问题,很多同学包括我自己,都很喜欢去baidu或者google,那么baidu或者google是怎么映射到IP地址呢?其实,这里就用到了一种叫做别名的技术,DNS服务。
    DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。

  • DNS协议运行在UDP协议之上,使用端口号53。
  • 关于UDP通信,请回顾博主 ESP8266开发之旅 网络篇⑩ UDP服务,重点理解UDP广播;

    但是,博主本次讲的并不是DNS服务,而是以它来引入域名和IP地址相互映射的概念,并且引入本帖子的重点内容 ESP8266mDNS库。
    其中,ESP8266mDNS是采用mDNS协议,跟DNS服务是两种不同的概念,请不要混淆。

2. mDNS详解

2.1 mDNS协议

    mDNS,即是组播dns(Multicast DNS),mDNS主要实现了在没有传统DNS服务器的情况下使局域网内的主机实现本地发现和域名访问,使用端口为5353,遵从dns协议,使用现有的DNS信息结构、名语法和资源记录类型,并且没有指定新的操作代码或者响应代码。
    这里需要注意几个点:

  1. 没有传统DNS服务器
  2. 局域网本地发现和域名发现
  3. mDNS基于UDP协议,确切来说,运用了UDP广播

    在局域网中,设备和设备之间相互通信需要知道对方的具体IP地址。而通常情况下,如果我们不去设置静态ip地址,一般都是通过DHCP client动态分配IP地址,这个时候如果没有可视化界面去查看,一般我们是无法得知具体的IP地址,我们也就无法与之通信。这个时候,就是mDNS大显神威的时候,我们通过具体的名字去访问就好。

2.2 组播地址

    mdns 使用组播地址为: 224.0.0.251 (ipv6: FF02::FB) 端口为5353,mdns 是用于局域网内部的,并且主机的域名为.local 结尾;

2.3 工作原理

    每个进入局域网的主机,如果开启了mDNS服务的话,都会向局域网内的所有主机组播一个消息,我是谁,和我的IP地址是多少。然后其他也开启了mDNS服务的主机就会响应,也会告诉你,它是谁,它的IP地址是多少。当然,具体实现要比这个复杂点。
    比如,A主机进入局域网,开启了 mDNS 服务,并向 mDNS 服务注册以下信息:我提供 FTP 服务,我的IP是 192.168.1.101,端口是 21。当B主机进入局域网,并向 B 主机的 mDNS 服务请求,我要找局域网内 FTP 服务器,B主机的 mDNS 就会去局域网内向其他的 mDNS 询问,并且最终告诉你,有一个IP地址为 192.168.1.101,端口号是 21 的主机,也就是 A 主机提供 FTP 服务,所以 B 主机就知道了 A 主机的 IP 地址和端口号了。
    大概的原理就是这样子,mDNS提供的服务要远远多于这个,当然服务多但并不复杂。

2.4 mDNS Service Discovery(mDNS-SD)

    在前面,我们说到开启了mDNS服务的主机在进入局域网后,会向局域网内的所有主机组播一个消息,我是谁,我的IP地址是多少,然后其他也开启了mDNS服务的主机也会响应告诉你它是谁,ip地址是多少。在ESP8266mDNS库中,除了前面的mDNSResponder功能外,还包括了mDNS-SD(服务注册,服务查询,服务解析)功能,通过方法可以获取到局域网内具体的服务信息。
    举个例子:在局域网内,要进行打印服务,必须要先知道打印服务器的IP地址。此IP地址一般由IT部门人员负责分配,然后他还得全公司发邮件来通知各个部门人员该IP地址。有了mDNS-SD服务,打印服务器注册一个打印服务,名为“print service”之类的。当有人需要打印服务时,一般会先搜索局域网的打印服务器。但是由于不知道打印服务器的IP地址,用户只能通过诸如“print service”的名字去查找打印机,在mDNS-SD的帮助下,用户最终能找到注册了“print service”名字的打印机,并获得它的IP地址以及端口号。

注意点:

  • 思考是否局域网会出现同样名字的服务?(局域网内部不能有重名的host或者service)

    ESP8266使用mDNS服务,请在代码中加入以下头文件:

#include <ESP8266mDNS.h>

3. ESP8266mDNS库

    mDNS用到了ESP8266mDNS库,所以接下来我们来了解一下这个库。使用这个库的时候ESP8266可以在AP模式或是以STA模式接入局域网。局域网中的其他开启mDNS服务的设备就可以通过网址访问ESP8266了;

  • Windowx系统下,一般都是没有安装mDNS服务的,读者可以安装 Bonjour window,这是由苹果公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。Bonjour技术在Mac OS以及Itunes、Iphone上都得到了广泛应用。
  • 苹果系统,默认有mDNS服务;
  • 安卓系统手机,也自带了mDNS服务,它是一个名为mdnsd的程序;

所以大家基本上可以放心使用mDNS服务,基本上的大平台都支持了。

    老规矩,先上一个博主总结的百度脑图:
ESP8266开发之旅 网络篇⑫ 域名服务——ESP8266mDNS库

总体上,根据功能可以把方法分为2大类:

  • 管理mDNS服务;
  • 管理mDNS-SD服务;

3.1 管理mDNS服务

3.1.1 begin —— 开启mDNS服务

函数说明:

/**
 * 启动mDNS服务
 * @param  hostName  const char* (与IP地址映射的域名)
 * @return bool 是否启动成功
 */
bool begin(const char* hostName);

3.1.2 notifyAPChange —— 当AP更新或者禁止后调用

函数说明:

/**
 * 通知AP改变
 */
void notifyAPChange();

3.2 管理mDNS服务

3.2.1 addService —— 注册服务

函数说明:

/**
 * 注册服务
 * @param  service  服务名字
 * @param  proto    服务协议
 * @param  port     服务端口
 */
void addService(char *service, char *proto, uint16_t port);
void addService(const char *service, const char *proto, uint16_t port);

3.2.2 queryService —— 查询服务

函数说明:

/**
 * 查询服务
 * @param  service  服务名字
 * @param  proto    服务协议
 * @return count    返回符合条件的服务个数
 */
int queryService(char *service, char *proto);
int queryService(const char *service, const char *proto);
int queryService(String service, String proto);

3.2.3 hostname —— 获取查询服务的主机名

函数说明:

/**
 * 返回服务域名
 * @param  idx  服务索引
 */
String hostname(int idx);

3.2.4 IP —— 获取查询服务的IP地址

函数说明:

/**
 * 返回服务IP地址
 * @param  idx  服务索引
 */
IPAddress IP(int idx);

3.2.5 port —— 获取查询服务的端口号

函数说明:

/**
 * 返回服务端口
 * @param  idx  服务索引
 */
uint16_t port(int idx);

4. 实例代码

4.1 演示ESP8266 mDNS responder功能

实例说明
    演示ESP8266 mDNS responder功能,烧录以下代码到NodeMcu,然后在电脑端输入以下地址:

http://esp8266.local/

以域名方式来访问webserver。

实例准备

实例源码

/**
 * Demo:
 *    演示ESP8266 mDNS responder功能
 * @author 单片机菜鸟
 * @date 2019/01/28
 */
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>

//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

const char* AP_SSID     = "TP-LINK_5344"; // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "6206908you11011010";  // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const unsigned long BAUD_RATE = 115200;// serial connection speed

//声明一下函数
void initBasic(void);
void initWifi(void);
void initWebServer(void);
void initmDNS(void);

ESP8266WebServer server(80);

/**
 * 处理根目录uri请求
 * uri:http://server_ip/
 */
void handleRoot() {
  DebugPrintln("handleRoot");
  server.send(200, "text/html", "Hello From ESP8266 mDNS demo");
}

/**
 * 处理无效uri
 * uri:http://server_ip/xxxx
 */
void handleNotFound() {
  DebugPrintln("handleNotFound");
  //打印无效uri的信息 包括请求方式 请求参数
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void setup(void) {
  initBasic();
  initWifi();
  initWebServer();
  initmDNS();
}

void loop(void) {
  server.handleClient();
}

/**
 * 初始化基础功能:波特率
 */
void initBasic(){
  DebugBegin(BAUD_RATE);
}

/**
 * 初始化wifi模块:工作模式 连接网络
 */
void initWifi(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PSK);
  DebugPrintln("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrint("Connected to ");
  DebugPrintln(AP_SSID);
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}

/**
 * 初始化webserver
 */
void initWebServer(){
  //以下配置uri对应的handler
  server.on("/", handleRoot);
  server.on("/inline", []() {
    DebugPrintln("handleInline");
    server.send(200, "text/plain", "this works as well");
  });
  server.onNotFound(handleNotFound);
  //启动webserver
  server.begin();
  DebugPrintln("HTTP server started");
}

/**
 * 初始化mDNS
 */
void initmDNS(){
  if (!MDNS.begin("esp8266")) {
    DebugPrintln("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  DebugPrintln("mDNS responder started,please input http://esp8266.local/ in your browser after install Bonjour");
}

实例结果:
ESP8266开发之旅 网络篇⑫ 域名服务——ESP8266mDNS库

ESP8266开发之旅 网络篇⑫ 域名服务——ESP8266mDNS库

4.2 演示ESP8266 mDNS 发现服务功能

实例说明
    演示ESP8266 mDNS 发现服务功能,分别烧录代码到两块NodeMcu。

实例准备

  • 两块NodeMcu开发板

实例源码

/*
  演示ESP8266 mDNS 发现服务功能

  注意:
  - 输入你的 WiFi SSID 和 password.
  - 烧写到两块 ESP8266  板子
*/

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>

const char* ssid     = "...";
const char* password = "...";
char hostString[16] = {0};

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("\r\nsetup()");

  sprintf(hostString, "ESP_%06X", ESP.getChipId());
  Serial.print("Hostname: ");
  Serial.println(hostString);
  WiFi.hostname(hostString);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(250);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (!MDNS.begin(hostString)) {
    Serial.println("Error setting up MDNS responder!");
  }
  Serial.println("mDNS responder started");
  //往mDNS里面注册服务
  MDNS.addService("esp", "tcp", 8080); 

  Serial.println("Sending mDNS query");
  //查找服务
  int n = MDNS.queryService("esp", "tcp"); // Send out query for esp tcp services
  Serial.println("mDNS query done");
  if (n == 0) {
    Serial.println("no services found");
  } else {
    Serial.print(n);
    Serial.println(" service(s) found");
    for (int i = 0; i < n; ++i) {
      // 打印查找到的服务具体信息
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(MDNS.hostname(i));
      Serial.print(" (");
      Serial.print(MDNS.IP(i));
      Serial.print(":");
      Serial.print(MDNS.port(i));
      Serial.println(")");
    }
  }
  Serial.println();
  Serial.println("loop() next");
}

void loop() {
  // put your main code here, to run repeatedly:
}

5. 总结

本节主要讲解mDNS在8266上的域名映射应用,整体上内容不多,但是是一个非常有用的知识点,希望读者能仔细学习。并且可以的话,回顾一下以下两个篇章:

  • ESP8266开发之旅 网络篇⑩ UDP服务
  • ESP8266开发之旅 网络篇⑪ WebServer——ESP8266WebServer库的使用