而连接的最初的动作是DNS解释,在第一篇中,描述DNS的状况是:

域名换ip。这一步看似简单却充满陷阱,10分钟的DNS的Cache过期时间,200~2000ms不等的DNS耗时,坑了无数应用。

解决无非有三个策略:

  • ip直连

  • 域名重用(收敛)

  • HttpDNS

不过仅仅用一段话来描述DNS的疼点还是比较生硬的,所以在学习ip直连和HttpDNS之前,我需要先弄懂DNS出现问题的具体原因。

2. DNS

========================================================================

2.1 DNS是什么


在网络世界中,我们如果想要访问某一个服务器的东西,我们需要知道它的ip地址是多少,这样才能去连接走Http。

但是 ip地址32位二进制的数字,它的常用格式: XXX.XXX.XXX.XXX 显然,我就是为了访问一个服务器,你让我记住这么一长串的数字,这不是在为难人吗?我每天都要访问数十个网站,一秒钟几亿上下,每个网站的ip都要记在小本子上,而且一个服务器可能还有好几个ip,然后在访问时还要很细心一个个敲在输入框中…

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200925154519274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Jpa2thdGhld29yb
GQ=,size_16,color_FFFFFF,t_70#pic_center)

所以为了解决这种体验问题,DNS(Domain Name System)就这么出现了。服务器可以申请得到域名,比如说我们熟知的百度的域名就是 : www.baidu.com。这样我们在浏览器的输入框只要输入这个域名,就能访问百度的服务器了。

那我们看到DNS出现,不用去记那么一长串的ip地址了,只用记住域名就可以了,域名一般来说都是英文加数字格式,很方便人类进行记忆。

那我们已经很清楚DNS的作用了,我们告诉DNS域名,DNS帮我们找到ip地址,即 域名换ip,来看下下图:

这样人类就可以很哈批的去访问各种网站了。

注:DNS大部分都是基于UDP的,所以DNS本身具有UDP连接的不可靠性

2.2 DNS服务器结构


每天都有成万上亿的人在互联网冲浪,这说明DNS没有一刻不在被使用,所以DNS服务器一定要保证高可用、高并发和分布式服务器。所以有必要了解DNS的结构。

我们先来看下域名的空间结构:

像 com、net、cn这些代表的就是顶级域名(也叫根域名),顶级域名前面的就是二级域名,二级域名前面的是三级域名。

比如 www.baidu.com.com就是根域名, .baidu就是二级域名, www就是三级域名。根据这个我们再来看看DNS的结构。

从理论上说,任何形式的标准域名要想被实现解析,按照技术流程,都必须经过全球“层级式”域名解析体系的工作,才能完成。所以DNS的服务器结构是根据官方的层级式来分类的,有下面四类:

  • 根域名服务器(Root Name Server)

  • 顶级域名服务器(Top-level Name Server)

  • 权威域名服务器(Authoritative Name Server)

  • 本地 DNS 服务器(Local DNS Server)

2.2.1 根域名服务器

根域名服务器是互联网域名解析系统(DNS)中最高级别的域名服务器,负责返回顶级域的权威域名服务器地址。

“层级式”域名解析体系第一层就是根服务器,负责管理世界各国的域名信息,在根服务器下面是顶级域名服务器,即相关国家域名管理机构的数据库。

一个域名必须首先经过根数据库的解析后,才能转到顶级域名服务器进行解析。

在根域名服务器中虽然没有每个域名的具体信息,但储存了负责每个域(如.com,.xyz,.cn,.ren,.top等)的解析的域名服务器的地址信息。

全世界域名的最高管理机构,是ICANN,在美国加州,它制定了根域名的规范,管理所有域名的顶级域名。它就是根域名服务器。

当然了,域名这么多,来自各个国家各个区域,所以为了便于管理,所有域都会被一些组织、群体所托管,比如 .cn域名的托管商就是中国互联网络信息中心(CNNIC),它决定了.cn域名的各种政策。而像Verisign管理着 .com.net等,而这些服务器就是顶级域名服务器了,如下图所示:

