救救孩子吧,到如今还搞不懂TCP的三次握手四次挥手

本文在我的技术博客同步发布,详情可用力戳
亦可扫描屏幕右侧二维码关注我的公众号,公众号内有我的联系方式,等你来撩...java

  前几天发了一个朋友圈,发现暗恋已久的女生给我点了个赞,因而我当晚展转反侧、彻夜未眠!想着妹子是否是对我有感受呢?否则怎么会忽然给我点赞呢?要不趁机表个白?web

  因而次日我在心中模拟了屡次表白的话语,连呼吸都反复练习。到了晚上,我拨通了妹子的微信语音,还没等对方开口我就按捺不住心里的想法,开始自说自话,一阵狂乱的表达...足足五分钟一鼓作气,一切都是那么天然!缓存

  但是在我说完以后却半天都没有等到妹子的回应...过了好一下子才听到对方的声音:“喂!喂!我这边信号很差,你刚刚在说啥我一句都没听到,我在跟我男友逛街呢...”。服务器

  我挂断了电话,我也对我此次失败的表白进行了深度的总结!缘由就是由于我没有学好TCP!微信

  若是我懂TCP,那我在表白以前至少要先问一句“在吗?”!先创建可靠的链接,确保链接正常才能开始表白!网络

  若是我懂TCP,那我在我说话的过程当中须要对方不断的确认,这样才能保证我说的每一句话对方都能听到!这样我才能表白成功!tcp

  因此一切都是由于我没有学好TCP,因而我走进了图书馆...post

咱们先来看下TCP的定义:大数据

TCP全称为Transmission Control Protocol(传输控制协议),是一种面向链接的、可靠的、基于字节流传输层通讯协议。TCP是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。网站

  这里面每个字咱们都认识,可是连在一块就不是那么好理解了!那咱们就提炼一些关键的词,也就是我上面高亮的那些:面向链接、可靠、基于字节流、传输层、协议、端到端!理解了这些关键字也就理解了TCP的实现原理,那咱们就来从这些关键字开始进行分析!

传输层

咱们先讲传输层,由于能够从比较高的层面去看TCP,咱们先看下经典的OSI七层网络参考模型:
image
  当咱们须要在网络上进行数据交换的时候,就须要通过这么几层。每一层都有相关落地的实现,咱们今天要讲的TCP就是传输层的一种落地实现。可能咱们平时在说到传输层的时候天然而然的就想到的TCP,可是TCP只是传输层的一种实现,其余比较常见的传输层协议还有UDP等!

  我知道干巴巴的文字对你来讲太抽象,那我就抓个包来看看,让这几层更加具象!本文中全部的包都是经过postman发送请求,而后用wireShark来抓的!若是对这两款软件还不了解的盆友能够先去了解下哈,这里不过多说明。咱们在postman中输入www.17coding.info的域名,而后发送请求,wireshark就能抓到数据包了。

image

  图上已经标明每一层与抓到的数据包对应的关系了!咦!咱们上面不是说的7层网络参考模型么?为何数据包只有5层呢?注意参考二字,7层模型是一个理论模型,实际的网络中每每都把应用层、会话层、表示层统为应用层!

什么是协议?

  说到协议,就是双方共同遵照的一种约定!好比我写的这篇文章里,你可以看懂我写的每个字并明白个人意思,那就是由于咱们都遵循了汉语的语法,这自己也就是一种协议。还有好比咱们写代码就必须按照规定的语法进行编写,这样编译器才能进行正确编译。

  在计算机网络中也有不少协议,好比常见的应用层协议http、ftp、dns协议等等。常见的传输层协议有TCP、UDP等等...其实这些协议都是发送方和接收方都在遵循的一种规范。若是咱们遵循了其规范,也能成为协议的实现者,好比本身写一个web服务器处理用户请求。甚至咱们还能本身规定一套协议,供别人使用!

TCP头部格式

  咱们前面说了协议的定义,那TCP协议确定也有必定的规范咯!这样通讯双方才能识别对方的数据报文,进行数据交换,咱们先看下TCP的报文格式

