Tcp协议

个人主页:Lei宝啊 

愿所有美好如期而遇


目录

概念

Tcp协议段格式

Tcp序号和确认序号

4位首部长度

标志位

超时重传机制

三次握手

 四次挥手

流量控制

滑动窗口

拥塞窗口

延迟应答

捎带应答 

粘包问题

Tcp异常情况


概念

TCP 全称为 "传输控制协议(Transmission Control Protocol"),要对数据的传输进行一个详细的控制。

Tcp协议段格式

Tcp序号和确认序号

客户端向服务端发送报文,我们说Tcp发送消息具有可靠性,那么可靠性来自哪里呢?我们举个例子:客户端向服务端发送消息,在服务端没有应答时,客户端能保证他的消息一定被服务端收到吗?能够保证没有丢包?怎么确定是丢包了,还是说消息在路上,还是说服务端收到了消息,应答丢包了?所以是不能保证的,这就需要确认应答,也就是说,当客户端给服务端发送消息后,服务端能够给客户端应答,那么客户端就可以保证,他的历史消息,服务端是一定收到的。

但是这种机制,客户端发送消息,服务端给出应答前,客户端要一直等待,虽然没有问题,但是效率不高,所以就有了下面这种机制:

客户端一次向服务端发出多条消息,而服务端也向客户端给出多个应答,但是这样就又有问题,我客户端怎么确认,哪条消息对应着哪个应答?所以也就有了序号和确认序号,每条发送的消息,他的报头中就会有序号,而服务端根据他的序号,确认序号就是序号+1,同时操作系统会将收到的报文按照他们的序号进行排序,确保数据的顺序性,将应答发回给客户端,这样就保证了即使发送的消息或应答有丢包, 客户端和服务端也能够知道是哪个消息没有发送或应答。

还有一种情况,就是客户端向服务端发送消息时,服务端正好也想向服务端发送消息,同时还要对客户端发送的消息做应答,那么就会这样:

这就是捎带应答,本来服务端需要向客户端发送两次报文,但是现在就结合成了一次,只需要填写确认序号和正文数据一次发送给客户端。

4位首部长度

4个比特位,能够表示的最大值也就是15,但是不带选项,报头就20字节,4位首部长度好像不能够对报头大小进行表示?其实,这里,他是乘4,就可以表示60个字节。

如果这样算的话,我们光报头大小,首部长度大小应该是20 / 4 = 5, 也就是0101。

所以,我们也可以侧面看出,每一个选项都是四字节大小。

标志位

为什么要有标志位?

我们的服务端在通信的过程中,一定会收到各种各样的报文,服务端怎么区分这些报文?所以报文是有类型的,我们使用标志位来对这些报文进行类型区分。

客户端调用connect发起连接时,会发送一个管理报文,只有报头,没有数据,报头中的SYN标志位会被置为1,只有处于listen状态的服务器,才能够处理设置了标志位SYN的报文,当服务器收到这个报文时,也会向客户端发起连接并对客户端发起的连接做应答,ACK就是确认应答的标志位,这些管理报文由OS自动处理,和我们应用层无关,这些通信细节由OS自动完成,也就是说,当客户端向服务端发送消息时,服务端的OS会自动向客户端发送设置了ACK标志位的管理报文,表示确认收到。

我们需要明确的是,向客户端发送消息也好,服务端也好,发送ACK发送的是报文,是设置了ACK标志位的报文。

再一个,如何理解连接?当客户端和服务端双方都建立了连接后,操作系统要不要对这些连接进行管理?怎么管理?先描述,再组织!描述组织成内核数据结构,由操作系统进行管理,那么连接也就是需要耗费时间和空间的,也就是说,维护连接是需要成本的!

我们再来看断开连接:

首先是客户端向服务端发起断开连接,FIN标志位表示断开连接,服务端收到后,发回ACK报文,接着服务端处理发送完所有数据后,关闭文件描述符,向客户端发送FIN报文,客户端发回ACK报文,至此,客户端和服务端双方断开连接。

超时重传机制

我们拿上面的一个图举例子:

因为Tcp需要保证可靠性,那么如果说,客户端向服务端发起请求,我们说,要客户端确认消息是发送成功的,需要服务端给出ACK应答,那么如果没有收到ACK应答,客户端就认为,发送的消息丢包了,那么就会重新发送消息。

那么这段等待应答的时间是多久呢?太长不行,效率太低;太短也不行,刚发送的消息,应答在路上,就认为丢包再发消息,就有点浪费资源。

最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回",但是这个时间的长短, 随着网络环境的不同, 是有差异的。

TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.

  • Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍.
  • 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
  • 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
  • 累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接

