AlexiaChen / AlexiaChen.github.io

My Blog https://github.com/AlexiaChen/AlexiaChen.github.io/issues
87 stars 11 forks source link

网络相关梳理 #92

Open AlexiaChen opened 4 years ago

AlexiaChen commented 4 years ago

网络相关梳理

网络协议有很多,但是对于互联网用的最多的就是Http,https和TCP协议了。所以重点会梳理这方面的知识点。

HTTP 1.0

http的基本特点就是“一来一回”。就是Client发起一个TCP连接,在连接上面发一个http request到server,server返回一个http response,然后TCP连接关闭。

这样导致了性能问题,TCP连接的建立和关闭是耗时操作,一个请求一个连接太耗费资源。还有一个问题就是服务端不能主动推送消息。

当然,http 1.0有解决方案了,设计了一个Keep-Alive机制来实现TCP连接的复用,客户端根据需求选择性的在http request header加一个Connecion:Keep-Alive。Http Server收到这个字段在处理完成请求后不会主动关闭连接,同时在http的Response里也会加上该字段,等待客户端在该连接上发送下一个请求。

又因为连接复用的问题,怕把服务器连接耗光,所以会有一个Keep-Alive timeout参数,超时关闭连接。连接复用还会有一个问题,就是连接不关闭的情况下,Client如何知道某个Request对应Response完毕呢?答案是,http response的header返回了一个Content-Length:xxx 的字段表示响应了多少字节。

HTTP 1.1

连接复用与Chunk机制

因为连接复用的必要性,http 1.1标准直接默认复用了,你即使不加Keep-Alive,Server也不会主动关闭连接,除非你在Request header中显式加入Connection:Close,Server才会主动关闭连接。

上一小节说明了用Content-Length判断Response大小,但是这个有问题,如果server返回的内容是动态语言生成的内容,要计算该字段,对于服务器负荷略大,所以在http 1.1中引用了Chunk机制(Http Streaming)。具体来说就是,在Response的header中加入Transfer-Encoding:Chunked,目的是告诉client,Reponse body是分成了一块块的,块与块之间有间隔符,所有块的结尾也有特殊标记,这样即使没有Content-Length,Client也能方便判断response末尾。

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25
This is the data in the first chunk
1C
and this is the second one
3
con
8
sequence
0

25是第一块的字节数,1C是第二块的字节数,以此类推。0是response的结尾。可以看到,整个response body都被切分成chunk了。

Pipeline与Head-of-line Blocking问题

http 1.1还引入了pipeline机制,在同一个TCP连接上,可以在Request发出去以后,Response没有回来之前就发送下一个,再下一个的Request,提高在同一个TCP连接上的请求并发度。不然若是以前,还需要等待Request对应的Response回来才能继续发送下一个Request。

但是这里有个问题就是,队头阻塞(Head-of-line Blocking),意思就是如果Request是123的顺序发送到server的,那么server端也必须123的顺序回复response,client必须收到的顺序也是123,如果client收不到1,那么23的response会被阻塞。因为网络是异步并发的,很可能23和1不在一个TCP连接上,那么这种阻塞情况就会经常发生。所以,正因为如此,很多浏览器默认把Pipeline关闭了。

Http/2出现之前的问题

pipeline默认关闭,不能用,同一个TCP连接上的请求都是串行的,对于同一个域名,浏览器限制只能开6-8个连接,怎样提高性能?

HTTP/2

2009年Google发布了SPDY协议,是个实验性质的东西。想在协议层面解决http 1.1的效率问题,然后后来,http工作组汲取SPDY的经验,在此基础上定制了http/2协议。为啥不叫http 2.0?因为工作组认为协议完善,后面不会有小版本了。再后来,SPDY被Google抛弃,全面转HTTP/2。

与HTTP 1.1兼容

由于协议兼容和不改变结构,所以http/2和http 1.1不是平级位置,而是处于http 1.1和TCP的中间,就是以下协议栈:

HTTP 1.1

HTTP/2(SPDY)

TCP

二进制分帧

HTTP/2这个特性主要解决http 1.1中pipeline队头阻塞的问题。因为http 1.1是明文的字符文本格式,所以在发送到TCP之前,需要把字符格式转换为二进制,然后分成多个帧(多个数据块)来通过TCP send出去。

这里的帧被乱序并发地通过TCP发送出去和接收回来,没有了pipeline的限制,然后通过上层把帧组装成逻辑上的流,编上号码。这样http response就可以乱序返回。

当然,这个特性没有完全解决对头阻塞问题,只不过降低了一点可能性,仅仅只是把Request和Response细化到了帧的粒度。只要依赖于TCP,那么就会有队头阻塞问题。到后面会讲到Google的QUIC协议就是彻底解决队头阻塞问题的协议。

头部压缩

http 1.1里对于http的body,已经有相应的压缩了,尤其是对于图片,但是对于Header部分,一直都没有压缩,因为随着互联网发展,http header越来越大,header的压缩成为必要,HTTP/2专门设计了HPACK协议和对应算法。

SSL/TLS

主要是现在都是搞https了,https都是构建在SSL/TLS上的。最初由网景设计SSL 1.0,后来逐渐标准化,IETF对SSL标准化后叫TLS 1.0。TLS1.0相当于SSL 3.1, TLS 1.1, TLS 1.2相当于SSL 3.2,SSL 3.3。所以业界习惯两者并称为SSL/TLS。

协议栈是:

HTTP FTP IMTP ... 各种应用层协议

SSL/TLS

TCP

这里需要说到,对称加密,非对称加密,中间人攻击的东西,我暂时不说了。为了防止中间人攻击,就要想办法证明Server收到的public key的确就是client发出的,中间没有人可以篡改public key。所以需要提到数字证书的概念。

