5.软件架构设计:大型网站技术架构与业务架构融合之道 --- 网络

第5章 网络 
5.1 HTTP 1.0 
	5.1.1 HTTP 1.0的问题 
		http协议的特点是"一来一回"。这样的协议有2个问题:
			1.性能问题
			2.服务端推送问题

	5.1.2 Keep-Alive机制与Content-Length属性 
		为了解决第一个问题,http 1.0 设计了一个 Keep-Alive 机制来实现tcp连接的复用。具体来说,就是客户端在http请求的头部加上一个字段
	Connection:Keep-Alive。服务端接收到这样字段的请求,在处理完请求后不会关闭连接。,同时在http 的 Response 里面也会加上该字段,然后
	等待客户端在该连接上发送下一个请求。

		当然,这会给服务器带来一个问题:连接数有限。如果每个连接都不关闭的话,一段时间后,服务器连接数就被耗光了。因此,服务器会有一个
	Keep-Alive timeout 的参数,过一段时间后,如果该连接上没有新的请求进来,则连接就会关闭。

		连接复用又产生一个新的问题:以前一个连接只发送一个请求,返回一个响应,服务器处理完毕,把连接关闭,这个时候客户端就知道连接的请求处理
	结束了。但现在,即使一个请求处理完了,连接也不关闭,那么客户端怎么知道连接处理结束了呢?或者说,客户端怎么知道接收回来的数据是完整的?

		答案是在http response 的头部,返回了一个 Content-Length:xxx 的字段,告诉客户端 http response 的 Body 共有多少个字节,
	客户端接收到这么多个字节后就知道响应成功接收完毕。

5.2 HTTP 1.1 
	5.2.1 连接复用与Chunk机制 
		连接的复用很有必要,所以到了http 1.1 后,就把连接复用变成默认属性了。除非在请求头显式的加上 Connection:close 属性。

		在http 1.0 里面可以利用 Content-Length 字段,让客户端判断一个请求的响应成功是否接收完毕。但Content-Length 有个问题,如果
	服务器返回的数据是动态语言生成的内容,则要计算Content-Length,这点对服务器来说比较困难。即使能够计算,也需要服务器在内存中渲染出整个
	页面,然后计算长度,非常耗时。

		为了,在http 1.1 中引入了 Chunk 机制。具体来说,就是在响应头加上 Transfer-Encoding:chunked 属性,其目的是告诉客户端,响应
	的Body 分成了一块块的,块与块之间有间隔符,所有块的结尾也有个特殊标记。这样,即使没有Content-Length 字段,也能够方便客户端判断出响应
	的尾部。

	5.2.2 Pipeline与Head-of-line Blocking问题 
		有了连接复用后,减少了建立连接,关闭连接的开销。但还存在一个问题,在同一个连接上,请求是串行的,客户端发送一个请求,收到响应,然后
	发送下一个请求,再接收响应。这种串行的方式,导致并发度不够。

		为了,http 1.1 引入了 Pipeline 机制。在同一个tcp连接上,可以在一个请求发出去之后,响应没回来之前,就可以发送下一个,再下一个
	请求,这样就提高了同一个tcp连接上面的处理请求的效率。

		但Pipeline有个致命的问题,就是 Head-of-Line Blocking 翻译成中文 "队头阻塞"。什么意思呢?

		客户端发送的请求顺序是1,2,3,虽然服务器是并发处理的,但客户端接收到的响应顺序也必须是1,2,3,如此才能把响应和请求成功配对,跟
	队列一样,先进先出。一旦队列头部请求1发生延迟,客户端迟迟收不到请求1的响应,则请求2,请求3的响应也会被阻塞。

		正因为如此,为了避免 Pipeline 带来的副作用,很多浏览器默认把 Pipeline 关闭了。

	5.2.3 HTTP/2出现之前的性能提升方法 
		一方面,pipeline 不能用,在同一个tcp连接上面,请求是串行的;另一方面,对于同一个域名,浏览器限制只能开6~8个连接。如何提升?

		1.Spriting技术(雪碧图)
			这种技术专门针对小图片,假设在一个网页里,要从服务器加载很多小图片(比如小图标),可以在服务器里把小图片拼成一个大图片,到了
		浏览器,再通过js或者css,从大图片截取一块显示。之前要发送很多小图片的http请求,现在只要发送一个请求就可以了。

		2.内联(Inlining)
			内联是另外一种针对小图片的技术,它将图片的原始数据嵌入在css文件里面。如:
			.icon1 {
				background:url(data:image/png;base64,<data>) no-repeat;
			}

		3.JS拼接
			把大量小的js文件合并成一个文件并压缩,让浏览器在一个请求里面下载完。

		4.请求的分片技术
			对于一个域名,浏览器会限制只能开6~8个连接。可以多做几个域名,这样就相当于绕开了浏览器的限制。

			如把静态资源放在cdn上,做一批cdn域名,这样浏览器可以为每个域名都建立6~8个连接,从而提高并发度。

	5.2.4 “一来多回”问题 
		无论是http 1.0 还是 http 1.1,都无法直接做到服务器主动推送。常见的解决方案有:
			1.客户端定期轮询
				比如客户端每5s向服务器发送一个http请求,服务器如果有新消息,就返回。

				定期轮询效率比较低下,又增加了服务器的压力。

			2.FlashSocket/WebSocket
				不再是http,而是直接基于tcp,但也有一定的局限性。

			3.Http长轮询
				客户端发送一个http请求,如果服务器有新消息,就立即返回;如果没有,则服务器夯住此连接,客户端一直等待该请求返回。然后经过
			一个约定的时间后,如果服务器还没有新消息,服务器就返回一个空的消息(客户端和服务端约定好的一个消息)。客户端收到空消息后关闭连接,
			再发起一个新连接,重复此过程。	

				这就相当于利用http实现了tcp长连接的效果,这是目前web最常用的服务器端推送的方法。

			4.HTTP Streaming
				服务端利用 Transfer-Encoding:chunked 机制,发送一个"没完没了"的chunk流,就一个连接,但其response永远接收不完。

				与长轮询的区别在于,这里只有一个http请求,不存在http header 不断重复的问题,但实现时没有长轮询简单。

	5.2.5 断点续传 
		http 1.1 还有一个很实用的特性:"断点续传"。当客户端从服务端下载文件时,如果下载到一半连接中断了,再建立新连接之后,客户端可以从
	上次断开的地方继续下载。具体实现也很简单,客户端一边下载一边记录下载的数据量大小,一旦连接中断了,重建立连接后,在请求的头部加上 
	Range:first offset - last offer 字段,指定从某个 offset 下载到某个offset,服务器就可以只返回(first offset, last offset)
	之间的数据。

		这里需要补充的是,http 1.1 的这种特性只适用于端点下载。要实现断点上传,就需要自己实现了。

