TCP

2020-06-11  本文已影响0人  达文西_Huong

TCP详解【转载】


关于TCP协议

tcp协议全称:传输控制协议,顾名思义,就是要对数据的传输进行一定的控制


image

下面来分析每部分的含义和作用

URG: 标记紧急指针是否有效
ACK: 标记确认序号是否有效
PSH: 用来提示接收端应用程序立刻将数据从tcp缓冲区中读走
RST: 要求重新建立连接,我们把含有RST的报文称为复位报文
SYN: 请求建立连接,我们把含有SYN表示的报文称为同步报文
FIN: 通知对端,本端即将关闭。我们把含有FIN标识的报文称为结束报文


TCP的三次握手

image

刚开始,客户端和服务端处于CLOSE状态。然后客户端向服务端主动发起请求,服务端被动接收链接请求。

  1. TCP服务器进程先创建传输控制块TCB,时刻准备接受客户端进程的连接要求,此时的服务端就进入了LISTEN状态

  2. TCP客户端进程也是先创建控制块TCB,然后向服务器发出请求报文,此时报文首部中同步标志SYN=1,同时选择一个初始序号seq=x,此时,TCP客户端进入了SYN-SENT(同步已发送)状态。TCP规定,SYN报文(SYN=1的报文不能携带数据),但需要消耗一个序列号

  3. TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中的ACK=1,SYN=1,确认序号(ack)是x +1,同时也为自己初始化一个序列号seq=y,
    此时TCP服务器进程进入SYN-RCVD(同步收到)状态。该状态下也是不能携带数据的,也要消耗一个序列号

  4. TCP客户端进程收到确认后,还要向服务器发出确认。确认报文ACK=1,确认序号ack=y+1,自己的序列号是x+1

此时,tcp连接建立,客户端进入ESTABLISHED(已建立连接)状态,当服务器收到客户端的确认后也进入ESTABLISHED状态,之后双方就可以开始通信了

小答疑

为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。我们可以设想一下这个场景:
假设,客户端在最开始的时候发送了一段请求连接的报文,但是因为网络不好,在网络中滞留太久。然后TCP客户端因为太久没有收到确认报文,以为服务器没有收到,然后又重新发了一份报文,结果这份成功了,然后建立了连接。可是在连接成功后,那一份在网络滞留的报文突然又好了,到达了服务器,导致服务器再次建立了连接。
这样就导致了服务器的不必要错误和浪费资源
所以如果采用的是三次握手。就算那一份失效的报文重新又发送了,但是因为客户端不会再发送确认报文了,而服务端接收不到确认报文,就会认为客户端并没有请求连接

TCP的四次挥手

