TCP/IP详解与OSI七层模型

时间:2023-03-08 16:55:25
TCP/IP详解与OSI七层模型

TCP/IP协议

  包含了一系列构成互联网基础的网络协议,是Internet的核心协议。基于TCP/IP的参考模型将协议分成四个层次,它们分别是链路层、网络层、传输层和应用层。下图表示TCP/IP模型与OSI模型各层的对照关系。

TCP/IP详解与OSI七层模型

  TCP/IP协议族按照层次由上到下,层层包装。最上面的是应用层,这里面有http,ftp,等等我们熟悉的协议。而第二层则是传输层,著名的TCP和UDP协议就在这个层次。第三层是网络层,IP协议就在这里,它负责对数据加上IP地址和其他的数据以确定传输的目标。第四层是数据链路层,这个层次为待传送的数据加入一个以太网协议头,并进行CRC编码,为最后的数据传输做准备。

TCP/IP详解与OSI七层模型

TCP/IP详解与OSI七层模型

数据链路 

  数据链路层负责将0、1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的(MAC,物理地址,一个主机会有一个MAC地址)。

TCP/IP详解与OSI七层模型

  1. 封装成帧: 把网络层数据报加头和尾,封装成帧,帧头中包括源MAC地址和目的MAC地址。

  2. 透明传输:零比特填充、转义字符。

  3. 可靠传输: 在出错率很低的链路上很少用,但是无线链路WLAN会保证可靠传输。

  4. 差错检测(CRC):接收者检测错误,如果发现差错,丢弃该帧。

物理层

  物理层负责0、1比特流与物理设备电压高低、光的闪灭之间的互换。

网络层

  IP协议是TCP/IP协议的核心,所有的TCP,UDP,IMCP,IGMP的数据都以IP数据格式传输。要注意的是,IP不是可靠的协议,这是说,IP协议没有提供一种数据未传达以后的处理机制,这被认为是上层协议:TCP或UDP要做的事情。

  32位IP地址分为网络位和地址位,这样做可以减少路由器中路由表记录的数目,有了网络地址,就可以限定拥有相同网络地址的终端都在同一个范围内,那么路由表只需要维护一条这个网络地址的方向,就可以找到相应的这些终端了。

  1. A类IP地址: 0.0.0.0~127.255.255.255

  2. B类IP地址:128.0.0.0~191.255.255.255

  3. C类IP地址:192.0.0.0~239.255.255.255

TCP/IP详解与OSI七层模型

  八位的TTL字段。这个字段规定该数据包在穿过多少个路由之后才会被抛弃。某个IP数据包每穿过一个路由器,该数据包的TTL数值就会减少1,当该数据包的TTL成为零,它就会被自动抛弃。
这个字段的最大值也就是255,也就是说一个协议包也就在路由器里面穿行255次就会被抛弃了,根据系统的不同,这个数字也不一样,一般是32或者是64。

ARP及RARP协议

  ARP 是根据IP地址获取MAC地址的一种协议。ARP(地址解析)协议是一种解析协议,本来主机是完全不知道这个IP对应的是哪个主机的哪个接口,当主机要发送一个IP包的时候,会首先查一下自己的ARP高速缓存(就是一个IP-MAC地址对应表缓存)。

  如果查询的IP-MAC值对不存在,那么主机就向网络发送一个ARP协议广播包,这个广播包里面就有待查询的IP地址,而直接收到这份广播的包的所有主机都会查询自己的IP地址,如果收到广播包的某一个主机发现自己符合条件,那么就准备好一个包含自己的MAC地址的ARP包传送给发送ARP广播的主机。

  而广播主机拿到ARP包后会更新自己的ARP缓存(就是存放IP-MAC对应表的地方)。发送广播的主机就会用新的ARP缓存数据准备好数据链路层的的数据包发送工作。

ICMP协议

  IP协议并不是一个可靠的协议,它不保证数据被送达,那么,自然的,保证数据送达的工作应该由其他的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。ICMP不是高层协议,而是IP层的协议。

  当传送IP数据包发生错误。比如主机不可达,路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机。给主机一个处理错误的机会,这 也就是为什么说建立在IP层以上的协议是可能做到安全的原因。

  ping可以说是ICMP的最著名的应用,是TCP/IP协议的一部分。利用“ping”命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障,ping程序来计算间隔时间,并计算有多少个包被送达。用户就可以判断网络大致的情况。我们可以看到, ping给出来了传送的时间和TTL的数据

