网络编程|【网络编程】TCP的五层协议栈之TCP协议


文章目录

  • 一、TCP协议段格式
  • 二、TCP原理
    • 1.确认应答
    • 2.超时重传
    • 3.连接管理(重点)
      • 1.2.3.1.三次握手过程
      • 1.2.3.2.四次挥手过程
    • 4.滑动窗口(效率机制)
    • 5.流量控制(安全机制)
    • 6.拥塞控制(安全机制)
    • 7.延迟应答(效率机制)
    • 8.捎带应答(效率机制)
    • 9.面向字节流
    • 10.TCP异常情况
  • 三、TCP小结
  • 四、TCP与UDP的区别
  • 五、最后的话

??TCP,即Transmission Control Protocol,传输控制协议。人如其名,要对数据的传输进行一个详细的控制。
一、TCP协议段格式 ??每一个协议都会有个“头部”,TCP也不例外,其实这个“头部”就是该协议的数据结构以及规则的说明,但无论协议如何变化,它还是离不开0和1的信息载体。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

  1. 16位源端口号:16位的源端口中包含初始化通信的端口。源端口和源IP地址的作用是标识报文的返回地址。
  2. 16位目的端口号:16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。
  3. 32位序号:32位的序列号由接收端计算机使用,重新分段的报文成最初形式。当SYN出现,序列码实际上是初始序列码 (Initial Sequence Number,ISN),而第一个数据字节是ISN+1。这个序列号(序列码)可用来补偿传输中的不一致。
  4. 32位确认序号:32位的序列号由接收端计算机使用,重组分段的报文成最初形式。如果设置了ACK控制位,这个值表示一个准备接收的包的序列码。
  5. 4位首部长度:4位包括TCP头大小,指示何处数据开始。
  6. 保留(6位):6位值域,这些位必须是0。为了将来定义新的用途而保留。
  7. 标志(6位):表示为:
    • URG:紧急标志。紧急标志为"1"表明该位有效。
    • ACK:确认标志。表明确认编号栏有效。大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号(w+1)为下一个预期的序列编号,同时提示远端系统已经成功接收所有数据。
    • PSH:推标志。该标志置位时,接收端 不将该数据进行队列处理,而是尽可能快地将数据转由应用处理。在处理Telnet或rlogin等交互模式的连接时,该标志总是置位的。
    • RST:复位标志。对方要求重新建立连接;我们把携带RST标识的称为复位报文段
    • SYN:同步标志。表示建立连接。表明同步序列编号栏有效。该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列编号,该序列编号为TCP连接初始端(一般是客户端)的初始序列编号。
    • FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段。
  8. 16位窗口大小:用来表示想收到的每个TCP数据段的大小。TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是一个16字节字段,因而窗口大小最大为65535字节。
  9. 16位校验和:16位TCP头。源机器基于数据内容计算一个数值,收信息机要与源机器数值 结果完全一样,从而证明数据的有效性。检验和覆盖了整个的TCP报文段:这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证的。(检验范围包括首部和数据两部分。和UDP用户数据报一样,在计算校验和 时,要在TCP报文段加上12字节的伪首部。)
  10. 16位紧急指针:指向后面是优先数据的字节,在URG标志设置了时才有效。如果URG标志没有被设置,紧急域作为填充。加快处理标示为紧急的数据段。
  11. 选项:长度不定,但长度必须为1个字节。如果没有选项就表示这个1字节的域等于0。
  12. 数据:该TCP协议包负载的数据。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