那么你可能会问了,那客户端发送消息,假如服务端收到了,但是他的确认应答丢包了,客户端在重传后,服务端不就收到了两个相同的报文?服务端会不会处理两次呢?事实上,因为有序号的存在,当服务端发现,两次收到的报文序号相同,那么他就会将第二次重传收到的报文丢弃,所以序号还有去重的作用。

三次握手

客户端:

  • 第一次握手:客户端发起连接,客户端状态变为SYN_SENT
  • 第二次握手:客户端收到服务端发回的确认应答以及连接请求
  • 第三次握手:客户端发回确认应答,客户端状态变为ESTABUSHEND,表示已经建立连接

服务端:

  • 第一次握手:服务端收到客户端连接请求
  • 第二次握手:服务端端发回确认应答以及连接请求,状态变为SYN_RCVD
  • 第三次握手:服务端收到确认应答,服务端端状态变为ESTABUSHEND,表示已经建立连接

那么这里就有几个问题,对客户端来说, 最后一次发回确认应答,就表示建立连接,但是,最后一次发送的确认应答,客户端能够保证服务端一定收到了吗?不能,但是客户端认为大概率服务端是能够收到的,前面的SYN以及SYN+ACK,即使没有收到,也能够重传。

这样,也就会产生一个问题,如果说,客户端发送了最后一次ACK,认为自己已经建立了连接,但是服务端没有收到这个确认应答,服务端就会等待客户端进行重传,但是客户端已经建立了连接,下一步要进行正常通信了,于是给服务端发送了数据报文,服务端收到后,就懵了,不是说好的,客户端和服务端双方都应该三次握手建立连接后才能通信吗?你怎么就给我发了数据报文,我还没建立好连接呢!所以,这个时候,服务端就判断,客户端已经建立了连接,而且最后一次确认应答丢包了,于是会向客户端发送设置了RST标志位的报文,意为重置,也就是重新进行三次握手,重新连接!

那么又有一个问题,为什么是三次握手,不是一次握手,也不是两次握手,更不是四次握手呢?

  • 第一点:需要保证双方通信的网络是健康的。三次握手,客户端和服务端,双方都会有一次确定的收发消息,能够保证彼此的全双工是正常的。这里我们解释一下,不考虑丢包问题,当客户端向服务端发送SYN,以及收到SYN+ACK,以及再次发送ACK,他就能够保证,客户端发送的SYN是被服务端收到的,以及他收到了一次SYN+ACK,表示收发都是健康的;而对于服务端来说,他收到一次SYN,以及他发出去的SYN+ACK是收到了确认应答的,也就保证了收发是健康的。
  • 第二点:确保双方OS健康且愿意通信。首先,服务端一定是欢迎客户端来访问他的服务的,所以,当服务端收到来自客户端的连接请求时,也就会发出表示愿意连接的SYN连接请求,并发回确认报文,这两个也就以捎带应答的形式发回。三次握手的本质,其实就是四次握手,只是因为服务端愿意连接,并发起连接,这两者直接捎带应答了。 

 四次挥手

客户端:

  • 第一次挥手:客户端关闭连接,客户端状态变为FIN_WAIT_1
  • 第二次挥手:客户端收到服务端发回的确认应答,状态变为FIN_WAIT_2
  • 第三次挥手:客户端收到服务端发来的断开连接报文
  • 第四次挥手:客户端发回ACK,状态变为TIME_WAIT,在等待一段时间后,状态变为CLOSED。

服务端:

  • 第一次挥手:服务端收到客户端断开连接请求
  • 第二次挥手:服务端端发回确认应答,状态变为CLOSE_WAIT
  • 第三次挥手:服务端发送断开连接报文,服务端端状态变为LAST_ACK
  • 第四次挥手:服务端收到确认应答,状态变为CLOSED

首先我们这里要注意,client和server的位置可以发生置换,也就是说,如果是sever先断开连接,那么上图的位置需要交换,我们这里就以上图为例进行讲解。 

这里我们重点说两个状态,也就是我们标红的两个状态。

  • CLOSED_WAIT:在服务端状态变为CLOSED_WAIT时,如果接下来服务端不关闭文件描述符,那么他的状态就不会继续往下,也就不会发送断开连接报文。
  • TIME_WAIT:当客户端处在这个状态时,即使发回应答,状态也不会立即变为CLOSED,而是会等待一段时间,这个时间为2MSL,也就是报文最大存活时长,我们可以通过这样的指令cat/proc/sys/net/ipv4/tcp_fin_timeout 查看 MSL 的值,在博主的机器下是60s,也就是说,需要等待2min。那么为什么要等待呢?官方给出的解释是,由于客户端在关闭了文件描述符后,仍然有服务端向客户端发送的历史报文在路由中,等待一段时间是为了使历史报文消散。这里有一个小概率事件,也就是,假如没有TIME_WAIT,客户端就是直接进入CLOSED状态,直接关闭了,那么如果有历史报文正在路由,同时,客户端重新向服务端发起连接,并且,这一次的源端口号和目的端口号和上次关闭时是一样的,发送的数据报文正好和历史报文的序号能够对应的上,那么服务端就有可能去处理这个历史报文,并正好发回重新建立连接的客户端,这样,数据也就紊乱出错了,当然,这只是等待的原因之一。