TCP的概述

  每个TCP头部包含了源端口号和目的端口号,这两个值与IP头部中的IP地址一起作为每个连接的唯一标识。这种IP地址+端口号的组合被称为端点(endpoint)或套接字(socket)。每个TCP连接由一对套接字唯一地标识。

  TCP提供了一个字节流接口,TCP必须把一个发送应用程序的字节流转换成一组IP可以携带的分组,这被称为组包(packetization)。这些分组包含序列号,该序列号在TCP中实际代表了每个分组的第一个字节在整个数据流中的字节偏移,而不是分组号,这允许分组在传送中是可变大小的,并允许它们组合,称为重新组包(repacketization)。应用程序数据被打散成TCP认为的最佳大小的块来发送,一般使得每个报文段按照不会被分片的单个IP数据报的大小来划分,这一点与UDP不同。

  TCP维持了一个强制的校验和,该校验和涉及其头部、任何相关应用程序数据和IP头部的所有字段。这是一个端到端的伪头部,它用于检测传送中引入的比特差错。如果一个带无效校验和的报文段到达,那么TCP会丢弃它。

  当TCP发送一组报文段时,它通常设置一个重传计时器,等待对方的确认接收。TCP不会为每个报文段设置一个不同的重传计时器。相反,发送一个窗口的数据,它只设置一个计时器,当ACK到达时再更新超时。如果一个确认没有及时接收到,这个报文段就会被重传。

  当TCP接收到连接的另一端数据时,它会发送一个确认。这个确认可能不会立即发送,而一般会延迟片刻。TCP使用的ACK是累积的,从某种意义来讲,一个指示字节号N的ACK暗示着所有直到N的字节(不包含N)已经被成功接收了。这对于ACK丢失来说带来了一定的鲁棒性——如果一个ACK丢失,很有可能后续的ACK就足以确认前面的报文段了。

  TCP给应用程序提供一种双工服务——数据可向两个方向流动,两个方向互相独立。因此,连接的每个端点必须对每个方向维持数据流的一个序列号。一旦建立了一个连接,这个连接的一个方向上的包含数据流的每个TCP报文段也包含了相反方向上的报文段的一个ACK。每个报文段也包含一个窗口通告以实现相反方向上的流量控制。因此,一个完整的TCP连接是双向和对称的,数据可以在两个方向上平等地流动。

  使用序列号,一个TCP接收端可丢弃重复的报文段和记录以杂乱次序到达的报文段。因为TCP是一个字节流协议(没有边界记录),所以TCP绝不会以杂乱的次序给应用程序发送数据。因此,TCP接收端可能会*先保持一个大序列号的数据不交给应用程序,直到缺失的小序列号的报文段(一个“洞”)被填满。

  虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。

TCP报文首部

  TCP的固定包头为20个字节,每一行32bit(4Byte),5行。TCP与UDP区别是,TCP是面向连接的可靠传输(数据有保证),UDP则是面向对象的不可靠传输(数据没保证)。