?
?
二、TCP原理 ??TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。
??这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。
1.确认应答
??确认应答机制是保证可靠传输的核心机制,它具有可靠性——发送方发送数据出去之后,能够知道对方有没有收到。当接收方收到数据之后,会给发送方返回一个应答报文ACK(acknowledge),表示自己已经收到了。
??假设今天滑稽哥要去找潇洒哥家玩,然后滑稽哥 给 潇洒哥 发消息,若消息没有出现错误且顺序正确的话,应该是如下图:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片
??但是,网络上信息的接受顺序,不一定与发送的顺序一致,它会存在一种后发先至的情况。比如:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片
??实际上,潇洒哥是下午在家,晚上不在家的。这就导致了我们收到信息的顺序错了。这是因为网络上的环境非常复杂,连续发送的两个包,不一定走的是同一个路线。
??如何解决这个问题呢?我们可以对发送的消息进行编号。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??这里确认编号的序号,表示当前的这个应答报文是针对那个消息进行的确认应答。这里就是我们上面的那个图里面的32位序列号与32位确认序列号。
??当我们在传输数据的时候,TCP会将每个字节的数据都进行了编号,即为序列号。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??然后进行传输。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片
??上图解析:
  1. A给B发送了1000个字节,序号为1-1000。这里的意思是TCP报头中的序号为1,报文长度为1000,通过这个信息来明确范围。
  2. 主机B给A返回的应答报文ACK就会带有一个确认序号,叫做1001,这个1001表示小于1001的数据都被主机B接收到了,那么接下来主机A就应该从1001这个序号开始往后传递。
【注意】1-1000这是同一个TCP数据报,这个TCP数据报通过层层封装,变成一个以太网数据帧,进行传输。里面的数据并不会有后发先至的情况。如果是传输了多个数据报,分装成了多个以太网数据帧,多个数据帧之间才会出现后发先至的情况。
??数据能被确认应答是比较理想的情况,但数据在传输过程中,可能是会丢包的。当发生了这种情况,就需要引入另一个机制了——超时重传。
2.超时重传
??主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B;如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??此外,主机A未收到B发来的确认应答,也可能是因为ACK丢失了。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片
??对于发送方来说,无法区分是哪种原因导致的没有收到ACK,那么发送方就往最差的情况假设,就认为是接收方压根没收到。于是重新再发一次,这里的重发也不是立即就重发的,而是得等一会(给点反应时间)。如果发送方等了10分钟,超出了等待时间,还没有收到这个ACK,就会重发一次——重传。
??如果情况是仅仅是ACK丢包了,此时触发了超时重传,就会导致接收方收到重复的信息。如果这些重复的信息是借钱就事大了。于是此时,TCP内部就会有一个去重的操作。
??接收方收到的数据会先放在操作系统内核的“接收缓冲区”中,接收缓冲区可以视为是一个内存空间,并且也可以视为是一个阻塞队列。收到新的数据,TCP就会根据序号,来检查看这个数据是不是在接收缓冲区中已经存在了。如果不存在,就放进去;如果存在,直接丢弃,保证应用程序调用socket api拿到的这个数据一定是不重复的。
??那么,如果超时的时间如何确定?
  • 最理想的情况下,找到一个最小的时间,保证 “确认应答一定能在这个时间内返回”。
  • 但是这个时间的长短,随着网络环境的不同,是有差异的。
  • 如果超时时间设的太长,会影响整体的重传效率;
  • 如果超时时间设的太短,有可能会频繁发送重复的包;
??TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
  • Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
  • 如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传。
  • 如果仍然得不到应答,等待 4*500ms 进行重传。依次类推,以指数形式递增。
  • 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接
??基于以上两个机制,TCP的可靠性,就得到了有效的保障。
3.连接管理(重点) ??这个连接管理机制,也是TCP保证可靠性的一个机制。同时,这里面的内容也是网络部分最高频的面试题,没有之一。
??连接管理机制主要的是解决两个问题:
  1. 如何建立连接
  2. 如何断开连接
1.2.3.1.三次握手过程
(1)如何建立连接
??这里建立连接,依靠的是三次握手的过程,这里的握手是一个形象的比喻。它是指客户端与服务器之间,通过三次交互,完成了建立连接的过程。
??在TCP中建立连接的过程是这样的:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??这里假设是主机A主动发起了连接。
??第一次握手: 客户端向服务器发送 SYN 报文 (SEQ=x,SYN=1),并进入 SYN_SENT 状态,等待服务器确认。
??
??第二次握手: 实际上是分两部分来完成的,即 SYN+ACK (请求和确认) 报文。
??服务器收到了客户端的请求,向客户端回复一个确认信息(ack=x+1)
??服务器再向客户端发送一个 SYN 包 (SEQ=y)建立连接的请求,此时服务器进入 SYN_RECV 状态
??
??第三次握手: 客户端收到服务器的回复 (SYN+ACK 报文0);此时,客户端也要向服务器发送确认包
(ACK);此包发送完毕客户端和服务器进入 ESTABLISHED 状态,完成 3 次握手
??建立连接的过程,相当于通信双方各自给对方发送 SYN,在各自给对方发送给 ACK,只不过中间的 ACK 和 SYN 合二为一了,于是最后就是"三次握手"。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??这里三次握手的过程就好比滑稽哥与潇洒哥的通话。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

