1.前言

当我们开发一个APP,可以直接使用系统的网络请求接口去服务端请求数据,或者使用开源的一些网络库(iOS AFNetWorking/ Android okHttp),管理好线程和队列,在做一些数据解析。

但是如何针对移动网络弱慢的问题进行优化呢?

  1. 速度提升:网络请求的速度如何提升?

  2. 弱网适应:移动网络环境恶劣,经常出现连接不稳定的情况,如何优化?

  3. 安全保障:怎么防止运营商劫持?

2.一次网络请求经历了哪些事情?

要发现网络性能问题,我们先来看看一次请求经历了哪些事情:

  1. DNS Lookup (DNS解析,请求DNS服务器获取IP)

  2. TCP HandShake (TCP的三次握手)

  3. TSL Handshake (TSL的握手,安全协议的同步流程)

  4. TCP/HTTP Request/Response (发送TCP/HTTP请求以及受到响应)

当我们了解到了这些过程,如果我们能够将这些流程逐一梳理的话,那么性能将会有不错的提升。

3.四个过程当中存在的问题?

那么四个过程当中存在什么问题呢?

1.DNS问题

DNS解析首先会从本地系统缓存获取,接着是最近的DNS服务器获取,再往后是到主域名服务器获取,每一层都有缓存,每一层缓存都有过期时间。

这种DNS解析机制存在以下缺点:

  1. DNS解析过程不受控制,速度取决于运营商

  2. 域名劫持,容易被中间人攻击

  3. 一次请求只能解析一个域名

2.TCP连接问题

DNS拿到IP之后发起TCP连接,因为TCP的连接好事也是网络性能的关键因素,常见的问题有TCP端口被封,TCP连接超时时长问题,会导致如下结果:

  1. 端口被封,则TCP无法连接。

  2. 在超时时间过长,那么可能导致用户等待时间太长。

  3. 在超时时间过短,那么在低速网络上可能总是无法连接。

3.TCP读写问题

在TCP连接之后,就会开始发送Request,然后服务端处理后返回Response,如果是HTTP请求,那么使用系统的API只能设置缓存策略以及超时时间;如果是TCP连接实现网络服务,那么久需要自己对读写超时时间负责,与网络连接超时时长一样,太小了在低速网络很容易读写失败,太大了有可能影响用户体验。

4.TSL握手带来的耗时问题

标准协议TLS保证了网络传输的安全,类似HTTPS就是HTTP协议加上TLS安全协议。目前比较常见的就是TLS1.2,但是TLS1.2每次简历一个安全连接需要2-RTT,这么这对用户的延迟的影响是相当明显的。

4.如何优化?

1.DNS优化

为了上述的DNS问题,我们可以自建DNS服务,可以通过HTTP请求后台拿到域名对应的IP地址,当然更好的方式是用UDP的方式去获取域名对应IP地址。

自己实现DNS服务的好处有一下几点:

  1. 域名解析与请求分离,所有请求直接用IP地址,减小了DNS解析时间。

  2. 避免了DNS劫持的问题

  3. 一次请求可以解析多个域名

2.TCP连接优化

因为HTTP也是基于TCP协议进行通信的,我这边会分为HTTP连接以及TCP连接进行分析

2.1 HTTP的连接优化

HTTP协议里面有个keep-alive,HTTP1.1默认开启,字面是连接保活的意思,原理是请求完成后不会立即释放连接,而是放入连接池中,若这是有另外一个请求要发出,请求的域名以及端口是一样的,那么直接从连接池中拿出连接进行发送和接受数据,减少了建立连接的耗时。

现有的客户端默认是开启了Keep-alive,但是这个keep-alive的连接只能发送接受一个请求,一个请求处理完成之前,无法接收新的请求。

HTTP2的多路复用机制则优化了这个问题,HTTP2的多路复用机制一样是复用连接,但是它复用的连接支持同事处理多条请求,所有的请求都可以并发的在这条连接上进行,从而解决了HTTP1.1的问题。

Http1.1的协议里,传送数据都是串行顺序发送的,必须等上一个请求全部处理完后,下一个请求才能进行处理。
Http2中的多路复用协议解决了这些问题,它把传输的数据都封装成一个个stream,每一个stream都有标识,stream的发送和接受可以是乱序的,不依赖顺序,接收端可以根据stream的标识去区分属于哪一个请求,再进行数据拼接。

2.2 TCP的连接优化(参考mars的策略)
2.2.1 超时的设置

TCP协议提供了可靠的端到端的传输,超时与重传也是TCP协议最重要的部分,超时重传的设计尤为重要。在大多数BSD实现中,若主动connect方没有收到SYN的回应,会在第6秒发送第2个SYN进行充实,第3个SYN则是与第2个间隔24秒,如果在75秒还没有收到回应,则connect超时。

这就意味着,需要75秒的时间,才能获得结果,但是失败的原因有可能是一下原因:

  • 网络不可用

  • 服务器繁忙

  • 弱网环境

如果是网络不可用,超时的设置不会有什么区别。如果是服务器繁忙的问题,我们希望连接超时短一点,使得可以通过切换IP的方式获得其他可用的服务器。如果是弱网情况,我们希望重传的时间稍微长一点,因为更多的重传反而会阻塞网络通道。