TCP/IP详解与OSI七层模型

  1. 源端口、目标端口:计算机上的进程要和其他进程通信是要通过计算机端口的,而一个计算机端口某个时刻只能被一个进程占用,所以通过指定源端口和目标端口,就可以知道是哪两个进程需要通信。源端口、目标端口是用16位表示的,可推算计算机的端口个数为2^16个。
  2. 序列号:表示本报文段所发送数据的第一个字节的编号。在TCP连接中所传送的字节流的每一个字节都会按顺序编号。由于序列号由32位表示,所以每2^32个字节,就会出现序列号回绕,再次从 0 开始。那如何区分两个相同序列号的不同TCP报文段就是一个问题了,后面会有答案,暂时可以不管。
  3. 确认号:表示接收方期望收到发送方下一个报文段的第一个字节数据的编号。也就是告诉发送发:我希望你(指发送方)下次发送的数据的第一个字节数据的编号是这个确认号。也就是告诉发送方:我希望你(指发送方)下次发送给我的TCP报文段的序列号字段的值是这个确认号。
  4. TCP首部长度:由于TCP首部包含一个长度可变的选项部分,所以需要这么一个值来指定这个TCP报文段到底有多长。或者可以这么理解:就是表示TCP报文段中数据部分在整个TCP报文段中的位置。该字段的单位是32位字,即:4个字节。
  5. URG:表示本报文段中发送的数据是否包含紧急数据。URG=1,表示有紧急数据。后面的紧急指针字段只有当URG=1时才有效。
  6. CWR:拥塞窗口减(发送方降低它的发送速率)
  7. ECE:ECN回显(发送方接收到了一个更早的拥塞通告)
  8. ACK:表示是否前面的确认号字段是否有效。ACK=1,表示有效。只有当ACK=1时,前面的确认号字段才有效。TCP规定,连接建立后,ACK必须为1。
  9. PSH:告诉对方收到该报文段后是否应该立即把数据推送给上层。如果为1,则表示对方应当立即把数据提交给上层,而不是缓存起来。
  10. RST:只有当RST=1时才有用。如果你收到一个RST=1的报文,说明你与主机的连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。或者说明你上次发送给主机的数据有问题,主机拒绝响应。
  11. SYN:在建立连接时使用,用来同步序号。当SYN=1,ACK=0时,表示这是一个请求建立连接的报文段;当SYN=1,ACK=1时,表示对方同意建立连接。SYN=1,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中SYN才置为1。
  12. FIN:标记数据是否发送完毕。如果FIN=1,就相当于告诉对方:“我的数据已经发送完毕,你可以释放连接了”
  13. 窗口大小:表示现在运行对方发送的数据量。也就是告诉对方,从本报文段的确认号开始允许对方发送的数据量。
  14. 校验和:提供额外的可靠性。具体如何校验,参考其他资料。
  15. 紧急指针:标记紧急数据在数据字段中的位置。
  16. 选项部分:其最大长度可根据TCP首部长度进行推算。TCP首部长度用4位表示,那么选项部分最长为:(2^4-1)*4-20=40字节
    1. MSS最大报文段长度(Maxium Segment Size):指明数据字段的最大长度,数据字段的长度加上TCP首部的长度才等于整个TCP报文段的长度。MSS值指示自己期望对方发送TCP报文段时那个数据字段的长度。通信双方可以有不同的MSS值。如果未填写,默认采用536字节。MSS只出现在SYN报文中。即:MSS出现在SYN=1的报文段中。
    2. 窗口扩大选项(Windows Scaling):由于TCP首部的窗口大小字段长度是16位,所以其表示的最大数是65535。但是随着时延和带宽比较大的通信产生(如卫星通信),需要更大的窗口来满足性能和吞吐率,所以产生了这个窗口扩大选项。
    3. SACK选择确认项(Selective Acknowledgements):用来确保只重传缺少的报文段,而不是重传所有报文段。比如主机A发送报文段1、2、3,而主机B仅收到报文段1、3。那么此时就需要使用SACK选项来告诉发送方只发送丢失的数据。那么又如何指明丢失了哪些报文段呢?使用SACK需要两个功能字节。一个表示要使用SACK选项,另一个指明这个选项占用多少字节。描述丢失的报文段2,是通过描述它的左右边界报文段1、3来完成的。而这个1、3实际上是表示序列号,所以描述一个丢失的报文段需要64位即8个字节的空间。那么可以推算整个选项字段最多描述(40-2)/8=4个丢失的报文段。
    4. 时间戳选项(Timestamps):可以用来计算RTT(往返时间),发送方发送TCP报文时,把当前的时间值放入时间戳字段,接收方收到后发送确认报文时,把这个时间戳字段的值复制到确认报文中,当发送方收到确认报文后即可计算出RTT。也可以用来防止回绕序号PAWS,也可以说可以用来区分相同序列号的不同报文。因为序列号用32为表示,每2^32个序列号就会产生回绕,那么使用时间戳字段就很容易区分相同序列号的不同报文。
    5. NOP(NO-Operation):它要求选项部分中的每种选项长度必须是4字节的倍数,不足的则用NOP填充。同时也可以用来分割不同的选项字段。如窗口扩大选项和SACK之间用NOP隔开。

