《计算机网络自顶向下方法》学习笔记——2、运输层

目录

1、多路复用与多路分解

2、UDP

3、TCP


1、多路复用与多路分解

       将主机间交付扩展到进程间交付被称为运输层的多路复用与多路分解:

        一个进程有一个或多个套接字,它相当于从网络向进程传递数据和从进程向网络传递数据的门户。因此,在接收主机中的运输层实际上并没有直接将数据交付给进程,而是将数据交给了一个中间的套接字。

       每个运输层报文段中具有几个字段,在接收端,运输层检查这些字段,标识出接收套接字,进而将报文段定向到该套接字。将运输层报文段中的数据交付到正确的套接字的工作称为多路分解。在源主机从不同套接字中收集数据块,并为每个数据块封装上首部信息(这将在以后用于分解)从而生成报文段,然后将报文段传递到网络层,所有这些工作称为多路复用。

  1. 无连接的多路复用与多路分解

       一个UDP套接字是由一个二元组来全面标识的,该二元组包含一个目的IP地址和一个目的端口号。因此,如果两个UDP报文段有不同的源IP地址和/或源端口号,但具有相同的目的IP地址和目的端口号,那么这两个报文段将通过相同的目的套接字被定向到相同的目的进程。

      2. 面向连接的多路复用与多路分解

       TCP套接字是由一个四元组(源IP地址,源端口号,目的IP地址,目的端口号)来标识的。特别与UDP不同的是,两个具有不同源IP地址或源端口号到达TCP报文段将被定向到两个不同的套接字,除非TCP报文段携带了初始创建链接的请求。

 

这里有一个思考:UDP的socket是二元组而TCP的是四元组的本质原因就是UDP是无连接的,发就完事了,而TCP是面向连接的,不可能同时几台主机与同一个socket建立连接。

 

2、UDP

       UDP只是做了运输协议能够做的最少工作。除了复用/分解功能及少量的差错检测外,它几乎没有对IP增加别的东西。许多应用更适合UDP的原因主要有以下几点:

  1. 关于何时、发送什么数据的应用层控制更为精细。采用UDP时,只要应用进程将数据传递给UDP,UDP就会将此数据打包进UDP报文段并立即将其传递给网络。实时应用通常不希望过分地延迟报文段的发送,并且能容忍一些数据的丢失,TCP服务模型不是特别适合这些应用。
  2. 无需连接建立。UDP不需要任何准备即可进行数据传输。因此UDP不会引入建立连接的时延。
  3. 无连接状态。连接状态包括接收和发送缓存、拥塞控制参数以及需要和确认号的参数。要实现TCP的可靠数据传输服务并提供拥塞控制,这些状态信息是必要的。UDP不维护连接状态,即不跟踪这些参数,因此,某些服务器当应用程序运行在UDP之上而不是TCP之上时,一般都能支持更多的活跃客户。
  4. 分组首部开销小。TCP首部开销20字节,UDP开销8字节。

       其实使用UDP的应用是可以实现可靠数据传输的,只是这需要在应用程序自身中建立可靠性机制来完成,即把运输层的工作放在了应用层进行。

 

       UDP的报文段结构如下:

 

       长度:字段指示了在UDP报文段中的字节数(首部加数据)。

       检验和:发送方的UDP对报文段中的所有16比特字的和进行反码运算,求和时遇到的任何溢出都被回卷。得到的结果就被放在UDP报文段中的检验和字段中。在接收方,全部的16比特字(包括检验和)加在一起。如果该分组中没有引入差错,则在接收方处该和将是1111111111111111。

 