image

  TCP报文包含数据头和数据体,头部有5行的固定长度以及1行可变长度!图上前面5行就是固定长度!固定长度的每一行占有4个字节(32位)。所以头部固定长度就为5*4=20个字节!

  到这里咱们能够抓个包来看下加深印象,咱们依然向www.17coding.info发送一个请求,而后看看其TCP部分的数据包

image

接下来那咱们就一行一行的来分析TCP的头部:

第一行:

一、源端口:发送方端口

二、目标端口:接收方端口

  前面咱们说到TCP是端到端的,这里就能很好的体现了!每一个数据包中都有发送方和接收方的端口。这里每一个端口占用2个字节(16位)。

第二行、第三行:

一、序号:tcp是面向字节流的,数据分块在缓存存放及发送,序号用来标记某个数据包最开始的字节是整个数据的第多少个字节。

二、确认号:每次收到请求后,接收方都会回复发送方,告诉对方本身已经接收了多少字节,下一个数据包须要从第多少字节开始发送。这里的值通常等于接收到的序号+接收到的数据包数据部分长度。

  这里的序号和确认号是保证TCP可靠特性所不可或缺的,咱们后面会经过抓包来详细分析!序号和确认号分别都占用了4个字节(32位)!

第四行:

一、数据偏移:这里叫头部长度更为合适。前面说过TCP头部长度有部分是可变的,因此须要标识数据包数据部分从哪里开始。这个值占用了4位。

二、保留:未使用,供扩展使用。这个值占了3位。

三、标志:标志一共有9个,每一个标识占1位,共占9位。上面的抓包截图就能看到这9个标识位!
3.一、NS:Nonce,与ECN显式拥塞通知相关。
3.二、CWR:CWR 标志与后面的 ECE 标志都用于 IP 首部的 ECN 字段,ECE 标志为 1 时,则通知对方已将拥塞窗口缩小
3.三、ECE:ECN-Echo,若设置了该标识,则会通知对方,从对方到这边的网络有阻塞。
3.四、URG:Urgent,用于在发送方加塞。好比在下载文件的时候,下到一半了须要中止下载,就须要发送一个紧急的请求告诉对方中止发送数据。数据包不排队。
3.五、ACK:Acknowledgment,标记为一个确认。
3.六、PSH:Push,与URG对应的,用于接收方加塞。
3.七、RST:Reset,表示出现严重差错,可能须要从新建立TCP链接。若是咱们打开某个网站一直没刷出来,咱们F5进行刷新,那以前的数据包就要拒绝。
3.八、SYN:用于同步,创建请求的时候用。在握手时候会带这个标记!
3.九、FIN:通讯结束,释放链接的时候用。在挥手时候会带这个标记!

四、窗口:无论是发送方仍是接收方,都有对应的发送窗口和接收窗口。在通讯以前,通讯双方会协商窗口的大小。发送方按照接收方的接收窗口设置本身的发送窗口,同时发送窗口还受拥塞窗口的限制,这个在拥塞控制部分会提到!在发送过程当中窗口会根据接收方的处理能力调整。这个值对TCP的可靠传输及流量控制起了很大的做用!这个值占了16位。

第五行:

一、校验和:用于校验数据包是否完整或者被修改。这个值占了16位。

二、紧急指针:用来标记本报文段中紧急数据的指针,也就是指明了从数据包数据部分的头部到指定位置的数据为紧急数据,只有在设置了标志位URG的时候才起做用。这个值占了16位。

第六行:

一、选项:选项里面也有些重要的数据,咱们挑几个讲一下

1.一、MSS:MSS的全称为Maximum segment size,双方协商的每个报文段所能承载的最大数据长度(不包括文段头)。

1.二、WS:WS的全称为Window scale,也叫窗口因子!是用来调整窗口大小的。前面咱们说到过窗口大小的字段,那这个窗口因子又是作什么用的呢?早期的网络带宽、硬件配置都比较差,因此窗口大小最大只预留了16个bit,也就是最大能设置的值为65535。随着硬件和网络的发展,65535已经不能知足。因此就增长了一个WS的选项来扩展!若是设置了WS,那实际的窗口大小就等于窗口大小乘以窗口因子

