本文档介绍了阿里云公共DNS iOS SDK的接入和开发方式。
1.概述
阿里云公共DNS SDK是阿里云面向广大移动开发者提供DNS域名解析服务的开发工具包。
开发者利用本SDK,可以在自己的iOS APP中轻松接入阿里云公共DNS,解决域名解析异常的问题,低成本实现域名解析精准调度。您可以参考Demo示例工程源码了解如何使用本SDK。
iOS 14 开始系统原生支持两种标准规范的 Encrypted DNS,分别是 DNS over TLS 与 DNS over HTTPS,您可以参考iOS14原生加密DNS方案了解如何设置阿里云公共DNS为加密DNS默认解析器。
SDK当前版本封装了阿里公共DNS的DoH JSON API,提供接口函数给iOS APP进行域名解析,并且提供了基于TTL和LRU策略的高效域名缓存功能。在公共DNS原有功能的基础上,SDK还可以为用户带来以下优势:
简单易用:
用户仅需集成我们提供的SDK,便可接入阿里云公共DNS业务。接入方法简单易用,为用户提供更为轻松便捷的解析服务。
零延迟:
SDK内部实现了LRU的缓存机制,将每次域名解析后的IP缓存到本地;并且主动更新TTL过期缓存,保证缓存及时有效,从而帮助用户达到域名解析零延迟的效果。
2.SDK集成
移动解析阿里公共DNS提供以下两种集成方式供iOS 开发者选择:
通过 CocoaPods 集成。
手动集成。
2.1 通过CocoaPods集成
1.Podfile中指定仓库位置:(Master仓库不要遗漏)
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/aliyun/aliyun-specs.git'
2.为工程target添加依赖:
pod 'AlicloudPDNS'
2.2 手动集成
通过控制台的链接获取阿里公共DNS iOS SDK。
获取到SDK的
pdns-sdk-ios.framework
后,手动集成到自己工程中。引入系统库:
Foundation.framework
SystemConfiguration.framework
CoreFoundation.framework
CoreTelephony.framework
在工程Build Settings中的Other linker flag 里加入 -ObjC 标志
2.3 SDK初始化
您需先在控制台注册自己的应用,获取应用的唯一标识和鉴权参数、,SDK集成之后进行SDK初始化
为了用户更好的使用SDK,避免出现解析不到IP地址的情况,请将SDK初始化的时间尽量提前。
在application:didFinishLaunchingWithOptions:
初始化SDK。
DNSResolver *resolver = [DNSResolver share];
//setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
//andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
//andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
[resolver setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];
//指定域名自动更新过期缓存,当前限制数组中最多包含10个域名
[resolver setKeepAliveDomains:@[@"用户指定域名1",@"用户指定域名2"]];
//对后续可能要解析的域名进行预加载
[resolver preloadDomains:@[@"域名1", @"域名2", @"域名3"] complete:^{
//所有域名预加载完成
}];
3.API介绍
3.1 常用设置
3.1.1 Account ID和鉴权
必传参数,您在控制台注册自己的应用后,控制台会为此应用生成唯一标识Account ID,鉴权功能来保障用户身份安全,防止被第三方未授权者盗用。用户请参考产品鉴权文档在控制台创建AccessKey,并在APP中通过如下代码设置:
//setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
//andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
//andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
[[DNSResolver share] setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];
3.1.2 解析协议设置
SDK支持设置DNS解析请求协议类型,可自主选择通过HTTP或HTTPS协议解析,具体可通过scheme属性进行设置。
SDK默认并推荐使用HTTPS协议进行解析,因为HTTPS协议安全性更好。公共DNS的计费项是按HTTP的解析次数进行收费,其中HTTPS是按5倍HTTP流量进行计费,开发者可以根据自身实际业务需要选择scheme类型。如下设置:
[DNSResolver share].scheme = DNSResolverSchemeHttps;
3.1.3 设置是否开启缓存
SDK可设置开启缓存功能,如果缓存功能开启,在第一次解析过域名后,后续再解析时,优先获取缓存中的数据,可大大提高解析速度。
SDK默认开启缓存。若要设置不开启缓存,则需要通过以下代码设置:
[DNSResolver share].cacheEnable=NO;
3.1.4 设置域名缓存保持
SDK在缓存功能已开启的情况下,可设置针对某些域名开启缓存保持功能,如果该功能开启,SDK会自动更新这些域名的过期缓存,保障用户缓存数据及时更新,但是可能会带来域名解析次数和客户端流量消耗的增多。如果不设置该功能,那么SDK不会自动进行过期缓存更新,只有当用户调用解析方法时,才会再次进行缓存更新。若要设置某些域名的缓存保持需要通过以下代码设置:
//当前限制数组中最多包含10个域名
[[DNSResolver share] setKeepAliveDomains:@[@"www.taobao.com",@"www.aliyun.com"]];
优势
1. 可及时(ttl过期前)更新记录
2. 配合预加载可降低首次解析延迟(0ms)
劣势
1. 根据ttl *75% 作为重新请求的时机,会带来额外的费用
3.1.5 预解析
由于SDK可以设置开启缓存功能,在第一次解析完域名产生缓存后,后续再次解析此域名时解析速度可提升至0时延。所以建议在app启动后,对app中可能要解析的域名进行预解析。
代码示例:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
DNSResolver *resolver = [DNSResolver share];
//setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
//andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
//andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
[resolver setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];
resolver.cacheEnable = YES;
//对后续可能要解析的域名进行预加载
[resolver preloadDomains:@[@"域名1", @"域名2", @"域名3"] complete:^{
//所有域名预加载完成
}];
return YES;
}
3.2 其它高级设置
3.2.1 设置是否使用服务端IPV6地址
阿里云公共DNS服务支持IPv4、IPv6双栈访问。SDK默认使用IPv4地址访问DNS服务器进行解析。
如果需要通过IPv6地址访问DNS服务器进行解析(当前网络需要支持IPv6),则需要通过以下代码设置:
[DNSResolver share].ipv6Enable = YES;
3.2.2 short模式
阿里公共DNS的DoH JSON API返回数据分为全量JSON和简要IP数组格式。SDK默认为全量JSON格式。
若要设置为简要IP数组格式,则需要通过以下代码设置:
[DNSResolver share].shortEnable = YES;
3.2.3 设置缓存数量
SDK若开启了缓存功能,可以自定义缓存数量(支持范围100~500之间)。
SDK默认的缓存数量为100个域名。若要自定义缓存数量,可通过cacheCountLimit属性进行设置:
[DNSResolver share].cacheCountLimit = 200;
3.2.4 设置是否开启IP测速
SDK可设置开启IP测速,如果IP测速开启,解析结果会优先返回速度最快的解析结果IP地址,数组会按照测速结果由快到慢的顺序排列。
SDK默认没有开启IP测速。若要设置开启IP测速,则需要通过以下代码设置:
[DNSResolver share].speedTestEnable=YES;
3.2.5 设置IP测速方式
SDK可设置IP测速的方式,如果IP测速开启,并且设置该参数为0,则使用ICMP探测;如果设置该参数为80、443或其它支持的端口号,则为socket指定端口探测。
SDK默认该参数为0。若要设置socket指定端口探测,则需要通过以下代码设置:
[DNSResolver share].speedPort = 80;
3.2.6 设置是否开启依据ISP网络区分域名缓存
SDK可设置是否开启依据ISP网络区分域名缓存。如果开启,则在不同网络环境下域名缓存数据分别存储互不影响。如果不开启,则不同网络下使用同一份域名缓存数据。
SDK默认没有开启依据ISP网络区分域名缓存功能,若要开启该功能,则需要通过以下代码设置:
[DNSResolver share].ispEnable = YES;
3.2.7设置否定缓存最大TTL时间
SDK可设置否定缓存(域名没有配置对应的IP地址,导致解析结果没有IP返回的无效缓存)的最大TTL时间,如果设置了该时间,则会限制否定缓存的最大TTL不超过该设置时间。
SDK默认该参数为30s。若要设置否定缓存最大TTL时间,则需要通过以下代码设置:
[DNSResolver share].maxNegativeCache = 30;
3.2.8 设置缓存最大TTL时间
SDK可设置缓存的最大TTL时间,如果设置了该时间,则会限制缓存的最大TTL不超过该设置时间。
SDK默认该参数为3600s。若要设置缓存最大TTL时间,则需要通过以下代码设置:
[DNSResolver share].maxCacheTTL= 3600;
3.2.9 设置是否开启缓存永不过期
[DNSResolver share].immutableCacheEnable = NO;
用户可以设置缓存永不过期功能(true为开启缓存永不过期功能,false为关闭缓存永不过期功能),SDK内部共有三种缓存更新机制详细描述请查看 阿里公共DNS功能问题汇总 。
3.2.10 timeout
timeout
属性为域名解析的超时时间。默认超时时间为3s,用户可自定义超时时间,建议设置在2~5s之间。
4.服务API
代码示例:
/// 预解析域名信息,可在程序启动时调用,解析结果会存入缓存,加快后续域名解析速度
/// 自动感知网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)解析得到适用于当前网络环境的ip
/// @param domainArray 域名数组
/// @param complete 解析完成后回调
- (void)preloadDomains:(NSArray<NSString *> *)domainArray complete:(void(^)(void))complete;
/// 获取域名解析后的ip数组,自动感知网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)得到适用于当前网络环境的ip,
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有ip地址)
- (void)getIpsDataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;
/// 自动感知网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)直接从缓存中获取适用于当前网络环境的ip数组,无需等待.
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain 域名
/// @param enable 是否允许返回过期ip
- (NSArray<NSString *> *)getIpsByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;
/// 获取域名解析后的IPv4信息数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有域名信息)
- (void)getIpv4InfoWithDomain:(NSString *)domain complete:(void(^)(NSArray<DNSDomainInfo *> *domainInfoArray))complete;
/// 获取域名解析后的IPv6信息数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有域名信息)
- (void)getIpv6InfoWithDomain:(NSString *)domain complete:(void(^)(NSArray<DNSDomainInfo *> *domainInfoArray))complete;
/// 获取域名解析后的IPv4信息
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有域名信息中随机一个)
- (void)getRandomIpv4InfoWithDomain:(NSString *)domain complete:(void(^)(DNSDomainInfo *domainInfo))complete;
/// 获取域名解析后的IPv6信息
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有域名信息中随机一个)
- (void)getRandomIpv6InfoWithDomain:(NSString *)domain complete:(void(^)(DNSDomainInfo *domainInfo))complete;
/// 获取域名解析后的IPv4地址数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有ip地址)
- (void)getIpv4DataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;
/// 获取域名解析后的IPv6地址数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有ip地址)
- (void)getIpv6DataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;
/// 获取域名解析后的IPv4地址
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有ip地址中随机一个)
- (void)getRandomIpv4DataWithDomain:(NSString *)domain complete:(void(^)(NSString *data))complete;
/// 获取域名解析后的IPv6地址
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有ip地址中随机一个)
- (void)getRandomIpv6DataWithDomain:(NSString *)domain complete:(void(^)(NSString *data))complete;
/// 预解析域名IPv4信息,可在程序启动时调用,将解析结果存入缓存,加快后续域名解析速度
/// @param domainArray 域名数组
/// @param complete 解析完成后回调
- (void)preloadIpv4Domains:(NSArray<NSString *> *)domainArray complete:(void(^)(void))complete;
/// 预解析域名IPv6信息,可在程序启动时调用,将解析结果存入缓存,加快后续域名解析速度
/// @param domainArray 域名数组
/// @param complete 解析完成后回调
- (void)preloadIpv6Domains:(NSArray<NSString *> *)domainArray complete:(void(^)(void))complete;
/// 直接从缓存中获取ipv4解析结果,无需等待.
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain 域名
/// @param enable 是否允许返回过期ip
- (NSArray<NSString *> *)getIpv4ByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;
/// 直接从缓存中获取ipv6解析结果,无需等待.
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain 域名
/// @param enable 是否允许返回过期ip
- (NSArray<NSString *> *)getIpv6ByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;
///统计信息收集
-(NSArray *)getRequestReportInfo;
5. API使用示例
5.1 设置基本信息
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//唯一初始化方式
DNSResolver *resolver = [DNSResolver share];
//setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
//andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
//andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
[resolver setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];
//指定域名自动更新过期缓存,当前限制数组中最多包含10个域名
[resolver setKeepAliveDomains:@[@"用户指定域名1",@"用户指定域名2"]];
//对后续可能要解析的域名进行预加载,可提前获取解析结果并存入缓存
[resolver preloadDomains:@[@"域名1", @"域名2", @"域名3"] complete:^{
//所有域名预加载完成
}];
return YES;
}
5.2 域名解析接口
SDK提供了多种域名解析方法供用户选择和使用,可在DNSResolver.h
头文件中查看。 下面以其中一个自动区分网络环境(IPv4-only、IPv6-only、IPv4和IPv6双栈)的解析方法为例,进行说明。
接口声明:
/// 获取域名解析后的ip数组,自动区分网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)得到适用于当前网络环境的ip
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain 域名
/// @param complete 回调(所有ip地址)
- (void)getIpsDataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;
接口调用示例:
[[DNSResolver share] getIpsDataWithDomain:@"www.taobao.com" complete:^(NSArray<NSString *> *dataArray) {
//dataArray为域名www.taobao.com所对应的IP地址数组
if (dataArray.count > 0) {
//TODO: 使用IP地址进行url连接
}
}];
5.3 直接从缓存中获取解析结果
接口声明:
/// 自动区分网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)直接从缓存中获取适用于当前网络环境的ip数组,无需等待.
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain 域名
/// @param enable 是否允许返回过期ip
- (NSArray<NSString *> *)getIpsByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;
调用示例:
NSArray *result = [[DNSResolver share] getIpsByCacheWithDomain:@"域名" andExpiredIPEnabled:YES];
//拿到缓存结果
if (result.count > 0) {
//TODO: 使用ip地址进行url连接
}
注意:直接从缓存中获取缓存结果,速度较快,但无缓存、或者有缓存但缓存的解析结果已过期并且enable为NO时,返回结果会为nil。
5.4 删除缓存
接口声明:
///hostArray为需要清除的host域名数组。如果需要清空全部数据,传nil或者空数组即可
-(void)clearHostCache:(NSArray <NSString *>*)hostArray;
调用示例:
[[DNSResolver share] clearHostCache:@[@"域名1", @"域名2"]];
5.5 统计信息收集
接口声明:
///统计信息收集
-(NSArray *)getRequestReportInfo;
调用示例:
NSArray *array = [[DNSResolver share] getRequestReportInfo];
数据格式:
(
{
avgRtt = "1"; // 域名平均解析时间ms
cacheDnsNum = 0; // 命中缓存次数
domain = "www.taobao.com"; // 解析的域名
gobackLocaldnsNum = 0; // 降级到LocalDNS的次数
localErro = 0; // LocalDNS解析失败次数
maxRtt = "60"; // 域名最大解析时间ms
noPermissionErro = 0; // 用户鉴权失败次数
noResponseErro = 0; // 请求超时无应答次数
requestPDnsNum = 1; // 递归查询的次数
sp = 中国移动; // 运营商名字
successNum = 1; // 解析成功次数
timeoutErro = 0; // 网络超时错误次数
type = 28; // ip类型1代表ipv4、28代表ipv6
urlParameterErro = 0; // 请求参数格式错误次数
urlPathErro = 0; // URL错误次数
}
......
);
6.注意事项
pdns-sdk-ios.framework
支持最低版本为iOS9.0。使用HTTP协议请求时,需要在
Info.plist
中设置App Transport Security Settings->Allow Arbitrary Loads
为YES
。通过阿里公共DNS获得域名的IP地址后,客户端可以使用这个IP发送业务请求,HTTP请求头的Host字段需改为原来的域名。
例如:
//ip为原域名解析后的ip地址 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", ip]]; NSMutableURLRequest *mutableReq = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval: 10]; //设置host [mutableReq setValue:@"原域名" forHTTPHeaderField:@"host"];
为了保证用户业务正常,当通过SDK没有获取到域名的IP时,需要开发者使用原域名的地址进行请求来作为兜底,示例代码如下:
NSArray *array = [[DNSResolver share] getIpsByCacheWithDomain:@"原域名" andExpiredIPEnabled:YES]; NSString *ip = array.firstObject; if (ip.length > 0) { //替换url中的host为ip进行接口请求 }else{ //添加兜底处理(使用原url进行接口请求) }
当存在中间HTTP代理时,客户端发起的请求中请求行会使用绝对路径的URL,在您开启公共DNS并采用IP URL进行访问时,中间代理将识别您的IP信息并将其作为真实访问的HOST信息传递给目标服务器,这时目标服务器将无法处理这类无真实HOST信息的HTTP请求。我们建议您检测当前设备是否开启了网络代理,然后在代理模式下不使用公共DNS进行域名解析。
为帮助用户更快的使用阿里公共DNS iOS SDK,我们为开发者提供Demo程序,您可以
下载到本地作为参考。请点击这里,下载Demo程序。