浅谈TCP协议与面试题分析

4.浅谈TCP协议

4.1 报文段结构分析

image-20210830230123219

图一:TCP报文段结构

**源端口和目的端口:**用于多路复用和多路分解时向指定的套接字分发数据。

**接收窗口字段:**用于在流量控制时通知发送方接收方此时的窗口大小,控制发送报文段数量。

**选项字段:**用于发送方和接收方协商最大报文段长度MSS时,或拥塞时作为窗口调节因子。

**标志字段:**ACK是用来指示报文段中的确认号字段是否有效; RST是在连接建立时,若客户端向服务端发送了一个不可连接的端口,那服务端会向客户端返回一个RST被置为1的报文,代表重新确认; SYN是连接建立时第一次握手时发送的报文段; FIN是在连接拆除时由客户端或服务端主动发送的FIN被置为1的连接拆除报文段; ECE和CWR是网络辅助拥塞控制中明确通告拥塞时向发送发主动发送拥塞提醒,这个时候会设置ECE,发送方改变拥塞窗口后会设置CWR;

序号:TCP把数据看成是无结构的、有序的字节流,序号是建立在字节流上的,而不是建立在报文段上的,假设主机A向主机B发送50 0000字节的数据,MSS为1000,这个时候TCP会为每个字节编号,字节流编号从0开始,那么划分报文段后第一个报文段的序号是0,第二个报文段的序号是1000,也就是首字节的编号。

确认号:确认号比较好理解,就是结合下面所说的累计确认的意思,当收到了前n个字节,就会将n+1作为一个确认号响应回去。

累计确认:累计确认是由报文段中的序号和确认号的搭配引出的,例:主机A收到了主机B发来的包含字节编号为0535的报文段和9001000的报文段,这个时候主机A会向主机B发送一个确认报文,确认号为536,因为还没收到536~899之间的数据,难么这个时候确认号为536,就代表在536之前的数据已经收到了,这就叫做累计确认。

MSS最大报文段长度:根据最初确定的由本地发送主机发送的最大链路层帧长度即(最大传输单元MTU),MTU一般为1500字节,MSS的典型值为1460,是指不包括首部的应用层数据的最大长度。

4.2 可靠传输分析

**超时间隔加倍:**是指在每次定时器过期时都会把下一次的超时间隔设置为当前值的两倍,因为有可能定时器的过期是因为网络拥塞所引起的,分组可能正在排队,以避免即将被确认的报文段再次发生超时,从而重传已有的分组,导致拥塞更加严重;但是当应用层有新的数据到来或者是有新的ACK报文段的时候又会重新计算超时间隔。

**快速重传:**当超时机制的时间较长时,这个时候有另外一种反映丢包事件的,就是接收方当发现报文段中缺少某个片段的报文段时,会重新确认先前已经到达的报文段,而发送方经常是一个接一个的发送报文,所以每收到一个缺失报文后的报文,接收方就会重新发送那个确认的ACK报文,所以当发送方连续收到三个冗余的ACK报文时就会重新发送那个缺失的报文,然后更新计时器。

4.3 流量控制分析

4.3.1 流量控制与拥塞控制的区分

​ 流量控制:是为了让发送方的发送速率与接收方的接收速率相匹配的控制;在TCP连接的两端都设置有发送缓存和接收缓存,当接收方没有对接收缓存数据进行及时的接收时,就会很容易造成缓存溢出,所以TCP提供了流量控制服务来匹配发送方和接收方的数据发送接收的同步率以消除缓存溢出的可能性。

​ 拥塞控制:是由于网络的拥塞而遏制、改变发送方的发送速率;在TCP连接的发送方会因为发送速率的不断叠加造成网络的拥塞,造成路由器的缓存溢出或是分组排队时延过高而导致不必要的重传,这个时候就需要对发送方的发送速率进行控制,称为拥塞控制。

4.3.2 如何进行流量控制

​ 流量控制的过程是围绕一个叫做rwnd接收窗口变量来进行的。

接收方:rwnd变量是用来给发送方一个指示的,代表接收方还有多大的空间接收数据,那发送方发送数据时就应该配合这个变量;rwnd变量的大小是由接收方确认的,当TCP建立连接时,会为该连接分配接收缓存RcvBuffer,在接收方还要维护两个变量LastByteRead(最后读取的字节的编码),和LastByteRcv(最后接收的字节的编码);

​ 缓存不溢出的条件是:LastByteRcv - LastByteRead <= RcvBuffer

​ rwnd接收窗口的计算:RcvBuffer - (LastByteRcv - LastByteRead)