【注意】TCP的状态其实有很多的,这些状态呢,也不用太深究,我们主要关注以下两个:
LISTEN:表示服务器启动成功,端口绑定成功,随时可以有客户端来建立连接.(手机开机,信号良好,随时可以有人给我打电话)
ESTABLISHED:表示客户端已经连接成功,随时可以进行通信了.(有人给我打电话,我接听了,接下来就可以说话了)
?
??那么这里三次握手的作用是什么呢?为什么说它能确保可靠性呢?
第一个作用:确保对方能够正常接收数据, 测试连接。三次握手相当于投石问路,可以检查当前这个网络情况是否满足可靠传输的基本条件。(最主要作用,确认是否可靠)
??如果你现在的网络本身就非常差,如果你强行进行TCP传输,肯定会涉及到大量的丢包。更具体的说,三次握手是在检查通信双方的发送和接收能力是否都正常。
??比如说打电话的时候,只有确认了彼此的通信都是正常的,我们才能确认这次通信是可行的,意味着我们接下来可以正常交流。如果对方问了几次你都听不见,那么这次通信就是有问题的,通信条件不具备,因此就会放弃通信。
第二个作用:建立系统开销
??在发送 UDP 包的时候, 因为其不可靠性, 所以基本不会用其发送很大的文件, 因为将较大的数据拆分后发出, 中间丢了几个数据包就尴尬了. 而且 UDP 也不能够保证包的顺序, 还是一样的原因. 但是 TCP 就不一样了, 它是可靠的啊, 你可以将多个数据包分开发给我, 到我这里, 我再把他们按顺序排列好就行了. 而这个按顺序排列的操作就需要专门开辟内存空间来保存收到的数据包了, 当握手成功后, 我就会为你留下用于保存数据包的内存空间及其他一些系统资源.
??而如果没有三次握手呢? 客户端发送的数据包, 可能因为某些原因(比如路不好走), 在网络中待的久了一些, 客户端因为没有收到回复, 已经放弃连接了, 但这时候, 服务器收到了这个数据包, 开辟系统资源, 返回确认包, 然后就没有然后了. 客户端已经放弃了, 根本不搭理你的回复. 系统的相关资源就白白浪费了.
第三个作用:测试超时时间
??当长时间没有收到接收方的回复时, 此时就认为接收方方没有收到发送方发出的数据, 那发送方就需要重新发送了. 那这个长时间是多久呢? 可以在握手期间进行测试, 测量请求包的往返时间,并依此计算重传的超时时间。
??这里就会有一些经典面试题:
  1. 描述TCP的三次握手过程。
  2. 为啥是三次握手,两次行不行,四次呢?
??第一,必须得画图:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片
??第二,两次不行,这意味着缺少最后一次ACK的发送.此时客户端这边关于发送接收能力正常的情报是完整的,但是服务器这边是残缺的。收不到回应,服务器不知道自己的发送能力是否ok,也不知道客户端的接收能力是否ok 。此时此刻,服务器对于当下能否满足可靠传输, 心里是没底的,这第三次交互,就是为了给服务器吃一个定心丸。
??而四次握手是完全可以的,不过这样做效率比较低,因为它分开传输了。
1.2.3.2.四次挥手过程
(2) 如何断开连接
??这里断开连接的过程就是我们常说的四次挥手的过程。在三次握手的过程中,让客户端与服务器之间建立好了连接。其实在建立好连接之后,操作系统的内核中就需要使用一定的数据结构来保存连接相关信息。而这些信息就是我们之前文章提到的五元组:源IP,源端口号,目的IP,目的端口,TCP。
??这里既然保存了信息,那么必定是占用系统资源(内存)。如果我们断开连接了,此时保存的连接信息就没有意义了,那么对应的内存空间也被释放了,因此我们需要把之前记录的东西也销毁。
  • 三次握手,一定是客户端主动发起的(主动发起的一方才叫客户端)
  • 四次挥手,可能是客户端主动发起,也可能是服务器主动发起
