计算机网络学习笔记(运输层)

  整理一下运输层计算机网络学习笔记


运输层协议概述

  从通信和信息处理的角度来看,运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最底层

  当网络边缘部分中的两个主机使用网络的核心部分进行端到端通信时候,只有位于网络边缘部分的主机的协议栈才会有运输层,而网络的核心部分中的路由器在转发分组时候只会用到下三层(物理层、数据链路层、网络层)的功能。


图1: 运输层提供应用进程之间的逻辑通信

运输层两种不同的协议

  • UDP协议:无连接的、用户数据报协议
  • TCP协议:面向连接的、传输控制协议

  UDP在传输数据之前不需要先建立连接。远程主机的运输层收到UDP报文后,不需要给出任何确认。TCP则提供面向连接的服务。在传输数据之前必须要先建立连接,数据传输结束后要释放连接。

运输层的作用

  应用层所有的应用进程都可以通过运输层再传输到IP层(网络层),这是运输层的复用功能。运输层从IP层收到发送给各种应用进程的数据之后,必须分别交付指明的各个应用进程,这是运输层的分用功能。

  应用层中的应用进程要通过运输层发送到互联网,必须通过端口,每个端口用一个端口号来表示。当应用层要发送数据时候,需要将数据发送到合适的端口,然后运输层从该端口读取数据,进行后续处理。


UDP协议

UDP协议的主要特点

  • UDP是无连接的,即发送数据之前不需要建立连接,因此减少了开销和发送数据之间的时延。
  • UDP使用尽最大努力交付, 即不保证可靠交付。
  • UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部之后就向下交付给IP层,UDP保留这些报文的边界。
  • UDP没用拥塞控制,因此网络出现的拥塞不会使得主机的发送率降低。
  • UDP支持一对一、一对多、多对一和多对多的交互通信。
  • UDP首部开销小,只有8个字节

  正式由于UDP无连接低时延的特点,我在实现树莓派实时视频流传输至Unity端的时候,选择使用UDP来实现。

UDP协议的首部格式

  首部字段8个字节,由4个字段组成,分别是:源端口、目的端口、长度、检验和。每个字段长度2个字节。


图2: UDP用户数据报的首部和伪首部


TCP协议

TCP协议的主要特点

  • TCP是面向连接的运输层协议

  • 每条TCP连接只能有两个端点,每一条TCP连接只能是一对一

  • TCP提供可靠交付的服务

  • TCP提供全双工通信

  • TCP面向字节流

    TCP中的流是指流入或者流出进程的字节序列


图3: TCP面向流的概念
TCP不关心应用进程一次把多长的报文发送到TCP缓存

  每条TCP连接唯一地被通信两端的两个端点所确定: \[TCP连接 ::= \{socket_1, socket_2\} = \{ (IP_1: port_1), (IP_2: port_2)\}\]

TCP报文段的首部格式

  TCP报文段首部前20个字节是固定的,后边有4n个字节是根据需要而添加的选项。因此首部的最小长度是20个字节。

首部包括一下几个部分:

  1. 源端口和目的端口各占2个字节
  2. 序号占4个字节,TCP传输中的每个字节都按照顺序编号
  3. 确认号占4个字节,是期望收到对方下一个报文段的第一个数据字节的序号
  4. 数据偏移4位,指出TCP报文段的首部长度
  5. 保留6位
  6. 紧急URG,当URG为1时候,表示此报文段中有紧急数据,需要尽快发送
  7. 确认ACK,当ACK为1时候,确认号有效
  8. PSH, 表示让接收端进行交付接收应用进程,而不用等到整个缓存都填满了之后再向上交付
  9. 复位RST,表示TCP连接中出现严重差错,必须释放连接
  10. 同步SYN,表示这是一个连接请求或者连接接收报文
  11. 终止FIN,用来释放一个连接
  12. 窗口字段,占2个字节,用来让对方设置发送窗口的依据
  13. 校验和,占2个字节
  14. 紧急指针,占16位,指出再本报文段中紧急数据有多少个字节
  15. 选项,长度可变,最长可达40字节


图4: TCP报文段首部

TCP可靠传输的实现

以字节为单位的滑动窗口


图5: 根据B给出的窗口值,A构造出自己的发送窗口