发送方:发送方就需要根据rwnd这个变量来控制自己的发送数据大小,发送方需要维护两个变量LastByteSent(最后发送的字节的编码),和LastByteAcked(最后确认的字节的编码),那么这个时候发送方在发送数据的时候就应该满足:LastByteSent - LastByteAcked <= rwnd;

​ 在连接刚建立的时候接收方需要给发送方在报文段中传递一个rwnd = RcvBuffer;而当rwnd为0时,发送方不能继续向接收方发送数据,这个时候接收方会慢慢消化缓存中的数据;但是,只有当接收端有数据要发送给TCP连接的发送端或者是有确认报文要回应的时候才会向发送方发送数据并且告诉接收方rwnd为0;所以就造成了发送方不知道接收方什么时候rwnd为0而阻塞住;在TCP规范中是这样解决的,即当发送方收到一个rwnd为0的报文时,需要继续发送一个只有1个字节数据的报文段,这个报文段将会被接收方确认,缓存开始清空后,响应给发送方的确认报文段中将包含一个rwnd不为0。

4.4 连接管理分析

4.4.1 三次握手

​ 第一步:首先客户端会向服务器端发送一个SYN报文;这个报文段的首部的SYN标志位会被设置为1,然后客户端会随机生成一个序号作为报文段的首部的序号字段的值,这个报文段不包含应用层数据。代表客户端向服务端请求建立一条TCP连接。(这一步的动作代表客户端有发送报文的能力。)

​ 第二步:服务端收到SYN报文段后会回应一个SYNACK报文;这个报文段里SYN标志位会被设置为1,服务端会将SYN报文段里的序号字段的值加1,然后作为SYNACK报文段的首部的确认号字段的值,同样的,服务端也会生成一个自己的序号字段的值;同时,服务端会为这条TCP连接分配缓存和变量。这个报文段不包含应用层数据。(这一步的动作代表服务端有接收报文和发送报文的能力。)

​ 第三步:客户端收到SYNACK后会回应一个确认报文;现在这个报文段里的SYN标志位不为1了,而是0,同时会将SYNACK报文段的序号字段的值确认加1后放到确认字段,然后发送回去。同时客户端会为这条TCP连接分配缓存和变量。并且,这个时候的报文段是可以携带数据的。(这一步代表客户端有接收报文的能力。)

​ 之后,客户端和服务端的发送接收能力被证明,连接建立,SYN不再为1,之后的报文可携带数据发送。

Q:随机生成序号的原因是什么?安全性的考虑是怎样的?
  1. 防止接受网络上粘滞的TCP包,如果都从0开始的话,极其容易接受之前断开连接发送的粘滞包。虽然可以采用每次TCP会话都使用一个UUID作为标记,但是考虑到每次都要携带UUID,比较浪费流量,所以就采用随机序列号的方法。
  2. 防止Hack猜测序列号,然后伪装TCP报文,当然这种防御其实很弱。
Q:三次握手引发的SYN洪泛攻击?

​ 在握手的过程中,服务器会给每一个连接分配资源和变量,这个时候如果攻击者利用这一性质,不断的给服务器发送SYN报文,而不完成三次握手,那么服务器就会不断的给连接分配资源,造成服务器资源的殆尽。

​ 解决方法:利用SYN cookie,当收到一个SYN报文段时,服务器不马上为其分配资源,这个时候会根据SYN报文段的源和目的IP地址和端口号以及仅有服务器知道的一个密码数的复杂函数生成的一个序列号作为一个TCP初始序列号,然后将该cookie发送过去;这个时候服务器不保留cookie和关于SYN报文段的任何信息;当一个合法的用户返回一个ACK确认报文时,服务器再根据这个确认报文的源和目的IP地址以及端口号计算出一个cookie然后加一,比对ACK确认报文段的确认序号,如果比对成功,则证明改用户合法,这个时候分配资源。

Q:分配缓存和变量的这个动作具体是怎样执行的呢?

第八章,SSL安全性考虑等,待补充…

Q:为什么是三次握手而不是四次、两次呢?

​ 为什么不是两次?我觉得可以从两个方面来解释,第一个方面:是包滞留导致的重复连接问题,如果当客户端发送一个SYN请求连接报文滞留在了网络中,此时并没有丢失,这个时候客户端会再次发送SYN报文建立连接,假设等到这条连接用完释放的时候,这个滞留的SYN报文到达了服务端,那么依据两次握手,这个时候只要服务端发送一个SYNACK报文连接就建立了,那无疑是在浪费资源。 第二个方面:是防止SYN洪泛措施问题,根据SYN洪泛的解决方法,两次握手是不足以分配资源,建立连接的,此时必然需要客户端通过第三次ACK报文携带SYN cookie来验证客户端的合法性再建立连接。