数字证书与SSL/TLS

有一种中间机构CA,类似于公证处(CA,Server都有自己的密钥对)。Server把自己的public key和个人信息发给CA,CA用自己的private key给Server生成一个数字证书(Cert),这个证书相当于Server的身份证,之后Server把自己的证书给Client,Client拿着CA的public key可以验证证书是否有效。

由于CA也可以冒充,所以CA会有一层层的信任链,CA有自己的上级CA,一直到顶级(root CA)。原理与之前的描述一样,反正CA也是服务器,一层层的数字证书组成的信任链。

Root CA证书一般浏览器,操作系统发布就内嵌在里面了,颁发证书的过程与验证过程是相反的,证书是网络上通信实体的身份证,并且这套体系也标准化了,就是所谓的PKI(Public Key Infrastructure)。

SSL/TLS协议通信

握手基本步骤,主要是为了协商对称秘钥

HTTPS

因为HTTPS = Http + SSL/TLS,所以流程就是:

TCP/UDP

可靠与不可靠

这里的可靠,不可靠算是TCP与UDP的区别之一,TCP的可靠的意思就是:

那么TCP的可靠是怎么解决的呢?

  1. 不丢包:ACK+重发

Client每发送一个数据包就给其编号,编号单调递增,基于编号顺序确认。

Server每收到一个包,就需要发送ACK给Client进行确认,意思就是反馈给CLient已经收到数据包了。不然,Client得重发。

比如,Server收到了数据包123,它只用回复ACK=3,意思就是小于等于3的数据包都收到了,过一会儿,Server又收到了456数据包,那么它只用回复ACK=6,意思就是小于等于6的数据包都收到了。

  1. 包不重复

只要超时,Client没有收到Server发回来的ACK,Client就会重发,既然重发,网络又会抖动延迟,那么数据包就有可能重复。利用顺序ACK就可以做到重复包舍弃,Server给Client回复的ACK都记录在案,那么之后再收到ACK范围内的数据包就判定为重复包,丢弃就好了。

  1. 时序保证

假设Server收到数据包123,回复Client ACK=3,之后收到数据包567,数据包4可能延迟了,迟迟没有收到,Server会把567数据包暂存,直到数据包4到来,再回复ACK=7,如果数据包4真没来,那么Server的ACK进度会一直保持在ACK=3,直到Client超时,会把4567全部重新发送,这时候人品好就会收到4567,回复ACK=7,这样就推进了ACK进度。并且也保证了顺序。

因为TCP是全双工的,所以对于Client回复ACK的情况也有,双方都可能回复ACK,或重发,原理一致。

TCP的这种思想朴素深刻,在分布式消息中间件里面的类似机制与这个思想差不多。

TCP的状态机相关

注意,这小节很重要,很多调试TCP网络的地方,会出现相关状态,这样好分析问题。特别是服务端开发,这些知识需要知道。

都知道TCP socket连接是一条上层的逻辑连接,用了不少机制保证可靠,每条连接都对应一个“状态机”,Client和Server都需要针对这条连接维护不同的状态迁移,在不同的状态下执行相应的操作。

20140306201147109

上图的状态迁移图大概解释下,最开始Client(主动)和Server(被动)都处于CLOSED状态,连接建立完成后,双方都处于ESTABLISHED状态,开始传输数据;最后连接关闭,双方再次回到CLOSED状态。

TCP三次握手

TCP标志位:

22312037_1365405910EROI

注意,上面的ACK与之前的TCP可靠性的ACK有点不同,注意看描述。

TCP四次挥手

20180717204202563

如果第一次,第二次握手之后,意味着连接处于半关闭状态,此时Client处于FIN_WAIT_2状态,Server处于CLOSE_WAIT状态,Client通往Server的通道关闭了,但是Server到Client的通道还没有关闭,需要等到第三次,第四次发生,连接才会完全处于CLOSE状态。

还有一种场景,Client和Server同时主动发起了关闭,双方都会处于FIN_WAIT_1状态,后面一起进入TIME_Wait状态,经过一段时间后进入CLOSE状态。为什么不直接进入CLOSE状态?

因为关闭连接是逻辑上的,如果直接进入CLOSE,那么网络上可能还有该连接“闲逛”的数据包,而连接还会再次打开,如果新打开的连接收到旧连接的数据包那是万万不行的,所以在TCP/IP网络上定义了一个叫做MSL(Maximum Segment Lifetime)的值,就是任何一个数据包在网络上存留的最大时间是MSL,这个值默认120秒,超过该时间,数据包自行被丢弃。有了这个限制,一个连接保持在TIME_WAIT状态会再等待2×MSL的时间才会进入CLOSE状态。

所以你看到了,关闭一个连接的代价挺高的,要等待一段时间后才能启用,所以主动关闭连接一般是Client关闭,Server不要主动关闭连接,不然会有一段时间的TIME_WAIT, 如果频繁关闭,Server会消耗完所有连接资源,所以:

QUIC协议

Google基于UDP推出的多路并发协议,可以取代TCP部分功能,实现SSL/TLS的所有功能,还可以取代部分HTTP/2的部分功能,优化了不丢包的算法性能,减小的RTT。从各种角度来看都比较优美,性能更高,不过用这个周边得有配套的设施,比较麻烦,听说youtube就采用了这个作为视频传输,降低了视频卡顿,国内B站视频网站也有落地,听说http3也要基于UDP,之前http3的全称叫HTTP over QUIC。就是建立在QUIC上的http协议。谁知道了,持续观望吧。