5.3 HTTP/2 
		因为http 1.1 的pipeline不够完善,web开发者们想出的各种办法去提高http1.1的效率,但这些方法都是从应用层面去解决的,没有普适性。
	因此有人想在协议层面去解决这个问题,这就是google公司的 SPDY 协议的初衷。	

		吸取SPDY的经验和教训,在此基础上制定了 http/2协议。可以看出,http/2 在一开始就有很好的业界实践基础,之所以叫http/2,没有叫
	http 2.0,也是因为工作组认为该协议已经很完善了,后续不会再有小版本,如果有的话,也是下一个版本 http/3。

	5.3.1 与HTTP 1.1的兼容 
		http 1.1 已经是当今互联网的主流,因此 http/2 在设计过程中,首先要考虑的是与http 1.1 的兼容,意味着:
			1.不能改变 http://, https:// 这样的url规范;
			2.不能改变http Request / http Response 的报文结构。

		如何做到不改变Request/Response报文结构的情况下,发明http/2? 这是理解 http/2 的关键。

		http/2 和 http 1.1 并不是处在平级的位置,而是处在 http1.1 和 tcp 之间。以前http 1.1 直接构建在 tcp 之上;现在相当于在
	http 1.1 和 tcp 之间多了一个转换层,这个转换成就是 SPDY,也就是现在的 http/2。

	5.3.2 二进制分帧 
		二进制分帧是 http/2 为了解决 http 1.1 的 "队头阻塞"问题所涉及的核心特性,是转换层所做的核心工作。

		http 1.1 本身是明文的字符格式,所谓的二进制分帧,是指在把这个字符格式的报文发送给tcp之前转换成二进制,并且分成多个帧(多个数据块)
	发送。

		对于每个域名,在浏览器和服务器之间,只维护一个tcp连接。因为tcp是全双工的,即来回两个通道。

		这里的请求1,2,3,响应1,2,3 是 http 1.1 的明文字符报文。每个请求在发送之前被转换成二进制,然后分成多个帧发送。每个响应在回复
	之前,也被转成了二进制,然后分成多个帧发送。如 请求1被分成F1,F2,F3 三个帧;请求2被分成F4,F5 2个帧;请求3被分成F6,F7 2个帧;F1~F7
	是被乱序发出去的,到了服务器被重新组装。

		这里有一个关键问题:请求和响应都是被打散后分成多个帧乱序的发送出去的,请求和响应都需要重新组装起来,同时请求和响应还要一一配对。
	那么组装和配对如何实现的呢?原理也很简单,每个请求和响应实际上组成了一个逻辑上的"流",为每一条流分配一个ID,把这个ID作为标签,打到
	每一个帧上。如上,有三个流,三个流ID,分别打到三条流里面每一个帧上。

		有了这个二进制分帧之后,在tcp层面,虽然是并行的;但从http层面来看,请求就是并发的发送出去,并发的接收的。\

		有了二进制分帧,是不是彻底解决了Pipeline的"队头阻塞"问题呢?其实还没有,只是把"队头阻塞"问题从http request 粒度细化到了"帧"
	的粒度。

		只要用tcp协议,就绕不开"队头阻塞"的问题,因为tcp协议是先进先出的。如,F3帧(队头的第一个帧)在网络上被阻塞了,则服务器会一直等待
	F3,如果F3不来,后面的包都不会成功被接收。反向队列FF1也是同样的道理。

		当然,虽然 http/2 的二进制分帧没有完全解决队头阻塞的问题,但降低了其发生的可能性,为什么这么说?下面具体分析下:
			原因1:服务器对请求1处理的慢;
			原因2:服务器对请求1处理的很及时,但网络传输慢了;

		对于原因2,如果刚好请求1的第一帧又在队头,则即使二进制分帧也解决不了队头阻塞的问题;但对于原因1,请求2,请求3的响应分帧之后,是先
	于请求1的响应发出去的,那么请求2和请求3的响应就不会被请求1阻塞,从而避免了队头阻塞问题。

		如果要彻底解决"队头阻塞"问题怎么解决呢?不用tcp。这正是google公司的 QUIC 协议要做的事情。

	5.3.3 头部压缩 
		除了二进制分帧,http/2 另外一个提示效率的地方是头部压缩。在http 1.1 里面,对于报文的报文体,已经有相应的压缩,尤其对于图片,本来
	就是压缩过的;但对于报文的头部,一直没做压缩。

		随着互利网的发展,应用场景越来越复杂,很多时候报文的头部也变得很大,这时候对头部进行压缩就很有必要了。为此,http/2 专门设计了一个
	HPACK 协议和对应的算法。

		为解决http 1.1 的效率问题,除引入的这2个关键特性之外,http/2 还有一些其他特性,比如 服务器推送,流重置等。

