计算机网络学习笔记(三)- 传输层(下)
TCP协议
TCP概述
- 点对点
• 一个发送方,一个接收方 - 可靠的、按序的字节流
- 流水线机制
• TCP拥塞控制和流量控制机制
• 设置窗口尺寸 - 发送方/接收方缓存
- 全双工(full-duplex)
• 同一连接中能够传输双向数据流 - 面向连接
• 通信双方在发送数据之前必须建立连接。
• 连接状态只在连接的两端中维护,在沿途节点中并不维护状态。
• TCP连接包括:两台主机上的缓存、连接状态变量、socket等 - 流量控制机制
- 序列号:
• 序列号指的是segment中第一个字节的编号,而不是segment的编号(这里有点不理解,推测是由于每次发送数据在发送之前都是未知的,所以第一个字节编号也可以认为是随机的)
• 建立TCP连接时,双方随机选择序列号 - ACKs:
• 希望接收到的下一个字节的序列号
• 累计确认:该序列号之前的所有字节均已被正确接收到(接收端每次收到数据回复ACK,都回复当前接收到的最大序列号的ACK,同时如果回复的ACK序列号大于发送端Sendbase,那么Sendbase也更新为回复的ACK序列号,而不管是否收到小于当前回复ACK序列号的段的ACK,如下图1所示,可见100,120序列号的ACK都已经超时,发送端又重新发送了一次100序列号,但接收端接收到100序列号,只回复序列号120的ACK;如下图2所示,可见虽然没有收到序列号100的段的ACK,但收到了序列号120的段的ACK,Sendbase直接更新为120,即使没有收到段100的ACK)
![](https://img-blog.csdnimg.cn/20200404093108821.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdsaXpoaTM3MzM=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200404094938647.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdsaXpoaTM3MzM=,size_16,color_FFFFFF,t_70)
- 处理乱序到达的Segment
• TCP规范中没有规定,由TCP的实现者做出决策
TCP可靠数据传输
- 定时器超时时间设置
• SampleRTT变化:测量多个SampleRTT,求平均值,形成RTT的估计值
EstimatedRTT = (1- α) × EstimatedRTT + α × SampleRTT (α = 0.125)
• 测量RTT的变化值: SampleRTT与EstimatedRTT的差值
DevRTT = (1- β) × DevRTT +β × |SampleRTT-EstimatedRTT| (β = 0.25)
• 定时器超时时间的设置:
TimeoutInterval = EstimatedRTT + 4 × DevRTT - TCP发送方事件
• 从应用层收到数据
创建Segment
序列号是Segment第一个字节的编号
开启计时器
设置超时时间:TimeOutInterval
• 超时
重传引起超时的Segment
重启定时器
• 收到ACK
如果确认此前未确认的Segment,更新SendBase,如果窗口中还有未被确认的分组,重新启动定时器(每次收到ACK后,都首先看是否能更新SendBase,然后查看当前窗口中是否还有未被确认的Segment,有的话重启定时器)
• 伪代码如下
NextSeqNum = InitialSeqNum TCP发送端程序
SendBase = InitialSeqNum
loop (forever) {
switch(event)
event: data received from application above
create TCP segment with sequence number NextSeqNum
if (timer currently not running)
start timer
pass segment to IP
NextSeqNum = NextSeqNum + length(data)
event: timer timeout
retransmit not-yet-acknowledged segment with smallest sequence number
start timer
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
}
} /* end of loop forever */
- TCP接收方事件
• 有序段到达,并且序列号是接收端预期的:在这段之前所有段都已经回复ACK了,那么将等待500毫秒,等下一段到来一起回复ACK,如果到时间了没有下一段,则发送ACK
• 有序段到达,并且序列号是接收端预期的:配合上一条理解,在这段之前有一段还没有回复ACK,那么回复当前段的ACK
• 有新的段到来,检测到间隙:立即发送重复的ACK,指示发送预期的字节段(如已经收到了序列100的段了,但段120没到,段130到了,立即发送段100的ACK)
• 部分或全部填补空白的段的到达:如果段从间隙的下端开始,则立即发送ACK(这句不是很理解)
![](https://img-blog.csdnimg.cn/2020040409535077.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdsaXpoaTM3MzM=,size_16,color_FFFFFF,t_70)
- 快速重传机制
• TCP的实现中,如果发生超时,超时时间间隔将重新设置,即将超时时间间隔加倍,导致其很大,重发丢失的分组之前要等待很长时间
• 通过重复ACK检测分组丢失,如果某个分组丢失,可能会引发多个重复的ACK,如果sender收到对同一数据的3个ACK,则假定该数据之后的段已经丢失
• 快速重传:在定时器超时之前即进行重传
• 伪代码如下:
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
}
else {
increment count of dup ACKs received for y
if (count of dup ACKs received for y = 3) {
resend segment with sequence number y
}
TCP流量控制
• 接收方为TCP连接分配buffer
![](https://img-blog.csdnimg.cn/20200404105601931.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdsaXpoaTM3MzM=,size_16,color_FFFFFF,t_70)
• Receiver通过在Segment的头部字段将RcvWindow 告诉Sender
• Sender限制自己已经发送的但还未收到ACK的数据不超过接收方的空闲RcvWindow尺寸
• Receiver告知Sender此时RcvWindow=0,Sender仍然会发送数据,但会对发送数据进行限制,因为没有发送就没有返回值,也无法了解RecWindow最新情况
TCP连接管理
-
建立连接三次握手
• 第一次握手:
建立连接时,客户端发送SYN包(包含syn,seq(序列号);syn=1,seq随机假设取a)到服务器,并进入SYN_SENT状态,等待服务器确认;
• 第二次握手:
服务器收到SYN包,必须确认客户的SYN,同时向发送端发送SYN+ACK包(包含syn,seq=,ack;syn=1,seq随机假设取b,ack=a+1),此时服务器进入SYN_RECV状态;
• 第三次握手:
客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(包含syn,seq,ack;syn=0,seq=a+1,ack=b+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手 -
关闭连接四次握手
• 第一次握手:
客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号
• 第二次握手:
服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间;客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
• 第三次握手:
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
• 第四次握手:
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态;服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。 -
为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。 -
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。 -
为什么不能用两次握手进行连接?
在谢希仁版《计算机网络》中有一个例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送ack包。而此时因为client没有发起建立连接请求,所以client处于CLOSED状态,接受到任何包都会丢弃,但server却以为新的运输连接已经建立,并一直等待client发来数据。这样server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。比如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。 -
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接
拥塞控制原理
拥塞概述
- 非正式定义:太多发送主机发送了太多数据或者发送速度太快,以至于网络无法处理”
- 表现:
• 分组丢失(路由器缓存溢出)
• 分组延迟过大(在路由器缓存中排队)
拥塞成因和代价
- 场景一
- 场景二
条件同场景一,但buffersize是有限的,而不是无限 - 场景三
由图可知,当a发送数据到c时,必然经过两个路由器或者交换机,如果数据在R2处被loss了,那么R2上游的传输能力也将会被浪费掉
拥塞控制方法
- 端到端拥塞控制:
• 网络层不需要显式的提供支持
• 端系统通过观察loss,delay等网络行为判断是否发生拥塞
• TCP采取这种方法 - 网络辅助的拥塞控制:
• 路由器向发送方显式地反馈网络拥塞信息
• 简单的拥塞指示(1bit):SNA, DECbit, TCP/IP ECN, ATM)
• 指示发送方应该采取何种速率
TCP拥塞控制
基本原理
- Sender限制发送速率
• LastByteSent-LastByteAcked <= CongWin
• CongWin:动态调整以改变发送速率,反映所感知到的网络拥塞 - 感知网络拥塞
• Loss事件 = timeout或3个重复ACK
• 发生loss事件后,发送方降低速率 - 合理地调整发送速率
• 加性增—乘性减: AIMD
• 慢启动: SS - AIMD原理:逐渐增加发送速率,谨慎探测可用带宽,直到发生loss
• 加性增:每个RTT将CongWin增大一个MSS——拥塞避免
• 乘性减:发生loss后将CongWin减半 - SS原理
初始时CongWin=1,每个RTT将CongWin翻倍,当达到Threshold时,CongWin转变为线性增长,当达到loss事件时,Threshold被设为Loss事件前CongWin值的1/2,CongWin = 1;
TCP关于loss事件处理
- 3个重复ACKs:
• CongWin切到一半
• 然后线性增长 - Timeout事件:
• CongWin直接设为1个MSS,然后指数增长
• 达到threshold后, 再线性增长 - 1/2处理的原因
3个重复ACKs表示网络还能够传输一些segments,timeout事件表明拥塞更为严重
伪代码如下:
Th = ?
CongWin = 1 MSS
/* slow start or exponential increase */
While (No Packet Loss and CongWin < Th) {
send CongWin TCP segments
for each RTT increase CongWin by *= 2
}
/* congestion avoidance or linear increase */
While (No Packet Loss) {
send CongWin TCP segments
for CongWin ACKs, increase CongWin by 1
}
Th = CongWin/2
If (3 Dup ACKs) CongWin = Th;
If (timeout) CongWin=1;
TCP公平性
- 如果n个纯粹的TCP服务共享瓶颈链接,则每个最终获得C / n,这是公平的
- 如果TCP + UDP共享链路,UDP将占用更多带宽,这是不公平的!
- 如果用户使用多个并行连接(如Web浏览器),即使在纯服务中,也是不公平的!
- 例子:
速率R的链路支持9个用户,每个用户使用一个TCP连接
如果新用户使用11个连接,他将占用11/20的带宽,这对其他用户不公平