​ 为什么不是四次?可以是四次,但是经过以上两次握手,三次握手的讨论,我们已经能建立连接分配资源,并且验证合法性,那么第四次的握手就会显得有点多余。

Q:连接建立时TCP端口不匹配和UDP套接字不匹配的处理方式?

TCP:当服务器收到一个SYN报文段的时候,如果该报文段上的端口不允许连接,那这个时候服务器会返回一个重置报文,也就是RST标志位被置为1的报文。

UDP:待补充,第四章中…

image-20210830225915050

图一:TCP三次握手:报文段交换

4.4.2 四次挥手

​ 第一步:客户端(服务端)向服务端(客户端)发送一个标志位FIN被置为1的报文段;

​ 第二步:服务端接收该FIN报文段,发送一个ACK确认报文段;

​ 第三步:服务端发送自己的FIN报文段;

​ 第四步:客户端向服务端发送一个ACK确认报文段,连接释放;然后客户端此时会进入TimeWait状态,这个等待状态一般的时间间隔设置为2个MSL(报文的最大存活时间);

​ 这个TimeWait状态有两个方面,一方面:若处于time_wait的客户端发送给服务器确认报文段丢失的话,服务器将在此重新发送FIN报文段,那么客户端必须处于一个可接收的状态就是time_wait状态而不是close状态;另一方面:保证让迟来的TCP报文段有足够的时间识别并丢弃linux中一个TCP端口不能被打开两次或两次以上,当客户端处于time_wait状态时我们将无法使用此端口建立新连接,如果不存在time_wait状态,新连接可能会收到就连接的数据。
​ 为什么在time_wait中就可以保证旧数据完全被销毁?
因为网络中数据存在时间最大为MSL(maxinum segment lifetime),而time_wait 持续时间为2MSL所以保证网络中的数据可以丢弃。

Q:在发送FIN报文的时候如果有新的分组到来要怎么处理?

​ 发送FIN报文仅表示发送方已经没有数据报文要发送,此时的连接还未完全释放,这个时候有新的数据报过来,还是可以正常接收,只有当收到对方的FIN报文时才表明两边都没有新的报文段要接收,最后等待TIMEWait后才真正的释放连接。

Q:为什么不是三次挥手?

​ 首先服务端接收到一个FIN报文的时候,这个时候首先发送一个ACK报文表示收到了FIN报文,然后等到服务端的报文都发送完毕之后,服务端再发送一个FIN报文,如果这个时候把ACK报文和FIN报文合并为一次挥手,很可能会造成过大的时间间隔,导致客户端收不到FIN确认报文,而重新发送FIN报文

Q:当同时发起释放时的情况是怎么样的?
image-20210830235149637 image-20210830235417428

图二:关闭一条TCP连接

4.4.3 状态转换

image-20210830113259664

图三:客户TCP经历的典型的TCP状态序列

image-20210830113323111

图四:服务TCP经历的典型的TCP状态序列

4.5 拥塞控制分析

4.5.1 为什么会发生拥塞呢?

​ 当有多台主机同时向网络中建立连接并且不断的增加发送速率向网络中发送数据分组的时候,当发送速率超过路由器的链路容量时,会将分组存放在路由器的缓存中;这个时候分组需要经历巨大的排队时延,当缓存不够用时,会发生分组的丢失,叫做缓存溢出,那么这时发送方需要重新发送分组,当分组经历巨大的时延后,到达了接收方,但是由于TCP的分组超时控制,发送方会再次向接收方发送一遍不必要的分组。

Q:什么叫链路容量?

​ 链路容量,即链路带宽,是指某通道传送数据的能力,可以表示在单位时间内,输出链路所能通过的“最高数据率”,单位:bit/s。

Q:分组的超时是怎么设计的?

1.需要先明确这个超时间隔的由来:

需要涉及到三个RTT变量:SampleRTT(样本RTT)、EstimatedRTT(样本RTT均值)、DevRTT(RTT偏差);SampleRTT:在TCP的设计中,在任意时刻仅为一个已发送但未被确认的报文估算一个SampleRTT,即报文的往返时间,然后通过一下公式指数加权移动平均,去估算出一个RTT均值EstimatedRTT;

EstimatedRTT = (1 - a) EstimatedRTT+ a * SampleRTT

注:RTT均值计算公式(a的推荐值为0.125)

除了计算出一个RTT均值,还要测量RTT的变化,即DevRTT,即SampleRTT相对EstimatedRTT的偏离程度,是通过以下公式计算出来的;

DevRTT = (1 - B) * DevRTT + B * |SampleRTT - EstimatedRTT|

注:RTT偏差计算公式(B的推荐值Wie0.25)