??四次挥手的过程如下:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

四次挥手:双方各自向对方发起建立连接的请求,再各自给对方回应,只不过,中间的 FIN 和 ACK 不一定能合并在一起。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??这里为什么说中间的 FIN 和 ACK 不一定能合并在一起?
??不能合并的原因,在于,B发送ACK和B发送FIN的时机是不同的!四次挥手中,B给A发的ACK,是内核负责的.B给A发的FIN是用户代码负责(B的代码中调用了socket.close()方法,才会触发FIN)。
??更具体一点就是收到FIN的时候,就需立即由内核返回ACK,但是它只有指定到用户代码中的close才会触发,什么时候触发,取决于用户代码。
??如果这两操作之间的时间差,比较大,就不能合并了!如果时间差比较小,这是可能会合并的(延时应答和捎带应答,后面再说)
??而三次握手中,B发送的ACK和SYN是在同一时机,就能够合并
??TCP 中断开连接的过程: (假设主机 A 主动断开连接)
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??第一次挥手: 客户端向服务器端发送断开 TCP 连接请求的 [FIN,ACK] 报文,在报文中随机生成一个序列号 SEQ=u,表示要断开TCP 连接
此时,客户端进入FIN_WAIT_1 (终止等待1) 状态
??第二次挥手: 当服务器端收到客户端发来的断开 TCP 连接的请求后,回复发送 ACK 报文,表示已经收到断开请求。回复时,随机生成一个序列号SEQ=v;由于回复的是客户端发来的请求,所以在客户端请求序列号 SEQ=u 的基础上加 1,得到 ack=u+1
此时,服务端就进入了CLOSE_WAIT (关闭等待) 状态,客户端收到ACK后,就进入FIN_WAIT_2 (终止等待2) 状态
??第三次挥手: 服务器端在回复完客户端的 TCP 断开请求后,不会马上进行 TCP连接的断开。服务器端会先确认断开前,所有传输到客户端的数据是否已经传输完毕。确认数据传输完毕后才进行断开,向客户端发送 [FIN,ACK] 报文,设置字段值为 1。再次随机生成一个序列号 SEQ=w;由于还是对客户端发来的 TCP 断开请求序列号 SEQ=x 进行回复,因此ack 依然为 x+1。
此时,服务器就进入了LAST_ACK (最后确认) 状态
??第四次挥手: 客户端收到服务器发来的 TCP 断开连接数据包后将进行回复,表示收到断开 TCP连接数据包。向服务器发送 ACK 报文,生成一个序列号 SEQ=u+1;由于回复的是服务器,所以 ACK 字段的值在服务器发来断开 TCP连接请求序列号 SEQ=w 的基础上加 1,得到 ack=w+1
??此时,客户端就进入了TIME_WAIT (时间等待) 状态;注意此时TCP连接还没有释放,必须经过2MSL (最长报文段寿命) 的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
??关于状态转换的详细情况,不深入讨论.仍然要认识两个重要的状态:
  1. CLOSE_WAIT:四次挥手挥了两次之后出现的状态,这个状态就是在等待代码中调用socket.close方法,来进行后续的挥手过程,正常情况下,一个服务器上不应该存在大量的CLOSE_WAIT.如果存在,说明大概率是代码
    bug , close没有被执行到。
    ??
  2. TIME_WAIT:谁主动发起FIN,谁就进入TIME_WAIT.起到的效果,就是给最后一次ACK提供重传机会。表面上看起来,A发送完ACK之后,就没有A的啥事了。按理说,A就应该销毁连接,释放资源了。但是并没有直接释放,而是会进TIME_WAIT状态等待一段时间,一段时间之后,再来释放连接。等这一会,是因为怕最后一个ACK丢包。如果最后一个ACK丢包了就意味着B过一会就会重传FIN。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??为什么是TIME_WAIT的时间是2MSL?
  • MSL是TCP报文的最大生存时间,因此TIME_WAIT持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程的迟到的数据,但是这种数据很可能是错误的);
    ?
  • 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发一个FIN。这时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK);
