计算机网络学习笔记2

计算机网络运输层学习

Posted by Xiaoxi on April 4, 2016

计算机网络运输层学习总结

运输层居于网络层之上,应用层之下。它主要为运行在不同主机上的应用进程之间提供逻辑通信。与之相对的网络层协议是为了两台不同的主机之间提供逻辑通信。

运输层主要有TCP与UDP协议,是在端系统中而不是在网络路由器中实现的。

主要有以下几个知识点:

1. 多路复用和多路分解
 	2. UDP协议
   3. TCP协议
   4. TCP协议的可靠传输机制
   5. TCP协议的流量控制
   6. TCP协议的拥塞控制
   7. TCP协议的连接管理

1. 多路复用和多路分解

多路分解是将在运输层收到的报文段中的数据交付到正确的套接字的工作。

多路复用是从源主机的不同套解字中收集数据块,并为每个数据块封装上首部信息(这将在多路分解时使用)从而生成报文段,然后将报文段传递到网络层的工作。

  • 复用:发送方不同的应用进程都可以使用同一个运输层协议传输数据
  • 分用:接收方的运输层在剥去报文的首部后能把这些数据正确交付目的应用进程

1.2无连接的多路复用和多路分解

  • UDP是无连接的。一个UDP套接字是由一个包含目的IP地址和目的端口号的二元组来全面标识的。
  • 如果两个UDP报文段具有不同的源IP/端口号,但具有相同的目的IP/端口号,那么,这两报文段会被送至相同的目的进程。
  • 所以,一个UDP的套接字使用的就是本地的IP和端口号进行标识。这样运输层中收到UDP报文段时,就会根据报文段中的目的IP/端口号向进程投递报文。

1.3面向连接的多路复用和多路分解

  • TCP是面向连接。一个TCP套接字是由一个四元组(源IP/端口号,目的IP/端口号)来标识的。
  • 这样当一个TCP报文段从网络到达一台主机时,主机使用全部四个值来将报文段定向(多路分解)到相应的套接字。 正是因为TCP是面向连接的,所以需要四个值来标识。不同客户机里面的不同进程都可以跟服务器的一个特定的服务进程建立各自的一条连接。而这每一条连接的两端的套接字都是以四个值进行标识的。
  • 服务器关注连接请求报文段里的这四个值,新创建的连接套接字通过四个值来标识。所有后续到达的报文段,如果它们这四个值都匹配,则被多路分解到这个套接字。

2.UDP协议

  1. UDP协议仅在网络层协议的基础上增加了一点多路复用和多路分解服务以及差错检测功能。
  2. UDP的特点:
    • 无连接,尽最大努力交付,面向报文,没有拥塞控制,支持一对一,一对多,多对一和多对多的交互通信,首部开销小
  3. UDP报文结构 报头: image

    ipv4结构: image

    image

    注意,图中的蓝色部分的两个源端口是错误的。正确的是先从左往右:源端口、目的端口。

    UDP报文首部真正只具有四个字段,每个字段只有2个字节,所以一共是8个字节。黄色部分的伪首部是用于求检验和字段的。
    在计算检验和的时候,在UDP用户数据报前增加12个字节的伪首部。伪首部既不向下传送也不向上递交,仅仅是为了计算检验和。
    UDP的差错检测是包括数据部分,与IP只检测首部是不同的。

    UDP的检验和求法采用的是IP的16位的反码求和。若UDP用户数据报的数据部分不是偶数个字节则填入一个全零字节(这个字节不发送),在接收方,对收到UDP数据报进行16位的反码求和,得到结果如果为全1则无差错。虽然UDP提供差错检测,但不提供差错恢复。

    image

  4. UDP的注意点:
    1. UDP的检验和是可选的。
    2. UDP的加伪首部去计算检验和的目的是确认数据是否正确到达目的地(IP没有接受地址不是本主机的数据和IP没有把应传送给另一高层的数据报传给UDP)。
    3. UDP的检验和计算出来全是0的话,就往检验和字段填充全1,这是在二进制反码计算中是等效的。但如果该字段全0则说明发送端没有做计算。
    4. 如果UDP数据报有错,会被悄悄地丢弃。

    虽然UDP是无连接,不可靠的协议。但是其占用资源小,速度快,实时性强等优点还是受到很多应用利用。


3.TCP协议

TCP协议是面向连接,提供可靠交付服务,提供全双工通信,面向字节流的。 面向连接是指通信前会在两个主机两个不同的通信进程之间建立一条虚拟的连接(逻辑连接)。

TCP报文结构:

image

  1. 源端口或目的端口(分别2个字节):略。
  2. 序号(4个字节):TCP连接中传送的字节流中的每一个字节都按顺序编号。传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值的是本报文段所发送的数据的第一个字节的序号。例如一报文段序号值为101,数据共有100字节,那么下一报文段序号值就为201。因为序号的大小是0,2^32 -1,超出了循环从0开始,所以序号是使用mod2^32运算的。
  3. 确认号(4个字节):期望收到对方下一个报文段的第一个数据字节的序号。
  4. 数据偏移(4位):指出TCP报文段的数据起始处距离TCP首部的起始处有多远。实际上就是指出TCP首部长度。数据偏移的单位是4个字节,而4位二进制的最大值是15,则说明首部最大只能是15*4=60字节。其实选项字段最大只能为40字节。
  5. 保留(6位):略。
  6. 紧急URG:表明紧急指针子字段有效。
  7. 确认ACK:等于1时确认号字段才有效。TCP规定连接建立后所有传送报文度必须把ACK置1。
  8. 推送PUSH:表明需要尽快地交付接收应用进程,不再等整个缓存都填满后再向上交付。
  9. 复位RST:等于1时表明TCP连接中出现严重差错,必须释放连接再重新建立。
  10. 同步SYN:用于连接建立时同步序号的。
  11. 终止FIN:用来释放一个连接。
  12. 窗口(2个字节):该字段用于流量控制,指示接收方愿意接收的字节数量。因为接收方的接收缓存有限。窗口值是动态变化的。
  13. 检验和(2个字节):检验和检查的范围包括首部和数据。
  14. 紧急指针(2个字节):指出紧急数据的末尾在报文段中的位置。即使窗口值为零也可以发送紧急数据。
  15. 选项(最大可达40个字节):略。

3.TCP协议的可靠传输机制

TCP协议的可靠传输机制主要依靠:

  1. 报文确认
  2. 超时重传
  3. 快速重传
  4. 差错恢复

报文确认

TCP协议规定,接收方对于正确接收到的来自发送方的报文段要给予确认返回报文。该报文中的首部ACK指向接收方期待下一个开始接收的字节。

超时重传

当发送方发送报文之后,会启动一个倒数计时器(重传超时间隔),计时器为零时,就认为报文可能在网络中丢失,需要重传报文。

那么这个重传超时间隔的值要设为多少呢?
首先,这个值必须大于TCP连接的往返时延(RTT)。  
TCP采用的是自适应的方法。设SampleRTT为样本RTT,就是从某报文被发出(即交给IP)到对该报文段的确认被收到之间的时间量。  
在任意时刻只为一个已发送但未被确认的报文段估测量,并且仅为传输一次的报文段测量SampleRTT。
可是SampleRTT的值会因为网络的拥塞情况而不段的变化,所以需要一个均值EstimatedRTT:
SampleRTT = (1-a)*EstimatedRTT + a*SampleRTTs
RFC 2988中给出a参考值是0.125。从统计学观点来说,这种平均被称为指数加权移动平均。
另外,测量RTT的变化也是有意义的。所以RFC 2988定义了RTT偏差DevRTT。
DevRTT = (1-b) * DevRTT + b * |SampleRTT - EstimatedRTT|
超时间隔应该是等于大于均值的,而超过的均值的范围值可以利用DevRTT。所以超时间间隔的公式就为
TimeoutInterval = EstimatedRTT + 4 * DevRTT

快速重传

超时触发重传存在的另一个问题就是超时周期可能相对较长。这种长超时周期迫使发送方等待很长时间才重传丢失分组。幸运的是,发送方可以通过冗余ACK较好地检测丢包情况。

冗余ACK就是再次确认某个报文段的ACK,而发送方先前已经收到对该报文段的确认。

image

如果TCP发送方接收到相同数据的3个冗余ACK,就认为跟在这个已被确认过3次的报文段之后的报文段已经丢失。一旦收到3个冗余ACK,TCP就执行快速重传。

差错恢复

TCP的差错恢复方法有两种:

  • 回退N步(GBN协议)
  • 选择重传(SR协议)
    1. GBN协议

    GBN协议常常被称为滑动窗口协议。 来看一个发送方的示例图: image

    base是基序号,指示最早的未确认分组的序号。 nextseqnum是下一个序号,指示最小的下一个待发送分组的序号。 window size就是窗口大小。

    每当收到ACK时,base指针就会变化,整个窗口就会前移。收到一个ACK时仍有未被确认分组,则计时器被重新启动。 如果出现超时,发送方将重传所有已发送但未确认的分组。 在GBN协议中,接收方会丢弃所有失序的分组。即使前面在失序分组前已经有好几个分组正确到达了。

    2.SR协议

    发送方中的每个分组必须拥有自己的逻辑定时器,因为超时后只能发送一个分组。可以使用单个硬件定时器模拟多个逻辑定时器的操作。 接收方将确认一个正确接收的分组而不管其是否按序。失序的分组将被缓存,直到一批分组按序接收成功了则交付上层。

    由于接收方接收了分组就发送ACK,移动自身的接收窗口,而发送方有可能因为ACK丢失等原因迟迟没收到确认报文以至于窗口不移动。所以,对于SR协议而言,这就意味着发送方和接收方的窗口并不总是一致。

    对于SR协议而言,窗口的长度必须小于或等于序号空间大小的一半。因为序号空间是有限的,必须是循环利用的。假如序号空间总大为4,那么4之后会从0循环开始。当窗口大小大于序号空间的一半时,就有可能会出现接收方接收到一个分组到来是不能判断得到这个分组是一个新的分组抑或是一个重传。 image


