运输层是居于网络层之上,应用层之下。在运输层中,有着两个十分重要的因特网协议: TCP 和 UDP 运输层协议。
运输层协议为运行在不同主机上的 应用进程之间 提供逻辑通信。而网络层协议是为了 两台不同的主机之间 提供逻辑通信。
运输层协议是在端系统中而不是在网络路由器中实现的。
运输层中,主要有以下的几个重要的知识点:
多路复用和 多路分解 是所有计算机网络都需要的。
多路分解是将在运输层收到的报文段中的数据交付到正确的套接字的工作。
多路复用是从源主机的不同套解字中收集数据块,并为每个数据块封装上首部信息(这将在多路分解时使用)从而生成报文段,然后将报文段传递到网络层的工作。
需要注意的是,两台主机中不同的进程间进行通信是通过 套接字 来进行的。它相当于从网络向进程传递数据和从进程向网络传递数据的门户。
在接收主机中的运输层实际上并没有直接将数据交付给进程,而是通过一个中间的套接字来传递。同一台主机上的所有套接字都具有唯一的标识符。
UDP是无连接的。一个UDP套接字是由一个包含 目的IP地址 和 目的端口号 的 二元组 来全面标识的。
如果两个UDP报文段具有不同的源IP/端口号,但具有相同的 目的IP/端口号 ,那么,这两报文段会被送至相同的目的进程。
所以,一个UDP的套接字使用的就是本地的IP和端口号进行标识。这样运输层中收到UDP报文段时,就会根据报文段中的 目的IP/端口号 向进程投递报文。
TCP是面向连接。一个TCP套接字是由一个 四元组(源IP/端口号,目的IP/端口号) 来标识的。
这样当一个TCP报文段从网络到达一台主机时,主机使用全部四个值来将报文段定向(多路分解)到相应的套接字。
正是因为TCP是面向连接的,所以需要四个值来标识。不同客户机里面的不同进程都可以跟服务器的一个特定的服务进程建立各自的一条连接。而这每一条连接的两端的套接字都是以四个值进行标识的。
服务器关注连接请求报文段里的这四个值,新创建的连接套接字通过四个值来标识。所有后续到达的报文段,如果它们这四个值都匹配,则被多路分解到这个套接字。
连接套接字与进程之间并非总是有着一一对应的关系。事实上,当今的高性能WEB服务器通常只使用一个进程,但是为每个新的客户机连接创建一个具有新连接的套接字的新线程。
UDP其实就是在网络层协议上增加了一点多路复用和多路分解服务以及差错检测功能。
UDP报文结构:
注意,图中的蓝色部分的两个源端口是错误的。正确的是先从左往右:源端口、目的端口。
UDP报文首部真正只具有四个字段,每个字段只有2个字节,所以一共是8个字节。黄色部分的 伪首部 是用于求检验和字段的。
在计算检验和的时,在UDP用户数据报前增加12个字节的伪首部。 伪首部既不向下传送也不向上递交,仅仅是为了计算检验和 。
UDP的差错检测是包括数据部分,与IP只检测首部是不同的。
UDP的检验和求法采用的是IP的 16位的反码求和 。若UDP用户数据报的数据部分不是偶数个字节则填入一个全零字节(这个字节不发送),在接收方,对收到UDP数据报进行16位的反码求和,得到结果如果为全1则无差错。虽然UDP提供差错检测,但不提供差错恢复。
UDP的特点:
UDP的注意点:
虽然UDP是无连接,不可靠的协议。但是其占用资源小,速度快,实时性强等优点还是受到很多应用利用。
TCP协议是 面向连接 , 提供可靠交付服务 , 提供全双工通信 , 面向字节流 的。
面向连接是指通信前会在两个主机两个不同的通信进程之间建立一条虚拟的连接(逻辑连接)。
TCP报文结构:
源端口或目的端口(分别2个字节):略。
序号(4个字节):TCP连接中传送的字节流中的 每一个字节都按顺序编号 。传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值的是本报文段所发送的数据的第一个字节的序号。例如一报文段序号值为101,数据共有100字节,那么下一报文段序号值就为201。因为序号的大小是[0,2^32 -1],超出了循环从0开始,所以序号是使用mod2^32运算的。
确认号(4个字节):期望收到对方下一个报文段的第一个数据字节的序号。
数据偏移(4位):指出TCP报文段的数据起始处距离TCP首部的起始处有多远。实际上就是指出TCP首部长度。数据偏移的单位是4个字节,而4位二进制的最大值是15,则说明首部最大只能是15*4=60字节。其实选项字段最大只能为40字节。
保留(6位):略。
紧急URG:表明紧急指针子字段有效。
确认ACK:等于1时确认号字段才有效。TCP规定连接建立后所有传送报文度必须把ACK置1。
推送PUSH:表明需要尽快地交付接收应用进程,不再等整个缓存都填满后再向上交付。
复位RST:等于1时表明TCP连接中出现严重差错,必须释放连接再重新建立。
同步SYN:用于连接建立时同步序号的。
终止FIN:用来释放一个连接。
窗口(2个字节):该字段用于流量控制,指示接收方愿意接收的字节数量。因为接收方的接收缓存有限。窗口值是动态变化的。
检验和(2个字节):检验和检查的范围包括首部和数据。
紧急指针(2个字节):指出紧急数据的末尾在报文段中的位置。即使窗口值为零也可以发送紧急数据。
选项(最大可达40个字节):略。
TCP协议的可靠传输机制主要依靠:
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,而发送方先前已经收到对该报文段的确认。
如果TCP发送方接收到相同数据的3个冗余ACK,就认为跟在这个已被确认过3次的报文段之后的报文段已经丢失。一旦收到3个冗余ACK,TCP就执行 快速重传 。
TCP的差错恢复方法有两种:
GBN协议常常被称为 滑动窗口协议 。
来看一个发送方的示例图:
base是基序号,指示最早的未确认分组的序号。 nextseqnum 是下一个序号,指示最小的下一个待发送分组的序号。 window size 就是窗口大小。
每当收到ACK时,base指针就会变化,整个窗口就会前移。收到一个ACK时仍有未被确认分组,则计时器被重新启动。
如果出现超时,发送方将重传所有已发送但未确认的分组。
在GBN协议中,接收方会丢弃所有失序的分组。即使前面在失序分组前已经有好几个分组正确到达了。
一个单分组的差错或失序就可能引起GBN重传大量分组,许多分组根本没有必要重传。所以,选择重传协议通过让发送方仅重传它怀疑在接收方出错(即丢失或受损)的分组而避免来不必要的重传。
这种按需重传就要求接收方逐个地确认正确接受的分组。
SR协议中:
发送方的中的每个分组必须拥有自己的逻辑定时器,因为超时后只能发送一个分组。可以使用单个硬件定时器模拟多个逻辑定时器的操作。
接收方将确认一个正确接收的分组而不管其是否按序。失序的分组将被缓存,直到一批分组按序接收成功了则交付上层。
由于接收方接收了分组就发送ACK,移动自身的接收窗口,而发送方有可能因为ACK丢失等原因迟迟没收到确认报文以至于窗口不移动。所以, 对于SR协议而言,这就意味着发送方和接收方的窗口并不总是一致 。
对于SR协议而言, 窗口的长度必须小于或等于序号空间大小的一半 。因为序号空间是有限的,必须是循环利用的。假如序号空间总大为4,那么4之后会从0循环开始。当窗口大小大于序号空间的一半时,就有可能会出现接收方接收到一个分组到来是 不能判断得到这个分组是一个新的分组抑或是一个重传 。
可以看到当发送方发送的是重传分组时,接收方是无法判别的。
当 窗口的长度必须小于或等于序号空间大小的一半 时,前面重传的数据报到达时会被丢弃,因为它的序号不在窗口内。但这里其实有个疑问,2^32大小的序号空间加上流量窗口和拥塞窗口,像 不能判断得到这个分组是一个新的分组抑或是一个重传 这种情况发生的概率大吗?
TCP为应用程序提供来 流量控制服务 ,以消除发送方使接收方缓存溢出的可能性。因此,可以说流量控制是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读速率相匹配。
注意:流量控制服务与拥塞控制不同。它们分别是针对不同原因而采取的措施。
TCP协议报文结构中的 窗口 字段就是用于流量控制的。
首先先来定义几个变量标识:
由上面的变量可以得出以下的关系式:
所以是要将发送方的 已发送未确认 的分组控制在接收方给出的窗口大小内。考虑到当接收方返回的window值为0时,发送方就不能再进行发送,而发送方不进行发送,当接收方有缓存窗口不为零就无法得知,因为 接收方不会主动推送窗口值 。所以TCP规定,当发送方收到窗口值为0时的报文,就本地开启一个 计时器 ,时间一到就主动地去发送一个 只有一字节的探测报文段 ,这时候接收方就把新的窗口值加上去ACK报文段进行返回。
IP层不提供显式的网络拥塞反馈,所以TCP必须使用端到端的拥塞控制。
拥塞指的是当前网络的环境。当拥塞发生时,路由器上的缓存就会面临溢出的可能,这样会导致分组在网络中被丢弃,影响通信的质量。
改善网络中的拥塞情况也是起到全局性作用的。
由于是TCP自身实现的拥塞控制,所以就会产死以下的几个问题:
当网络拥塞,会对发送方导致的直接结果是: 超时重传 和 冗余的ACK 。自然可以根据这两种情况去判断网络是否有拥塞。
TCP拥塞控制机制让连接的每一端都记录一个额外的变量,即 拥塞窗口 ,表示为 CongWin(以字节为单位) 。它对一个TCP发送方能向网络中发送流量的速率进行了限制。
结合在TCP中的流量控制RcvWindow。可以得到:
LastByteSent - LastByteAcked <= min( CongWin, RcvWindow)在一个发送方中未被确认的数据量不会超过CongWin和RcvWindow中的最小值。
TCP拥塞控制算法有:
加性增的思想是,当检测到网络无拥塞(即有对未确认的数据有ACK到达时),就每收到一个确认就把Congwin增加一个MSS*(MSS/CongWin)。 (CongWin=CongWin+MSS*(MSS/CongWin)) 注:MSS意思是最大报文段长度。
乘性减的思想是,当发送丢包的事件时,CongWin的值会减半。CongWin值也许会持续地减少,但是不能降到低于1个MSS。
TCP拥塞控制协议的线性增长阶段被称为 避免拥塞 。CongWin值会重复地经历一个升降循环,即重复地线性增长又突然降至一半。所以CongWin的变化会是锯齿状的。
TCP连接开始时,CongWin的值初始为1。但发送方在初始阶段不是线性地增长的,而是以指数的形式。即每收到一个ACK,CongWin就会增加一个MSS。 (CongWin=CongWin+MSS)
当 CongWin>Threshold(阀值) 时,TCP就会由慢启动的状态转换到拥塞避免。
上面已经说道,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,然后状态变为 慢启动 |
一台主机想要向另一台主机发起TCP通信时需要 建立连接 ,当传输结束之后需要 释放连接 。
TCP建立连接是通过 三次握手 。
步骤:
一旦建立连接成功,双方就可以互相发送含有数据的报文段,并且这些报文段SYN都置为0。
TCP释放连接是通过 四次挥手 。
如图所示,客户端应用进程发出一个关闭连接命令。这会引起客户端TCP向服务端进程发送一个特殊的TCP报文段。这个特色的报文段首部中的 FIN 比特被置1。当服务接收到该报文段后,就向客户端会送一个确认报文段。这时,TCP连接处于 半关闭状态 。半关闭指的是客户端已经不能再给服务端发送数据了,但可以接收服务端发过来的数据。数据传送完毕之后,服务端发送其终止报文段,同样 FIN 被置1。最后客户端对这个服务端的终止报文段进行确认。两台主机的TCP连接成功释放。
注意,如果服务端不需要向客户端传输数据了,那么ack=u+1的这个两个报文是可以结合为一个的。
为什么客户端最后需要等待2MSL呢?
解释一下,MSL意思是报文段最大生存时间(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。因为网络层有限制生存时间字段TTL,所以MSL值是有限的,常用可能会30秒,1分钟,2分钟。
关于2MSL还有别的有趣事情,可以参考 <<计算机网络笔记2-运输层>> 。
如果因为机器故障而导致连接被中断呢?
TCP设有一个 保活计时器 。服务端每收到一次客户的数据都会重新设置该计时器。计时器到时之后,服务端会主动发送探测报文,连续几个探测报文没反应的话,服务端就会自动关闭。
文章笔记理论为多,实验部分可能会随着review会更新上去。学习协议建议更多以实践为主。
文章中使用到的图片均来源于网络