TCP四次挥手(图解)-为何要四次挥手
当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。那 对于TCP的断开连接,这里就有了神秘的“四次挥手”。
第一次挥手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和 Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示 主机1没有数据要发送给主机2了;
第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭 请求;
第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT 状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复, 则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
为何要四次分手呢?
那四次分手又是为何呢?TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全 双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉 主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2 返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当 主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数 据要发送了,之后彼此就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理,就需要了解 四次分手过程中的状态变化。
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待 对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态 时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方 回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该 马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用 netstat看到。(主动方)
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即 有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连 接。(主动方)
CLOSE_WAIT:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送 FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下 来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可 以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成 的事情是等待你去关闭连接。(被动方)
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK 报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态 了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到 TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)
为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文 是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立 即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我 Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不 可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发 送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以 Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT 状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发 ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网 络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有 再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
为什么不能用两次握手进行连接?
3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要 允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假 定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认 为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下, 将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在 这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S 在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服 务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收 到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探 测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
http协议版本区别
HTTP 是基于 TCP/IP 协议的一个应用层协议,是现代互联网的一个基础协议。规定了客户端与服务端之 间的通信格式以及所占用的服务端口80(HTTPS是443)。
版本
HTTP 协议从开始立项到现在一共经历了 4 个版本:
HTTP 0.9 -> HTTP 1.0 -> HTTP 1.1 -> HTTP 2
HTTP 0.9
HTTP 0.9 是一个最古老的版本
只支持GET请求方式:由于不支持其他请求方式,因此客户端是没办法向服务端传输太多的信息
没有请求头概念:所以不能在请求中指定版本号,服务端也只具有返回 HTML字符串的能力
服务端相响应之后,立即关闭TCP连接
HTTP 1.0
随着 HTTP 1.0 的发布,这个版本:
请求方式新增了POST,DELETE,PUT,HEADER等方式
增添了请求头和响应头的概念,在通信中指定了 HTTP 协议版本号,以及其他的一些元信息 (比如: 状态码、权限、缓存、内容编码)
扩充了传输内容格式,图片、音视频资源、二进制等都可以进行传输
在这个版本主要的就是对请求和响应的元信息进行了扩展,客户端和服务端有更多的获取当前请求的所 有信息,进而更好更快的处理请求相关内容。
请求头
一个简单请求的头信息
GET / HTTP/1.0 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) Accept: / 可以看到在请求方法之后有 请求资源的位置 + 请求协议版本,之后是一些客户端的信息配置
响应头
一个简单响应的头信息(v1.0)
HTTP/1.0 200 OK Content-Type: text/plain Content-Length: 137582 Expires: Thu, 05 Dec 1997 16:00:00 GMT Last-Modified: Wed, 5 August 1996 15:55:28 GMT // 这是一个空行 ...数据内容服务端的响应头第一个就是 请求协议版本,后面紧跟着是这次请求的状态码、以及状态码的描述,之后 的内容是一些关于返回内容的描述。
Content-Type
在 HTTP 1.0 的时候,任何的资源都可以被传输,传输的格式呢也是多种多样的,客户端在收到响应体 的内容的时候就是根据这个 Content-Type 去进行解析的。所以服务端返回时候必须带着这个字段。
这些 Content-Type 有一个总称叫做MIME type。
关于MIME type,这里想播插一个小插曲:
在 chrome 浏览器中,当跨域请求回来的数据 MIME type 同跨域标签应有的 MIME type 不匹配时,浏 览器会启动 CORB 保护数据不被泄漏。被保护的数据有: html、xml、json。(eg: script、img 标签所支 持的 MIME type和他们都不一致),所以服务端在返回资源的时候一定要对应返回正确的 Content Type,以免浏览器屏蔽返回结果。
笔者遇到的问题是在 chrome v76 版本之后,跨域图片资源当请求回来的数据 Content-Type 不是 image/*,图片会被拦截,页面不展示图片。
特性
无状态:服务器不跟踪不记录请求过的状态
无连接:浏览器每次请求都需要建立tcp连接
无状态
对于无状态的特性可以借助cookie/session机制来做身份认证和状态记录
无连接
无连接导致的性能缺陷有两种:
无法复用连接
每次发送请求,都需要进行一次tcp连接(即3次握手4次挥手),使得网络的利用率非常低
队头阻塞
HTTP 1.0 规定在前一个请求响应到达之后下一个请求才能发送,如果前一个阻塞,后面的请求也 给阻塞的
HTTP 1.1
HTTP 1.1 是在 1.0 发布之后的半年就推出了,完善了 1.0 版本。目前也还有很多的互联网项目基于 HTTP 1.1 在向外提供服务。
特性
长连接:新增Connection字段,可以设置keep-alive值保持连接不断开
管道化:基于上面长连接的基础,管道化可以不等第一个请求响应继续发送后面的请求,但响应的 顺序还是按照请求的顺序返回
缓存处理:新增字段cache-control
断点传输
长连接
HTTP 1.1默认保持长连接,数据传输完成保持tcp连接不断开,继续用这个通道传输数据
管道化
基于长连接的基础,我们先看没有管道化请求响应:
tcp没有断开,用的同一个通道
请求1 > 响应1 --> 请求2 > 响应2 --> 请求3 > 响应3
管道化的请求响应: 请求1 --> 请求2 --> 请求3 > 响应1 --> 响应2 --> 响应3
即使服务器先准备好响应2,也是按照请求顺序先返回响应1
虽然管道化,可以一次发送多个请求,但是响应仍是顺序返回,仍然无法解决队头阻塞的问题
缓存处理
当浏览器请求资源时,先看是否有缓存的资源,如果有缓存,直接取,不会再发请求,如果没有缓存, 则发送请求。
通过设置字段cache-control来控制缓存。
断点传输
在上传/下载资源时,如果资源过大,将其分割为多个部分,分别上传/下载,如果遇到网络故障,可以 从已经上传/下载好的地方继续请求,不用从头开始,提高效率
HTTP 2
特性:
二进制分帧
多路复用: 在共享TCP链接的基础上同时发送请求和响应
头部压缩
服务器推送:服务器可以额外的向客户端推送资源,而无需客户端明确的请求
二进制分帧
HTTP 1.x 的解析是基于文本,HTTP 2之后将所有传输的信息分割为更小的消息和帧,并对它们采用二进 制格式的编码,提高传输效率
多路复用
在共享TCP链接的基础上同时发送请求和响应,基于二进制分帧,在同一域名下所有访问都是从同一个 tcp连接中走,http消息被分解为独立的帧,乱序发送,服务端根据标识符和首部将消息重新组装起来。
头部压缩
由于 HTTP 是无状态的,每一个请求都需要头部信息标识这次请求相关信息,所以会造成传输很多重复 的信息,当请求数量增大的时候,消耗的资源就会慢慢积累上去。所以 HTTP 2 可以维护一个头部信息 字典,差量进行更新头信息,减少头部信息传输占用的资源,详见 HTTP/2 头部压缩技术介绍。
HTTPS 和 HTTP
HTTPS 协议需要申请证书
HTTP 和 HTTPS 使用端口不一样,前者是80,后者是443
HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,HTTPS 运行在 SSL/TLS 之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的
HTTPS 可以有效的防止运营商劫持