星星博客 »  > 

TCP/IP网络协议以及Android网络优化方案

引用文章地址:TCP/IP网络协议

文章目录

  • TCP/IP
      • TCP/IP 模型
      • TCP 和 UDP
      • TCP怎么保证传输过程的可靠性?
      • TCP 为什么要三次握手/四次挥手
      • 网页请求一个 URL 的流程
      • DNS 工作原理
      • HTTPS 加密原理
  • 移动端如何优化一个网络请求呢?
      • DNS 优化
      • CacheControl
      • Q:HTTP2.0 的多路复用和 HTTP1.X 中的长连接复用有什么区别?
      • 由 GRRC 升级 QUIC
      • 基于OkHttp的网络监控

TCP/IP

TCP/IP 模型

tcp四层模型解释:通过物理手段把电脑连接起来,数据链路层则对比特流的数据进行分组,网络层建立主机到主机的通信,传输层建立端口到端口的通信,应用层负责建立连接以及数据转换,并呈现给用户;

TCP 和 UDP

TCP
特点:面向连接、面向字节流、可靠、有序、速度慢、较重量,流量控制、拥塞控制
适用场景:文件传输、浏览器等
应用:HTTP、HTTPS、RTMP、FTP、SMTP、POP3

UDP
特点:无连接、面向报文、不可靠、无序、速度快、轻量、实时性高(无队首阻塞)
适用场景:适用于一对多、即时通讯、视频通话等
应用:DHCP、DNS、QUCI、VXLAN、GTP-U、TFTP、SNMP

TCP怎么保证传输过程的可靠性?

  • 校验和:发送方在发送数据之前计算校验和,接收方收到数据后同样计算,如果不一致,那么传输有误。
  • 确认应答,序列号:TCP进行传输时数据都进行了编号,每次接收方返回ACK都有确认序列号。
  • 超时重传:如果发送方发送数据一段时间后没有收到ACK,那么就重发数据。
  • 连接管理:三次握手和四次挥手的过程。
  • 流量控制:TCP协议报头包含16位的窗口大小,接收方会在返回ACK时同时把自己的即时窗口填入,发送方就根据报文中窗口的大小控制发送速度。
  • 拥塞控制:刚开始发送数据的时候,拥塞窗口是1,以后每次收到ACK,则拥塞窗口+1,然后将拥塞窗口和收到的窗口取较小值作为实际发* 送的窗口,如果发生超时重传,拥塞窗口重置为1。这样做的目的就是为了保证传输过程的高效性和可靠性。

TCP 为什么要三次握手/四次挥手

三次握手过程
建立连接前服务端必须要监听接口,所以初始状态是LISTEN;
1.client建立连接,发送一个SYN同步包,发送之后状态变成SYN_SENT;
2.server端收到SYN后,同意建立连接,返回一个ACK响应,同时也给client发送一个SYN包,发送之后状态变成SYN_RCVD;
3.client端收到server的ACK之后,状态变成ESTABLISHED,返回ACK给server端,server收到之后状态也变成ESTABLISHED,连接建立完成;

为什么需要3次?2次,4次不行吗? 因为TCP是双工传输模式,不区分客户端和服务端,连接的建立是双向的过程;
如果只要2次,无法做到双向连接的建立;同时SYN和ACK合并成一次可以看出,不需要4次;
挥手为什么要4次,因为挥手的ACK和FIN不能同时发送,因为数据发送的截止时间不同;

四次挥手的过程
1.client向server发送FIN包, 进入FIN_WAIT_1状态,这代表client端已经没有数据要发送了;
2.server端收到后,返回一个ACK,进入CLOSE_WAIT等待关闭的状态,因为server端可能还没有发送完成的数据;
3.等到server端数据都发送完毕,server端就向client发送FIN,进入LAST_ACK状态;
4.client收到ACK之后,进入TIME_WAIT的状态,同时回复ACK,server收到之后直接进入CLOSED状态,连接关闭;
但是client要等待2MSL(报文最大的生存时间)的时间,才会进入CLOSED的状态;

为什么要等待2MSL的时间才关闭? a.为了保证连接的可靠关闭。如果server没有收到最后一个ACK,那么就会重发FIN;
b.为了避免端口重用带来的数据混淆。如果client直接进入CLOSED状态,又用相同的端口向server建立一个连接,上一次连接的部分数据在网络延迟达到server,数据可能发生混淆;

网页请求一个 URL 的流程

网页请求URL过程

1.首先通过DNS服务器把域名地址解析成IP地址,通过IP地址和子网掩码是否同属于同一个子网;
2.构造应用层请求http报文,传输层添加TCP/UDP头部,网络层添加IP头部,数据链路层添加以太网协议头部;
3.数据经过路由器、交换机转发,最终到达目标服务器,目标服务器同样解析数据,最终拿到Http报文,返回响应报文;

DNS 工作原理

HTTPS 加密原理