也就是说,当我们输入 www.example.cn时,会先去找根域名服务器ICANN,然后它会翻列表,发现 .cn的解析是被CNNIC管的,那么你就要去找CNNIC做顶级域名解析。

一般来说,根域名列表很少变化,所以ISP会帮我们缓存这些列表,这样就可以省去找根域名服务器的时间了。

2.2.2 顶级域名服务器

所有顶级域名服务器的名称和IP地址是在根服务器注册的。它们的作用是 解析顶级域名

比如上一节中:

  • 用来解析 .cn 域名的服务器-------CNNIC

  • 用来解析 .com.net域名的服务器------Verisign

而顶级域名服务器里面解析好域名后,会得到该域名所在权威域名服务器地址:

2.2.3 权威域名服务器

权威域名服务器, 本质上是一个 Map表: K -> 域名 V -> ip列表

这样,权威域名服务器就会在这个表里面根据域名找到最终的ip,返回请求者这个ip地址。

2.2.4 本地DNS服务器

显然,本地服务器并不具备寻址能力,因为不可能所有的机器都做为DNS服务器来存储地址,所以本地DNS服务器的作用是 缓存DNS域名与ip地址的映射

一般来说,Local DNS服务器在运营商那里,而DNS劫持一般的目标都会在本地DNS服务器上。

2.2.5 为什么要这样分级?

看到这里,应该已经有很多同学把DNS服务器结构和 HTTPS的证书信任链联想在一起了…(可以看这篇:Android 深入Http(3)Https的工作机制)

那大家就知道为什么一个简简单单的 域名换ip的事情,要分成这么多级了。

因为根服务器不可能管理者成千上万个域名-ip映射表。所以它会把这个根据顶级域名交给下层,如果你是 .net你就交给谁管,你是 .cn你就交给谁管。

而 一个顶级域名服务器也不可能管理着所有的 域名-ip映射表,因为并发量太高了,所以它会在解析好后,交由下层处理。当到了由大量权威服务器支撑的映射表后,就能保证高并发量、高可用了。

2.3 DNS寻址过程


这里总结一下DNS的寻址过程,可以看下图:

  1. 客户端输入一个网址即域名,到本地DNS服务器

  2. 本地服务器DNS查找域名缓存,如果命中对应的ip地址,则返回该ip,如果没有命中,则发请求给根域名服务器请求解析

  3. 根域名解析好后,返回 顶级域名服务器的地址

  4. 本地DNS 向顶级域名服务器发送请求。

  5. 顶级域名查看缓存,有没有对应ip,如果有直接返回,如果没有,则解析该域名,查找到目标 权威域名服务器的地址

  6. 本地DNS 向 权威域名服务器发送请求

  7. 权威域名服务器在数据库中查表,返回对应的ip地址

  8. 本地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的域名寻址。 但是实际上,我们也不会这样简单的用,有三点:

  1. ip地址是会变的。比如人家服务器重启一下,就变了

  2. 一个服务器下面是允许有多个子服务器的,所以这些子服务器对外的ip是一样的。在访问时,如果不带上Host,可能会找不到想要的服务器。

  3. 现在基本都是使用 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

}