滑动窗口与流量控制

  TCP采用可变滑动窗口来实现流量控制。TCP连接的两端交互作用,互相提供数据流的相关信息,包括报文段序列号、ACK号和窗口大小(即接收端的可用空间)。发送端根据这些信息动态调节窗口大小来控制发送,以达到流量控制的目的。每个TCP头部的窗口大小字段表明接收端可用缓存空间的大小,以字节为单位。该字段长度为16位,但窗口缩放选项可用大于65535的值。报文段发送方在相反方向上可接受的最大序列号值为TCP头部中ACK号和窗口大小字段之和(单位保持一致)。

滑动窗口

  TCP连接的两端都可收发数据,连接的收发数据量是通过一组窗口结构(window structure)来维护的,如下图所示:

TCP/IP详解与OSI七层模型

  

   每个TCP连接的两端都维护一组窗口:发送窗口结构(send window structure)和接收窗口结构(receive window structure)。TCP以字节为单位维护其窗口结构。TCP头部中的窗口大小字段相对ACK号有一个字节的偏移量。发送端计算其可用窗口,即它可以立即发送的数据量。可用窗口(允许发送但还未发送)计算值为提供窗口(即由接收端通告的窗口)大小减去在传(已发送但未得到确认)的数据量。图中P1、P2、P3分别记录了窗口的左边界、下次发送的序列号、右边界。
TCP/IP详解与OSI七层模型

  如上图所示, 随着发送端接收到返回的数据ACK,滑动窗口也随之右移。发送端根据接收端返回的ACK可以得到两个重要的信息:一是接收端期望收到的下一个字节序号;二是当前的窗口大小(再结合发送端已有的其他信息可以得出还能发送多少字节数据)。

需要注意的是:发送窗口的左边界只能右移,因为它控制的是已发送并受到确认的数据,具有累积性,不能返回;右边界可以右移也可以左移(能左移的右边界会带来一些缺陷,下文会讲到)。

接收端也维护一个窗口结构,但比发送窗口简单(只有左边界和右边界)。该窗口结构记录了已接收并确认的数据,以及它能够接收的最大序列号,该窗口能保证接收数据的正确性(避免存储重复的已接收和确认的数据,以及避免存储不应接收的数据)。由于TCP的累积ACK特性,只有当到达数据序列号等于左边界时,窗口才能向前滑动。

发送窗口与接收窗口的关系
       TCP是双工的可靠传输协议,连接的双方都可以同时接收、发送数据。TCP连接的双方都各自维护一个“发送窗口”和一个“接收窗口”。其中各自的“接收窗口”大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的“发送窗口”则要求取决于对端通告的“接收窗口”,要求相同。
TCP/IP详解与OSI七层模型

零窗口与TCP持续计时器
       当窗口的左右边界重合(即窗口大小为0)时,发送端将停止发送数据直到窗口大小恢复为非零值,这样的窗口称为零窗口。当接收端重新获得可用空间,会给发送端发送一个窗口更新(window update),告知其可以继续发送数据。这样的窗口更新通常不包含数据(为纯ACK)。

但接收端发送的窗口更新是ACK报文,不能保证传输的可靠性。因此如果一个包含窗口更新的ACK丢失,通信双方就会一直处于等待状态:接收方等待接收数据(它已经发送了窗口更新),发送方等待窗口更新告知其可以继续发送,这样就会陷入死锁状态。为避免死锁发生,发送端会采用一个持续计时器间歇性地询问接收端,看其窗口是否增长。持续窗口计时器会触发窗口探测(window probe)消息的发送,强制要求接收端返回ACK。窗口探测包含一个字节的数据,采用TCP可靠传输(重传),强制要求接收端返回ACK,因此可以避免因窗口更新丢失而导致的死锁。

糊涂窗口综合征
       TCP基于窗口的流量控制机制的缺陷是可能会出现糊涂窗口综合征(Silly Window Syndrome,SWS)。当出现该问题时,TCP连接之间的数据交换可能是较小的数据段(极端情况下只有1字节的数据负载,却要消耗40字节的TCP头部),导致传输效率低下,造成资源浪费。一些针对这一特性的网络攻击的原理是,在TCP连接建立后,要么不做任何行为,要么只产生很小的应答,使得发送速率不断减慢。