3、TCP

       TCP是面向连接的,一旦建立起一条TCP连接,两个应用进程之间就可以相互发送数据了。TCP将这些数据引导到该连接的发送缓存(发送缓存是在三次握手初期设置的缓存之一)中,接下来TCP就会不时从发送缓存中取出一块数据。TCP规范中描述“TCP应该在它方便的时候以报文段的形式发送数据”。TCP可从缓存中取出并放入报文段中的数据数量受限于最大报文段长度(Maximum Segment Size,MSS)。MSS通常根据最初确定的由本地发送的最大链路层帧长度(即所谓的最大传输单元(Maximum Transmission Unit,MTU))来设置。以太网和PPP链路层协议都具有1500字节的MTU,因此MSS的典型值为1460字节。

 

       TCP报文段结构如下:

 

       其中检验和与UDP一样,6比特的标志字段意义中,ACK比特用于指示确认字段中的值是有效的。RST、SYN、和FIN比特用于连接建立和拆除。PSH比特指示接收方应立即将数据交给上层。URG比特用于指示报文段中存在着被发送端的上层设置为“紧急”的数据。紧急数据的最后一个字节由16比特的紧急数据指针字段指出。(实践中,PSH、URG和紧急数据指针并没有使用)。

  • 序号与确认号

       一个报文段的序号是该报文段首字节的字节流编号,假定数据流由一个包含500000字节的文件组成,其MSS为1000字节,数据流的首字节编号是0,该TCP将为该数据流构建500各报文段。给第一个报文段分配序号0,第二个报文段分配序号1000,第三个报文段分配序号2000,以此类推。

       而确认号是当前主机期望从通信主机收到的下一字节的序号。

       再举一个例子,假设主机A已收到一个来自主机B的包含字节0~535的报文段,以及另一个包含字节900~1000的报文段。由于某种原因,主机A还没有收到字节536~899的报文段。在这个例子中,主机A仍在等待字节536,因此,A到B的下一个报文段将在确认号字段中包含536。因此,TCP被称为提供累计确认。

 

  • 往返时间的估计与超时

       在如TCP这样的实际协议中实现超时/重传机制时还是会产生许多微妙的问题的。其中最明显的一个问题就是超时间隔长度的设置。显然,超时间隔必须大于该连接的往返时间(RTT),即从一个报文段发出到它被确认的时间。

       报文段的样本RTT(表示为SampleRTT)就是从某报文段被发出(即交给IP)到该报文段的确认被收到之间的时间量。大多数TCP的实现仅在某个时刻做一次SampleRTT测量,TCP维持一个SampleRTT均值(称为EstimatedRTT)。一旦获得一个新的SampleRTT时,TCP就会根据公式更新EstimatedRTT。除了估算RTT外,测量RTT的变化也是有价值的。RTT偏差DevRTT用于估算SampleRTT一般会偏离EstimatedRTT的程度。

       显而易见,超时间隔应该等于EstimatedRTT加一个余量,所以当SampleRTT值波动较大时,余量应该大些,波动较小时,余量应该小些,这时DevRTT就发挥作用了,一般TimeoutInterval = EstimatedRTT + 4 * DevRTT。

       TCP中,每当超时时间发生时,TCP重传具有最小序号的还未被确认的报文段。只是每次TCP重传时都会将下一次的超时间隔设为先前值的两倍,而不是用从EstimatedRTT和DevRTT推算出的值。然而,每当定时器在另两个时间(即受到上层应用的数据和收到ACK)中的任意一个启动时,TimeoutInterval由最近的EstimatedRTT值与DevRTT值推算得到。

 

  • 快速重传

       一旦收到3个冗余ACK(总共收到4个),TCP就执行快速重传,即在该报文段的定时器过期之前重传丢失的报文段。

       那么,TCP确认是累积式的,它是否是一个GBN(回退N步,重新从超时的报文段开始发送)协议呢,答案是否定的,许多TCP实现会将正确接收但失序的报文段缓存起来。

 

  • 流量控制

       TCP为它的应用程序提供了流量控制服务,以消除发送方使接收方缓存溢出的可能性。

       TCP通过让发送方维护一个称为接收窗口(报文段中的窗口)的变量来提供流量控制。接收窗口用rwnd表示,根据接收方缓存可用空间的数量来设置:

       rwnd = RcvBuffer - (LastByteRcvd - LastByteRead)

       发送方则跟踪LastByteSent - LastByteAcked的值,即发送方发送到连接中但未被确认的数据量。通过将未确认的数据量控制在值rwnd以内,就可以保证接收方的接收缓存不会溢出。

       特殊地,当接收主机的接收窗口为0时,发送主机继续发送只有一个字节数据的报文段。这些报文段将会被接收方确认,最终缓存开始清空,并且确认报文中将包含一个非0的rwnd值。

 

  • TCP的连接与关闭

       建立一条TCP连接流程如下:

  • 客户端的TCP向服务器端的TCP发送一个特殊的TCP报文段。该报文段中不包含应用层数据。但是在报文段的首部中的SYN标志位被置为1。另外,客户回随机地选择一个初始序号(client_isn)放在序号字段中。
  • 服务器为该TCP连接分配TCP缓存和变量,并向该客户TCP发送允许连接的报文段,这个报文段也不包含应用层数据,但是SYN被置为1,确认号字段被置为client_isn + 1,并选择一个初始序号。
  • 客户也为该连接分配缓存和变量,然后向服务器再发送一个报文段,该报文段可以在报文段负载中携带客户到服务器的数据。

 

       为什么不使用两次握手呢?

       因为若服务器发送的SYNACK报文段没有成功发送到客户机,对服务器来说,一个连接已建立起来,会一直等客户机发消息,而对客户机来说,并没有建立这个连接,此时服务器资源就被浪费。

 