5. TCP协议的流量控制

TCP为应用程序提供来流量控制服务,以消除发送方使接收方缓存溢出的可能性。因此,可以说流量控制是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读速率相匹配。

注意:流量控制服务与拥塞控制不同。它们分别是针对不同原因而采取的措施。 TCP协议报文结构中的窗口字段就是用于流量控制的。

首先先来定义几个变量标识:

  • LastByteRead : 接收方的应用程序从缓存中读出的最后一个字节编号
  • LastByteRcvd : 从网络到达接收方缓存中的最后一个字节
  • RcvBuffer : 接收方缓存大小
  • RcvWindow : 接收方的接收窗口大小
  • LastByteSent : 发送方最后发送的字节编号
  • LastByteAcked : 发送方最后接收到ACK的字节编号

由上面的变量可以得出以下的关系式:

  • LastByteRcvd - LastByteRead <= RcvBuffer
  • RevWindow = RcvBuffer - ( LastByteRcvd - LastByteRead )
  • LastByteSent - LastByteAcked <= RevWindow 所以是要将发送方的已发送未确认的分组控制在接收方给出的窗口大小内。考虑到当接收方返回的window值为0时,发送方就不能再进行发送,而发送方不进行发送,当接收方有缓存窗口不为零就无法得知,因为接收方不会主动推送窗口值。所以TCP规定,当发送方收到窗口值为0时的报文,就本地开启一个计时器,时间一到就主动地去发送一个只有一字节的探测报文段,这时候接收方就把新的窗口值加上去ACK报文段进行返回。

6. TCP协议的拥塞控制

IP层不提供显式的网络拥塞反馈,所以TCP必须使用端到端的拥塞控制。 拥塞指的是当前网络的环境。当拥塞发生时,路由器上的缓存就会面临溢出的可能,这样会导致分组在网络中被丢弃,影响通信的质量。 改善网络中的拥塞情况也是起到全局性作用的。

由于是TCP自身实现的拥塞控制,所以就会产生以下的几个问题:

发送方如何去检测当前网络的拥塞情况?

当网络拥塞,会对发送方导致的直接结果是:超时重传和冗余的ACK。自然可以根据这两种情况去判断网络是否有拥塞。

当检测到拥塞的时候,发送方是如何限制发送的速率?

TCP拥塞控制机制让连接的每一端都记录一个额外的变量,即拥塞窗口,表示为CongWin(以字节为单位)。它对一个TCP发送方能向网络中发送流量的速率进行了限制。

结合在TCP中的流量控制RcvWindow。可以得到:

LastByteSent - LastByteAcked <= min( CongWin, RcvWindow)

在一个发送方中未被确认的数据量不会超过CongWin和RcvWindow中的最小值。 在拥塞控制中,采用的是什么算法进行调整发送的速率?

TCP拥塞控制算法有:

  1. 加性增(additive-increase),乘性减(multiplicative-decrease)
  2. 慢启动(slow start)
  3. 对超时事件作出反应
加性增(additive-increase),乘性减(multiplicative-decrease)
  1. 加性增的思想是,当检测到网络无拥塞(即有对未确认的数据有ACK到达时),就每收到一个确认就把Congwin增加一个MSS*(MSS/CongWin)。(CongWin=CongWin+MSS*(MSS/CongWin))注:MSS意思是最大报文段长度。
  2. 乘性减的思想是,当发送丢包的事件时,CongWin的值会减半。CongWin值也许会持续地减少,但是不能降到低于1个MSS。

TCP拥塞控制协议的线性增长阶段被称为避免拥塞。CongWin值会重复地经历一个升降循环,即重复地线性增长又突然降至一半。所以CongWin的变化会是锯齿状的。

image

慢启动(slow start)

TCP连接开始时,CongWin的值初始为1。但发送方在初始阶段不是线性地增长的,而是以指数的形式。即每收到一个ACK,CongWin就会增加一个MSS。(CongWin=CongWin+MSS) 当CongWin>Threshold(阀值)时,TCP就会由慢启动的状态转换到拥塞避免。 image

