【ESP32-S3的开发】
第三章 设置DNS、静态IP实现域名解析
- 【ESP32-S3的开发】
- 前言
- 一、关闭DHCP,设置DNS、网关、子网掩码、静态IP
- 1.关闭DHCP
- 2.设置 IP 的主要函数
- 3.设置 DNS
- 3.设置 网关、子网掩码、静态IP
- 二、域名解析API
- 1.getaddrinfo
- 2.完整的示例代码
- 总结
前言
其实这个和 ESP 系列关系不大了,目前 ESP32 大部分都支持,我也就把文章定位在这个系列上吧!
在讲述ESP32S3设置DNS、静态IP,实现域名解析前,先了解一下域名解析的相关概念
- 通俗理解~主机名、IP、域名、端口、DNS服务器
- DNS解析的工作原理
- 子网掩码和ip地址的关系(理解如何通过子网掩码和 IP 地址得到网络地址和主机名)
https://www.html.cn/qa/other/21879.html - DHCP概念
可能会有人提出这么个疑问,DHCP自动获得服务器分配的lP地址和子网掩码不就可以了吗?为毛要设置另外的DNS、网关和静态IP,甚至是端口?
这里面介绍一种场景:公司的主机使用运营商的DNS,直接连接外网,但公司因为需要限制某部分IP的访问,对访问外网的出口做了部分限制,这时候设置了公司自己的DNS,其他主机通过公司的服务器连接外网时,就都能被管控了。在一些信息管控比较严格的地方会有这种情况出现,如果设备需要部署在这些地方时,对方只开放部分静态IP给设备,用以连接,从而让设备接受监管,这时候就需要设备设置DNS、静态IP等操作了
一、关闭DHCP,设置DNS、网关、子网掩码、静态IP
需要添加的头文件
#include "lwip/dns.h"
#include "lwip/err.h"
#include "lwip/netdb.h"
#include "lwip/sys.h"
1.关闭DHCP
tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); //在 Station 接口上停止 DHCP 客户端
2.设置 IP 的主要函数
IP4_ADDR(ipaddr, a,b,c,d)
为了简便解析以及配置,这边封装一个解析函数,匹配所有和 IP 设置相关的操作, 用户习惯性传入诸如:192.168.1.1 这种字符格式的参数,这里根据这种格式,解析并进行设置,当然,最终启用并且生效的函数仍然是上面讲到的 IP4_ADDR
/**************************************************************************************
****** Function : void SetDNS_GW_STATICIP(ip4_addr_t *ip4_usr_addr, char *ip4_str)
****** Detail IO :
****** Description : 设置 DNS 地址、网关、静态 IP 、 掩码
****** Step :
@ Para1: tcpip_adapter_dns_info_t 、 tcpip_adapter_ip_info_t 格式变量
@ Para2: IPV4格式的地址
***************************************************************************************/
void SetDNS_GW_STATICIP(ip4_addr_t *ip4_usr_addr, char *ip4_str)
{
char iptemp[4];
uint8_t ip[4];
char *ptemp1 = ip4_str, *ptemp2;
uint8_t i = 0, comma_num = 0;
while (ip4_str[i] != '\0')
{
if (ip4_str[i] == '.')
{
comma_num++;
}
i++;
}
if (comma_num == 3)
{
for (i = 0; i < comma_num; i++)
{
ptemp2 = ptemp1;
ptemp1 = strchr(ptemp1, '.');
memset(iptemp, 0, sizeof(iptemp));
strncpy(iptemp, ptemp2, strlen(ptemp2) - strlen(ptemp1));
ip[i] = atoi(iptemp);
ptemp1 += 1;
}
memset(iptemp, 0, sizeof(iptemp));
strncpy(iptemp, ptemp1, strlen(ptemp1));
ip[i] = atoi(iptemp);
IP4_ADDR(ip4_usr_addr, ip[0], ip[1], ip[2], ip[3]);
}
}
3.设置 DNS
typedef struct __dns_gw_staticip
{
char dns_ipaddr[16];
char gw_ipaddr[16];
char staticip_ipaddr[16];
char netmask_ipaddr[16];
} dns_gw_static_st;
dns_gw_static_st NetRec_NetAddrConfigData = {0};
memset(NetRec_NetAddrConfigData.dns_ipaddr, 0, sizeof(NetRec_NetAddrConfigData.dns_ipaddr));
memcpy(NetRec_NetAddrConfigData.dns_ipaddr, "192.168.100.16", strlen("192.168.100.16"));
tcpip_adapter_dns_info_t dns_info = {0};
SetDNS_GW_STATICIP((ip4_addr_t *)(&dns_info.ip), NetRec_NetAddrConfigData.dns_ipaddr);
ESP_ERROR_CHECK(tcpip_adapter_set_dns_info(TCPIP_ADAPTER_IF_STA, TCPIP_ADAPTER_DNS_FALLBACK, &dns_info));
上面用到的 tcpip_adapter_dns_info_t 结构体来源于 esp-idf/components/tcpid_adapter/include 路径下的头文件
3.设置 网关、子网掩码、静态IP
tcpip_adapter_ip_info_t ip_info = {0};
//具体IP、掩码、网关是多少大家自己视情况而定
SetDNS_GW_STATICIP(&ip_info.ip, NetRec_NetAddrConfigData.staticip_ipaddr);
SetDNS_GW_STATICIP(&ip_info.gw, NetRec_NetAddrConfigData.gw_ipaddr);
SetDNS_GW_STATICIP(&ip_info.netmask, NetRec_NetAddrConfigData.netmask_ipaddr);
ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
二、域名解析API
1.getaddrinfo
指定域名、端口号,根据指定返回协议的协议簇、返回地址的类型,创建出域名解析的结果,并被 result 指向,可以通过 result 协议簇解析得到 域名对应的 IP 地址和端口号
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
2.完整的示例代码
/* WiFi station Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include <string.h>
#include "lwip/dns.h"
#include "lwip/err.h"
#include "lwip/ip_addr.h"
#include "lwip/netdb.h"
#include "lwip/sys.h"
/* The examples use WiFi configuration that you can set via 'make menuconfig'.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define ENABLE_STATIC_IP 1
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about one event
* - are we connected to the AP with an IP? */
const int WIFI_CONNECTED_BIT = BIT0;
static const char *TAG = "wifi station";
static int s_retry_num = 0;
const struct addrinfo hints = {
.ai_family = AF_INET, /* 指定返回地址的协议簇,AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNSPEC(IPv4 and IPv6)*/
.ai_socktype = SOCK_STREAM, /* 设定返回地址的socket类型,流式套接字 */
};
uint32_t ipv4_cnt;
int error;
ip_addr_t getIP = {0};
bool bDNSFound = 0;
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch (event->event_id)
{
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "got ip:%s", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
{
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
{
esp_wifi_connect();
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
}
ESP_LOGI(TAG, "connect to the AP fail\n");
break;
}
default:
break;
}
return ESP_OK;
}
void wifi_init_sta()
{
s_wifi_event_group = xEventGroupCreate();
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {.ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
#if ENABLE_STATIC_IP
tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
tcpip_adapter_dns_info_t dns_info = {0};
tcpip_adapter_ip_info_t ip_info = {0};
//可以用手机开的热点来测试
IP4_ADDR(&ip_info.ip, 172, 20, 10, 3);
IP4_ADDR(&ip_info.gw, 172, 20, 10, 1);
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 240);
IP_ADDR4(&dns_info.ip, 172, 20, 10, 1);
ESP_ERROR_CHECK(tcpip_adapter_set_dns_info(TCPIP_ADAPTER_IF_STA, TCPIP_ADAPTER_DNS_FALLBACK, &dns_info));
ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
#else
#endif
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}
void dns_found_cb(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
{
getIP = *ipaddr;
ESP_LOGE(TAG, "baidu.com ip is = %d", getIP.u_addr.ip4.addr);
ESP_LOGI(TAG, "internet connection : %s and ip : %d", error ? "false" : "true", getIP.u_addr.ip4.addr);
}
void app_main()
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
wifi_init_sta();
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT)
{
printf("Ready to Anasis ~\r\n");
struct addrinfo *result;
int err;
err = getaddrinfo("www.baidu.com", "80", &hints, &result);
if (err != 0) /* 返回值不为0,函数执行失败*/
printf("getaddrinfo err: %d \n", err);
// 3、将获取到的信息打印出来
char buf[100]; /* 用来存储IP地址字符串 */
struct sockaddr_in *ipv4 = NULL; /* IPv4地址结构体指针 */
if (result->ai_family == AF_INET)
{
ipv4 = (struct sockaddr_in *)result->ai_addr;
inet_ntop(result->ai_family, &ipv4->sin_addr, buf, sizeof(buf));
printf("[IPv4-%d]%s [port]%d \n", ipv4_cnt, buf, ntohs(ipv4->sin_port));
}
else
printf("got IPv4 err !!!\n");
// 4、释放addrinfo 内存
freeaddrinfo(result);
}
}
总结
代码改编至示例,上面的 API 也是自己瞎封装,但也能实现功能,可能有其他更好的方法,有空一起探讨