5.4 SSL/TLS 
	5.4.1 背景 
		SSL(Secure Sockets Layer)安全套接层,TLS(Transport Layer Security)传输层安全协议。

		TLS 1.0 相当于 SSL 3.1;
		TLS 1.1 相当于 SSL 3.2;
		TLS 1.2 相当于 SSL 3.3;

		SSL/TLS 处在 tcp 层面的上层,它不仅可以支撑 http 协议,还可以支撑 ftp,imap 等其他各种应用层协议。

	5.4.2 对称加密的问题 
		客户端和服务端用同一个密钥。

		这种加密方式存在2个问题:
			1.密钥如何传输?密钥A的传输也需要另外一个密钥B,密钥B的传输也需要密钥C... 如此循环,无解。
			2.如何存储密钥?对于浏览器网页来说,都是明文的,肯定存储不了;对于Android/iOS客户端,即使能把密钥存储在安装包的某个位置,
			也很容易被破解。

		当然,这2个问题其实是一个问题。因为如果把密钥的传输问题解决了,就可以在建立连接之后获取密钥,然后只存储在内存中,因为当连接断开后,
	密钥就在内存中销毁了,也就解决了存储问题。

	5.4.3 双向非对称加密 
		客户端为自己准备一对公私钥(PubA,PriA),服务器为自己准备一对公私钥(PubB,PriB)。公私钥有2个特性:公钥PubA是通过私钥PriA计算出来
	的,但反过来不行,不能根据PubA推算出PriA。

		客户端和服务端把自己的公钥公开出去,自己保留私钥。

		当客户端给服务端发送信息时,就用自己的私钥PriA签名,再用服务端的公钥PubB加密。所谓的"签名",相当于自己盖了章,或者说签了字,证明
	这个信息是客户端自己发的,客户端不能抵赖;用服务器的公钥PubB加密,意味着只有服务器B可以用自己的私钥PriB解密。即使这个信息被C截获了,
	C没有B的私钥,也无法解密这个信息。

		服务器收到这个信息后,先用自己的私钥PriB解密,再用客户端的公钥验签(证明信息是客户端发出的)。反向过程同理:服务器发送给客户端信息时,
	先用自己的私钥PriB签名,然后用PubA加密,客户端收到服务器的信息后,先用自己的私钥PriA解密,再用PubB验签。

		在这个过程中,存在着签名和验签与加密和解密两个过程:
			1.签名和验签
				私钥签名,公钥验签,目的是防篡改。如果第三方截取到信息之后篡改,则接收方验签肯定过不了,同时也是防抵赖,既然没有人可以
			篡改,只可能是发送方自己发出的。

			2.加密和解密
				公钥加密,私钥解密。目的是防止信息被第三方拦截和偷听。第三方即使能获取信息,但没有私钥,也解密不了。

		在双向非对称加密中,客户端需要提前知道服务器的公钥,服务器需要知道客户端的公钥,和对称加密一样,同样面临公钥如何传输的问题。

	5.4.4 单向非对称加密 
		在互联网上,网站是完全对外公开的,网站的提供者没有办法验证每个客户端的合法性,只有客户端可以验证网站的合法性,是否钓鱼网站等。

		在这种情况下,客户端并不需要公钥和私钥对,只有服务器有一对公钥和私钥。服务器把公钥给到客户端,客户端给服务器发送信息时,用公钥加密,
	然后服务器用私钥解密。反过来,服务器给客户端发送的消息,采用明文发送。

		当然,对于安全场景要求很高的地方,比如银行的个人网银,不仅客户端要验证服务端的合法性,服务器也要验证每个访问的客户端的合法性。对于
	这种场景,往往会给用户发一个U盘,里面装的就是客户端一方的公钥和私钥,用的就是上面的双向非对称加密。

		对于单向非对称加密,只有客户端到服务器的单向传输是加密的,服务器返回的是明文的。

		假设PubB的传输过程是安全的,客户端知道了服务器的公钥。客户端就可以利用加密通道给服务器发送一个对称加密的密钥。

		客户端对服务器说,"hi,我们的对称密钥是xxx,接下来用xxx密钥通信。"这句话是通过PubB加密的,所以只有服务器用自己的PriB解密。然后
	服务器回复了一句"好的,我知道了。"虽然是明文的,但没有任何密钥信息在里面,所以才有明文也没有关系。接下来,双方就可以基于对称加密进行通信
	了。这个密钥在内存里,不会落地存储,所以也不会有盗取问题,而这就是 ssl/tls 的原型。

	5.4.5 中间人攻击 
		上面的分析我们知道,我们并不需要双向的非对称加密,而用单向的非对称加密就能达到传输的目的了。

		但无论是单向还是双向,都存在着公钥如何安全传输的问题。

		中间人攻击的过程:
			1.客户端本来要把自己的公钥发送给服务器:"hi,我是客户端1,我的公钥是PubA"
			2.被中间人C劫持后,C用自己的公钥替换客户端的公钥,然后发送给服务器,"hi,我是客户端1,我的公钥是PubC"
			3.反过来,服务器本来是要把自己的公钥发送给客户端的:"hi,我是服务器,我的公钥是PubB"
			4.被C劫持后,C用自己的公钥替换服务器的公钥,然后发送给客户端:"hi,我是服务器,我的公钥是PubC"

		最后的结果是:客户端和服务端都以为是在和对方通信,但其实他们都是在和中间人通信。接下来,客户端发给服务端的信息,会用PubC假面,
	C当然可以解密这个信息;同样,服务器发送给客户端的信息也会被PubC加密,C也可以解密。

		这个问题为什么会出现?因为公钥的传输是不安全的。

	5.4.6 数字证书与证书认证中心 
		引入一个中间机构CA。当服务器把公钥发给客户端时,不是直接发送公钥,而是发送公钥对应的证书。

		从组织上来说,CA类似于现实中的"公证处",从技术上来说,就是一个服务器。服务器先把自己的公钥PubB发给CA,CA给服务器颁发一个数字证书,
	这个证书相当于服务器的身份。之后,服务器把证书给客户端,客户端可以验证证书是否为服务器下发的。

		反过来同理,客户端用自己的公钥PubA给CA换取一个证书,相当于客户端的身份证,客户端把这个证书给了服务器,服务器就能验证证书是否为客户端
	下发的。

		当然,对于通常的互联网来说,只需要客户端验证服务器,不需要服务器验证客户端。

		具体过程:
			CA有一对公私钥,私钥只有CA自己知道,公钥在网上,谁都可以知道。服务器把 个人信息+服务器的公钥发给CA,CA用自己的私钥为服务器生成
		一个数字证书。通俗地讲,服务器把自己的公钥发给CA,让CA改个公章,之后别人就不能再伪造公钥了。如果被中间人伪造了,客户端拿着CA的公钥
		去验证这个证书,验证将无法通过。

	5.4.7 根证书与CA信任链 
		CA 面临和客户端,服务器同样的问题:客户端和服务器需要证明公钥的确是自己发出去的,不是被伪造的。CA同样需要证明,自己的公钥是自己发
	出去的,而不是被伪造的。

		答案是给CA颁发证书,CA的证书由谁来颁发的呢?CA的上一级CA。最终形成证书信用链。

		1.证书信任链的验证过程
			客户端需要验证服务器的合法性,需要拿着服务器的证书C3,到CA2去验证(C3是CA2颁发的,验证方法是拿着CA2的公钥,去验证证书C3的
		有效性)。

			客户端验证CA2的合法性,需要拿着CA2的证书C2,到CA1去验证(C2是CA1颁发的)。

			客户端要验证CA1的合法性,需要拿着CA1的证书C1,到CA0去验证(C1是CA0颁发的)。

			而CA0呢,只能无条件信任。怎么做到无条件信任呢?Root CA 机构都是一些世界上公认的机构,在用户的操作系统,浏览器发布的时候,
		里面就已经嵌入这些机构的Root 证书。信任这个操作系统,信任这个浏览器,也就信任了这个 Root 证书。

		2.证书信任链的颁发过程
			颁发的过程与验证的过程刚好是逆向的,上级CA给下级CA颁发证书。从根CA(CA0)开始,CA0给CA1颁发证书,CA1给CA2颁发证书,CA2给
		服务器颁发证书。	

			最终,证书成为网络上每个通信实体的"身份证",在网络上传输的都是证书,而不再是原始的那个密钥。把这套体系标准化后,就是在网络
		安全领域常见的一个词,PKI。

	5.4.8 SSL/TLS协议:四次握手 
		在建立tcp连接之后,数据发送之前,ssl/tls 协议通过4次握手,2个来回,协商出客户端与服务器之间的对称加密密钥。第一个来回,是公钥
	的传输与验证过程(通过数字证书);第二个来回基于第一个来回的公钥,协商出对称加密的密钥。接下来,就是正常的应用层数据包的发送操作了。

		当然,为了协商出对称加密密钥,ssl/tls 协议引入了几个随机数。