关闭一条TCP连接流程如下:

  1. 客户机发送一个FIN标志位被置为1的报文段。
  2. 服务器回送一个确认报文段。
  3. 服务器发送它自己的终止报文段,FIN标志位被置为1。
  4. 客户对这个服务器的终止报文段进行确认。

  • SYN洪泛攻击

       攻击者发送大量的SYN报文段,而不完成第三次握手的步骤。随着这种SYN报文段纷至沓来,服务器不断为这些半开连接分配资源,导致服务器的连接资源被消耗殆尽。现在有一种有效的防御系统,称为SYN cookie。

  1. 当服务器收到一个SYN报文段时,服务器不会为该报文段生成一个半开连接。相反,服务器生成一个初始TCP***,该***是SYN报文段的源和目的IP地址与端口号通过仅有服务器知道的哈希函数算得。重要的是,服务器并不记忆该cookie或任何对应与SYN的其它状态信息。(之前三次握手讲的方法分配了缓存和变量,即记录了状态信息)
  2. 如果客户是合法的,那么它将返回一个ACK报文段,当服务器收到ACK,需要验证该ACK是否与前面发送的某些SYN相对应(之前三次握手讲的方法分配了缓存和变量所以不需要验证)。做法是将SYNACK报文段中的源和目的地IP地址与端口号执行同样的哈希函数,如果结果+1与SYNACK报文段中的确认号相同的话,则认为该ACK对应于较早的SYN报文段,因此它是合法的,生成一个具有套接字的全开的连接。

 

  • TCP拥塞控制

       TCP必须使用端到端拥塞控制而不是网络辅助的拥塞控制。运行在发送方的TCP拥塞控制机制跟踪一个额外的变量,即拥塞窗口,使用cwnd表示。在一个发送方中未被确认的数据量不会超过cwnd和rwnd中的最小值,即LastByteSent - LastByteAcked <= min{cwnd,rwnd}。

       那么,为什么拥塞窗口可以表示发送速率呢,因为在每个RRT的起始点,上面的限制条件允许发送方向该连接发送cwnd个字节的数据,在RRT结束时发送方接收对数据的确认报文,因此发送速率大概是cwnd/RTT字节/秒。

       那么,一个TCP连接怎么感知路径上是否拥塞呢?一个丢包事件(超时或收到3个冗余ACK)意味着拥塞,一个确认报文段意味着通畅。

       TCP拥塞控制算法包括慢启动,拥塞避免和快速恢复(注意别和快速重传搞混了)。

  • 慢启动

当一条TCP连接开始时,cwnd值通常初始值为一个MSS。然后每收到一个确认报文将cwnd增加一个MSS,这一过程每过一个RTT发送速率就翻倍,呈指数增长。

何时终止慢启动呢?

  1. 超时:慢启动阈值ssthresh设置为cwnd/2,cwnd设为1,重新开始慢启动。
  2. cwnd等于ssthresh时:转移到拥塞避免模式。
  3. 检测到3个冗余ACK:执行快速重传,进入快速恢复状态。

 

  • 拥塞避免

每个RTT只将cwnd增加一个MSS。

何时终止拥塞避免呢?

  1. 超时:慢启动阈值ssthresh设置为cwnd/2,cwnd设为1,重新开始慢启动。
  2. 检测到3个冗余ACK:慢启动阈值ssthresh设置为cwnd/2,cwnd值减半(算上收到的3个冗余ACK,所以加上3个MSS),执行快速重传,进入快速恢复状态。

 

  • 快速恢复

对于引起TCP进入快速恢复状态的缺失报文段,对每个冗余ACK,cwnd的值增加一个MSS。

何时终止快速恢复呢?

  1. 丢失报文段的一个ACK到达时:cwnd=ssthresh,进入拥塞避免状态。
  2. 超时:慢启动阈值ssthresh设置为cwnd/2,cwnd设为1,重新开始慢启动。

 

参考文章:《计算机网络自顶向下方法》