AlexiaChen.github.io
AlexiaChen.github.io copied to clipboard
网络相关梳理
网络相关梳理
网络协议有很多,但是对于互联网用的最多的就是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个连接,怎样提高性能?
- 图片拼接为大图片,Spriting技术
- 小图片原始数据嵌入到CSS中
- 大量小js文件合并压缩为一个
- 静态资源在CDN上
- 无法服务端主动推送,用WebSocket,或者客户端定期轮询(最耗性能),http长轮询,http streaming(Chunk机制)
HTTP/2
2009年Google发布了SPDY协议,是个实验性质的东西。想在协议层面解决http 1.1的效率问题,然后后来,http工作组汲取SPDY的经验,在此基础上定制了http/2协议。为啥不叫http 2.0?因为工作组认为协议完善,后面不会有小版本了。再后来,SPDY被Google抛弃,全面转HTTP/2。
与HTTP 1.1兼容
- 不改变URL范式
- 不改变HTTP Request/Response的报文结构
由于协议兼容和不改变结构,所以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协议通信
握手基本步骤,主要是为了协商对称秘钥
- Client向Server 获取证书(明文)
- Server回复证书(明文)
- Client验证证书,并发送对称加密的秘钥给Server(密文)
- OK,已收到秘钥(明文)
HTTPS
因为HTTPS = Http + SSL/TLS,所以流程就是:
- TCP三次握手
- SSL/TLS四次握手
- Http Request/Response对称加密传输
TCP/UDP
可靠与不可靠
这里的可靠,不可靠算是TCP与UDP的区别之一,TCP的可靠的意思就是:
- 不丢包
- 包不会重复
- 包的时序不乱
那么TCP的可靠是怎么解决的呢?
- 不丢包:ACK+重发
Client每发送一个数据包就给其编号,编号单调递增,基于编号顺序确认。
Server每收到一个包,就需要发送ACK给Client进行确认,意思就是反馈给CLient已经收到数据包了。不然,Client得重发。
比如,Server收到了数据包123,它只用回复ACK=3,意思就是小于等于3的数据包都收到了,过一会儿,Server又收到了456数据包,那么它只用回复ACK=6,意思就是小于等于6的数据包都收到了。
- 包不重复
只要超时,Client没有收到Server发回来的ACK,Client就会重发,既然重发,网络又会抖动延迟,那么数据包就有可能重复。利用顺序ACK就可以做到重复包舍弃,Server给Client回复的ACK都记录在案,那么之后再收到ACK范围内的数据包就判定为重复包,丢弃就好了。
- 时序保证
假设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都需要针对这条连接维护不同的状态迁移,在不同的状态下执行相应的操作。

上图的状态迁移图大概解释下,最开始Client(主动)和Server(被动)都处于CLOSED状态,连接建立完成后,双方都处于ESTABLISHED状态,开始传输数据;最后连接关闭,双方再次回到CLOSED状态。
TCP三次握手
TCP标志位:
- URG:紧急指针(urgent pointer)有效。
- ACK:确认序号有效。
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:发起一个新连接。
- FIN:释放一个连接。

-
第一次握手:Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
-
第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
-
第三次握手:Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
注意,上面的ACK与之前的TCP可靠性的ACK有点不同,注意看描述。
TCP四次挥手

- 第一次挥手:Client发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态
- 第二次挥手:Server收到Client的后,发出ACK=1确认标志和Client的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态
- 第三次挥手:Client收到Server确认结果后,进入FIN-WAIT-2状态。此时Server发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,Server进入LAST-ACK(最后确认态)
- 第四次挥手:Client收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。Client经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。
如果第一次,第二次握手之后,意味着连接处于半关闭状态,此时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会消耗完所有连接资源,所以:
- 不要让Server主动关闭连接,这样Server的连接不会处于TIME_WAIT状态。
- Client做连接池复用连接,不要频繁的创建和关闭连接,http 1.1和http/2都是这样的思路。
QUIC协议
Google基于UDP推出的多路并发协议,可以取代TCP部分功能,实现SSL/TLS的所有功能,还可以取代部分HTTP/2的部分功能,优化了不丢包的算法性能,减小的RTT。从各种角度来看都比较优美,性能更高,不过用这个周边得有配套的设施,比较麻烦,听说youtube就采用了这个作为视频传输,降低了视频卡顿,国内B站视频网站也有落地,听说http3也要基于UDP,之前http3的全称叫HTTP over QUIC。就是建立在QUIC上的http协议。谁知道了,持续观望吧。