TCP连接的收发方两者都可能导致SWS的出现:接收端的通告窗口较小(例如没有等到窗口变大才通告),或者发送端发送的数据段较小(没有等待其他数据组合成一个更大的报文段)。引起SWS的原因不同,解决方案也不同。

对于接收端来说,不应通告较小的窗口值。某些协议规定,在窗口可增至一个全长的报文段(MSS)或者接收端缓存空间的一半(取两者中的较小者)之前,不能通告比当前窗口更大的窗口值。

而对于发送端来说,不应发送较小的报文段。具体实现的规则有所不同。

流量控制

  设A向B发送数据。在连接建立时,B告诉了A:“我的接收窗口是 rwnd = 400 ”(这里的 rwnd 表示 receiver window) 。因此,发送方的发送窗口不能超过接收方给出的接收窗口的数值。请注意,TCP的窗口单位是字节,不是报文段。假设每一个报文段为100字节长,而数据报文段序号的初始值设为1。大写ACK表示首部中的确认位ACK,小写ack表示确认字段的值ack。

TCP/IP详解与OSI七层模型

  从图中可以看出,B进行了三次流量控制。第一次把窗口减少到 rwnd = 300 ,第二次又减到了 rwnd = 100 ,最后减到 rwnd = 0 ,即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。B向A发送的三个报文段都设置了 ACK = 1 ,只有在ACK=1时确认号字段才有意义。

  TCP为每一个连接设有一个持续计时器(persistence timer)。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口控测报文段(携1字节的数据),那么收到这个报文段的一方就重新设置持续计时器。

慢开始和拥塞避免

  发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。

  发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。

慢开始算法

  当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是 先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。

  通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。