对超时事件作出反应

上面已经说道,TCP是根据超时事件和冗余ACK判断网络拥塞的。 但是TCP的拥塞控制机制对于这两种事件所采取的对策是不一样的。因为冗余ACK的发生,说明还是有一部分的分组能够到达,但是超时事件意味着不能到达。

所以: 当发生冗余ACK时,TCP先把CongWin减半(乘性减),然后进行加性增。 当发生超时事件时,TCP会把CongWin当前值的一半记录下来(我们记为Threshold)。然后将拥塞窗口设为1MSS,再以慢启动的形式增长直至CongWin达到Threshold为止。这时再以加性增的形式增长。 这种在收到冗余ACK后取消慢启动阶段的行为称为快速恢复。

最后,针对各种的不同的情况做一个总结:

状态 事件 TCP发送方拥塞控制动作  
慢启动(ss) 收到未确认数据的ACK CongWin=CongWin+MSS,if(CongWin<Threshold)  
拥塞避免(ca) 收到未确认数据的ACK CongWin=CongWin+MSS*(MSS/CongWin)  
ss或ca 冗余ACK CongWin=CongWin/2,然后状态变为拥塞避免  
ss或ca 超时 Threshold=CongWin/2,CongWin=1MSS,然后状态变为慢启动  

7. TCP协议的连接管理

一台主机想要向另一台主机发起TCP通信时需要建立连接,当传输结束之后需要释放连接。

建立连接

TCP建立连接是通过三次握手。

image

步骤:

  1. 客户端向服务端发送一个特殊的TCP报文段。该报文段不包含应用层数据,但需要把报文段首部的SYN标志位置1。这个报文被称为SYN报文段。除了SYN,还需要加上SEQ字段指示客户端的开始序号。
  2. 服务端接收到SYN报文之后,就为该TCP连接分配TCP缓存和变量,并向客户端发送允许连接的报文段。该报文段包含SYN和SEQ,意义同上。但还附带一个ACK指示想要收到的下一个字节。该允许连接的报文段有时被称为SYNACK报文段。
  3. 在收到来SYNACK报文段后,客户机也要给该连接分配缓存和变量。这时客户端还会向服务端发送另一个报文段,是对SYNACK报文段进行确认。因为连接已经成功建立,所有该SYN被置0。

一旦建立连接成功,双方就可以互相发送含有数据的报文段,并且这些报文段SYN都置为0。

释放连接

TCP释放连接是通过四次挥手。 image

如图所示,客户端应用进程发出一个关闭连接命令。这会引起客户端TCP向服务端进程发送一个特殊的TCP报文段。这个特色的报文段首部中的FIN比特被置1。当服务接收到该报文段后,就向客户端会送一个确认报文段。这时,TCP连接处于半关闭状态。半关闭指的是客户端已经不能再给服务端发送数据了,但可以接收服务端发过来的数据。数据传送完毕之后,服务端发送其终止报文段,同样FIN被置1。最后客户端对这个服务端的终止报文段进行确认。两台主机的TCP连接成功释放。 注意,如果服务端不需要向客户端传输数据了,那么ack=u+1的这个两个报文是可以结合为一个的。

为什么客户端最后需要等待2MSL呢? 解释一下,MSL意思是报文段最大生存时间(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。因为网络层有限制生存时间字段TTL,所以MSL值是有限的,常用可能会30秒,1分钟,2分钟。

为了保证服务端能够接收到客户端的确认报文段,因为服务端在规定时间内收不到会进行重发。 经过2MSL之后可以使本连接持续的时间内产生的所有报文都从网络中消失。 所以,服务端关闭连接要比客户端要快。 关于2MSL还有别的有趣事情,可以参考<<计算机网络笔记2-运输层»。

如果因为机器故障而导致连接被中断呢? TCP设有一个保活计时器。服务端每收到一次客户的数据都会重新设置该计时器。计时器到时之后,服务端会主动发送探测报文,连续几个探测报文没反应的话,服务端就会自动关闭。


备注

文章笔记多为选自参考3,然后再结合书本知识与网络相关进行总结。建议大家学习的时候,也查看下原始页面。最后,大家在学习的时候请尝试通过wireshark抓包学习,从实践中学习总是最快的😊


参考:

  1. https://zh.wikipedia.org/wiki/%E7%94%A8%E6%88%B7%E6%95%B0%E6%8D%AE%E6%8A%A5%E5%8D%8F%E8%AE%AE
  2. http://zhenhua-lee.github.io/tech/network.html
  3. http://blog.163.com/magicc_love/blog/static/