HTTPS加密原理
1.用户通过浏览器请求HTTPS网站,服务器收到请求,选择浏览器支持的加密和hash算法,同时返回数字证书给浏览器,包含颁发机构、网址、公钥有效期等信息、(实际在移动端,一般自己内置数字证书)
2.浏览器对证书内容进行校验,如果有问题则警告。否则,生成一个随机数X,同时使用公钥进行加密,并且发送给服务器。
3.服务器收到之后,使用私钥解密,得到随机数X,然后对网页内容进行加密,返回给浏览器;
4.浏览器则使用X和之前约定的加密算法进行解密,得到最终的网页内容。

移动端如何优化一个网络请求呢?

1、连接复用:节省连接建立时间,如开启 keep-alive。于Android来说默认情况下HttpURLConnection和HttpClient都开启了keep-alive。(okhttp默认开启,但是仍需服务器支持)
2.使用http2(头部压缩、文本协议变成二进制协议)
3、请求合并:即将多个请求合并为一个进行请求,比较常见的就是网页中的CSS Image Sprites。如果某个页面内请求过多,也可以考虑做一定的请求合并。
4、开启数据压缩(okhttp默认支持接收gzip压缩)
使用protobuf代替json、xml
5、根据用户的当前的网络质量来判断下载什么质量的图片;
6.使用webp代替png/jpg
7、使用HttpDNS优化DNS:DNS存在解析慢和DNS劫持等问题,DNS 不仅支持 UDP,它还支持 TCP,但是大部分标准的 DNS 都是基于 UDP 与 DNS 服务器的 53 端口进行交互。HTTPDNS 则不同,顾名思义它是利用 HTTP 协议与 DNS 服务器的 80 端口进行交互。不走传统的 DNS 解析,从而绕过运营商的 LocalDNS 服务器,有效的防止了域名劫持,提高域名解析的效率。

DNS 优化

DNS 则是典型的应用层的协议,是 Domain Name System 的缩写

一个 Http 请求在建立 Tcp 连接的过程中,肯定会产生一次 DNS,那么我们是不是可以通过内存缓存的方式,通过一个 HashMap 持有这个 Host 的 IP,当下次发起 Tcp 连接的时候,就可以用直接用内存中的这个 Ip,而不需要再去走一遍 Dns 服务了呢?

这个时候你肯定会问我,卧槽,你这个不是搞我吗,这可怎么改呀?

如果你的网络层用的是OkHttp的话,Okhttp在封装的时候就已经考虑到这个部分了,其内部提供了Dns的接口,可以让外部在构造Client的时候传入。

class HttpDns : Dns {

    private val cacheHost = hashMapOf<String, InetAddress>()
    
    override fun lookup(hostname: String): MutableList<InetAddress> {
        if (cacheHost.containsKey(hostname)) {
            cacheHost[hostname]?.apply {
                return mutableListOf(this)
            }
        }
        return try {
            InetAddress.getAllByName(hostname)?.first()?.apply {
                cacheHost[hostname] = this
            }
            mutableListOf(*InetAddress.getAllByName(hostname))
        } catch (e: NullPointerException) {
            val unknownHostException =
                UnknownHostException("Broken system behaviour for dns lookup of $hostname")
            unknownHostException.initCause(e)
            throw unknownHostException
        }
    }
}

这里可以稍微给大家展开下,LocalDns 是不可以被信任的,经常会有运营商会搞一些奇奇怪怪的 Dns 拦截,导致大家收到的请求是运营商所缓存的(目的是为了省流量),所以阿里腾讯等都有自己对外输出的 HttpDns 的服务。这个服务可以帮助大家找到真实准确的 Host 的 Ip,就是这个服务是收钱的。

