传输层
网络层协议只负责将数据从一个主机传送到另一个主机,传输层则需要将数据传输到具体的进程。
传输层并不通过进程号来识别进程,而是通过端口号来识别进程。因此,两个计算机要进行通信,需要知道对方的IP地址与端口号。
UDP
特点:
-
无连接
-
尽最大努力交付
-
面向报文
面向报文的意思是对应用层传过来的报文不作任何处理只添加头部就传给网络层,既不作拆分,也不作合并,一次性交付一个完整的报文。
因此如果要使得UDP能高效工作,需要用户手动设置每次报文的长度。
-
无拥塞控制
对于实时性要求高的应用很重要
-
支持一对一、一对多、多对一、多对多的通信
-
首部只有8个字节,开销小
首部格式
8个字节,4个部分:源端口、目的端口、数据报长度、校验和,每个部分2字节。
报文提交
当报文到达传输层,UDP根据报文首部的目的端口,将报文交给对应的进程。如果进程不存在,则丢弃报文,并由ICMP返回“报文不可达”的差错报文。
伪首部
UDP在计算校验和时还要添加一个伪首部
伪首部仅仅用于计算校验和,并不向下传递。
UDP与IP计算校验和的区别
IP数据报的校验和只校验IP数据报的首部,UDP的校验和则把首部和数据部分一起校验。
计算校验和的方法
在发送方:
- 将全零放入校验和字段
- 将伪首部和UDP数据报看作许多16位的字串接起来的
- 若数据部分不是偶数个字节,则需要添加一个全零字节(但不发送)
- 按二进制反码计算出这些字的和,将和的二进制反码写入校验和字段
接收方:
将伪首部连同数据报和可能添加的零字节按二进制反码计算和,如果没有差错,结果应为全1.
TCP
特点:
- 面向连接
- 点对点
- 提供可靠支付
- 全双工通信,通信双方均设置有接收和发送缓存
- 面向字节流
TCP连接
每一条TCP连接唯一地被通信两端的两个套接字(IP地址:端口号)所确定。
知乎有一个问题是计算机只有65536个端口,所以一台计算机是不是只能最多建立65536个TCP连接。答案显然是否定的,一个热门网站的主机每秒建立的TCP连接可能都不止这个数。因为就算两个TCP连接的目的地址和端口都是同一个,只要其从不同的主机发送出来,就应该算是不同的TCP连接。
可靠传输的基本原理
停止等待协议
发送方每发送一个数据报就等待回复,接收方每接收到一个数据报就发送确认回复。
超时重传:发送方超过一定时间没有收到回复确认,则重新发送报文。这就要求:
- 发送方必须保留每次发送报文的副本,只有在收到回复确认后才能删除。
- 数据报发送顺序会被打乱,必须给数据报进行编号
- 超时时间应该比数据报的正常往返时间更长一些
确认丢失和确认迟到:有一种情况是接收方收到了报文,但是确认回复却丢失或迟到。这时接收方可能收到重传的报文,需要:
- 丢弃这组报文
- 再次确认回复
发送方也可能收到重复的确认(因为前面的确认迟到了),要做的也是将确认丢弃。
TCP报文段的首部格式
TCP最初规定的选项只有一个MSS(Maximum Segment Size),MSS应该尽可能的大,只要在网络层不需要再分片就好了。默认是536字节长。
后来又有窗口扩大选项、时间戳选项等
时间戳的两种用途:
- 用于计算往返时间RTT
- 用于区别两个序列号相同的报文
TCP可靠传输的实现
以字节为单位的滑动窗口
窗口的大小由接收方的窗口字段值和网络拥塞程度共同控制。
处于窗口内的所有序列号的报文都可以发送出去,如果收到窗口末端的一个确认,则窗口可以向前移动一个。
如果发送到接收方的数据没有按序到达,窗口不能移动,也不能发送确认,因为接收方只能对按序收到的数据中的最高序列号给出确认。当31号到达时,接收方B就可以一次性将3个报文上交,窗口一次移动3位。
发送缓存和接收缓存
发送缓存用来存放:
- 应用程序传送给TCP准备发送的数据;
- 已经发送还没有接收到确认的数据
发送应用程序最后写入发送缓存的字节减去最后被确认的字节,就是还保留在发送缓存中的被写入的字节数。
接收缓存用来存放:
- 按序到达的、但尚未被接收应用程序读取的数据
- 未按序到达的数据
需要注意三点:
- 同一时刻,A的发送窗口和B的接收窗口并不总是一样大
- 对不按序到达的数据应如何处理,TCP标准并无明确规定。通常是临时存放在接收窗口中
- TCP要求接收方必须有累计确认的功能,以减小传输开销。累计确认的推迟时间不应超过0.5秒
超时重传时间的选择
TCP采用自适应算法来适应不同速率的网络。
其记录一个报文段发出的时间,以及收到响应的确认的时间。这两个时间之差就是RTT。TCP保留了RTT的一个加权平均往返时间,计算公式:
超时重传的时间RTO(Retransmission Time-Out)应该略大于上面的时间,计算公式:
RTTD是RTT的偏差的加权平均值。
这个算法无法判断一种情况:当超时重传之后,收到确认时无法确定是第一次发送的报文的确认,还是第二次发送的报文的确认。
Karn算法:每次发生重传时,就不采用该次的时间样本。
问题:如果时延的增加并非暂时性的, 则RTO无法更新,导致每次发生重传,进而导致RTO无法更新。
修正:在Karn算法的基础上(即发生重传时不采用时间样本),每次发生重传时,就将RTO翻倍,直到不再发生重传,接着又可以运用上面的自适应算法重新计算新的RTO.
选择确认SACK
若收到的报文段无差错,只是未按序号,中间还缺少一些序号的数据,则可以通过选择确认只传送缺少的数据到接收方。
SACK需要在首部中添加SACK选项,以便报告收到的不连续的字节块的边界。
TCP流量控制
利用滑动窗口实现流量控制
接收方通过窗口大小告诉发送方自己还能接收多少数据。
死锁情况:当接收方不能再接收数据之后,将窗口大小改为0, 则发送方将停止发送数据,直到有新的非零窗口确认到达。但是如果接收方的这个确认丢失了,则发送方会继续等待,接收方则等待发送方新的数据到来,从而造成死锁。
解决办法:设置一个持续计时器,只要收到零窗口通知就开始计时,如果计时器到期,则发送一个零窗口探测报文段(仅携带1字节数据);如果有新的零窗口确认到达,则计时器置零。
TCP传输效率
Nagle算法:若发送应用进程把要发送的数据逐个字节送到TCP的发送缓存,则发送方就把第一个数据字节先发送出去,将后面到达的字节缓存起来;当第一个字节的确认到达时,将后面的字节打包成一个数据报发送出去;同样后面的字节缓存起来等待数据报的确认到达后再发送。
糊涂窗口综合征:接收方每次只读取一个字节,然后向发送方发送确认。
TCP的拥塞控制
拥塞控制与流量控制并不一样。
在实际的拥塞控制下,网络吞吐量还未达到饱和就已经有一部分的输入分组被丢弃了。
从控制理论的角度看拥塞控制
分为开环控制和闭环控制。
闭环控制需要检测网络的运行情况,指标:丢弃分组的百分数、平均队列长度、超时重传的分组数、平均分组时延等
慢开始算法
发送方维持一个拥塞窗口(Congestion Window)的状态变量,大小取决于网络的拥塞程度。发送方让自己的发送窗口等于拥塞窗口。
只要路由器出现了丢弃分组,发送方就可以猜想网络出现了拥塞。
所谓慢开始,就是逐渐增大发送窗口,避免一次性注入大量数据到网络引起拥塞。
慢开始规定,每收到一个新的报文段的确认后,可以把拥塞窗口增加最多一个SMSS(Sender Maximum Segment Size)的数值,更具体为:
N为刚收到的报文的字节数。
在TCP的实际运行中,发送方只要收到一个对新报文段的确认,其拥塞窗口cwnd就立即加1, 并可以立即发送新的报文段,而不用等到这一轮所有的报文段到达。
慢开始算法到拥塞避免算法的转变
设置一个慢开始门限,只要当拥塞窗口的大小小于门限时才使用慢开始算法。
拥塞避免算法
让拥塞窗口缓慢地增大,即每经过一个往返时间RTT就把发送方的cwnd加1, 而不是像慢开始阶段那样成倍地增长。
慢开始与拥塞避免的综合
在图中2处,使用拥塞避免依然超时时,判断出现了网络拥塞,此时调整门限ssthresh = cwnd/2, 并设置cwnd = 1,进入慢开始阶段。
问题
当网络并未出现拥塞,但是出现个别报文的丢失时,也会导致拥塞窗口跌落,降低了TCP效率。
快重传算法
要求接收方不要等待自己发送数据时才捎带确认,而是立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。
快重传算法规定:发送方只要一连收到3个重复确认,就表示接收方没有收到报文,就会立即进行重传,避免超时。
快恢复算法
由快重传算法,发送方也可以知道并没有发送拥塞,因此不启动慢开始,而是执行快恢复算法。
发送方调整门限 ssthresh = cwnd/2,并设置cwnd = ssthresh, 并开始执行拥塞避免算法。
正如前面提到的,发送窗口的大小由接收窗口的大小和网络拥塞程度共同决定:
主动队列管理AQM
网络层策略对TCP拥塞控制的影响
假定一个路由器对某些分组的处理时间特别长,导致发送方对这些报文段的重传,并引起了拥塞控制措施,但实际上网络并没有发生拥塞。
网络层的策略对TCP拥塞控制影响最大的就是路由器的分组丢弃策略,由于队列已满时,后到达的所有分组都会被丢弃。
当因为网络出现拥塞而导致分组丢失时,TCP将开启拥塞控制策略,导致网络中很多TCP连接都进入慢开始状态,网络恢复正常后,通信量又突然增大很多。这种现象称为全局同步。
因此可以启用主动队列管理AQM,在网络出现一定的拥塞时,不等到队列满就丢弃一些分组,即通知一些TCP连接拥塞的到达,以提前开启拥塞控制,减小进入网络的数据量。
AQM使用RED(Random Early Detection)进行拥塞检测,需要路由器维护两个参数:队列长度最小门限和最大门限。
每当一个分组到达时,RED按照规定的算法先计算当前的平均队列长度:
- 若平均队列长度小于最小门限:分组放入队列;
- 若平均队列长度大于最大门限:丢弃分组;
- 若平均队列长度位于两者之间,按照一定的概率丢弃分组。
正是这个概率的丢弃使得只有部分的TCP连接出现拥塞控制,而避免出现全局同步现象。
TCP的运输连接管理
建立连接
A为客户端进程,主动创建连接;B为服务器进程,监听连接。
一开始,B的服务器进程就已经创建了传输控制块TCB, 负责监听客户的连接请求。
A创建连接时首先创建TCB, 向B发送请求报文段,SYN=1, 同时选择初始序列号seq = x. SYN=1的报文段不能携带数据,但会消耗掉一个序列号。A进程进入SYN-SENT状态
B收到报文段后,向A发送确认,SYN=1, ACK=1,ack=x+1,同时选择一个初始序列号seq = y,这个报文段也不能携带数据。B进程进入SYN-REVD状态。
A还要向B发送确认,ACK=1, ack = y+1, seq = x+1。如果不携带数据则不消耗序列号。A进入ESTABLISHED状态
B收到确认后进入ESTABLISHED状态。
为什么需要A再次确认
如果A发送的第一次请求在某个网络节点滞留比较长时间,A会以为请求丢失,会再次发送请求。在请求已经建立的情况下,第一次请求到达B, 如果没有A的第二次确认,则B会以为A发起了一个新的连接请求,但是A并不知道,因此导致B的许多资源被浪费。
连接释放
初始阶段:A与B均处于ESTABLISHED状态
A的应用进程首先向TCP发出连接释放报文段,FIN=1,seq=u, 为前面已传送过的数据的最后一个字节的序号加1,并停止发送数据。这时A进入 FIN-WAIT-1 状态
B收到连接释放报文段后发出确认,ack=u+1, seq = v, 同样等于B发送的最后一个字节加1。B进入 CLOSE-WAIT 状态。这时B到A的数据通道仍未关闭。
A收到来自B的确认后,进入 FIN-WAIT-2 状态,等待B发出的连接释放报文段。
若B已经没有数据要发送,其应用进程就通知TCP释放连接,报文FIN=1, seq = w, 重复上次的确认号 ack = u+1, B进入 LAST-ACK 状态
A收到连接释放报文段后,发出确认,ACK=1,ack = w+1, seq = u+1,进入 TIME-WAIT 状态。
这时连接还未释放,必须等待计时器经过 2MSL后A才进入 CLOSED 状态。
等待2MSL的理由
- A最后的确认报文可能丢失,B将会超时重传,A再次收到连接释放报文后会重传,计时器将重置。
- 保证本次连接的所有报文都消失在网络中,不会出现已经失效的报文出现在下一次连接中。