如果说,最后一次ACK丢包了,那么因为客户端处于TIME_WAIT状态, 没有退出,那么就可以向客户端端重传FIN直到得到ACK应答,或者重传次数到达上限,服务端强制关闭连接。

这里,我们提一点,如果我们发现服务器上有大量的CLOSE_WAIT状态,那么就说明我们的服务端写的有问题,没有关闭文件描述符;以及,三次握手和四次挥手都是由OS自动完成的,这些通信细节不需要我们应用层去关心。

至于为什么是四次挥手,原因和三次握手一样,这里不做赘述。

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应,所以就需要流量控制。

发送端发送数据太慢也不行,这样效率就太低了。

那么如何进行流量控制呢?这就需要一个字段,16位窗口大小,当发送端向接受端发送报文,接收端发回ACK报文时,会将接受缓冲区剩余大小填入窗口大小字段发回给发送端,发送端就能够根据接受端的窗口大小来控制发送报文的快慢。

当接收端接收缓冲区满的时候,窗口大小就是0,这样,发送端也就不能再继续发送数据给接收端了,那么,发送端难道就要一直等着吗?不是,接收端会向发送端发送窗口更新通知,如果过了重发超时的时间,发送端还没有接收到窗口更新通知,此时,发送端就会定期向接收端发送窗口探测,只有报头,没有数据,以此来获得接收端的ACK报文以获得窗口大小,来判断是否可以继续发送数据。如果说,发送端需要接收端赶紧腾出接收缓冲区,让接收端的应用层赶快读取数据,就可以发送设置了PSH标志位的报文,这样,假如说,recv本来需要读100字节返回,现在,仅仅读了20字节就立即返回。

同时,还有一种场景,就是发送端正在给接收端发送数据,但是,发送端不想让接收端继续读取数据了,那么即使接收端接收缓冲区仍然有数据,也想让他丢弃,那么就向接收端发送设置了URG标志位的报文,他表示紧急指针有效,紧急指针会指向正文数据中的一个字节的内容,双方可以约定好这个字节内容大小,比如是1,就不再读取数据,同时丢弃接收缓冲区数据。当接收端接收到这个报文时,会优先读取紧急数据。recv有标志位参数,我们可以通过设置MSG_OOB来获取URG数据,即紧急数据。

滑动窗口

对每一个发送的数据段, 都要给一个 ACK 确认应答. 收到 ACK 后再发送下一个数据段,像这种发送模式效率较低,所以我们希望能够并发发送多条数据,同时还能保证可靠性。

那么,并发发送数据的大小是多少呢?我们称无需等待确认应答而可以继续发送多条数据的这个大小为主机的一个窗口大小(并不是等同于协议段格式中的窗口大小,但是有联系),我们称为滑动窗口的大小。

那么滑动窗口是什么?他在哪里?

这里我们先不考虑拥塞窗口,滑动窗口的大小 = 接收端发回的窗口大小(即接收缓冲区的剩余大小),也就是根据接收端的接收能力来确定滑动窗口的大小,这里我们提出几个问题:

一、如何理解超时重传? 
对已经发送,但没有接收到确认应答的报文,将会在滑动窗口内进行保存,方便我们进行重传。

二、如何理解滑动窗口?滑动窗口只能向右滑动吗?滑动窗口可以变大吗?可以变小吗?可以为0吗?

我们将滑动窗口四等分段,也就是发送四次报文,序号分别是1001~2000,2001~3000等等,也就是说,他们应该收到的确认报文分别是2001,3001等等 ,那么这里也就有三个问题:
最左侧丢包怎么办?中间丢包怎么办?最右边丢包怎么办?

最左侧丢包,那么假设其他三个报文没有丢包,但是接收端敢给发送端返回5001确认序号吗?敢给发回3001,4001确认序号吗?滑动窗口敢右移吗?答案是不敢,因为确认序号表示,确认序号之前的数据已经全部收到,但是1001~2000的数据丢包了,所以,不能返回这些序号,滑动窗口也不能右移。

这里我们介绍TCP协议的一个策略,叫做快重传,如果发送端主机连续收到三次及以上相同的确认序号,那么,发送端就认为从确认序号开始的一个报文是丢包了,就会立即补发这个报文,那么超时重传呢?假如发送端主机没有收到三次及以上相同的确认序号,那么在等待确认应答超时后,就会重传。