Android 网络性能优化(2)DNS优化,安卓插件化开发相关推荐

  1. 安卓插件化开发!我的头条面试经历分享,含爱奇艺,小米,腾讯,阿里

    前言 在老东家呆了将近四年光景,受益颇多,无奈在技能上遇到瓶颈,深感自己技能上不能再进步,毅然离职,加入求职大军.说实话,遇上18年的金融危机.互联网寒冬.裁员,大量求职人员迸涌上来,找工作并不是那么 ...

  2. Android 网络性能优化(4)弱网优化

    系列文章目录 1. Android 网络性能优化(1)概述 2. Android 网络性能优化(2)DNS优化 3. Android 网络性能优化(3)复用连接池 4. Android 网络性能优化( ...

  3. Android插件化开发实现动态换肤

    今晚实在不想coding,于是想着整理点知识点,那么简单整理了下插件化开发实现动态更换皮肤.插件化开发大家应该不陌生或多或少用过或听过,插件化开发在项目业务拓展.模块化等方面有不小优势,当然实现一个完 ...

  4. Android插件化开发指南——实践之仿酷狗音乐首页

    文章目录 1. 前言 2. 布局分析 3. 底部导航栏的实现 4. 顶部导航栏和ViewPager+Fragment的关联 1. 前言 在Android插件化开发指南--2.15 实现一个音乐播放器A ...

  5. 安卓插件化与热修复的选型

    参考文章: 安卓插件化的过去现在和未来 张涛 http://kymjs.com/code/2016/05/04/01 安卓插件化从入门到放弃 包建强 http://www.infoq.com/cn/n ...

  6. Android插件化开发之动态加载三个关键问题详解

    本文摘选自任玉刚著<Android开发艺术探索>,介绍了Android插件化技术的原理和三个关键问题,并给出了作者自己发起的开源插件化框架. 动态加载技术(也叫插件化技术)在技术驱动型的公 ...

  7. 安卓插件化学习 - 类的加载

    安卓插件化学习 - 类的加载 引言 一.类的加载 1. 原理 2. 代码 2.1 宿主apk代码 2.1.1 插件管理器 2.1.2 配置文件 2.1.3 插件初始化 2.1.4 调用插件方法 2.2 ...

  8. Android组件化和插件化开发

    项目发展到一定程度,就必须进行模块的拆分.模块化是一种指导理念,其核心思想就是分而治之.降低耦合.而在 Android 工程实践,目前有两种途径,一个是组件化,一个是插件化. 组件化开发 说起组件化少 ...

  9. Android插件化开发之解决OpenAtlas组件在宿主的注冊问题

    Android插件化开发之解决OpenAtlas组件在宿主的注冊问题 OpenAtlas有一个问题,就是四大组件必须在Manifest文件里进行注冊,那么就必定带来一个问题,插件中的组件都要反复在宿主 ...

最新文章

  1. 知乎社区核心业务 Golang 化实践
  2. Swift中关于元组的某些特性
  3. android SharedPreferences实现用户的注册和保存账号密码
  4. python图片显示英文字符_python如何解析字符串中出现的英文人名?
  5. 细说flush、ob_flush的区别
  6. android默认代码混淆,Android SDK默认混淆配置文件
  7. Go程序:利用命令行参数做四则运算
  8. 3GPP(3rd Generation Partnership Project)
  9. Leetcode 233.数字1的个数
  10. 安装增强功能失败:Could not mount the media/drive C:\Program Files\Oracle\VirtualBox/VBoxGuestAdditions.iso
  11. sicily 1295. 负权数
  12. 【文摘】《创新者》-沃尔特·艾萨克森
  13. 电子罗盘简单介绍和应用
  14. 安全测试者偏爱的安全测试工具
  15. 使用PYQT5打开海康威视工业相机并获取图像进行显示
  16. quartz 定时任务调度框架demo
  17. (四)机器学习方法的分类
  18. 51单片机之一套完整的实验流程
  19. 任意波形发生器的主要功能
  20. 图片alt标签是什么?如何优化Alt标签

热门文章

  1. 60行Shell代码实现在命令行中优雅地听歌
  2. basler恢复出厂设置_如何恢复您的Xbox出厂设置
  3. 面试Java遇到的尴尬问题
  4. Android Studio 的logcat不捕捉loge信息
  5. 辐射神经场算法——Wild-NeRF / Mipi-NeRF / BARF / NSVF / Semantic-NeRF / DSNeRF
  6. android anko,Announcing Anko for Android
  7. 期权市场和股票市场谁的日内涨幅更大?
  8. 和海鲜浴血奋战一天,垮了5人
  9. centos8 stream 百度网盘
  10. MiniUI关闭当前Tab页面并回到指定Tab