1.三、SACK:SACK的全称为Selective ACK,选择性确认是创建在累计确认(后面讲) 的基础上的!只有收到失序的分组时才会可能会发送SACK,若是接收方接收到了后面的数据包,而发现前面的数据包丢失,则会通知发送方哪些报文段丢失,须要重发!

二、填充:这个字段是为了让整个头部为4个字节的倍数。java中也有不少相似的用法!

  咱们找到一个数据包,看看其详细的头部数据:
image

一、红色部分显示了TCP头部的长度为32byte,以及选项部分为12byte。前面咱们说了TCP首部固定长度为20byte,因此20+12=32。
二、黄线部分的窗口大小为259byte,窗口因子为256。因此实际的窗口大小为259*256=66304!

面向链接怎么理解

  从我表白失败的例子就能看到,我还未确保链接的正常就开始表白,致使我说完了对方却由于信号很差没有听到。若是我事先确保链接正常,就不会出现这样的状况了!咱们前面说了TCP是面向链接的,那TCP是怎么面向链接的呢?

三次握手交代了什么?

  没错,都是从握手开始!咱们都知道,tcp创建链接须要通过三次握手,那每次握手都交代了什么呢?若是只进行两次握手行不行?咱们先看一个电话接通的场景:

A:你好,你能听到吗?
B:我能听到,你能听到吗?
A:我也能听到。
.......

  在正式通话以前,为了确保通话的可靠,每每都须要通过上面的三次对话进行确认。那这三次对话是必须的吗?每一次对话的必要性又是什么呢?