如果你是个 IOS 开发人员,那么你一定要注意 SNI(Server Name Indication),一个 IP 对应多个多个 Https 证书的问题 (https://blog.csdn.net/firefile/article/details/80532161)。

CacheControl

Http 请求在 1.1 阶段就引入了 CacheControl了,通过 CacheControl 可以让后端直接控制请求内容的缓存策略。所以还有比缓存更简单粗暴的网络优化方式吗?

在 http 中,控制缓存开关的字段有两个:Pragma 和 Cache-Control。

如果说一句不负责任的话,这个只要后端大佬开启 CacheControl 就好了呀,原生网络库本来就支持的。当然后端大佬一般都不是特别愿意,其实各位安卓也可以通过添加 OkHttp 拦截器的方式给网络请求添加一个统一的 CacheControl,当然如果你有定制化的需求肯定还是要自己开发的,我这里只负责科普下这个面试可以回答的地方,细节大家可以参考下 这个仓库RetrofitCache。

HTTP 协议规格说明定义 ETag 为“被请求变量的实体值”。另一种说法是,ETag 是一个可以与 Web 资源关联的记号(token)。典型的 Web 资源可以是一个 Web 页,但也可能是 JSON 或 XML 文档。服务器单独负责判断记号是什么及其含义,并在 HTTP 响应头中将其传送到客户端,以下是服务器端返回的格式:ETag:“50b1c1d4f775c61:df3”。客户端的查询更新格式是这样的:If-None-Match : W / "50b1c1d4f775c61:df3"如果 ETag 没改变,则返回状态304然后不返回,这也和Last-Modified一样。测试 Etag 主要在断点下载时比较有用。

而我们只要使用了 CacheControl,就可以用到 ETag, 如果当数据内容没有发生变更的情况下,就不会传输数据,这样也可以给大家略微优化下你们的 Api 请求。

Http 1.0 - 1.1 - 1.X - 2.0

当然我们还可以让后端升级接口协议版本,这个可以明显提升你请求响应性能。

  • 长连接,HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启 Connection:keep-alive,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。

  • header 压缩,如上文中所言,对前面提到过 HTTP1.x 的 header 带有大量信息,而且每次都要重复发送,HTTP2.0 使用 encoder 来减少需要传输的 header 大小,通讯双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需要传输的大小。

  • 新的二进制格式(Binary Format),HTTP1.x 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。

  • 多路复用(MultiPlexing),即连接共享,即每一个 request 都是是用作连接共享机制的。一个 request 对应一个 id,这样一个连接上可以有多个 request,每个连接的 request 可以随机的混杂在一起,接收方可以根据 request 的 id 将 request 再归属到各自不同的服务端请求里面。

Q:HTTP2.0 的多路复用和 HTTP1.X 中的长连接复用有什么区别?

HTTP/1.* 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;

HTTP/1.1 Pipeling 解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;

HTTP/2 多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;

好了,下面要开始真的进入牛逼的东西了,前文你肯定以为我是个大水逼,复制黏贴。

GRPC( A high-performance, open-source universal RPC framework)

不知道各位有没有听说过一个都市怪谈,字节的网络库优化有多厉害多厉害,网络底层采用的是 Webview 底层的 Chromium 的网络库,在弱网情况下对于 api 的优化啥的,巴拉巴拉…

Cronet 是 Chromium 网络引擎对不同操作系统做的封装,实现了移动端应用层、表示层、会话层协议,支持 HTTP1/2、SPDY、QUIC、WebSocket、FTP、DNS、TLS 等协议标准。支持 Android、IOS、Chrome OS、Fuchsia,部分支持 Linux、MacOS、Windows 桌面操作系统。实现了 Brotli 数据压缩、预连接、DNS 缓存、session 复用等策略优化以及 TCP fast open 等系统优化。本文内容基于 Chromium 75版本。

字节用的就是 Chrome 的 cronet 网络库(顺便展开下,cronet 同时支持 ios,android,前端)。而由于 grpc 协议的问题,所以传输内容直接使用的 protobuf 格式,所以其不仅仅是网络层上的优化,同时由于流能直接转化成实体类,同时也减少了可序列化的时间。

protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。

Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

但是正常的网络框架基本都使用了 Retrofit+Okhttp,而且大家都已经使用的很习惯了,所以我大胆的猜测,字节其实应该用 OkHttp 桥接了cronet。所以这样基本就能无缝桥接当前已有的网络库了。

由 GRRC 升级 QUIC

QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于 UDP 的低时延的互联网传输层协议。在 2016 年 11 月国际互联网工程任务组(IETF)召开了第一次 QUIC 工作组会议,受到了业界的广泛关注。这也意味着 QUIC 开始了它的标准化过程,成为新一代传输层协议

其实整个 QUIC协议(Http3.0协议)本来就是谷歌写的,所以谷歌的 Cronet 本身就支持这也是正常的。我其实之前就特地去查过 OKHttp 支持的协议内容,当前还是只停留在 2.0 阶段,主要就还是因为当前的 Connection 写的太好了,而且需要把 Tcp 直接更换成Udp,所以迟迟没有更新 3.0 协议的支持。

所以各位如果想从协议层去做对应的优化,那么可能OkHttp带给大家的应该还是无尽的等待了。

基于OkHttp的网络监控

其实优化方面我的大概的姿势点就这么多了,但是我们可以考虑从监控方面的角度去再重新审视这个话题哦。客户端请求从发起到网关实际接收到,其实中间有很复杂的链路,简单的说,OKhttp 内也走过了这么多个拦截器了。但是当一个线上用户反馈这个界面怎么刷出来的这么慢的情况下,我们以后端网关开始作为请求的开始节点,就会出现难以定位真实问题的情况。

我们是不是可以考虑把整个api发起到结束进行监控,从而可以方便线上去监控一个 Api 真实的发起到结束的状况呢?我们先简单的把一个请求的节点拆分下。基于OKHttp提供的EventListener,我们就可以对于一个请求发起到最后的各个节点进行监控,之后上报日志数据,这样在后续的撕逼过程中,其实就可以做到有理有据,有话可说,你真的慢了。

相关文章