5. 重传机制
(1)重传机制
TCP实现可靠传输的方式之一,是通过序列号与确认应答。
在TCP中,当发送端的数据达到接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。
数据在传输过程中丢失了怎么办?—重传机制
常见的重传机制:
1. 超时重传 2. 快速重传 3. SACK 4. D-SACK
(2)超时重传?
超时重传:在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的ACK确认应答报文,就会重发该数据,即超时重传。 发生超时重传的两种情况: 1. 数据包丢失 2. 确认应答丢失
问:RTT和RTO? RTT:就是数据从网络一段传送到另一端所需的时间,也就是包的往返时间。 RTO:Retransmission Timeout;超时重传时间 RTO设置的不合理? 1. RTO较大:重发就慢,没有效率,性能差 2. RTO较小:会导致可能并没有丢就重发,会增加网络拥塞,导致更多的超时,更多的超时又导致更多的重发。 RTO值需略大于RTT的值 RTT是动态变化的,所以RTO也是动态的
(3)超时重传时间如何选择?
TCP采用了一种自适应算法,记录一个报文段发送的时间,以及收到相应确认的时间,这两个时间之差就是报文段的往返时间RTT。TCP保留了加权平均往返时间RTTs,按照下式进行计算:
RFC 6298推荐alpha=1/8
,且超时重传时间RTO应略大于RTTs,按照下式进行计算:
其中,RTT_D
是RTT的偏差的加权平均值,第一次测量时取为RTT样本值的一般,之后按照下式进行计算:
RFC 6298推荐beta=1/4
。
但是出现的问题:在发生重传之后,如何判定返回的确认报文段是对先发送的报文段的确认,还是对后来重传的报文段的确认?
想要解决这个问题,可以:报文段每次重传一次,就把超时重传时间RTO增大为原来的2倍,当不再发生报文段的重传时,才根据公式计算超时重传时间。
(4)快速重传
它不以时间为驱动,而是以数据驱动重传
Seq1到,ACK回2 Seq丢失 Seq3到,Ack回2;Seq3、Seq4也是如此 发送端收到了三个Ack=2的确认,知道了Seq2还没有收到,就会在定时器过期之前,重传丢失的Seq2
快速重传机制只解决了一个问题,就是超时时间的问题;
但是它依然面临着另外一个问题,就是重传的时候,是重传一个,还是重传后面的全部;于是有了SACK方法
(5)SACK方法
SACK:Selective Acknowledgment;选择性确认
在TCP头部选项
字段里面加一个SACK,它可以将缓存的地图发送给发送方,这样发送方可以知道哪些数据收到了,哪些没有收到,知道了这些信息,就可以只重传丢失的数据。
(6)Duplicate SACK
其主要使用了SACK来告诉发送方有哪些数据被重复接收了;确认号丢失,或者传输的数据网络延迟
例子:
例子:
D-SACK的几个好处:
1. 可以让「发送⽅」知道,是发出去的包丢了,还是接收⽅回应的 ACK 包丢了; 2. 可以知道是不是「发送⽅」的数据包被⽹络延迟了; 3. 可以知道⽹络中是不是把「发送⽅」的数据包给复制了;
6. 流量控制
(1)TCP为什么需要流量控制?怎么实现?
问:为什么需要? 流量控制是避免发送方的数据填满接收方的缓存。 * 双方在通信的时候,发送方与接收方的速率是不一定相等的,如果发送方的发送速率太快,会导致接收方处理不过来,这时候接收方只能把处理不过来的数据存在缓存区。 * 如果缓存区满了发送方还在不停的发送数据,接收方只能把收到的数据包丢掉,大量的丢包会造成网络资源的浪费。 * 所以TCP引入流量控制机制来控制发送方的速率。
问:怎么实现?如何控制? TCP通过滑动窗口实现流量控制,即发送方的发送窗口不能超过接收方给出的接收窗口的数值,使得发送方的发送速率不要太快,要让接收方来得及接收。
(2)窗口关闭是什么?有什么风险?
问:什么是窗口关闭? TCP通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制; 如果窗口大小为0时,就会阻止发送方给接收方传递数据,知道窗口变为非0为止,即窗口关闭。
问:窗口关闭潜在风险?--死锁 窗口关闭后,接收方的接收缓存又有了一些存储空间,于是向发送方返回非零窗口的报文段,但这个报文段在传送过程中丢失了,双方都在等待,形成死锁。 解决方法: 每个TCP连接设置一个持续计时器,发送方只要收到零窗口通知,就启动持续计时器;若持续计时器设置的时间到期,就发送一个零窗口探测(window probe)的报文段,仅携带1字节的数据,而接收方就在确认这个探测报文段时给出了现在的窗口值;如果此时窗口依然是零,那么发送方重置持续计时器。 窗口探测是次数一般为3次,每次大约30-60秒,如果3次过后接收窗口还是0,有的TCP就会发送RST报文来中断连接。
(3)糊涂窗口综合症
如果接收方太忙了,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。
到最后,如果接收⽅腾出⼏个字节并告诉发送⽅现在有⼏个字节的窗⼝,⽽发送⽅会义⽆反顾地发送这⼏个字节, 这就是糊涂窗⼝综合症。
TCP+IP头有40字节,为了传输几字节的数据,要搭上这么大的开销,不合理。
问:怎么让接受方不通告小窗口呢? 当窗口大小小于一定值时(min(MSS, 缓存空间/2)),就会向发送方通告窗口为0,阻止发送方再发送数据
问:怎么让发送方避免发送小数据? 使⽤Nagle算法,该算法的思路是延时处理,它满⾜以下两个条件中的⼀条才可以发送数据: 1. 要等到窗⼝⼤⼩ >= MSS 或是 数据⼤⼩ >= MSS 2. 收到之前发送数据的ack回包
(4)滑动窗口是什么?
引入窗口原因? TCP每发送一个数据,都要进行一次确认应答,当上一个数据包收到了应答,再发送下一个。这样效率很低。 //缺点:数据包的往返时间越长,通信的效率就越低。 于是引入窗口的概念,实现累积确认。 //窗口大小:指无需等待确认应答,而可以继续发送数据的最大值。 //窗口:实际上是操作系统开辟的一个缓存空间
问:谈谈你对滑动窗口的了解? TCP利用滑动窗口(Sliding window)实现流量控制。 早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据,由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。 TCP中采用滑动窗口进行控制传输,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过窗口的大小来确定发送多少字节的数据。 当当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据,例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。
(5)累积确认
问:为什么TCP接收方必须有累积确认的功能? 答:累计确认的功能可以减少传输开销,接收方可以在合适的时候发送确认,接收方不应过分推迟发送确认,否则会导致发送方不必要的重传,这反而浪费了网络资源; TCP标准规定,确认推迟的时间不应超过0.5秒,若收到一连串具有最大长度的报文段,则必须每隔一个报文段就发送一个确认。
(6)窗口的大小由哪一方决定?
通常窗口的大小由接收方的窗口大小来决定
TCP头部有一个Window
字段,这个字段是接收端告诉发送端自己还有多少个缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
(7)发送方的滑动窗口swnd
程序如何表示发送方的四个部分呢?
可用窗口大小 = SND.WND - (SND.NXT - SND.UNA)
(8)接收方的滑动窗口rwnd
(9)关于窗口的一些问题
问:接收方和发送方窗口是否相等呢? 答:并不完全相等,约等于 因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样接收窗口可以很快的就空缺出来。 那么新的接收窗口大小,是通过TCP报文中的Window字段来告诉发送方。 那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。
问:接收窗口固定吗? 答:不固定,根据某种算法动态调整
问:接收方窗口越大越好吗? 答:不是。 接受窗口如果太小的话,显然这是不行的,这会严重浪费链路利用率,增加丢包率。 当接收窗口达到某个值的时候,再增大的话也不怎么会减少丢包率的了,而且还会更加消耗内存。 所以接收窗口的大小必须根据网络环境以及拥塞窗口来动态调整。
7. 拥塞控制
(1)拥塞控制、流量控制的区别?
流量控制是点对点的通信量控制;而拥塞控制是全局的网络流量整体性的控制。 流量控制与接收方的缓存状态相关联;而拥塞控制与网络的拥堵情况相关联。
(2)网络什么时候出现拥塞?
发送方判断网络拥塞的依据是:出现了超时。
`只要发送方没有在规定时间内接收到ACK应答报文`,也就是发生了【超时重传】,就会认为网络出现了拥塞
(3)为什么需要拥塞控制?
答:避免发送方的数据填满整个网络。 在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时TCP就会重传数据,但是一重传就会导致网络的负担更重,导致更大的延迟以及更多的丢包,于是就有了拥塞控制。
为了在发送方调节所要发送数据的量,定义了一个叫做拥塞窗口的概念
(4)拥塞窗口cwnd
拥塞窗口cwnd是发送方维护的一个状态变量,它会根据网络的拥塞程度动态变化
swnd = min(cwnd, rwnd)
只要网络中没有出现拥塞,cwnd就会增大 一旦网络出现了拥塞,cwnd就减小
(5)谈一谈你对拥塞控制的了解?如何实现拥塞控制?
拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致于过载。 TCP进行拥塞控制的算法共有四种:`慢启动、拥塞避免、快重传、快恢复。` //发送方维持拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就可以再增大一些,以便发送更多的分组,从而提高网络利用率;但是只要网络出现拥塞或可能出现拥塞,则必须把拥塞窗口减小,以便缓解网络拥塞。
拥塞算法:
1. 慢启动 最开始发送方的拥塞窗口为1,由小到大逐渐增大发送窗口和拥塞窗口。每经过一个往返RTT,拥塞窗口cwnd加倍。当cwnd超过慢启动门限ssthresh(slow start threshold,一般为65535字节),则使用拥塞避免算法,避免cwnd增长过大。 2. 拥塞避免 每经过一个往返时间RTT,cwnd就增加1。 3. 拥塞发生 * 若重传计时器超时,则超时重传:ssthresh设为cwnd/2,cwnd重置为1,之后重新开始慢启动。 * 若当发送方连续收到三个重复ACK确认应答的时候,则快速重传:cwnd = cwnd/2,ssthresh = cwnd,进入快速恢复算法 4. 快速恢复 快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到3个重复ACK确认应答说明网络没那么糟糕,所以没有必要像RTO超时那么强烈。 快速恢复算法如下: * cwnd = ssthresh + 3 * 重传丢失的数据包 * 如果再收到重复的ACK,那么cnwd+1 * 如果收到新数据的ACK后,把cwnd设置为第⼀步中的ssthresh的值,原因是该ACK确认了新的数据,说明从duplicated ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进⼊拥塞避免状态;
8. Socket编程
(1)使用Socket进行网络编程的一般步骤有哪些?
- TCP:
- 服务端:socket -> bind -> listen -> accept -> read/write -> close
- 客户端:socket -> connect -> write/read -> close
- UDP:
- 服务端:socket -> bind -> read/write -> close
- 客户端:socket -> write/read -> close
(2)基于TCP的socket编程
1. 服务端和客户端初始化socket,得到文件描述符。 socket()函数 2. 服务端调用bind,绑定IP地址和端口。 bind()函数 3. 服务端调用listen,进行监听 listen()函数 4. 服务端调用accept,等待客户端连接 accept()函数 5. 客户端调用connect,向服务器端的地址和端口发起连接请求 6. 服务端accept返回用于传输的socket的文件描述符 7. 客户端调用write写入数据;服务端调用read读取数据 8. 客户端断开连接时,会调用close,那么服务端read读取数据的时候,就会读取到EOF,待处理完数据后,服务端调用close,表示连接关闭。
这里需要注意的是:服务端调用accept时,连接成功了会返回一个已完成连接的socket,后续用来传输数据。
(3)基于UDP的socket编程
服务器流程: 1. 建立套接字文件描述符,socket()函数 2. 服务端调用bind,绑定IP地址和端口。 3. 接收客户端的数据, recvfrom()函数 4. 向客户端发送数据,sendto()函数 5. 关闭套接字,释放资源。close()函数 客户端流程: 1. 建立套接字文件描述符。socket()函数 2. 设置服务器地址和端口,struct sockaddr 3. 向服务器发送数据 sendto() 4. 接收服务器数据 recvfrom() 5. 关闭套接字 close()
(3)accept发生在三次握手的哪一步?
客户端connect成功返回是在第二次握手 服务端accept成功返回是在第三次握手成功后
(4)客户端调用close了,连接断开的流程是什么?
1. 客户端调用close,表明客户端没有数据需要发送了,则此时会想服务器发送FIN报文,进入FIN_WAIT_1状态 2. 服务端接收到了FIN报文,TCP协议栈会为FIN包插入一个文件结束符EOF到接收缓冲区,应用程序可以通过read调用来感知这个FIN包。这个EOF会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为EOF表示在该连接上再无数据到达。此时,服务器进入CLOSE_WAIT状态。 3. 接着,当处理完数据后,自然就会读到EOF,于是也调用close关闭它的套接字,这会使得服务端会发出一个FIN包,之后处于LSAT_ACK状态。 4. 客户端接收到服务端的FIN包,并发送ACK确认包给服务端,此时客户端将进入TIME_WAIT状态; 5. 服务端接收到ACK确认包后,就进入了最后的CLOSE状态 6. 客户端经过2MSL时间之后,也进入CLOSE状态
(5)请你介绍一下UDP的connect函数
(6)请描述一下Socket编程的send()、recv()、accept()函数
send()函数用来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户端发送应答。send的作用是将要发送的数据拷贝到缓冲区,协议负责传输。 recv函数用来从TCP连接的另一端接收数据,当应用程序调用recv函数时,recv先等待send的发送缓冲中的数据被协议传送完毕,然后从缓冲区中读取接收到的内容给应用层。 accept函数用来接收一个连接,内核维护了半连接队列和一个已完成连接队列,当队列为空的时候,accept阻塞,不为空的时候accept函数从上边取下来一个已完成连接,返回一个文件描述符