A:你好,你能听到吗?(让B知道A能说话
B:我能听到,你能听到吗?(让A知道B能听到,且能说话
A:我也能听到。(让B知道A能听到
.......

  只有通过三次的对话,才能确认本身的声音能被对方听到且能听到对方的声音。这也才能开展后续的对话。这里咱们就不得不祭出经典的三次握手图了:

image

咱们分析三次握手过程及每次握手后的状态以下:

一、A主机发送标识SYN=1(SYN表示A请求跟B创建链接,前面在讲TCP头部时候有说到过),序号Seq=x,第一次握手请求发送后A的状态为SYN_SENT,B在接收到请求后状态由LISTEN变为SYN_RCVD

二、B主机收到链接请求后向A主机发送标识SYN=1,ACK=1(SYN表示B请求跟A创建链接,ACK表示对A的链接请求进行应答),序号Seq=y,确认号Ack=(x+1),A接收到B的确认后,状态变为ESTABLISHED,B的状态依然为SYN_RCVD

三、主机A收到后检查Ack是否正确,若正确,则发送标识ACK=1(表示对B的链接请求进行应答),序号Seq=(x+1),确认号Ack=(y+1)。B接收到A的确认后,A和B的状态都变为ESTABLISHED

这里咱们要注意的几点是:

一、图中的发送请求中中括号里面的SYN、ACK就是前面说TCP头部中的那几个标志位!而Seq和Ack分别表明序号和确认号。

二、接收方在接收到发送方发送的Seq后,应答一个Ack,Ack的值等于Seq+1,表示已要发送方开始发送Seq+1位置的数据。

二、B在接收到了A的链接请求后回复中同时发送了SYN、ACK两个标识位,将创建链接的请求和对A的应答在同一个包中发送了,这也是为何只须要三次握手,就能创建链接。

咱们依然向www.17coding.info发送请求,下面为三次握手的包:

image

  在info那一栏,咱们很明显的能看到发送的数据包头部有咱们上面说到的那些标志位,还有Seq、Ack等头部信息,还有Win、MSS等头部选项数据!所以三次握手不只仅是单纯创建链接,还会协商一些参数

  当我鼠标选择某一行时,若是这个数据包包含了对某个数据包的确认(也就是有ACK的标记),就能在对应的数据包的No列上面看到一个小勾勾,好比上面图中我鼠标选择的是第三次握手的数据包,在第二次握手的数据包前面就有个小勾勾。

为何握手只须要三次而挥手须要四次?

  经过三次握手,双方就创建了一个可靠的链接,就能进行数据的传输了!当数据传输完成,就得将链接关闭,由于链接也是一种资源!链接的关闭须要通过四次挥手!

  为何握手能够三次完成,可是挥手却须要四次呢?我偏要三次行不行?其实也没啥不能够的!好比下面的对话场景:

A:我说完了,你说完就挂电话吧!
B:好嘞,我也说完了,能够挂电话了!
A:好嘞,拜拜。
挂断......

  这样三次对话就能够实现挥手了,可是在实际的网络中,当我发出一个请求的时候,可能服务器的响应体比较大,须要较长时间的传输!因此当客户端主动发起断开请求的时候,服务器先回应一个确认,等全部数据传输完毕后再发送服务器断开的请求。

A:我说完了,你说完就挂电话吧!
B:好嘞...
B:......
B:我也说完了,能够挂电话了
A:好嘞,拜拜
挂断......

  因此大部分状况下都须要进行四次挥手!可是,在我我的的抓包实践中,也会有三次挥手就能完成断开链接的状况。

这里咱们又不得不祭出经典的四次挥手图了:

image

咱们分析四次挥手过程及每次挥手后的状态以下:

一、主机A发送标识FIN=1(FIN表示A请求关闭链接)用来关闭A到B的数据传输。此时A的状态为FIN_WAIT_1

二、主机B收到关闭请求后向A发送ACK(ACK表示应答A的关闭链接请求),A再也不向B发送数据。此时A的状态为FIN_WAIT_2,B为CLOSE_WAIT

三、主机B发送标识FIN=1用来关闭B到A的数据传输。此时A的状态为TIME_WAIT,B为LAST_ACK

四、主机A收到关闭请求后向B发送ACK,此时B再也不向A发送数据。此时A、B都关闭了,状态变为CLOSED。

  在图中咱们能看到,A的TIME_WAIT状态会持续2MSL再变成CLOSED,MSL(Maximum Segment Lifetime)的中文能够译为“报文最大生存时间”!他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。那TIME_WAIT维持2MSL的做用是什么呢?

一、第4次挥手的时候主机A发送ACK到主机B,若是发送完成后就直接就关闭链接,那若是因为网络缘由B没有收到ACK,那B就无法关闭链接了!所以A在回复确认后,还须要等待,万一B没有收到应答还会继续发送FIN的请求。

二、若是不等待2MSL,那客户端的端口可能会被重用,若是再次用这个端口创建与服务器的链接,那先后两个使用相同四元组的的链接之间会造成干扰!

image

咱们看上面向www.17coding.info发送请求的挥手数据包:

image

  可能你们在抓包的时候不能立马看到四次挥手的数据包!那是由于在HTTP1.1及以后,默认都开启了长链接!也就是在一次请求以后,创建的链接并不会立马关闭,而是供后续的其余请求继续使用,以减小每次从新创建链接的资源消耗!若是想发出请求后立马能抓到四次挥手的数据包,能够设置Http的头部Connection:close。这样每次发送请求都能看到完整的三次握手四次挥手的过程啦!

TCP是怎么保证可靠传输的?

  保证传输的可靠咱们前面已经说到了面向链接,创建链接是保证数据传输的第一步。那在链接创建以后的数据传输怎么保证可靠呢?

  咱们再次回到咱们打电话的场景,通常在对话的过程当中,都是得双方都有互动,给与对方回应。而不是一我的一个劲的说而另外一方没有任何回应!好比下面场景:

A:跟你讲哦,我上周网上认识了一个妹子
B:嚯,牛逼啊!
A:而后我昨天约出来见面了
B:666啊!而后呢?
A:而后咱们@#¥%……&
B:卧槽,你刚刚说啥我没听清,你再说一遍?...

  这样的确认和应答就确保了双方的通讯可以完整可靠。TCP也采用了这种y应答和确认重传的机制,保证在不可靠的网络上实现可靠的传输。只要我没有收到确认,我就认为没有发送成功,就会重发。

中止等待协议

  中止等待协议就是每次给对方发送数据包后,须要等待对方的回应而后再发送下一个数据包!中止等待协议会出现以下几种状况:

一、无差错状况:A发送M1包到B,B收到后会给A一个确认,当A收到B的确认后再发送包M2。

image

二、超时重传:A发送M1包到B,若是发送过程当中包丢失,A会从新发送。A等待重发的时间是比一个报文的往返时间(RTT)稍微多一点。

image

三、确认丢失:若是B在给A发送确认的时候丢失,A会从新发送M1包给B,因为B已经处理过M1的数据包因此B会丢弃报文,而后重传确认M1给A。

image

四、确认迟到:若是A发送数据包M1给B,B回复确认的时候延迟了。这时A又会从新发送包M1给B,B收到后丢弃数据包,而后重传确认M1给A。这时A会收到屡次确认,当第二次收到迟到的确认后A也会丢弃该确认。

image

  咱们从上面能看到,中止等待协议每次都是等到收到确认后再发下一个数据包。只要我没收到你给个人确认,我就认为你没有收到我发的数据包,我就会进行重发!这样虽然可靠,可是会致使信道利用率较低

流水线传输

  流水线传输就是每次发送多组数据包,没必要每次发完一组就停下来等待对方的确认。因为信道上一直有数据不间断的传输,所以能够得到较高的信道利用率!

  流水线传输如何保证可靠的呢?须要发送方维持发送窗口,假如发送窗口是5,那5个数据包会同时发送,而后等确认!若是有收到接收方的确认,窗口就会滑动,进行第6个数据包的发送。

  若是都是单个确认,可能效率会比较低,因此有了累计确认!也就是说假如发送方发送了数据包一、二、三、4,接收方只须要回复对数据包4的确认,那表示1234数据包都已经收到了,就能够进行第五个数据包的发送了!假如发送了数据包一、二、三、4,其中第三个数据包丢失,那该怎么确认呢?TCP只会回复对数据包2的确认,而且对数据包4进行选择性确认(TCP头部选项讲到过的SACK),这样发送方就知道数据包4已经成功发送,只须要重发数据包3。

image

继续前面抓包的例子,接收方并非对每一个数据包都进行确认,而是对多个数据包进行累计确认:

image

这里咱们能看到服务器发送多个数据包后,客户端才进行了一次确认。

流量控制和拥塞控制

  经过前面咱们知道了,经过创建可靠的链接和确认机制,保证了TCP的链接的可靠!可是每一个人使用的计算机的处理能力都是不同的,我发送太快了对方处理不过来怎么办呢?通讯双方怎么去协调发送和接收数据的频率呢?

以字节为单位的滑动窗口技术

  在介绍TCP头部的时候,咱们已经提到过滑动窗口,而且介绍了相关的控制参数Win!也说到了接收窗口和发送窗口!那他们的关系是怎么样的呢?

  假设如今A须要传输数据给B,B就先要告诉A本身的接收窗口有多大。A根据B的接收窗口设置本身的发送窗口!A的发送窗口时不能大于B的接收窗口的!在开始传输数据以前,初始的窗口设置以下图:

image

  如上图咱们可否看到,B的接收窗口设置为10个字节,那A的发送窗口设置不能超过10个字节!若是开始传送数据,A会将数据封装成多个数据包进行传输,以下图

image

  在没有收到B的确认以前,A的窗口不会滑动,也就是说最多能发10个字节的数据。若是B接受到数据且回复确认给了A,那A的窗口则进行滑动,以下图:

image

  这样,A又能够进行第十一、12个字节的发送啦!若是B的处理能力变弱了,也能够通知A将发送窗口调小!这样也也就很好的协调了双方的接收和发送能力!这也就很好的实现了TCP的可靠传输和流量控制!

  上面的数据包继续发送,若是在发送过程当中,三、四、5这三个字节组成的数据包丢了,可是后面的数据却收到了,这时候A的发送窗口会移动么?

image

  若是是这种状况,A的发送窗口是不会移动的。B在接收到后面数据包的时候回复给A的Ack会设置为3,且在选项中设置一个SACK(在TCP头部选项里面有描述),告诉A哪部分数据收到了,而哪部分数据须要进行重发!

拥塞控制

  利用滑动窗口技术,能够很好的协调双方的收发能力。可是,网络情况是很是复杂的,且在同一个网络上可能有千千万万个发送方和接收方!若是你们都须要传输数据都须要占用网络,不作好控制措施,就会致使整个网络会堵塞甚至瘫痪。

  若是我要从深圳开车去广州,我就会走高速。若是只有我一我的开车,那确定能畅通无阻!可是高速公路不是我家的,你们都能通行!因此一到了节假日,你们都一拥而上,而高速的承运能力不会由于节假日而调整!这时候每每就须要交通管制、限流等措施去舒缓交通!

image

一、绿线表明理想情况下,若是高速公路的吞吐量为100!当须要经过的车辆不超过100时,全部车辆都能顺利经过!当须要经过的车辆超过100,那每次通行的车辆为100,能提供的负载比较稳定。

二、红色表明没有任何交通管制状况下,若是高速公路的吞吐量为100!当须要经过的车辆不超过100时,会出现轻微的塞车现象!可是随着车辆的增多,就会出现严重的阻塞,甚至瘫痪!

三、蓝色表明在交通管制下,若是高速公路的吞吐量为100!当须要经过的车辆不超过100时,会出现轻微的塞车现象!可是随着车辆的增多,交通一直保存较高的负载,不会出现瘫痪的状况!

  网络就比如高速公路,传输的数据包就比如要经过的车辆,而TCP则就更像一个交警,维护着数据传输的秩序!那TCP是怎么作的呢?

慢开始与拥塞避免

  发送方维持一个cwnd(拥塞窗口,注意这里的拥塞窗口不能大于前面说到的发送窗口!),刚开始拥塞窗口设置为1。若是发现这个包没有丢失,则调整拥塞窗口为2!若是又没有丢包,则调整拥塞窗口为4!这样每次以2倍的速度一直增加到16!而后1七、1八、19这样一个一个的增长,直到大小与发送窗口一致。这就是所谓的慢开始和拥塞避免,16就是慢开始门限......

image

有没有得寸进尺的感受!

我就蹭蹭不进去...
...
我就进去不动...
...
我就..

  若是在发送的过程当中发现有丢包现象,则会调整拥塞窗口大小为1,而且设置新的慢开始门限为出现拥塞时的二分之一,也就是说当拥塞窗口为24的时候出现丢包现象,那新的慢开始门限就调整为12!若是理解了上面的文字描述,下面的图就不难理解了!

image

快重传

  前面说过累计确认,还说到了选择性确认。这个就跟快重传有关!接收方若是发现丢包,不会等到累计确认,就通知发送方三个重复的确认通知对方从新发送丢失的包。当接收方收到三个重复的确认,则意识到数据包丢失,进行重传!

  经过下图能看到,当出现丢包的状况,接收方的Ack都是等于50,而SACK分别对60~89之间的字节都进行了选择性的确认!这时候发送方也就知道50~59这部分数据丢失而进行重传!

image

快恢复

  若是一旦发生丢包,拥塞窗口就变成1,这种方式也太傻了吧。若是能有个快速恢复的机制就行了!TCP就使用了快恢复机制!当出现丢包时,不会再次进行慢开始,而是直接转入拥塞避免!也就是重新的慢开始门限进行加法增长!

image

看彻底文,咱们再回到TCP的定义,你是否是又能有更多的理解了呢?

TCP全称为Transmission Control Protocol(传输控制协议),是一种面向链接的、可靠的、基于字节流传输层通讯协议。TCP是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。