发送窗口表示:在没有收到B的确认的情况下,A可以连续把窗口内的数据都发送出去。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时候使用。

  窗口越大,发送方就可以在收到对方确认之前连续发送更多的数据,因而可能获得更高的传输效率。

  假设发送窗口内的所有数据都已经正确到达B,B也早已发出了确认。但是不幸的是,所有这些确认都滞留在网络之中。这种情况下A就只能认为B还没有收到数据。于是,A在一段时间之后就会重传这些数据并重新设置超时计时器,直到A收到B发送的确认为止。


图6: A的发送窗口序号已经用完,但是还没有是收到确认,必须停止发送

发送缓存和接收缓存

  发送方的应用进程会把字节流写入TCP的发送缓存,接收方应用进程从TCP的接收缓存中读取字节流。


图7: 发送缓存和发送窗口


图8: 接收缓存和接收窗口

它们的作用

  • 发送缓存用来暂时存放:发送应用程序传送给发送方TCP准备发送的数据;TCP已发送出但是尚未收到确认的数据
  • 接收缓存用来暂时存放:按序到达的、但是尚未被接收应用程序读取的数据;不按序到达的数据

需要注意的是

  1. A的发送窗口并不总是和B的接收窗口一样大
  2. TCP标准没有规定对不按序到达的数据应该如何处理。通常是先临时存放在接收窗口,等到字节流中缺少的字节收到后,在按序交付上层的应用进程
  3. TCP要求接收方必须有累计确认的功能,这样可以减小传输开销

超时重传时间的选择

  TCP采用了一种自适应算法,它记录了一个报文段发出的时间,以及收到相应的确认的时间。这两个时间之差就是报文段的往返时间RTT。TCP保留了RTT的一个加权平均往返时间RTTs。每当第一次测量RTT样本时候,RTTs值就为取到的RTT样本的值。之后每次测量一个新的RTT样本,就按照下式重新计算一次RTTs:

\[新RTT_s=(1-\alpha)\times(旧的RTT_s)+\alpha\times(新的RTT_s样本)\]

  上式中,\(\alpha\)的推荐值是\(\frac{1}{8}\)

  超时重传时间RTO按照下式计算:

\[RTO=RTT_s+4\times RTT_D\]

  其中,\(RTT_D\)\(RTT\)的偏差的加权平均值。在第一次测量的时候,\(RTT_D\)值取为测量到的\(RTT\)值的一半,在之后的测量中,用下式计算\(RTT_D\)

\[新RTT_D=(1-\beta)\times (旧RTT_D)+\beta \times \lvert RTT_s - 新的RTT样本 \rvert\]

  这里的\(\beta\)推荐值是\(\frac{1}{4}\)

修正的Karn算法

  报文段每重传一次,就把RTO增大一些: \[ 新RTO=\gamma\times (旧RTO)\]

  其中,系数\(\gamma\)的典型值是2。当不再发生报文段的重传时候,才根据报文段的往返时延更新平均往返时延RTT和超时重传时间RTO的数值。

选择确认SACK

  为了能够在收到的报文段无差错,只是未按序号情况下只传送缺少的数据而不重传已经正确到达接收方的数据,可以利用选择确认来处理。


图9: 接收到的字节流不连续

  如果这些字节的序号都在接收窗口之内,那么接收方就先收下这些数据,但是要把这些信息准确地告诉发送方,让发送方不要再重复发送这些已经收到的数据

TCP的流量控制

  所谓流量控制就是:让发送方的发送速率不要太快,要让接收方来得及接收

利用滑动窗口实现流量控制


图10: 利用可变窗口实现流量控制

  发送方的发送窗口不能超过接收方给出的接收窗口的数值。TCP的窗口单位是字节

存在的问题:死锁

  在B向A发送零窗口的报文之后不久,B的接收缓存中有了新的空间,于是B向A发送rwnd为400的报文段。然而这个报文段在传输过程中丢失了,这会导致A迟迟收不到B发送的非零窗口通知,并且B也不会收到A发送过来的报文,这就会导致死锁。

  为了解决这个问题,TCP为每个连接设有一个持续计时器,只要TCP连接的一方接收到对方的零窗口通知,就启动该计时器。若持续计时器设置的时间到期,则发送一个零窗口的探测报文段,而对方就在确认这个探测报文段时候给出当前的窗口值。如果当前窗口值为零,则重新设置持续计时器;如果不是零,则死锁就可以被打破了。