5.5 HTTPS 
	https = http + ssl/tls 。整个https的传输过程大致可以分为三个阶段:	
		1.tcp连接的建立
		2.ssl/tls 四次握手协商出对称加密的密钥
		3.基于密钥,在tcp连接上对所有的http request/response 进行加密和解密。

		其中阶段1 和阶段2 只在连接建立时,做1次,之后只要不关闭,每个请求都只需要经过阶段3,因此相比http,性能没太大损失。

	最后分析下http2和https的关系:http2 主要是解决性能问题,https 是解决安全问题。从理论上来说,两者没有必然的联系,http2 可以不依赖于
https;反过来也是。
	
	http 1.1
	http/2 (SPDY)  (可选)
	SSL/TLS     (可选)
	TCP


5.6 TCP/UDP 
	5.6.1 可靠与不可靠 
		不可靠:
			1.丢包
			2.时序乱了

		可靠:
			1.数据包不丢
			2.数据包不重
			3.时序不乱

		tcp 核心:
			1.解决不丢问题:ACK + 重发
				丢包是一定的,如何确保不丢包?只有一个办法:重发。收到一个包,就确认。如果超时没收到,就重发。

			2.解决不重的问题
				如何判重呢?就是顺序ACK。服务器回复给客户端 ACK=6,意思是所有小于或者等于6的数据包全部收到了,之后凡是再收到这个范围的
			数据包,就判定为是重复的包,丢弃。

			3.解决乱序的问题
				服务器虽然收到的数据包是并发的,但数据包的ACK是按照序号从小到大逐一确认的,所以数据包的时序是有保证的。

				最终通过 消息顺序编号+客户端重发+服务器顺序ACK,实现了客户端到服务器的数据包的不重,不漏,时序不乱。

	5.6.2 TCP的“假”连接(状态机) 
		在物理层面,在客户端和服务器之间不存在一条可靠的"物理管道"。只是在逻辑层面,让tcp之上的应用层就像在客户端和服务器之间架起了一个"可靠
	的连接"。但实际上这个连接是"假的",是通过数据包的 不重,不漏,时序不乱的机制,给上层应用一个假象。

		每条连接用(客户端IP,客户端Port,服务器IP,服务器Port)4元组唯一确定,在代码中是一个个socket。其中有一个关键问题要解决,既然"连接
	是假的",在物理层面不存在,但在逻辑层面是存在的,每条连接都要经历建立阶段,正常数据传输阶段,关闭阶段,要完整的维护在这三个阶段过程中连接
	的每种可能的状态。

	5.6.3 三次握手(网络2将军问题) 
		客户端的状态转移: closed => syn_send => established;
		服务端的状态转移: closed => listen => syn_rcvd => established;

		无论是两次握手,三次握手,还是四次握手,都绕不开网络的2将军问题。那为什么是三次?
			因为三次握手刚好可以保证客户端和服务器对自己发送,接收能力做了一次确认。

	5.6.4 四次挥手 
		客户端的状态转移:established => fin_wait_1 => fin_wait_2 => time_wait => closed;
		服务端的状态转移:established => close_wait => last_ack => closed

		为什么是四次挥手?
			因为tcp是全双工的,有可能处于 half-close 状态。

		为什么要等待 2MSL time_wait:
			1.所谓的"连接"是假的,物理层面没有连接。这意味着当双方都进入close后,仍可能有数据包还在网络上"闲逛",如果此时收到了这些闲逛
			的数据包,丢到即可,但问题是连接可能重开。

			  一个连接由(客户端ip,客户端port,服务端ip,服务端port)4个元组组成唯一标识,连接关闭后重新打开,应该是一个新的连接,但用
			4元组无法区分新连接和老连接。这会导致,之前闲逛的数据包在新连接打开后被当做新的数据包,这样一来,老连接上的数据包会"串"到新
			连接上。

			  怎么解决这个问题?

			  在整个tcp/ip网络上,定义了一个值叫做 MSL,任何一个ip数据包在网络上逗留的最长时间是msl,这个值默认是120s。意味着一个数据包
			最多在msl时间内,从源点到目的地,如果超出了这个时间,中间的路由节点就会把该数据包丢弃。

			  有了这个限定后,一个连接保持在 time_wait 状态,再等待 2msl 的时间进入 close 状态,就会完全避免旧的连接数存在闲逛的数据
			包串到新连接上。为什么是2msl呢?

			2.因为网络2将军问题。第4次发送的包,服务器是否收到是不确定的。服务器采用的方法是在无法收到第4次的情况下,重新发送第3次的数据
			包,客户端重新收到第3次数据包,再次发送第4次数据包。第4次包的传输时间+服务器重新发送第3次包的时间,最长2msl,所以要客户端在
			time_wait 等待 2msl。

			  还有一个问题:客户端处于time_wait状态,要等到2msl进入closed;但是服务器收到第4次 ack后,立即进入了closed状态。为什么
			服务器不等待呢?

			  原因是没有必要。任何一个连接都是4元组,同时关闭了客户端和服务器,客户端处于time_wait状态,意味着这个连接要等待2msl时间
			之后才能重新启用,服务器想立即启用也无法实现。


		通过分析会发现,一个连接并不是想关闭就能关闭的,关闭之后还要等待2msl时间才能重新打开,这就造成了一个问题,如果频繁的创建连接,
	最后可能会导致大量的连接处于 time_wait 状态,最终耗光了所有资源。如何避免?
		1.不要让服务器主动关闭连接,这样服务器就不会处于 time_wait 状态
		2.客户端做连接池,复用连接,而不要频繁的创建和关闭,这其实也是http 1.1 和 http/2 的思路。