问:四次挥手,三次挥完行不行??
??通常情况下不行,若触发了延时应答机制,就可以三次挥完。“不行”,即:上述的 ② ③ 为什么没有合并在一起。 因为中间两次操作的时机不一样。ACK 是收到 FIN 之后立刻由内核返回的数据报,FIN 是应用程序处理完接收缓冲区的数据之后,调用的 close 方法触发的。
?
?
4.滑动窗口(效率机制)
滑动窗口存在的意义就是在保证可靠性的前提下,尽量提高传输效率!
??我们的确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,大量的时间被花在了等待ACK的回应返回上,导致了效率低和性能较差,尤其是数据往返的时间较长的时候。如下图:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??这样一发一收的方式性能较低,那么我们一次发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。
滑动窗口,本质就是在"批量的发送数据"一次发一波数据,然后一起等一波ACK。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。
  • 发送前四个段的时候,不需要等待任何ACK,直接发送;
  • "滑动”的意思是,并不用把N组数据的ACK都等到了,才继续往下发送而是收到一个ACK,就继续往下发送一组,比如上图是在等待1001,2001,3001,4001四组ACK.不需要等到4001到了,才继续往下发。只要1001到了,就可以往下多发一组.(4001-5000),此时等待ack 的范围2001,3001,4001,5001。如果是2001到了,就继续再往下发一组(5001-6000),此时等待的ack的范围3001,4001, 5001,6001。
  • 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;
  • 窗口越大,则网络的吞吐率就越高;
示意图:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??因此,这里提高效率是这样的:当前这个窗口大小越大,可以认为就是传输速度就越快,窗口大了,同一份时间内等待的ACK就更多,总的等待ACK的时间就少了,从而提高了传输效率。
??
??虽然是一组传输,但是一组这里会不会出现2001没到,而3001到了的情况呢?
??这个问题的核心,实际上就是丢包。那么如果出现了丢包,如何进行重传?这里分两种情况讨论。
(1)数据包已经抵达,但ACK被丢了。
这种情况其实不用处理,因为可以通过后续的ACK进行确认。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

  • 第一个丢包,在发送4001之前,发现收到了一个2001 . 此时没有收到1001。而收到2001就代表之前的数据都已经确认收到了,所以1001能否确认收到已经不重要了。ACK确认序号的特定含义,就保证了后一条ACK就能涵盖前一条。因此,只要2001收到了,那么1-2000的数据都已经收到了。
  • 同理,下面的丢包,当发送方收到5001的时候,意味着1-5000的数据都确认收到了。尽管3001和4001被丢包,也毫无影响.只要收到了5001,就代表1-5000的数据都收到了。
?
?
(2)数据包就直接丢了。
当数据报丢了的时候,会触发"高速重发控制"(也叫 “快重传”)。
示意图:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

  • 当某一段报文段丢失之后,发送端会一直收到 1001 这样的ACK,就像是在提醒发送端 “我想要的是 1001” 一样;
  • 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答,就会将对应的数据 1001 -2000 重新发送,也就是触发了重传;
  • 这个时候接收端收到了 1001 之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端。而7001之前已经收到了的数据,被放到了接收端操作系统内核的接收缓冲区中,如下图:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

??因此,这里的重传只是需要把丢了的那一块数据给重传了即可,其他已经到了的数据就不必再重传了.整体的重传效率还是比较高的。
?
?
5.流量控制(安全机制)
??接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。
??因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制(FlowControl)
??流量控制的关键,就是得能够衡量接收方的处理速度,此处就直接使用接收方接收缓冲区的剩余空间大小,来衡量当前的处理能力.
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

在这个过程中:
  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段,通过ACK端通知发送端;
  • 窗口大小字段越大,说明网络的吞吐量越高;
  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;
  • 发送端接受到这个窗口之后,就会减慢自己的发送速度;
  • 如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。
?
那么接收端如何把窗口大小告诉发送端呢?
通过ACK报文获知。在我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息。发送发通过这个16位窗口大小,来衡量当前接收方剩余空间的大小.发送方收到这个数据之后,就会灵活的调整发送速度,调整窗口大小。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