这样,快重传后,如果收到了对应的确认应答,那么滑动窗口就可以右移了,那么,滑动窗口大小是如何变更的呢?我们将滑动窗口理解成一个环形数组,有两个指针,一个指向滑动窗口起始,一个指向末尾,这样,这两个指针就标识了滑动窗口的大小:

对于滑动窗口的起始位置为什么等于确认序号,我们可以想象的到,发送端接收到的确认序号是乱序的,但是,如果他没有收到2001确认序号,但是收到了3001确认序号,那么可不可以保证,1001~2000的数据是被收到的?可以保证!所以滑动窗口可不可以直接右移?可以!如果主机再收到2001确认序号,也就不需要理会了。所以,滑动窗口更新时的起始位置就等于确认序号。而末尾就是窗口更新后的其实位置+接收端的窗口大小,就是滑动窗口的末尾。 

如果中间报文丢失呢?那中间报文左边的报文,是不是就是收到的?是不是就可以收到确认报文,滑动窗口是不是就可以直接右移?所以问题是不是又转化成了最左边报文丢失?所以最右边报文丢失也是同理。

我们也就可以回答最开始的三个问题了,滑动窗口只能向右滑动!滑动窗口可以变大,也可以变小,这就取决于startwin和endwin谁变大的快。滑动窗口可以为0,当接收端发送缓冲区为0时,滑动窗口大小也就为0了。

这里我们额外提到,接收端接收缓冲区内的报文都是按序到达,也就是说,当报文到达时,OS会按照他们的序号进行排序,如果数据报没有按照顺序到达,接收方会将其暂时存储起来,直到收到缺失的数据包。而接收方一但收到了顺序化的数据,就将这些数据按照正确的顺序传递到接收缓冲区中。同时我们需要明确的是,ACK应答报文是没有按序到达机制的。

拥塞窗口

网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵.,在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的,所以仅仅根据接收端的接收能力发送数据是不够的,因为网络中可不止一个发送端和一个接收端。

这里也就引入拥塞窗口,拥塞窗口初始值大小为1,每次收到一个 ACK 应答, 拥塞窗口加的值发生一次变化(我们后面说);每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口。

滑动窗口大小 = min(接收端窗口大小,拥塞窗口大小)。

执行拥塞控制策略不仅仅是单个通信主机执行,而是网络中所有导致网络拥塞的主机全部都要执行拥塞控制。

拥塞窗口会有一个阈值,刚开始会执行慢开始,也就是拥塞窗口初始值翻倍增加,直到到达阈值后开始线性增加,达到网络拥塞的值,重新慢开始,阈值变为网络拥塞值的一半,按照这个策略,拥塞窗口的值会一直变化。

而这个算法的好处在于,当网络拥塞时,滑动窗口变为拥塞窗口的初始值,这样慢慢等到网络状况变好,需要尽快恢复网络通信,而这种按照指数级增长的策略是非常合适的。先是慢开始解决网络拥塞问题,然后通过指数级增长尽快恢复网络通信。

延迟应答

如果接收数据的主机立刻返回 ACK 应答, 这时候返回的窗口可能比较小。
假设接收端缓冲区为 1M. 一次收到了 500K 的数据; 如果立刻应答, 返回的窗口就是 500K;但实际上可能处理端处理的速度很快, 10ms 之内就把 500K 数据从缓冲区消费掉了;在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;如果接收端稍微等一会再应答, 比如等待 200ms 再应答, 那么这个时候返回的窗口大小就是 1M;

那么所有的包都可以延迟应答么? 肯定也不是;

  • 数量限制: 每隔 N 个包就延迟应答一次;
  • 时间限制: 超过最大延迟时间就延迟应答一次;

具体的数量和超时时间, 依操作系统不同也有差异; 一般 N 取 2, 超时时间取 200ms;

捎带应答 

客户端或服务器在发回应答的同时也发回数据,前面的文章我们有说过。

粘包问题

需要应用层去区分,明确包与包之间的边界。

这里我们解释一下,在发送缓冲区中,是没有Tcp或Udp报头的,向下交付时才会添加报头,而在接收缓冲区中,是有报头的,正是因为Tcp中没有像Udp中有报文长度,所以才会有粘包问题,而Udp因为此时报头还在,在向上交付数据时,每个报文的数据都有明确的边界。

Tcp异常情况

进程终止: 进程终止会释放文件描述符, 仍然可以发送 FIN. 和正常关闭没有什么区别.

机器重启: 和进程终止的情况相同.

机器掉电/网线断开: 假设发送端机器掉电或网线断开,但接收端认为连接还在, 一旦接收端对发送端发送数据, 发送端发现连接已经不在了, 就会进行 reset. 即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lei宝啊

觉得博主写的有用就鼓励一下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值