5.7 QUIC 
	QUIC(Quick UDP Internet Connection) 是由google 公司提出的基于udp协议的多路并发传输协议。

	只要使用tcp,就没有办法完全解决队头阻塞问题,因为tcp是先发送后接收的,而udp没有这个限制。正因为如此,google提出了QUIC协议,想基于
udp构建上层的应用网络。	
	
	如下为quic协议在网络分层中的位置,首先,它取代了 tcp的部分功能(数据包的不丢);然后它实现了 ssl/tls 的所有功能;最后它还取代了http/2
的部分功能(多路复用)。

http 1.1  http 1.1
http/2    http/2
ssl/tls   QUIC
TCP 	  UDP
      IP 

	5.7.1 不丢包(Raid5算法和Raid6算法) 
		虽然针对udp会丢包,tcp可以通过 "ack+重传"来解决,但很多时候重传的效率不够高。

		除了重传,是否还有其他办法解决丢包?这就要用到磁盘存储领域经典的 Raid5和Raid6 算法。每发送5个包,就发送一个冗余包。冗余包是对5个
	数据包做异或运算得到的。这样一来,服务器收到6个包,如果5个当中,有一个丢失了,可以通过其他几个包计算出来。这样就好比做简单的数学运算:
	A+B+C+D+E=R,假设数据包D丢失了,可以通过 D=R-A-B-C-E 计算得到。

		但这种丢包的恢复办法有一个限制条件:每5个包中只能丢失1个,如果把它改造成每发送10个数据包,再生成一个冗余包,就是每10个当中只能
	丢失1个。

		在raid5的基础上,把可靠性向上提一个级别,生成两个冗余块,就是raid6。每5个块生成2个冗余块,这样就允许每5个丢失2个。数据恢复的过程,
	相当于解一个二元一次方程:
		A+B+C+D+E=R1
		A-B+C-D+E=R2

		对于QUIC 来说,它采用的是 RAID5,目前是发送10个包,冗余1个包。允许丢1个包,如果丢失了2个。那就要重传了。通过设置合理的冗余比,
	QUIC 减少了数据重传的概率。

	5.7.2 更少的RTT 
		要建立一个 https 连接需要7次握手,tcp的3次握手加上 ssl/tls 的4次握手,是3个RTT。而造成网络延迟的原因,一个是带宽,另外一个是
	RTT。因为RTT有个特点是同步阻塞,在数据包发送出去之后,必须要等待对方的确认回来,接着再发送下一个。

		所以,无论是tcp,还是ssl/tls,都尝试优化协议,减少rtt的次数。基于QUIC协议,可以把前面的7次握手(3个RTT),减少为0次。

	5.7.3 连接迁移 
		tcp 的连接是4元组组成的,这在pc端上问题不大。而在移动端上,客户端是wifi或者4g,客户端的ip一直是变化的,意味着频繁的建立和关闭
	连接。有没有可能,在客户端的ip和端口的浮动下,连接仍然是可以维持的呢?

		tcp的连接本来就是"假"的,一个逻辑上的概念而已,QUIC协议也可以创造一个逻辑上的连接。具体做法是,不再以4元组来标识连接,而是让
	客户端生成一个64位的数字来标识连接,虽然客户端的ip和port在漂移,但64位的数字没有变化,这条连接就会一直存在。这样,对于应用层来说,
	就感觉连接一直存在。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值