那这里16位数字最大表示65535,那么TCP窗口最大就是65535字节么,这不是很小吗?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位;因此,这里的实际大小可以不止16k.
??这里就像一个蓄水池一样:
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

?
?
6.拥塞控制(安全机制)
拥塞控制也是滑动窗口的延伸,也是限制滑动窗口发送的速率;
拥塞控制衡量的是,发送方到接收方,这整个链路之间,拥堵情况(处理能力)
??在我们网络传输之中,并不是只有两根网线来进行这么简单,而是中间有许多复杂的东西,假如其中一个不行了,其他的就会收到限制。因此,我们的发送端能发送多快,不光取决于接收端的处理能力,也取决于中间链路的处理能力。而这里的中间链路究竟有多少,我们也不知道,只能通过拥塞控制的处理,逐渐调整发送速度,寻找到一个合适的发送值。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

  • 发送端一开始以一个比较小的窗口来发送数据,如果数据很流畅的到达,就逐渐增大窗口的大小;如果加大到一定程度,出现了丢包,丢包意味着通信链路出现了拥堵,那么这时候就缩小窗口。
  • 通过反复的增大和缩小过程逐渐寻找到一个合适的范围,拥塞窗口就在这个范围中不断变化,达到“动态平衡”。
??
??如下图:
  • 发送开始的时候,定义拥塞窗口大小为1;
  • 每次收到一个ACK应答,拥塞窗口加1;
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

  • 上面这样的拥塞窗口增长速度,是指数级别的
??这个拥塞控制的坐标图如下图,详细描述了拥塞窗口的变化过程。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

  • 慢开始:最开始的时候,取的初始窗口大小,这个数值很小,一般定义拥塞窗口大小为1单位。这个一单位是多少看操作系统的代码实现,可能是1字节或者10字节等等。
  • 指数增长规律:由于初始值很小,可能合适的值很大,为了更快寻找到这个值就直接指数增长。
  • ssthresh初始值:这个阈值决定了什么时候开始从指数增长变为线性增长。这个阈值每次丢包的时候都会更新为当前出现丢包窗口大小的一半。
  • 拥塞避免“加法增大”:表示指数增长到一定程度,就会进入线性增长。
  • 网络拥塞:当这里的线性增长到一定程度就会出现丢包,一旦丢包,发送方立即就让窗口变小,回归初始窗口大小,然后继续重复开始时的指数增长和线性增长的过程。
这里为什么会回到初始窗口大小呢?
?? 直接让窗口回归初始值,一个主要的目的,网络的情况是复杂,不稳定的,如果出现丢包,很可能你光把速度降下来一点,不能解决问题的。
?? 如果降的太慢,就会出现持续性的丢包,就对网络通信质量带来了很大的影响.一下让窗口变的很小,就是期望这次传输,一定能成功
  • 乘法减小:期待能得到最理想的效果。
??少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;而拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的。折中方案。
?
??那么这里就有个问题:既然流量控制与拥塞控制都是安全机制来保证我们的可靠传输,那么当二者都需要用到的时候,我们取何值?
取二者的最小值,比如说流量控制窗口是只允许10k,而拥塞控制窗口只允许5k,那么我们取5k。
?
?
7.延迟应答(效率机制) ??放假前的最后一天,你语数英的作业都没做,你的老师通过微信问你作业都做好了没,然后你故意不回她信息。过了大概2-3个小时,你赶着做了语文数学作业,然后回你老师说,你还剩英语作业没做。
??如果你立即回复了,那么你什么作业都没做,如果你晚点回复,你就有了多一点的时间去完成作业了。不至于一个作业都没做,这么落魄。
??在延迟应答机制也是这样:
??如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
??
??假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;
??在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
??如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;
??因为窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。
??
??那么是不是所有的包都可以延迟应答么?
??肯定不是,这里存在两个限制。数量限制与时间限制。
  • 数量限制:每隔N个包就应答一次;
  • 时间限制:超过最大延迟时间就应答一次;
  • 具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms;