最后,超时间隔很显然需要大于RTT均值,那需要大多少呢,就需要有RTT偏差一起来决定;超时间隔不能比RTT均值偏差太大,大太多,会造成当报文段丢失时不能很快重传,导致数据传输时延过大;如果比均值RTT小的话则会造成不必要的重传;也就是当SampletRTT波动小时这个余量要小,当SampleRTT波动大时余量要大,那就要由RTT偏差来决定DevRTT,超时间隔计算公式如下;

TimeOutInterval = EstimatedRTT + 4 * DevRTT

注:超时间隔计算公式

2.超时机制的设计:

​ 首先明确的是,TCP不是为每一个报文段都开启一个定时器,每一条TCP连接会使用一个重传定时器,当该定时器被占用时,接下来的报文段将不能被计时(定时器的作用其实就是还有计时的作用);如果超时事件发生,TCP会将下一次的超时间隔设置为这一次的两倍,而不是用EstimatedRTT和DevRTT计算出来的值,以免即将被确认的后继报文段过早的出现超时;然后当定时器在另两个事件启动的时候,即收到上层应用层的数据或收到ACK的时候,这个时候超时间隔会重新由最新的EstimatedRTT和DevRTT计算得出;

image-20210830103325460

图:一个定时器

Q:为什么发送速率要不断提升?

​ 因为当分组被确认过之后,就代表接收方有能力及能够拿出与发送方相匹配的处理速率来处理分组,那 么这个时候就可以增加分组的发送速率来提高吞吐量。

4.5.2 出现拥塞时的标志和处理方式?

​ 标志:根据TCP的可靠传输机制,当连接中出现分组的丢失,也就是接收方没有按照分组序号收到对应分组的时候,这个时候会有两种反馈事件:一种是发送方会收到相同的三个冗余ACK确认报文,代表在这个ACK之后的那个分组已经在网络中丢失;另一种是发送方的发送报文定时器出现了超时的情况,那这个时候就代表着网络中出现了拥塞。

​ 处理方式:发送方通过跟踪一个叫做拥塞窗口的变量cwnd,这个变量代表着发送方每一个RTT内发送分组的大小。当出现拥塞时就会通过调节这个cwnd变量的大小来控制发送速率。

4.5.3 cwnd拥塞窗口变量控制流程?

第一阶段的慢启动:

​ 当一条TCP连接建立的时候,cwnd窗口会被设置为一个MSS大小的值,那这个时候发送方的发送速率就是MSS/RTT,然后收到一个确认报文的时候cwnd就会加一;这个时候发送方的速率变成了两个报文段每RTT,再次收到两个ACK确认报文的时候,cwnd的窗口会加二,由此规律在出现拥塞之前,发送方的发送速率会以指数形式的增长,速率会非常快。

image-20210830103759696

图一:TCP慢启动

第二阶段的慢启动:

​ 1,当出现超时指示引起的丢包事件时,这个时候需要将cwnd的值设置为1,然后引入一个变量ssthresh(慢启动阙值),将这个值设置为出现拥塞时cwnd的值的一半,然后重新开始第二阶段的慢启动过程;

​ 2,当出现收到三个冗余的ACK时引起的丢包事件,cwnd的值会设置成出现拥塞时cwnd的值的一半加三,(cwnd会回退到一半然后有多少个冗余的ACK就加多少个MSS)也就是会比ssthresh的值大;这个时候会进入快速恢复阶段,而非进入 拥塞避免阶段;

拥塞避免阶段:

​ cwnd的值不再是跟慢启动的时候一样的翻一番形式的指数增长了,而是每一个RTT的时间内增加一个MSS,也就是如果现在cwnd是14600字节, MSS是1460字节,也就是cwnd是十个MSS,那一个RTT内会发送十个报文段,对于每收到一个报文段,会增加MSS*(MSS/cwnd)个字节,也就是会增加1/10MSS字节,那么这个时候可以理解成一个线性增加的模式。

快速恢复阶段:

​ 当收到冗余的ACK确认报文后,cwnd的值设置为 ssthresh + 冗余ack个数,然后重传指定的报文段;之后如果再次收到冗余的ack,则cwnd的值会逐步加一;如果收到了新的ACK确认,则快速恢复阶段结束,cwnd的值再降低到ssthresh,然后进入拥塞避免阶段。

快速恢复版本:

​ Reno、New Reno、SACK

​ New Reno和SACK详解参考博客:https://blog.csdn.net/m0_38068229/article/details/80417503

image-20210830105550295

图二:TCP窗口的演化

image-20210830105710720

图三:三个冗余ACK的拥塞控制:加性增、乘性减(锯齿形)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值