数据传输完毕之后,双方都可以释放连接了,此时客户端和服务端都处于ESTABLISHED(已建立状态),然后客户端主动断开连接,服务端被动断开连接

  1. 客户端进程发出释放报文,并且停止发送数据。
    释放数据报文首部,FIN=1,序列号seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时客户端进入FIN-WAIT-1(终止等待1)状态。此时需要消耗一个序号
  2. 服务器收到连接释放报文,通知高层应用程序,客户端向服务端的方向就释放了,发出确认报文,ACK=1,确认序号为u+1,并且带上自己打的序号seq=v,此时服务端进入close-wait(关闭状态)状态
    此时的服务端处于半关闭状态。意思就是此时客户端已经不发送数据了,但是如果客户端发送,服务端也能接受。这个状态会持续一段时间(整个CLOSE-WAIT的时间)
  3. 客户端收到服务器的确认请求后,此时客户端进入FIN-WAIT-2(终止等待2),等待服务器发送连接释放报文(在此之前需要接受服务器发送的最终数据)
  4. 服务器将最后的数据发送完毕后,就像客户端发送连接释放报文,FIN=1,确认序列号ack=v+1,由于处于半关闭状态服务器很可能又发送一些数据,假定此时的序列号为seq=w。服务器进入了LASK-ACK(最后确认)状态,等待客户端的确认
  5. 客户端收到服务器的连接释放报文后,必须发出确认ACK=1,确认序列号seq=w+1,而自己的系列号u+1。
    此时的客户端就进入了TIME-WAIT(时间等待)状态。
    不过此时的TCP连接还没有释放,必须经过2*MLS(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样撤销TCB后,就结束了TCP连接。
image
小答疑

(1) 保证客户端发送的最后一个ACK报文能够到达服务器。因为这个ACK报文可能丢失,站在服务器的角度看来。我服务器已经发送了FIN+ACK报文请求断开了,客户端还没给我回应,我猜测我自己的报文它没有收到,于是我又重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应,并且会重启*2MSL计时器。
(2) 防止类似三次握手中的“网络滞留导致误以为报文失效”的情况。客户端在发出最后一个确认报文之后,在2MSL时间中,就可以使本连接持续的时间内所有的报文段都从网络中小时。这样新的连接中,就不会出现旧的请求报文了。

建立连接的时候,服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端
而关闭连接时,服务器收到对方的FIN报文时,仅仅标识对方不在发送数据了,但是还能接受数据而自己也未必全部数据都发给对方了,所以我可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方表示同意现在关闭连接因此,己方ACK和FIN报文一般都是分开发送的,从而导致多了一次

TCP设有一个保活计时器,显然客户端如果出现故障,服务器不可能一直等下去,白白浪费资源。服务器每收到一次客户端请求后都会重新复位这个计时器,时间通常设置再2小时。如果两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文,以后每隔75分钟就会发送一次。若一连发送10个探测报文还没反应,服务器就会认为客户端出现了故障,接着就会关闭连接。

确认应答机制(ACK机制)

TCP将每个字节的数据都进行了编号,即为序列号。
每一次ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到哪些数据了,下一次你要从哪里开始发
比如:客户端向服务器发送了1005字节的数据,服务器返回给客户端的确认序列号是1003,那么说明服务器只收到了1-1002的数据
1003,1004,1005 都没收到
此时客户端就会从1003开始重新发送

image

超时重传机制

image

主机A向主机B发送数据之后,可能因为网络原因,数据B无法到达主机B。如果主机A在一个特定的时间内没有收到确认应答,就会进行重新发送。
但是也有可能是因为B的确认应答丢失了


image

这种情况下,B主机可能会有很多重复的数据,但是利用前面提到的序列号,就可以轻松完成去重的工作。

TCP为了保证任何环境下都能保持较高的性能通信,因此会采用动态计算这个最大的时间


滑动窗口

如上文说描述,如果每次发送一条数据,都需要等待一个ACK应答,这样的通信效率会比较低。
故而能不能一次发出多条数据,再统一应答呢,答案是有的。
于是就有了一个新得概念窗口

窗口大小指的是无需等待确认应答就可以继续发送数据的最大值。

image

窗口在发送前四个段的时候不需要等待ACK直接发送
等收到第一个ACK确认应答后,窗口会向后移动。这个窗口不断向后移动的行为叫做滑动窗口

image

操作系统内核为了维护这个滑动窗口, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答
只有ACK确认应答过的数据, 才能从缓冲区删掉。

这里存在一个问题,万一丢包了咋办?

  1. 第一种情况:数据包已经收到了但是确认应答ACK丢失了


    image

    这种情况下, 部分ACK丢失并无大碍, 因为还可以通过后续的ACK来确认对方已经收到了哪些数据包.

  2. 数据包丢失
    image
    当某一段报文丢失后,发送端会一直收到1001这样的ACK,意思就是提醒发送端缺失了1001
    如果发送端连续收到3次同样的1001,发送端就会将对应的1001-2000部分重新发送
    这个时候服务端接收到了1001之后,再次返回的ACK就是7001了,因为,2001-7000接收端已经是接收成功了的,被放到了接收端操作系统内核的缓冲区中

这种机制被称为 "高速重发控制" 也叫(重传)


流量控制

因为接收端处理数据的能力是有限的,如果发送端发送太快,导致接收端的缓冲区域被填满,而此时发送端继续发送就会造成丢包,从而导致重传等现象。
因此TCP支持根据接收端的处理能力,决定发送端的发送速度
这个机制就是流量控制(Flow Control)

接收端会把自己可以接收的缓冲区大小放入TCP首部中的 "窗口大小" 字段中。通过ACK通知发送端。
窗口大小越大,说明网络的吞吐量越高
接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知发送端
发送端接收到这个窗口大小通知之后,就会减慢自己的发送速度
如果缓冲区满了,就会将窗口大小设置为0
这是发送发将不再发送数据,但是需要定期发送一个窗口探测数据段,让接收端把窗口大小再告诉发送端

image

其实在TCP首部中,有一个16位窗口大小的字段,就存放了窗口大小的信息


拥塞控制

虽然TCP有了滑动窗口,能够高效的发送大量数据,但是如果在刚开始就发送大量数据,很容易引发一些问题。
比如说,有些计算机当前网络本身已经处于拥堵状态,而如果此时再向他发送大量数据很可能会更加堵。
(我个人理解这个行为其实就是,比如你去游泳,你肯定是要先用脚去试试水的温度,而不能直接就跳到水里面。)

因此TCP引入了慢启动机制,先发少量的数据,探测一下,摸清当前网络环境之后再决定按照多大的数据传输数据

image

在此再引入一个概念:拥塞窗口s

拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.


延迟应答

如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小
假设,接收端的缓冲区大小为1M,一次收到500k的数据
如果立即应答,那么窗口大小就是500k
但实际上,接收端处理数据能力很快,10ms内就能把这500k的数据处理掉。从而返回更大的窗口值。
所以延迟应答就是为了让返回的窗口值大一些。
因为我们直到,窗口值越大,网络的吞吐量越大,传输的效率就高

TCP的目标是再保证网络不拥堵的情况下,尽可能提高传输效率

但是也并不是所有的数据包都可以延迟应答的。有两个限制

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

具体的数量和时间,依据操作系统的而异。
一般数量是2, 最大延迟时间是200ms


捎带应答

在延迟应答的基础上, 我们发现, 很多情况下
客户端和服务器在应用层也是 “一发一收” 的
意味着客户端给服务器说了 “How are you”
服务器也会给客户端回一个 “Fine, thank you”
那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起发送给客户端

面向字节流

创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区
调用write时,数据会先写入发送缓冲区
如果发送的字节数太大,会呗拆成多个TCP的数据包发出
如果太小,就会先放在缓冲区中,等到缓冲区大小差不多了,或者其他适合的实际再一起发送

接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区
然后应用程序可以调用read从接收缓冲区拿数据
另一方面,TCP的一个连接既有发送缓冲区也有接收缓冲区
对于这么一个连接,它既可以读数据,又能写数据,就叫做全双工

由于缓冲区的存在,TCP程序的读和写不需要一一匹配了。


TCP异常情况


总结

为啥TCP这么复杂?

因为既要保证可靠性,同时又要尽可能提高性能

保证可靠性的机制

校验号
序列号(按序到达)
确认应答
超时重传
连接管理
流量控制
拥塞控制

提高性能的机制

滑动窗口
快速重传
延迟应答
捎带应答

定时器

超时重传定时器
保活定时器
TIME_WAIT定时器

以上,还有2个概念一个是粘包问题。我暂时应该也用不到,就没看,如果哪天想看了就点击下方的原文链接吧~

原文https://blog.csdn.net/sinat_36629696/article/details/80740678

上一篇下一篇

猜你喜欢

热点阅读