TCP的传输效率

  在应用进程将数据交给TCP的发送缓存之后,剩下的任务就交给TCP才执行了。可以用不同的机制来控制TCP报文段的发送时机。

  • 第一种机制是TCP维持一个变量,它等于最大报文段长度MSS。只要缓存中存放的数据达到MSS字节时候,就组成一个报文段发送出去。
  • 第二种机制是由发送方的应用进程指明要发送报文段,即TCP支持的推送push操作。
  • 第三种机制是发送方设定的一个定时器期限到了,这时候就把当前已有的缓存数据装入报文段(长度不能超过MSS)发送出去。

Nagle算法

第一个问题:假设用户只发送一个字符,在加上20字节的首部之后,得到21字节长的TCP报文段,再加上20字节的IP首部,形成41字节长的IP数据报。在接收方TCP立即发送确认,构成的数据报是40字节长度。若用户要求远程的主机回送这一字符,则又要发回41字节长的IP数据报和40字节长的确认数据报。当线路带宽并不宽裕时候,这种传送方法效率并不高。

  为了解决这个问题,广泛采用Nagle算法。算法如下:

  • 若发送应用进程把要发送的数据逐个字节地送到TCP的发送缓存,则发送方就要把第一个数据字节先发送出去,把后边到达的数据字节都缓存起来。
  • 当发送方收到第一个数据字符的确认之后,再把发送缓存中的所有数据都组装成一个报文段发送出去,同时继续对随后到达的数据进行缓存。
  • 只有在收到对前一个报文段的确认后才继续发送下一个报文段。
  • 当到达的数据已经达到发送窗口大小的一半或者已经达到报文段的最大长度时候,就立即发送一个报文段。

接收方糊涂窗口综合征

  当接收方的TCP缓冲区已满,接收方会向发送方发送窗口大小为0的报文。若此时接收方的应用进程以交互方式每次只读取一个字节,于是接收方又发送窗口大小为一个字节的更新报文,发送方应邀发送一个字节的数据,于是接收窗口又满了,如此循环往复,这就会导致网络的效率很低。


图11: 接收方应用进程消耗数据太慢

  为了解决这个问题,可以让接收方等待一段时间,使得或者接收缓存已经又足够的空间容纳一个最长的报文段,或者等到接收缓存已有一半的空闲的空间。只要出现这两种情况之一,接收方就发送确认报文,并向发送方通知当前的窗口大小。此外,发送方也不要发送太小的报文段,而是将数据积累成足够大的报文段,或者达到接收方缓存的空间的一半大小。

TCP的拥塞控制

拥塞:在某段时间,对网络中某资源的需求超过了该资源所能提供的可用部分,网络的性能就会明显变坏,整个网络的吞吐量将随输入负荷的增大而下降。

\[\Sigma对资源的需求 > 可用资源\]

拥塞控制的一般原理

出现拥塞的原因:

  • 节点缓存容量太小
  • 链路容量不足
  • 处理机处理速率太慢
  • 拥塞本身会进一步加剧拥塞

拥塞控制流量控制的区别

  拥塞控制是防止过多的数据注入到网络中,使得网络中的路由器或者链路不至于过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传属性能有关的所有因素。

  流量控制往往是指点对点通信量的控制,是端到端的问题。流量控制所要做的就是抑制发送端发送数据的速率,以便接收端来得及接收。


图12: 流量控制和拥塞控制的比喻

拥塞控制宏观上的两种方法

  从大的方面来看,拥塞控制有开环控制闭环控制两种方法。开环控制就是在设计网络时事先将发生拥塞的有关因素考虑周到,力求网络在工作时候不产生拥塞。

  闭环控制是基于反馈环路的概念,主要有一下几种措施:

  1. 监测网络系统以便检测到拥塞在何时、何处发生。
  2. 把拥塞发生的信息传送到可采取行动的地方。
  3. 调整网络系统的运行以解决出现的问题。

监测网络拥塞的主要指标有:

  • 由于缺少缓存空间而被丢弃的分组的百分数

  • 平均队列长度

  • 超时重传的分组数

  • 平均分组时延

  • 分组时延的标准差

    ……


TCP的拥塞控制方法

  TCP采用基于窗口的方法进行拥塞控制。该方法属于闭环控制方法。

  TCP发送方会维持一个拥塞窗口CWND。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送端利用拥塞窗口根据网络的拥塞情况调整发送的数据量。

\[真正的发送窗口值=\min(公告窗口值, 拥塞窗口值)\]

  控制拥塞窗口的原则:

  1. 只要网络没有出现拥塞,拥塞窗口就可以再增大一些,以便将更多的分组发送出去,这样就可以提高网络利用率。
  2. 只要网络出现拥塞或者可能出现拥塞,就必须将拥塞窗口减小一些,以减小注入到网络中的分组数,以便环节网络出现的拥塞。

