Android 网络性能优化(2)DNS优化
客户端输入一个网址即域名,到本地DNS服务器
本地服务器DNS查找域名缓存,如果命中对应的ip地址,则返回该ip,如果没有命中,则发请求给根域名服务器请求解析
根域名解析好后,返回 顶级域名服务器的地址
本地DNS 向顶级域名服务器发送请求。
顶级域名查看缓存,有没有对应ip,如果有直接返回,如果没有,则解析该域名,查找到目标 权威域名服务器的地址
本地DNS 向 权威域名服务器发送请求
权威域名服务器在数据库中查表,返回对应的ip地址
本地DNS服务器 把 ip地址 返回给 客户端,并且缓存该地址。
[](()2.4 DNS坑点
[](()2.4.1 DNS劫持
DNS劫持又叫域名劫持,是指攻击者通过某种手段篡改了某个域名的解析结果,使得指向该域名的IP变成了另一个IP。
这里不过多讲解,因为内容很多,而且和本章没啥关联。
PS:你在看这篇文章时,左下角、右下角如果出现了广告,这很有可能能使DNS被劫持了,当然了,这也可能就是官方自己的广告= =。
[](()2.4.2 DNS 服务器故障
每年都会经常出现 一些DNS服务器的故障,如果你恰好遇上,体验就很不好。这是因为DNS寻址是我们访问服务器的必要过程,DNS服务器故障直接导致寻不到址,或者寻址速度降低。
[](()2.4.3 DNS 调度不准确
说不好一些DNS服务器里面的算法写的有点水,调度性能低下,这样会也会直接导致我们寻址速度变慢。
[](()2.5 小结
总的来说,DNS寻址有下面几个缺点:
- 不稳定
遭遇DNS劫持或DNS服务器宕机
- 不准确
某些小运营商没有 DNS 服务器,直接调用其他运营商的 DNS 服务器,最终直接跨网传输
- 不及时
运营商可能会修改 DNS 的 TTL(Time-To-Live,DNS 缓存时间),导致 DNS 的修改,延迟生效。
除了运营商,DNS服务器本身的索引算法也可能会有调度的问题(不过概率蛮小的)
所以看到DNS其实也有蛮多缺点的,那么我们该怎么去优化呢?
首先我们是开发App的,我们面向的问题是App在进行网络请求时,更快更稳定。那么最暴力的方法就是绕过DNS,我们使用DNS的原因是我们记不住冗长的ip地址,但在App开发中,App程序本身是可以记得住这些ip地址的,那就让它去记就行了,让它直接访问ip地址,就减少了DNS的寻址过程。
而 ip直连 就是以此为基础的实现方案。
[](()3. ip直连
=========================================================================
[](()3.1 原理
客户端从服务器拉取配置文件,配置文件包含了域名和ip地址的映射,客户端在之后的网络请求中,在配置中根据域名找到ip,直接连接这个ip地址进行请求。
而最开始拉取配置文件这个动作是经过DNS的,而之后都是绕过DNS的。
ip直连有以下的优势:
在之后的连接中,都会绕过DNS,这样可以提高连接速率
可以降低DNS劫持的风险
一般都能拿到多个ip地址,所以可以进跑马比较,就快连接,这样可以提高连接质量
[](()3.2 一个Http的直连demo
举一个最简单的直连的例子。假如我们需要直连百度,那我们先在cmd中,直接获取到百度的ip:
ping www.baidu.com
正在 Ping www.a.shifen.com [14.215.177.39] 具有 32 字节的数据:
来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50
来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50
来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50
来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50
14.215.177.39 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 6ms,最长 = 6ms,平均 = 6ms
上面就已经拿到了百度的ip。接着在我们App中运用:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 直接加载这个页面
webview.loadUrl(“http://14.215.177.39/”)
}
}
这就是最简单的ip直连,绕过了DNS的域名寻址。 但是实际上,我们也不会这样简单的用,有三点:
ip地址是会变的。比如人家服务器重启一下,就变了
一个服务器下面是允许有多个子服务器的,所以这些子服务器对外的ip是一样的。在访问时,如果不带上Host,可能会找不到想要的服务器。
现在基本都是使用 Https来访问了,而Https需要验证验证书,所以步骤会更加复杂。
[](()3.3 Https的ip直连
Https应用到ip直连上,需要面临一个问题:证书的校验。
在Https建立连接中,有这么一步,客户端需要拿着操作系统的根证书列表去验证的域名的,而一般来说,这个动作都是由运营商DNS来做的。而ip直连没有了DNS,代表着我们需要手动去验证。
举个例子:当你打开Chrome,使用ip直连的方式访问某个网站,并且使用Https的形式,即: https://xxx.xxx.xx/
,你会看到下面这个页面:
你需要在界面输入 "thisisunsafe"
来绕过证书验证访问该界面。
[](()3.3.1 使用HttpsURLConnection实现ip直连
为了解决上述问题,我们需要手动验证,所幸的是Android SDK提供api供我们去验证,看了下别的语言的情况,好像Dart就没有。
Thread {
val url = “https://140.205.160.59/”
val hostName = “baidu.com”
val connection = URL(url).openConnection() as HttpsURLConnection
// 1
connection.hostnameVerifier = HostnameVerifier { host, session ->
HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)
}
connection.connect()
}
来看看上面代码。
注释1:
SDK提供了 HttpsURLConnection.getDefaultHostnameVerifier().verify()
的接口来验证,我们将我们要访问的域名代入,走低层校验逻辑。这部分我没阅读过源代码,我的猜测就是使用操作系统的根证书来验证。
[](()3.3.2 解决多域名ip直连问题(SNI技术)
在3.2节中,ip直连还面临了一个问题,即一个服务器下面是允许有多个子服务器的,所以这些子服务器对外的ip是一样的。在访问时,如果不带上Host,可能会找不到想要的服务器。
但是Https中存在一个问题:即TLS握手的建立是在Http请求之前的,这说明就算你在请求行中带上Host也没用,因为顺序先后的问题,服务器根本不知道你要访问的是哪个域名。
而SNI(Server Name Indication)技术则解决了这个问题:
SNI通过让客户端发送虚拟域名的名称作为TLS协商的一部分来解决此问题。这使服务器能够提前选择正确的虚拟域名,并向浏览器提供包含正确名称的证书。
而这个动作一般由运营商DNS来做的,所以绕过DNS,就需要自己手动连接。而SDK也提供了这些Api给我们。
下面是高于Api24的做法:
// 拿到SSLSocket
val ssl = SSLSocketFactory.getDefault().createSocket() as SSLSocket
// 使用 SSLParameters来设置SNI技术
val sslParameters = ssl.sslParameters
// 设置SNI,该Api要高于24
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sslParameters.serverNames = listOf(SNIServerName(…))
}
低于24的代码,则需要继承 SSLSocketFactory,重写 createScoekt
来返回一个实现SNI的SSLSocket,大致代码如下:
/**
创建一个SSLSocket,就是建立握手前协商通道,这个时候利用SNI,为服务器传入一个虚拟域名
这样服务器根据可以根据虚拟域名,返回该域名的证书
*/
override fun createSocket(s: Socket?, host: String?, port: Int, autoClose: Boolean): Socket {
// 拿到设置的Host
var peerHost = conn.getRequestProperty(“Host”)
// 获取Socket连接的ip地址
val address = s?.inetAddress
if(peerHost == null){
// 如果没有,则拿默认的
peerHost = host
}
if(autoClose){
// 原来的Socket可以不需要了
s?.close()
}
// 获取一个 SSLCertificateSocketFactory
val sslSocketFactory = SSLCertificateSocketFactory.getDefault(0) as SSLCertificateSocketFactory
// 建立一个SSLSocket, 但是这时还没有带证书验证
val ssl = sslSocketFactory.createSocket(address, port) as SSLSocket
// 支持 TLS 1.1/1.2 版本
ssl.enabledProtocols = ssl.enabledProtocols
// 为该域名设置SNI
sslSocketFactory.setHostname(ssl, peerHost)
val session = ssl.session
//获取低层默认证书验证器
val hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
if(!hostnameVerifier.verify(peerHost, session)){
// 如果验证失败,抛出错误
throw SSLPeerUnverifiedException(“Cannot verify hostname: $peerHost”)
}
return ssl
}
然后为 HttpsConnectionURL设置这个Factory:
connection.sslSocketFactory = SNISocketFactory(connection)
这大概就是ip直连的思路,当然了,在实际实战时,还是会有一些坑的,但是Android网络这方面的坑都被前人踩完了,所以大胆使用,不懂的可以直接百度、google就行了。
[](()4. HttpDNS
============================================================================
[](()4.1 原理
传统的DNS基本上都是基于UDP协议的,加上咱运营商参差不齐的管理Local DNS服务器,导致我们发出域名解析的那一瞬间,一切就变得不可控了。
而优化DNS的本质是让域名解析的过程变得可控,ip直连的本质做法是绕过DNS,直接不走DNS解析了,从根本上上解决问题。
而 Https另辟蹊径:既然udp不可靠,那么就换成可靠的tcp,既然运营商Local DNS容易被劫持,那就不用运营商的,用自己的服务器。
HttpDNS顾名思义,使用Http,去发出域名解析的请求,而请求的服务器是HttpDNS服务器集群,是云服务器,效率更优于运营商DNS,它利用算法优势查询最优域名,我们不需要了解其内部实现,只需要知道,它比传统Local DNS要强大就可以了。如下图所示:
而HttpDNS服务器集群,可以自己开发, 也可以使用现成的,比如腾讯云、阿里云、微信都有他们自己研发的HttpDNS,收费都不高,接入方便。
[](()4.2 使用HttpDNS的优势
请看下表:
| 功能 | HTTPDNS | 传统DNS |
| — | — | — |
| 防劫持 | Http基于Tcp,具有一定防劫持功能,传输可靠 | 大部分基于Udp,容易被黑客篡改,防劫持能力垃圾的一批 |
| 调度算法 | 根据来源ip,就近、就快的接入业务节点 | 黑匣子,每个运营商都有自己的接入策略,调度策略参差不齐 |
| 解析延迟 | 对热点域名解析、缓存DNS解析结果、解析结果懒更新策略等方式实现0ms更新 | 无差别多重服务器查询,解析时长200~2000ms不等 |
| 快速生效 | 避免Local DNS不遵循权威TTL,解析结果长时间无法更新的问题 | 会自行修改DNS 的 TTL(Time-To-Live,DNS 缓存时间),导致 DNS 的修改,延迟生效。 |
| 业务成功率优化 | 有效降低无线场景下解析失败的比率 | 未知 |
[](()4.3 OkHttp接入HttpDNS
OkHttp组件允许我们接入HttpDNS,所以实现起来不是很难。 OkHttp默认是使用Android SDK的服务 InetAddress
来解析域名,所以我们需要替换这个就行了。
步骤如下:
在App init的时候根据域名去HttpDNS拉取ip地址,将这些ip地址存储在本地DnsCache列表中
实现
DNS
接口,重写lookup()
,当OkHttp使用网络请求时,会走这个方法解析域名,那么让它去DnsCache列表中取就行了,如果没有的话,就走系统默认的DNS解析调用OkHttp的
.dns()
方法,使用第二步中的对象,替换OkHttp的域名解析实现。
我们需要自己实现DNS接口,并实现 lookup
方法:
class HttpDNS : Dns {
// DNS cache
val dnsCache: MutableMap<String, MutableList>? = null
/**
- 寻址方法, 必须重写
*/
override fun lookup(hostname: String): MutableList {
val ip = getIpByHost(hostname)
// 如果读不到ip,则使用系统默认的 InetAdaaress 来解析地址
return ip ?: Dns.SYSTEM.lookup(hostname)
}
/**
- 查找缓存
*/
private fun getIpByHost(hostname: String): MutableList? {
return dnsCache?.get(hostname)
}
/**
- 获取dns方法,一般在打开App的时候根据域名去获取ip列表,放在缓存里面
*/
private fun refresh(hostName: String): MutableList? {
val ipAddress: MutableList? = null
// 做网络请求, 这里是HttpDNS的服务器url, 拿着HostName去解析
val reqUrl: String = HTTPDNS_SERVER_URL;
val request = Request.Builder().url(reqUrl)
.addHeader(“domain”, hostName) // 在请求中代入hostname
.build()
OkHttpClient().newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: Call, e: IOException) {
Log.w(“Rikka”, “解析失败”)
}
override fun onResponse(call: Call, response: Response) {
Log.d(“Rikka”, “解析成功”)
// 反正这里就是把解析的数据丢进cache中
dnsCache?.put(hostName, call.xxxxx)
}
})
return ipAddress
}
}
Android 网络性能优化(2)DNS优化相关推荐
- Android 网络性能优化(4)弱网优化
系列文章目录 1. Android 网络性能优化(1)概述 2. Android 网络性能优化(2)DNS优化 3. Android 网络性能优化(3)复用连接池 4. Android 网络性能优化( ...
- Android 网络性能优化-概述和DNS优化
1. 移动App网络优化背景 对于Android来说,开发者可以轻松的打造一套 MVP + Retrofit + RxJava 的框架来处理所有的网络请求.因为 Retrofit下层封装的OkHttp ...
- Android 网络性能优化(1)概述
但是对于大型的App来说,仅仅是使用这些是不够的,它太机械,不能帮我们处理复杂多变的网络情况. 在我的上个公司,智能设备的网络连接是老大难问题,有时候设备连不上Wifi的情况下,开发人员去跟进,到最后 ...
- Linux 网络性能的 15 个优化建议【转自微信公众号菜鸟教程】
Linux 网络在性能方面有哪些优化手段可用呢? 本文将给出一些开发或者运维中的 Linux 网络性能优化建议. 要注意的是,每一种性能优化方法都有它适用或者不适用的应用场景,应当根据当前的项目现状灵 ...
- Linux 网络性能的 15 个优化建议
建议1:尽量减少不必要的网络 IO 我要给出的第一个建议就是不必要用网络 IO 的尽量不用. 是的,网络在现代的互联网世界里承载了很重要的角色.用户通过网络请求线上服务.服务器通过网络读取数据库中数据 ...
- 叮!快收好这份Android网络性能监控方案
简介:移动互联网时代,移动端极大部分业务都需要通过App和Server之间的数据交互来实现,所以大部分App提供的业务功能都需要使用网络请求.如果因为网络请求慢或者请求失败,导致用户无法顺畅的使用业务 ...
- Android网络性能监控方案
背景 移动互联网时代,移动端极大部分业务都需要通过App和Server之间的数据交互来实现,所以大部分App提供的业务功能都需要使用网络请求.如果因为网络请求慢或者请求失败,导致用户无法顺畅的使用业务 ...
- Android 网络性能优化(2)DNS优化,安卓插件化开发
而连接的最初的动作是DNS解释,在第一篇中,描述DNS的状况是: 域名换ip.这一步看似简单却充满陷阱,10分钟的DNS的Cache过期时间,200~2000ms不等的DNS耗时,坑了无数应用. 解决 ...
- 前端性能优化之-DNS优化
什么是 Preload,Prefetch 和 Preconnect?文章链接 Preload:Preload 与 prefetch 不同的地方就是它专注于当前的页面,并以高优先级加载资源,Prefet ...
最新文章
- LAMP(linux下apache+mysql+php)平台编译安装的实现
- php+中午截取,PHP_php字符串截取中文截取2,单字节截取模式,//中文截取2,单字节截取模式 - phpStudy...
- 快速理解binary cross entropy 二元交叉熵
- Installshield关于.NET安装时需要重启动的处理办法,以及延伸出的重启后继续安装的安装包的一点想法...
- linux启动redis指定端口,linux redis实现自定义运行多端口、多实例 | 极安全-JiSec
- 大数据面试求职经验总结
- python DataFrame获取行数、列数、索引及第几行第几列的值
- RxJava -- 从 create 开始 (一)
- 汉诺塔问题的c语言实现
- 搭建深度学习环境及跑通Github代码
- matlab 语法_MATLAB中的语法
- 《了不起的Markdown》之第1章 人人都应学会Markdown
- 音频加速 foobar_如何使用Foobar2000将音频CD翻录到FLAC
- 《算法笔记》9.7 堆
- 开机后我的计算机打不开,电脑开机后图标打不开怎么办
- [NOIP2018 普及组] 龙虎斗
- JAVA数据类型与转换
- name '_name_' is not defined
- 删除设备和驱动器中的迅雷下载、百度网盘的图标(win10、win11可用)
- 【C语言】小游戏系列——扫雷(内含详细过程)
热门文章
- 达梦数据迁移问题罗列
- hive 建表,分桶表(clustered by)、分桶且桶内排序(clustered by+sorted by)、分区表(partitioned by)、分区分桶一起用
- 享受稳定的交易环境,MT4软件让外汇交易更加简单!
- 骏安产业一站式解决企业纳税筹划方面的痛点
- C# 高级特性(四):索引器(Indexer)
- JAVA this关键字 static关键字 代码块
- 微信测试号——scope参数错误或没有scope权限
- CSS:实现一个斑马线效果 (条纹背景)
- Halcon 二维码识别QR
- Transactional注解原理解析