因为移动端是无法区分这些场景的,所以没有根据这些特定的场景来配置不同的超时时间。因此我们只能根据系统特征,设置一个超时间隔。我们来看一下iOS、Android的TCP层连接超时重传时间间隔是多少:

  • 1,1,1,1,1,2,4,8,16,32(iOS 的connect超时重传间隔,总共是67秒)

  • 1,2,3,8,16,32(总用时63秒)

由以上数据我们可以发现:

  1. 在iOS系统中,connect初期使用了更加积极的策略,在Android系统中,connect重传则更加谨慎。

  2. 不管什么平台,连接总超时时长都是严重损坏用户体验的。

  3. 连接的初始阶段,TCP重传都采用更加积极的策略.

因此不同的系统特征,可以设置不同的连接超时间隔。例如在iOS系统中,由于才去了更加积极的超时间隔,我们可以将超时设置为10s,在10s内,iOS系统会进行6次的重发。在Android中,我们则可以设计为15秒,Android系统会进行4次的重试。

2.2.2 复合连接

在上述的连接超时策略中,我们需要10秒的时间来确认一个IP&Port组合的connect是否可用,这对于我们来讲还是太久了,但是又不能把超时时间设置得太短。因此我想到了并发连接的办法,这样就能很快找到有效的IP&Port组合了,但是这又会产生服务器负载高的问题。

因此我们想到了复合连接,一开始应用发起IP1&Port1的调用,在第4秒的时候,如果第一个connect还没有返回,则发起IP2&Port2的调用,一次类推,直至发起第五组的connect连接。

3.TCP的读写优化

3.1 减小数据包的大小

传得多久传的慢,因此如何减小数据包的大小,显得尤为重要。目前最流行的数据格式是json、protobuf和自定义二进制数据,json相对于体积要大很多,尽可能的采用protobuf或者自定义二进制数据。

控制传输包的大小最好控制在1400字节以下,因为TCP/IP数据报文如果超过物理网络层的限制时,会引发IP分片,从而增加时空开销。

3.2 动态的设置超时时间

在上述我们已经提到,TCP协议已经帮助我们进行了超时和重传的控制。但是我们也了解到,我们希望在不同的网络环境下,设置数据读写的时间是不一样,从而来适应移动端不稳定的网络环境。

比较简单好用的是根据用户是在2G/3G/4G/5G/WIFI的网络环境来设置不同的超时参数,不过这种网络质量检测机制是比较粗糙的。

微信的做法是对总堵写超时、首包超时、包包超时来指定不同的计算方案,来加快对超时的判断,这种网络质量检测机制相对而言要精细得多。

4.TLS优化

标准协议TLS保证了网络传输的安全,常见的HTTPS就是在HTTP协议基础上加上了TLS安全协议。我们现在比较用的比较多的是TLS1.2X协议,但是TLS1.2每次简历一个安全连接都需要额外的2-RTT,这种RTT对用户的影响是很冥想的,为此我们该如何解决?

我们可以参考MMTLS方案,使用ECDH来做密钥协商,ECDSA进行签名验证,AES-GCM作为对称加密算法来对业务数据进行认证加密,使用HKDF进行密钥扩展,摘要算法为SHA256。

5.TCP参数调优

我们可以对服务端的TCP协议参数进行调优,从而使得更加适应移动端网络环境。

5.1 放大TCP拥塞窗口

流量控制是为了避免数据发送太快对端应用处理不过来造成SCOKET缓存溢出,目标是在拥塞发生时能即时发现,并通过减少数据报文进入网络的速率和数量,达到防止网络拥塞的目的。TCP/IP协议栈的算法是通过分组丢失来判断网络某处可能有拥塞情况发生,评判的具体指标为分组发送超时和收到对端对某个分组的重复ACK。在有线网络时代,丢包发生确实能比较确定的表明网络中某个交换设备故障或因为网络端口流量过大,路由设备转发处理不及时造成本地缓存溢出而丢弃数据报文,但在移动网络中,丢包的情况就变得非常复杂,其它因素影响和干扰造成丢包的概率远远大于中间路由交换设备的故障或过载。比如短时间的信号干扰、进入一个信号屏蔽的区域、从空闲基站切换到繁忙基站或者移动网络类型切换等等

5.2 调大SOCKET读写缓冲区

可以通过 setsockopt 函数设置SO_RCVBUF和SO_SNDBUF选项来分别调整SOCKET读缓冲区和写缓冲区的大小,从而避免因为缓存溢出导致包的反复传送。

5.3 调大RTO初始值

因为移动网络的特点之一就是高延时,这就意味着在一个RTT比较大的网络上进行数据传输,所以我们可以适当的将RTO调大,从而避免不必要的重传。

5.4 禁用TCP快速回收

TCP快速回收是一种链接资源快速回收和重用的机制,当TCP链接进入到TIME_WAIT状态时,通常需要等待2MSL的时长,但是一旦启用TCP快速回收,则只需等待一个重传时间(RTO)后就能够快速的释放这个链接,以被重新使用。

6.总结

这篇文章分别从DNS优化、TCP连接优化、TCP读写优化、TLS优化以及TCP参数调优来提升移动端网络性能,借鉴了不少别人成熟的方案。每一个模块都可以继续深挖,因为篇幅有限,这里只是简单的提出方向。