?
?
8.捎带应答(效率机制)
捎带应答是延时应答的延申。很多情况下,客户端服务器在应用层也是 “一发一收” 的。那么这就意味着客户端给服务器说了 “What’s your name?”,服务器也会给客户端回一个 “Tony,What is your name?”;那么这个时候ACK就可以搭顺风车,和服务器回应的 “Tony,What is your name?” 一起回给客户端.
??因为延时应答的存在,导致ACK不一定是立即返回的。如果当前的延时应答,导致ACK的返回时机和应用代码中返回的响应时机重合了,就可以把这个ACK和响应数据,合二为一。
?
?
9.面向字节流 ??这里的TCP面向字节流问题会存在一个粘包问题。实际上很多面向字节流问题都会存在粘包问题,比如读文件。在TCP中的粘包问题指的是在TCP接收缓冲区中,若干个应用层数据包混在一起了,分不出来谁是谁了。这里“粘包”粘的是应用层数据报。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片
如上图:
  • 站在传输层的角度,TCP是一个一个报文过来的。按照序号排好序放在缓冲区中。
  • 站在应用层的角度,看到的只是一串连续的字节数据。
  • 那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。
?
那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界。
  • 对于定长的包,保证每次都按固定大小读取即可;那么就从缓冲区从头开始按sizeof(结构)依次读取即可;
  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;
  • 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可)
比如对于我们上面图的例子,我们可以在每个包的结尾用 ; 来区分。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

?
既然TCP存在粘包问题,那么对于UDP协议来说,是否也存在 “粘包问题” 呢?
??不存在.UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题
??对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在。同时,UDP是一个一个把数据交付给应用层。就有很明确的数据边界站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收。不会出现"半个"的情况
?
?
10.TCP异常情况 TCP异常可能会有以下三种情况:
  • 进程终止
  • 机器重启
  • 机器掉电或者网线断开
(1)进程终止:进程终止会释放文件描述符,仍然可以发送FIN。和正常关闭没有什么区别。
??TCP连接,是通过socket来建立的. socket 本质上是进程打开的一个文件.文件其实就存在于进程的PCB里面有个文件描述符表每次打开一个文件(包括socket),都在文件描述符表里,增加一项.
??每次关闭一个文件,都在文件描述符表里,进行删除一项。如果直接杀死进程, PCB也就没了.里面的文件描述符表也就没了。
??此处的文件相当于’自动关闭’了这个过程其实和手动调用socket.close()一样,都会触发4次挥手。
?
(2)机器重启:和进程终止的情况相同。
?
(3)机器掉电或者网线断开
????接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。
????即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。
网络编程|【网络编程】TCP的五层协议栈之TCP协议
文章图片

?
??实际上,应用层的某些协议,也有一些这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态。例如QQ,在QQ断线之后,也会定期尝试重新连接。
?
?
三、TCP小结 为什么TCP这么复杂?因为要保证可靠性,同时又尽可能的提高性能。
可靠性:
  • 校验和
  • 序列号(按序到达)
  • 确认应答
  • 超时重传
  • 连接管理
  • 流量控制
  • 拥塞控制
提高性能:
  • 滑动窗口
  • 超时重传
  • 延迟应答
  • 捎带应答
其他:
  • 定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)
?
?
四、TCP与UDP的区别 1)TCP提供面向连接的传输,通信前要先建立连接(三次握手机制); UDP提供无连接的传输,通信前不需要建立连接。
2) TCP提供可靠的传输(数据的顺序有序,数据准确无差错,数据不丢失,数据不重复); UDP提供不可靠的传输(可能丢包,不保证数据顺序)。
3) TCP面向字节流的传输,因此它能将信息分割成组,并在接收端将其重组; UDP是面向数据报的传输,没有分组开销。
4) TCP提供拥塞控制和流量控制机制; UDP不提供拥塞控制和流量控制机制。
5) 对系统资源的要求:TCP较多(TCP头部有20个字节信息包),UDP少(UDP信息包只有8个字节)
6) 虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为。
7) 对数据准确性要求高,速度可以相对较慢的,可以选用TCP
?
?
五、最后的话 【网络编程|【网络编程】TCP的五层协议栈之TCP协议】??这篇真的写了好几天了,太多重要的东西了,都是干货啊。

    推荐阅读