TCP/IP详解与OSI七层模型

  每经过一个传输轮次,拥塞窗口 cwnd 就加倍。一个传输轮次所经历的时间其实就是往返时间RTT。不过“传输轮次”更加强调:把拥塞窗口cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。
  慢开始的“慢”并不是指cwnd的增长速率慢,而是指在TCP开始发送报文段时先设置cwnd=1,使得发送方在开始时只发送一个报文段(目的是试探一下网络的拥塞情况),然后再逐渐增大cwnd。为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。慢开始门限ssthresh的用法如下:

    1. 当 cwnd < ssthresh 时,使用上述的慢开始算法

    2. 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法

    3. 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法

    拥塞避免

      让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。

    TCP/IP详解与OSI七层模型

      无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。

      这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。

      如下图,用具体数值说明了上述拥塞控制的过程。现在发送窗口的大小和拥塞窗口一样大。

    TCP/IP详解与OSI七层模型

    TCP重传

      基于时间的重传在其下的数据链路层、网络层乃至同层的UDP协议都有使用,即设置一个计时器来判断数据传输是否超时,当然它们对于计时器时间的设定规则有所不同

    快重传

      快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。

    TCP/IP详解与OSI七层模型

      接收方收到了M1和M2后都分别发出了确认。现在假定接收方没有收到M3但接着收到了M4。

      显然,接收方不能确认M4,因为M4是收到的失序报文段。根据 可靠传输原理,接收方可以什么都不做,也可以在适当时机发送一次对M2的确认。

      但按照快重传算法的规定,接收方应及时发送对M2的重复确认,这样做可以让 发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后,也还要再次发出对M2的重复确认。这样,发送方共收到了 接收方的四个对M2的确认,其中后三个都是重复确认。

      快重传算法还规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段M3,而不必 继续等待M3设置的重传计时器到期。

      由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。

    快速重传算法可以概括如下:TCP发送端在观测到至少dupthresh个重复ACK后,即重传可能丢失的数据分组,而不必等到重传计时器超时。不采用SACK时,在接收到有效ACK前最多只能重传一个报文段。根据重复ACK推断的丢包通常与网络拥塞有关,因此快速重传也会触发拥塞控制机制。采用SACK(见下文),ACK可包含额外信息,使得发送端在每个RTT时间内可以填补多个空缺。

    SACK
           由于TCP采用累积ACK确认,因此TCP有时候并不能正确地确认之前已经接收的数据。由于接收的数据是无序的,所以接收到的数据的序列号也是不连续的。在这种情况下,TCP接收方的数据队列中会出现空洞的情况。因此在提供字节流传输服务时,TCP接收方需要防止应用程序使用超出空间的数据。一种方法是直接禁止交付存在空洞的数据,直到空洞被填补。而另一种方法则是使用SACK选项。

    如果TCP发送方能够了解接收方当前的空洞(以及序列空间中超出空洞的乱序数据块),它就能在报文段丢失或接收方遗漏时更好地进行重传工作。接收方通过SACK选项能够提供确认信息描述乱序数据,以帮助发送方有效地利用信息进行重传。

    SACK信息保存于SACK选项中,包含了接收方已经接收的数据块的序列号范围,每个范围被称作一个SACK块,由一对32位的序列号表示。因此,一个SACK选项包含了n个SACK块,长度为(8n+2)个字节,增加的2个字节用于保存SACK选项的种类与长度。由于TCP头部选项的空间有限,因此一个报文段中发送的最大的SACK块数目为3(假设使用了时间戳选项)。虽然只用SYN报文段才能包含“选择确认”选项,但是只要发送方已经发送了该选项,SACK块就能通过任何报文段发送出去。

    带选择确认的重传
           合理采用SACK信息能更快地填补数据空洞,且能减少不必要的重传,原因在于一个RTT内能获知多个空缺。当采用SACK时,一个ACK可包含3个告知失序数据的SACK信息,每个SACK信息包含32位的序列号,代表接收端存储的失序数据的起始至最后一个序列号(加1)。

    伪超时与重传
           在很多情况下,即使没有出现数据丢失也可能引发重传。这种不必要的重传称为伪重传(spurious retransmission),造成伪重传的主要原因是伪超时(spurious timeout),即过早判定超时,其他因素如包失序、包重复,或ACK丢失也可能导致该现象。在实际RTT显著增长以致超过了当前RTO时,就可能出现伪超时。

    包失序
           在IP网络中出现包失序的原因在于IP层不能保证包传输是有序进行的,但这种策略是有利的,因为IP可以选择不同的传输链路,合理利用现有链路。还有其他的原因,例如一些高性能路由器会采用多个并行数据链路进行分组转发,以及不同的处理延时也会导致包的离开顺序与到达顺序不匹配。

    当传输出现失序时,TCP可能会在某些方面受到影响。如果失序发生在反向(ACK)链路,就会使得TCP发送端窗口快速前移,接着又可能会收到一些显然重复而应被丢弃的ACK。由于TCP的拥塞控制行为,这种情况会导致发送端出现不必要的流量突发(即瞬时的高速发送)现象,影响可用网络带宽。

    如果失序发生在正向链路,TCP可能无法正确识别失序和丢包——数据的丢失和失序都会导致接收端收到无序的包,造成数据空缺。当失序程度不是很大时(如两个相邻的包交换顺序),这种情况可以迅速得到处理。反之,当出现严重失序时,TCP会误认为数据已经丢失,这就会导致伪重传。对此问题,可以设置一个合适的快速重传触发阙值来一定程度解决这一个问题。

    包重复
           IP协议可能出现将单个包传输多次的情况。例如,当链路层网络协议执行一次重传,并生成同一个包的两个副本。包的多次重复会使接收端生成一系列的重复ACK,这足以触发伪快速重传。使用SACK(特别是DSACK)就可以简单地忽略这个问题。

    快恢复

    与快重传配合使用的还有快恢复算法,其过程有以下两个要点:

    1. 当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。

    2. 与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为 慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。

    TCP/IP详解与OSI七层模型

    重置报文段与TCP错误链接处理

      当TCP头部中的RST位被置位时,该报文段就被称作“重置报文段”。一般来说,当发现一个到达的报文段对于相关连接而言是不正确的,TCP就会发送一个重置报文段(所谓的相关连接是指由重置报文段的TCP与IP头部的4元组所指定的连接)。重置报文段的触发通常在以下场景中。

    针对不存在端口的连接请求
      通常情况下,当一个连接请求到达本地却没有相关进程在目的端口侦听时就会产生一个重置报文段。UDP协议规定,当一个数据报到达一个不能使用的目的端口时就会生成一个ICMP目的地不可达(端口不可达)的消息,TCP则使用重置报文段来代替完成相关工作。

    终止连接
      终止连接的正常方法是发送FIN报文段,即连接终止的四次挥手过程,有时也被称为有序释放。因为FIN是在之前所有排队数据都已发送后才被发送出去,通常不会出现丢失数据的情况。然而在任何时刻,我们都可以通过发送一个重置报文段替代FIN来终止一个连接。与有序释放相对,这种终止方式被称为终止释放。

      终止连接的行为会为应用程序提供两个特性:(1)任何排队的数据都将被抛弃,一个重置报文段会被立即发送出去;(2)重置报文段的接收方会知道另一端并没有正常关闭连接。

    半开连接
      如果在未告知另一端的情况下通信的一端关闭或终止连接,该条TCP连接就处于半开状态(这与半关闭状态是两个不同的概念)。这种情况下只要不尝试通过半开连接传输数据,正常工作的一端将不会检测到另一端已经崩溃(因为崩溃的一端连重置报文段或者FIN报文段都没办法发出去)。这时如果崩溃的一端重新连接,它对这条连接上另一端发送过来的数据一无所知,TCP规定此时崩溃一方将回复一个重置报文段以关闭这个连接。

    时间等待错误
      TCP的TIME_WAIT状态的目的是让任何一个受制于与数据相关的关闭连接的数据被丢弃。在这段时期,等待的一方通常不需要任何操作,它只需要维持当前状态直到2MSL的计时结束。然而,如果它在这段时期内接收到来自于这条连接的一个重置报文段时,它的TIME_WAIT状态就会被破坏而提前进入CLOSED状态。

      为什么连接的被动关闭方会发送重置报文段呢?在连接的主动关闭方进入TIME_WAIT状态后,它回复一个ACK以告知被动关闭方自己已经接收到FIN报文段,被动关闭方收到这个ACK后随即进入CLOSED状态,此时主动关闭方还在TIME_WAIT状态等待2MSL计时结束。在这个时期,网络中可能存在延时,被动关闭方之前发送的ACK在这个时候才姗姗来迟,此时这个ACK对处于TIME_WAIT状态的主动关闭方来说是旧的消息,因此它会发送一个ACK作为响应,其中包含了最新的序列号与ACK值。已经处于CLOSED状态的被动关闭方收到这个ACK后就会发送一个重置报文段作为响应。这会导致另一端的TIME_WAIT状态过早结束而进入CLOSED状态。解决这个错误的最简单方法就是让处于TIME_WAIT状态的TCP连接不对重置报文段做出响应。

    TCP三次握手

    1. 最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。
    2. 服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
    3. 客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
    4. TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
    5. TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
    6. 当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

    TCP/IP详解与OSI七层模型

    为什么TCP客户端最后还要发送一次确认呢?
        主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
        如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
        如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

    TCP连接的释放(四次挥手)

      数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。

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

    TCP/IP详解与OSI七层模型

    time_wait 详解:https://www.cnblogs.com/tianzeng/p/9839137.html

    为什么客户端最后还要等待2MSL?
        MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
        第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
        第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
    为什么建立连接是三次握手,关闭连接确是四次挥手呢?
        建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
        而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
    如果已经建立了连接,但是客户端突然出现故障了怎么办?
        TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

    UDP报头

    TCP/IP详解与OSI七层模型

    1. 源端口(2 字节):发送方端口号
    2. 目的端口(2 字节 ):接收方端口号
    3. 报文长度(2 字节):UDP 用户数据报的总长度,以字节为单位。
    4. 校验和(2 字节):检测 UDP 用户数据报在传输中是否有错,有错就丢弃。
    5. 数据:UDP 的数据部分如果不为偶数需要用 0 填补,就是说,如果数据长度为奇数,数据长度加“1”。

      用于校验 UDP 数据报的数字段和包含 UDP 数据报首部的“伪首部”。

      伪首部, 又称为伪包头(Pseudo Header):是指在 TCP 的分段或 UDP 的数据报格式中,在数据报首部前面增加源 IP 地址、目的 IP 地址、IP 分组的协议字段、TCP 或 UDP 数据报的总长度等共12字节,所构成的扩展首部结构。此伪首部是一个临时的结构,它既不向上也不向下传递,仅仅只是为了保证可以校验套接字的正确性。

    TCP/IP详解与OSI七层模型