拥塞的判断

  • 重传定时器超时

    确认报文段没有及时返回,判断报文段丢失,即出现了拥塞

  • 收到三个相同(重复)的ACK

    个别报文段会在网络中丢失,预示可能会出现拥塞,因此可以尽快采取控制措施


图13: 收到对相同报文段的3个冗余ACK

慢开始控制算法

算法思路:当主机在已建立的TCP连接上开始发送数据时候,并不清楚网络当前的负荷状况。如果立即将大量的数据字节注入到网络,那么就有可能引起网络发生拥塞。所以在发送之前最好先探测一下,即由小到大逐渐增大注入到网络中的数据字节,也即是说,由小到大逐渐增加拥塞窗口值

初始拥塞窗口值的设置:新的规定将初始拥塞窗口cwnd设置为不超过2至4个SMSS(发送方的最大报文段)的数值

  慢开始规定:在每收到一个新的报文段的确认之后,可以把拥塞窗口增加最多一个SMSS的数值。

\[拥塞窗口cwnd每次的增加量=\min(N, SMSS)\]

  其中N是原先未被确认的、但现在被刚收到的确认报文段所确认的字节数。


图14: 发送方每收到一个确认就把拥塞窗口加一
cwnd呈指数增长

  为了防止拥塞窗口增大过大引起网络瘫痪,还需要设置一个慢开始门限ssthresh状态变量:

  • 当cwnd < ssthresh时候,使用上述慢开始算法
  • 当cwnd > ssthresh时候,停止使用慢开始算法而改用拥塞避免算法
  • 当cwnd = ssthresh时候,既可使用慢开始算法、又可以使用拥塞避免算法

拥塞避免算法

算法思路:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍,使得拥塞窗口cwnd按照线性规律缓慢增长。

  无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(重传定时器超时):

  • ssthresh = max(cwnd/2, 2)
  • cwnd = 1
  • 执行满开始算法

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


图15: cwnd在拥塞控制时候的变化情况

快重传算法

  快重传算法首先要求接收方不要等待自己发送数据时候才进行稍待确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。

  采用快重传方法可以让发送方尽早知道发生了个别报文段的丢失。发送方只要一连收到三个重复确认,就知道接收方确实没有接收到报文段,因而应当立即执行重传,这样就不会出现超时,发送方也就不会误认为出现了网络拥塞。


图16: 快重传示例

快恢复算法

  当发送端接收到三个重复的确认时候,由于发送方认为现在网络很可能并没有发生拥塞,因此现在不执行慢开始算法,而是执行快恢复算法,如下:

  1. 慢开始门限ssthresh = 当前拥塞窗口cwnd/2
  2. 新拥塞窗口cwnd = 慢开始门限ssthresh
  3. 开始执行拥塞避免算法,使得拥塞窗口缓慢地线性增大

  在图15中点4就开始执行快恢复算法。


图17: TCP拥塞控制流程图


主动队列管理AQM

  所谓“主动”就是不要等到路由器的队列长度已经到达最大值时候才不得不丢弃后边到达的分组。这样就太被动了。应当在队列长度达到某个值得警惕的数值时候就主动丢弃到达的分组。

  作为AQM实现方式之一的RED执行流程如下:

  1. 使路由器的队列维持两个参数:队列长度最小门限\(TH_{min}\)和最大门限\(TH_{max}\)

  2. RED对于每一个到达的分组都先计算平均队列长度\(L_{AV}\)

    • 若平均队列长度小于最小门限\(TH_{min}\),则将新到达的分组放入队列进行排队
    • 若平均队列长度超过最大门限\(TH_{max}\),则将新到达的分组丢弃
    • 若在\(TH_{min}\)\(TH_{max}\)之间,则按照某一概率p将新到达的分组丢弃


图18: RED示意图

TCP运输连接的管理

  TCP的运输连接有三个阶段:连接建立数据传送连接释放。TCP建立连接采用客户服务器的方式。

TCP的连接建立

TCP建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个TCP报文段


图19: 用三报文握手建立TCP连接

采用三报文握手只要是为了防止已经失效的连接请求报文段突然又收到了,因而产生错误。

TCP的连接释放

数据传输结束后,通信的双方都可以释放连接。TCP连接的释放是四报文握手


图20: TCP连接释放的过程