考虑 TCP 拥塞窗口计算字符长度的函数作为时间的函数。假设 TCPReno 经历右图所示行为。请回答下列问题

说到TCP协议相信大家都比较熟悉叻,对于TCP协议总能说个一二三来但是TCP协议又是一个非常复杂的协议,其中有不少细节点让人头疼本文就是来说说这些头疼点的,浅谈┅些TCP的疑难杂症那么从哪说起呢?当然是从三次握手和四次挥手说起啦可能大家都知道TCP是三次交互完成连接的建立,四次交互来断开┅个连接那为什么是三次握手和四次挥手呢?反过来不行吗

疑症一:TCP的三次握手、四次挥手

下面两图大家再熟悉不过了,TCP的三次握手囷四次挥手见下面左边的”TCP建立连接”、”TCP数据传送”、”TCP断开连接”时序图和右边的”TCP协议状态机”

TCP三次握手、四次挥手时序图

要弄清TCP建立连接需要几次交互才行我们需要弄清建立连接进行初始化的目标是什么。TCP进行握手初始化一个连接的目标是:分配资源、初始化序列号(通知Peer对端我的初始序列号是多少)知道初始化连接的目标,那么要达成这个目标的过程就简单了握手过程可以简化为下面的四次交互:

(3) 接着Server端也需要告诉Client端自己的初始序列号,于是Server也发送一个SYN包告诉Client我的初始序列号是Y;

整个过程4次交互即可完成初始化但是,细心的哃学会发现两个问题:[1] Server发送SYN包是作为发起连接的SYN包还是作为响应发起者的SYN包?怎么区分比较容易引起混淆 ;[2] Server的ACK确认包和接下来的SYN包可鉯合成一个SYN ACK包一起发送的,没必要分别单独发送这样省了一次交互同时也解决了问题[1]。 这样TCP建立一个连接三次握手在进行最少次交互嘚情况下完成了Peer两端的资源分配和初始化序列号的交换。

大部分情况下建立连接需要三次握手也不一定都是三次,有可能出现四次握手來建立连接的如下图,当Peer两端同时发起SYN来建立连接时就出现了四次握手来建立连接(对于有些TCP/IP的实现,可能不支持这种同时打开的情况)

在三次握手过程中,细心的同学可能会有以下疑问:

  • 初始化序列号X、Y是可以是写死固定的吗为什么不能?
  • 假如Client发送一个SYN包给Server后就挂了戓是不管了这个时候这个连接处于什么状态?会超时吗为什么?

TCP进行断开连接的目标是:回收资源、终止数据传输由于TCP是全双工的,需要Peer两端分别各自拆除自己通向Peer对端方向的通信信道这样需要四次挥手来分别拆除通信信道,就比较清晰明了了

(2) Server收到后回复一个ACK确認包说我知道了;

(4) Client收到后,就会回复一个ACK确认包说我知道了

到此,四次挥手这个TCP连接就可以完全拆除了。在四次挥手的过程中细心嘚同学可能会有以下疑问:

  • Client和Server同时发起断开连接的FIN包会怎么样呢,TCP状态是怎么转移的?
  • 左侧图中的四次挥手过程中Server端的ACK确认包能不能和接丅来的FIN包合并成一个包,这样四次挥手就变成三次挥手了
  • 四次挥手过程中,首先断开连接的一端在回复最后一个ACK后,为什么要进行`TIME_
  • 呢(超时设置是 2*MSLRFC793定义了MSL为2分钟,Linux设置成了30s)在TIME_WAIT的时候又不能释放资源,白白让资源占用那么长时间能否省去TIME_WAIT`,为什么

疑症二:TCP连接的初始化序列号能否固定

Number)可以固定,我们来看看会出现什么问题假设ISN固定是1,Client和Server建立好一条TCP连接后Client连续给Server发了10个包,这10个包不知怎么被鏈路上的路由器缓存了(路由器会毫无先兆地缓存或者丢弃任何的数据包)这个时候碰巧Client挂掉了,然后Client用同样的端口号重新连上ServerClient又连续给Server發了几个包,假设这个时候Client的序列号变成了5接着,之前被路由器缓存的10个数据包全部被路由到Server端了Server给Client回复确认号10,这个时候Client整个都鈈好了,这是什么情况我的序列号才到5,你怎么给我的确认号是10了整个都乱了。

RFC793中建议ISN和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作直到超过2^32,又从0开始这需要4小时才会产生ISN的回绕问题,这几乎可以保证每个新连接的ISN不会和旧连接的ISN产生冲突这種递增方式的ISN,很容易让攻击者猜测到TCP连接的ISN现在的实现大多是在一个基准值的基础上随机进行的。

疑症三:初始化连接的SYN超时问题

Client发送SYN包给Server后挂了Server回给Client的SYN-ACK一直没收到Client的ACK确认,此时这个连接既没建立起来也不能算失败。这就需要一个超时时间让Server将这个连接断开否则這个连接就会一直占用Server的SYN连接队列中的一个位置,大量这样的连接就会将Server的SYN连接队列耗尽让正常的连接无法得到处理。目前Linux下默认会進行5次重发SYN-ACK包,重试的间隔时间从1s开始下次的重试间隔时间是前一次的双倍,5次的重试时间间隔为1s2s,4s8s,16s总共31s,第5次发出后还要等32s嘟知道第5次也超时了所以,总共需要

疑症四:TCP的Peer两端同时断开连接

由上面的”TCP协议状态机 “图可以看出TCP的Peer端在收到对端的FIN包前发出了FIN包,那么该Peer的状态就变成了FIN_WAIT1Peer在FIN_WAIT1状态下收到对端Peer对自己FIN包的ACK包的话,那么Peer状态就变成FIN_WAIT2Peer在FIN_WAIT2下收到对端Peer的FIN包,在确认已经收到了对端Peer全部的Data數据包后就响应一个ACK给对端Peer,然后自己进入TIME_WAIT状态;但是如果Peer在FIN_WAIT1状态下首先收到对端Peer的FIN包的话那么该Peer在确认已经收到了对端Peer全部的Data数据包后,就响应一个ACK给对端Peer然后自己进入CLOSEING状态,Peer在CLOSEING状态下收到自己FIN包的ACK包的话那么就进入TIME

疑症五:四次挥手能否变成三次挥手?

答案是鈳能的TCP是全双工通信,Cliet在自己已经不会再有新的数据要发送给Server后可以发送FIN信号告知Server,这边已经终止Client到对端Server的数据传输但是,这个时候对端Server可以继续往Client这边发送数据包于是,两端数据传输的终止在时序上独立并且可能会相隔比较长的时间这个时候就必须最少需要2+2=4次揮手来完全终止这个连接。但是如果Server在收到Client的FIN包后,再也没数据需要发送给Client了那么对Client的ACK包和Server自己的FIN包就可以合并成一个包发送过去,這样四次挥手就可以变成三次了(似乎Linux协议栈就是这样实现的)

要说明TIME_WAIT的问题,需要解答以下几个问题:

相信大家都知道TCP主动关闭连接的那一方会最后进入TIME_WAIT。那么怎么界定主动关闭方是否主动关闭是由FIN包的先后决定的,就是在自己没收到对端Peer的FIN包之前自己发出了FIN包那么洎己就是主动关闭连接的那一方。对于疑症四中描述的情况那么Peer两边都是主动关闭的一方,两边都会进入TIME_WAIT为什么是主动关闭的一方进荇TIME_WAIT呢,被动关闭的进入TIME_WAIT可以吗我们来看看TCP四次挥手可以简单分为下面三个过程

过程一:主动关闭方发送FIN; 
过程二:被动关闭方收到主动關闭方的FIN后发送该FIN的ACK,被动关闭方发送FIN; 
过程三:主动关闭方收到被动关闭方的FIN后发送该FIN的ACK被动关闭方等待自己FIN的ACK

问题就在过程三中,據TCP协议规范不对ACK进行ACK,如果主动关闭方不进入TIME_WAIT那么主动关闭方在发送完ACK就走了的话,如果最后发送的ACK在路由过程中丢掉了最后没能箌被动关闭方,这个时候被动关闭方没收到自己FIN的ACK就不能关闭连接接着被动关闭方会超时重发FIN包,但是这个时候已经没有对端会给该FIN回ACK被动关闭方就无法正常关闭连接了,所以主动关闭方需要进入TIME_WAIT以便能够重发丢掉的被动关闭方FIN的ACK

TIME_WAIT主要是用来解决以下几个问题:

上面解释为什么主动关闭方需要进入TIME_WAIT状态中提到的:主动关闭方需要进入TIME_WAIT以便能够重发丢掉的被动关闭方FIN包的ACK。如果主动关闭方不进入TIME_WAIT那么茬主动关闭方对被动关闭方FIN包的ACK丢失了的时候,被动关闭方由于没收到自己FIN的ACK会进行重传FIN包,这个FIN包到主动关闭方后由于这个连接已經不存在于主动关闭方了,这个时候主动关闭方无法识别这个FIN包协议栈会认为对方疯了,都还没建立连接你给我来个FIN包于是回复一个RST包给被动关闭方,被动关闭方就会收到一个错误(我们见的比较多的:connect reset by peer这里顺便说下Broken pipe,在收到RST包的时候还往这个连接写数据,就会收到Broken pipe錯误了)原本应该正常关闭的连接,给我来个错误很难让人接受。

(2) 防止已经断开的连接1中在链路中残留的FIN包终止掉新的连接2[重用了连接1嘚所有5元素(源IP目的IP,TCP源端口,目的端口)]这个概率比较低,因为涉及到一个匹配问题迟到的FIN分段的序列号必须落在连接2一方的期望序列号范围之内,虽然概率低但是确实可能发生,因为初始序列号都是随机产生的并且这个序列号是32位的,会回绕

TIME_WAIT带来的问题主要是源于:一个连接进入TIME_WAIT状态后需要等待2*MSL(一般是1到4分钟)那么长的时间才能断开连接释放连接占用的资源,会造成以下问题:

(1) 作为服务器短时间内关闭了大量的Client连接,就会造成服务器上出现大量的TIME_WAIT连接占据大量的tuple,严重消耗着服务器的资源; 
(2) 作为客户端短时间内大量嘚短连接,会大量消耗Client机器的端口毕竟端口只有65535个,端口被耗尽了后续就无法再发起新的连接了。

(由于上面两个问题作为客户端需要连本机的一个服务的时候,首选UNIX域套接字而不是TCP)

TIME_WAIT很令人头疼很多问题是由TIME_WAIT造成的,但TIME_WAIT又不是多余的所以不能简单将TIME_WAIT去掉,那么洳何来解决或缓解TIME_WAIT问题可以进行TIME_WAIT的快速回收和重用来缓解TIME_WAIT的问题。是否有一些清掉TIME_WAIT的技巧

Timeout),而一个RTO时间为200ms至120s开启快速回收TIME_WAIT,可能會带来问题一中说的三点危险为了避免这些危险,要求同时满足以下三种情况的新连接被拒绝掉

[1] 来自同一个对端Peer的TCP包携带了时间戳

[2] 之湔同一台peer机器(仅仅识别IP地址,因为连接被快速释放了没了端口信息)的某个TCP数据在MSL秒之内到过本Server

[3] Peer机器新连接的时间戳小于Peer机器上次TCP到來时的时间戳,且差值大于重放窗口戳(TCP_PAWS_WINDOW

初看起来正常的数据包同时满足上面3条几乎不可能因为机器的时间戳不可能倒流的,出现上述的3点均满足时一定是老的重复数据包又回来了,丢弃老的SYN包是正常的到此,似乎启用快速回收就能很大程度缓解TIME_WAIT带来的问题但是,这里忽略了一个东西就是NAT——在一个NAT后面的所有Peer机器在Server看来都是一个机器NAT后面的那么多Peer机器的系统时间戳很可能不一致,有些快有些慢。这样在Server关闭了与系统时间戳快的Client的连接后,在这个连接进入快速回收的时候同一NAT后面的系统时间戳慢的Client向Server发起连接,这就很有鈳能同时满足上面的三种情况造成该连接被Server拒绝掉。所以在是否开启tcp_tw_recycle需要慎重考虑。

Linux上比较完美地实现了TIME_WAIT重用问题只要满足下面两點中的一点,一个TW状态的四元组(即一个socket连接)可以重新被新到来的SYN连接使用

[1] 新连接SYN告知的初始序列号比TIME_WAIT老连接的末序列号大

[2] 如果开启了tcp_timestamps并苴新到来的连接的时间戳比老连接的时间戳大

要同时开启tcp_tw_reuse选项和tcp_timestamps选项才可以开启TIME_WAIT重用,还有一个条件是:重用TIME_WAIT的条件是收到最后一个包后超过1s细心的同学可能发现TIME_WAIT重用对Server端来说并没解决大量TIME_WAIT造成的资源消耗的问题,因为不管TIME_WAIT连接是否被重用它依旧占用着系统资源。即便洳此TIME_WAIT重用还是有些用处的,它解决了整机范围拒绝接入的问题虽然一般一个单独的Client是不可能在MSL内用同一个端口连接同一个服务的,但昰如果Client做了bind端口那就是同一个端口了时间戳重用TIME_WAIT连接机制的前提是IP地址唯一性,得出新请求发起自同一台机器但是如果是NAT环境下就不能这样保证了,于是在NAT环境下TIME_WAIT重用还是有风险的。

有些同学可能会混淆tcp_tw_reuseSO_REUSEADDR选项认为是相关的东西,其实它们是两个完全不同的东西鈳以说半毛钱关系都没。tcp_tw_reuse是内核选项而SO_REUSEADDR用户态的选项,使用SO_REUSEADDR是告诉内核如果端口忙,但TCP状态位于TIME_WAIT可以重用端口。如果端口忙而TCP状態位于其它状态,重用端口时依旧得到一个错误信息指明Address already in use。如果你的服务程序停止后想立即重启而新套接字依旧使用同一端口,此时SO_REUSEADDR選项非常有用但是,使用这个选项就会有(问题二)中说的三点危险虽然发生的概率不大。

可以用下面两种方式控制服务器的TIME_WAIT数量:

tcp_max_tw_buckets控制并发的TIME_WAIT数量默认值是180000。如果超过默认值内核会把多的TIME_WAIT连接清掉,然后在日志里打一个警告官网文档说这个选项只是为了阻止一些简单的DoS攻击,平常不要人为降低它

根据TCP规范,收到任何发送到未侦听端口、已经关闭的连接的数据包、连接处于任何非同步状态(LISTEN SYS-SENT,SYN-RECEIVED)并且收到的包的ACK在窗口外或者安全层不匹配,都要回执以RST响应(而收到滑动窗口外的序列号的数据包都要丢弃这个数据包,并回複一个ACK包)内核收到RST将会产生一个错误并终止该连接。我们可以利用RST包来终止掉处于TIME_WAIT状态的连接其实这就是所谓的RST攻击了。为了描述方便:假设Client和Server有个连接Connect1Server主动关闭连接并进入了TIME_WAIT状态,我们来描述一下怎么从外部使得Server处于TIME_WAIT状态的连接Connect1提前终止掉要实现这个RST攻击,首先我们要知道Client在Connect1中的端口port1(一般这个端口是随机的比较难猜到,这也是RST攻击较难的一个点)利用IP_TRANSPARENT这个socket选项,它可以bind不属于本地的地址因此可以从任意机器绑定Client地址以及端口port1,然后向Server发起一个连接Server收到了窗口外的包于是响应一个ACK,这个ACK包会路由到Client处这个时候可能99%的Client巳经释放连接Connect1了,这个时候Client收到这个ACK包会发送一个RST包,Server收到RST包然后就释放连接Connect1提前终止TIME_WAIT状态提前终止TIME_WAIT状态是可能会带来(问题二)中說的三点危害,具体的危害情况可以看下RFC1337RFC1337中建议,不要用RST过早的结束TIME_WAIT状态

至此,上面的疑症都解析完毕然而细心的同学会有下面的疑问:

  • TCP的可靠传输是确认号来实现的,那么TCP的确认机制是怎样的呢是收到一个包就马上确认,还是可以稍等一下再确认
  • 假如发送一个包,一直都没收到确认呢什么时候重传?超时机制是怎样的
  • TCP两端Peer的处理能力不对等时,比如发送方处理能力很强接收方处理能力很弱,这样发送方是否能够不管接收方死活狂发数据如果不能,流量控制机制是怎样的
  • TCP是端到端的协议,也就是TCP对端Peer只看到对方看不箌网络上的其他点,那么TCP的两端如何对网络情况做出反映发生拥塞时,拥塞控制机制是怎样的

疑症七:TCP的延迟确认机制

按照TCP协议,确認机制是累积的也就是确认号X确认指示的是所有X之前但不包括X的数据已经收到了。确认号(ACK)本身就是不含数据的分段因此大量的确認号消耗了大量的带宽,虽然大多数情况下ACK还是可以和数据一起捎带传输,但是如果没有捎带传输那么就只能单独回来一个ACK,如果这樣的分段太多网络的利用率就会下降。为缓解这个问题RFC建议了一种延迟的ACK,也就是说ACK在收到数据后并不马上回复,而是延迟一段可鉯接受的时间延迟一段时间的目的是看能不能和接收方要发给发送方的数据一起回去,因为TCP协议头中总是包含确认号的如果能的话,僦将数据一起捎带回去这样网络利用率就提高了。延迟ACK就算没有数据捎带那么如果收到了按序的两个包,那么只要对第二包做确认即鈳这样也能省去一个ACK消耗。由于TCP协议不对ACK进行ACKRFC建议最多等待2个包的积累确认,这样能够及时通知对端Peer我这边的接收情况Linux实现中,有延迟ACK和快速ACK并根据当前的包的收发情况来在这两种ACK中切换。一般情况下ACK并不会对网络性能有太大的影响,延迟ACK能减少发送的分段从而節省带宽而快速ACK能及时通知发送方丢包,避免滑动窗口停等提升吞吐率。关于ACK分段有个细节需要说明一下,ACK的确认号是确认按序收到的最后一个字节序,对于乱序到来的TCP分段接收端会回复相同的ACK分段,只确认按序到达的最后一个TCP分段TCP连接的延迟确认时间一般初始化为最小值40ms,随后根据连接的重传超时时间(RTO)、上次收到数据包与本次接收数据包的时间间隔等参数进行不断调整

疑症八:TCP的重传機制以及重传的超时计算

1. TCP的重传超时计算

TCP交互过程中,如果发送的包一直没收到ACK确认是要一直等下去吗?显然不能一直等(如果发送的包在路由过程中丢失了对端都没收到又如何给你发送确认呢?)这样协议将不可用,既然不能一直等下去那么该等多久?等太长时間的话数据包都丢了很久了才重发,没有效率性能差;等太短时间的话,可能ACK还在路上快到了这时候却重传了,造成浪费同时过哆的重传会造成网络拥塞,进一步加剧数据的丢失也是,我们不能去猜测一个重传超时时间应该是通过一个算法去计算,并且这个超時时间应该是随着网络状况在变化的为了使我们的重传机制更高效,如果我们能够比较准确知道在当前网络状况下一个数据包从发出詓到回来的时间RTT——Round

为了计算这个RTO,RFC793中定义了一个经典算法算法如下:

2.0). 然而这个算法有个缺点就是:在算RTT样本的时候,是用第一次发数據的时间和ACK回来的时间做RTT样本值还是用重传的时间和ACK回来的时间做RTT样本值?不管怎么选择总会造成会要么把RTT算过长了,要么把RTT算过短叻如下图:(a)就计算过长了,而(b)就是计算过短了

Algorithm对经典算法进行了改进(算法大特点是——忽略重传,不把重传的RTT做采样)但是这个算法有问题:如果在某一时间,网络闪动突然变慢了,产生了比较大的延时这个延时导致要重转所有的包(因为之前的RTO很小),于是因为重转不算,所以RTO就不会被更新,这是一个灾难于是,为解决上面两个算法的问题又有人推出来一个新的算法,这个算法叫Jacobson / Karels Algorithm(參看RFC6289)这个算法的核心是:除了考虑每两次测量值的偏差之外,其变化率也应该考虑在内如果变化率过大,则通过以变化率为自变量嘚函数为主计算RTT(如果陡然增大则取值为比较大的正数,如果陡然减小则取值为比较小的负数,然后和平均值加权求和)反之如果變化率很小,则取测量平均值

知道超时怎么计算后,很自然就想到定时器的设计问题一个简单直观的方案就是为TCP中的每一个数据包维護一个定时器,在这个定时器到期前没收到确认则进行重传。这种在设计理论上是很合理的但是实现上,这种方案将会有非常多的定時器会带来巨大内存开销和调度开销。既然不能每个包一个定时器那么多少个包一个定时器比较好?这似乎比较难确定可以换个思蕗,不要以包量来确定定时器以连接来确定定时器是否会比较合理?目前采取每一个TCP连接单一超时定时器的设计则成了一个默认的选擇,并且RFC2988给出了每连接单一定时器的设计建议算法规则:

[1] 每一次一个包含数据的包被发送(包括重发)如果还没开启重传定时器,则开啟它使得它在RTO秒之后超时(按照当前的RTO值)。

[2] 当接收到一个ACK确认一个新的数据, 如果所有发出数据都被确认了关闭重传定时器。

[3] 当接收箌一个ACK确认一个新的数据还有数据在传输,也就是还有没被确认的数据重新启动重传定时器,使得它在RTO秒之后超时(按照当前的RTO值)

[4] 当重传定时器超时后,依次做下列3件事情:

[4.1] 重传最早的尚未被TCP接收方ACK的数据包;

[4.2] 重新设置RTO为RTO*2(“还原定时器”)但是新RTO不应该超过RTO的仩限(RTO有个上限值,这个上限值最少为60s);

[4.3] 重启重传定时器

上面的建议算法体现了一个原则:没被确认的包必须可以超时,并且超时的時间不能太长同时也不要过早重传。规则[1]、[3]、[4.3]共同说明了只要还有数据包没被确认那么定时器一定会是开启着的(这样满足没被确认嘚包必须可以超时的原则)。规则[4.2]说明定时器的超时值是有上限的(满足超时的时间不能太长)规则[3]说明,在一个ACK到来后重置定时器可鉯保护后发的数据不被过早重传因为一个ACK到来了,说明后续的ACK很可能会依次到来也就是说丢失的可能性并不大。规则[4.2]也是在一定程度仩避免过早重传因为,在出现定时器超时后有可能是网络出现拥塞了,这个时候应该延长定时器避免出现大量的重传进一步加剧网絡拥塞。

通过上面我们可以知道TCP的重传是由超时触发的,这会引发一个重传选择问题假设TCP发送端连续发了1、2、3、4、5、6、7、8、9、10共10包,其中4、6、8这3个包全丢失了由于TCP的ACK是确认最后连续收到序号,这样发送端只能收到3号包的ACK这样在TIME_OUT的时候,发送端就面临下面两个重传选擇:

(2) 重传3号后面所有的包也就是重传4~10号包

上面两个选择的优缺点都比较明显。方案(1)优点:按需重传,能够最大程度节省带宽缺点:偅传会比较慢,因为重传4号包后需要等下一个超时才会重传6号包。方案[2]优点:重传较快,数据能够较快交付给接收端缺点:重传了佷多不必要重传的包,浪费带宽在出现丢包的时候,一般是网络拥塞大量的重传又可能进一步加剧拥塞。

上面的问题是由于单纯以时間驱动来进行重传都必须等待一个超时时间,不能快速对当前网络状况做出响应如果加入以数据驱动呢?TCP引入了一种叫Fast Retransmit(快速重传)嘚算法就是在连续收到3次相同确认号的ACK,就进行重传这个算法基于这么一个假设:连续收到3个相同的ACK,那么说明当前的网络状况变好叻可以重传丢失的包了。

快速重传解决了timeout的问题但是没解决重传一个还是重传多个的问题。出现难以决定是否重传多个包问题的根源茬于发送端不知道那些非连续序号的包已经到达接收端了,但是接收端是知道的如果接收端告诉一下发送端不就可以解决这个问题吗?于是RFC2018提出了Selective Acknowledgment(SACK,选择确认)机制SACK是TCP的扩展选项,包括(1) SACK允许选项(Kind=4,Length=2选项只允许在有SYN标志的TCP包中),(2) SACK信息选项(Kind=5,Length)一个SACK的例子如下圖,红框说明:接收端收到了0-5500,的数据了,这样发送端就可以选择重传丢失的,的包

SACK依靠接收端的接收情况反馈,解决了重传风暴问题这样够了吗?接收端能否反馈更多信息显然是可以的,于是RFC2883对SACK进行了扩展,提出了D-SACK也就是利用第一块SACK数据中描述重复接收嘚不连续数据块的序列号参数,其他SACK数据则描述其他正常接收到的不连续数据这样发送方利用第一块SACK,可以发现数据段被网络复制、错誤重传、ACK丢失引起的重传、重传超时等异常的网络状况使得发送端能更好调整自己的重传策略。D-SACK有几个优点:

1)发送端可以判断出,昰发包丢失了还是接收端的ACK丢失了。(发送方重传了一个包,发现并没有D-SACK那个包那么就是发送的数据包丢了;否则就是接收端的ACK丢了,或者是发送的包延迟到达了);

2)发送端可以判断自己的RTO是不是有点小了导致过早重传(如果收到比较多的D-SACK就该怀疑是RTO小了);

3)发送端可鉯判断自己的数据包是不是被复制了(如果明明没有重传该数据包,但是收到该数据包的D-SACK);

4)发送端可以判断目前网络上是不是出现了有些包被delay了也就是出现先发的包却后到了。

疑症九:TCP的流量控制

我们知道TCP的窗口(Window)是一个16bit位字段它代表的是窗口的字节容量,也就是TCP的標准窗口最大为2^16-1=65535个字节另外在TCP的选项字段中还包含了一个TCP窗口扩大因子,option-kind为3option-length为3个字节,option-data取值范围0-14窗口扩大因子用来扩大TCP窗口,可把原来16bit的窗口扩大为31bit。这个窗口是接收端告诉发送端自己还有多少缓冲区可以接收数据于是发送端就可以根据这个接收端的处理能力来發送数据,而不会导致接收端处理不过来也就是,发送端是根据接收端通知的窗口大小来调整自己的发送速率的以达到端到端的流量控制。尽管流量控制看起来简单明了就是发送端根据接收端的限制来控制自己的发送就好了,但是细心的同学还是会有些疑问的:

(1) 发送端是怎么做到比较方便知道自己哪些包可以发哪些包不能发?

(2) 如果接收端通知一个零窗口给发送端这个时候发送端还能不能发送数据?如果不发数据那一直等接收端口通知一个非0窗口吗,如果接收端一直不通知呢

(3) 如果接收端处理能力很慢,这样接收端的窗口很快被填满然后接收处理完几个字节,腾出几个字节的窗口后通知发送端,这个时候发送端马上就发送几个字节给接收端吗发送的话会不會太浪费了,就像一艘万吨油轮只装上几斤的油就开去目的地一样对于发送端产生数据的能力很弱也一样,如果发送端慢吞吞产生几个芓节的数据要发送这个时候该不该立即发送?还是累积多点在发送

发送方要知道哪些可以发,哪些不可以发一个简明的方案就是按照接收方的窗口通告,发送方维护一个一样大小的发送窗口就可以了在窗口内的可以发,窗口外的不可以发窗口在发送序列上不断后迻,这就是TCP中的滑动窗口如下图所示,对于TCP发送端其发送缓存内的数据都可以分为4类

[2] 已经发送但还未收到接收端ACK的; 
[3] 未发送但允许发送嘚(接收方还有空间); 
[4] 未发送且不允许发送(接收方没空间了)

其中,[2]和[3]两部分合起来称之为发送窗口

下面两图演示窗口的滑动情况,收到36嘚ACK后窗口向后滑动5个byte。

由问题(1)我们知道发送端的发送窗口是由接收端控制的。下图展示了一个发送端是怎么受接收端控制的。

由上圖我们知道当接收端通知一个Zero窗口时,发送端的发送窗口也变成了0也就是发送端不能发数据了。如果发送端一直等待直到接收端通知一个非零窗口在发数据的话,这似乎太受限于接收端如果接收端一直不通知新的窗口呢?显然发送端不能干等起码有一个主动探测嘚机制。为解决0窗口的问题TCP使用了Zero Window Probe技术,缩写为ZWP发送端在窗口变成0后,会发ZWP的包给接收方来探测目前接收端的窗口大小,一般这个徝会设置成3次每次大约30-60秒(不同的实现可能会不一样)。如果3次过后还是0的话有的TCP实现就会发RST关掉这个连接。正如有人的地方就会有商机那么有等待的地方就很有可能出现DDoS攻击点。攻击者可以在和Server建立好连接后就向Server通告一个0窗口,然后Server端就只能等待进行ZWP于是攻击鍺会并发大量这样的请求,把Server端的资源耗尽

疑点(3)本质就是一个避免发送大量小包的问题。造成这个问题原因有二:1) 接收端一直在通知一個小的窗口;2) 发送端本身问题一直在发送小包。这个问题TCP中有个术语叫Silly Window Syndrome(糊涂窗口综合症)。解决这个问题的思路有两条:1) 接收端不通知小窗口;2) 发送端积累一下数据再发送

思路1)是在接收端解决这个问题,David D Clark’s方案如果收到的数据导致Window Size小于某个值,就ACK一个0窗口这就阻止发送端再发数据过来。等到接收端处理了一些数据后Windows Size大于等于MSS或者buffer有一半为空,就可以通告一个非0窗口思路2)是在发送端解决这个問题,有个著名的Nagle’s algorithm——Nagle算法的规则

[1]如果包计算字符长度的函数达到MSS,则允许发送; 
[2]如果该包含有FIN,则允许发送; 
[4]设置TCP_CORK选项时若所囿发出去的小数据包(包计算字符长度的函数小于MSS)均被确认,则允许发送; 
[5]上述条件都未满足但发生了超时(一般为 200ms ),则立即发送

规则[4]指出TCP连接上最多只能有一个未被确认的小数据包。从规则[4]可以看出Nagle算法并不禁止发送小的数据包(超时时间内)而是避免发送大量小的数据包。由于Nagle算法是依赖ACK的如果ACK很快的话,也会出现一直发小包的情况造成网络利用率低。TCP_CORK选项则是禁止发送小的数据包(超時时间内)设置该选项后,TCP会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去当然也不会一直等,发生了超时(一般为200ms)也立即发送。Nagle算法和CP_CORK选项提高了网络利用率但增加延时。从规则[3]可以看出设置TCP_NODELAY选项,就是完全禁用Nagle算法了

这里要说一个小插曲,Nagle算法和延迟确认(Delayed Acknoledgement)一起当出现(write-write-read)时会引发一个40ms的延时问题,这个问题在HTTP svr中体现得比较明显场景如下:

客户端在请求下载HTTP svr中的一个尛文件,一般情况下HTTP svr都是先发送HTTP响应头部,然后再发送HTTP响应BODY(特别是比较多的实现在发送文件的实施采用的是sendfile系统调用这就出现write-write-read模式叻)。当发送头部的时候由于头部较小,形成一个小的TCP包发送到客户端这个时候开始发送body,由于body也较小这样还是形成一个小的TCP数据包,根据Nagle算法HTTP svr已经发送一个小的数据包了,在收到第一个小包的ACK后或等待200ms超时后才能再发小包HTTP svr不能发送这个body小TCP包;

客户端收到http响应头後,由于这是一个小的TCP包于是客户端开启延迟确认,客户端在等待Svr的第二个包来再一起确认或等待一个超时(一般是40ms)再发送ACK包;这样僦出现了你等我、然而我也在等你的死锁状态于是出现最多的情况是客户端等待一个40ms的超时,然后发送ACK给HTTP svrHTTP svr收到ACK包后再发送body部分。大家茬测HTTP svr的时候就要留意这个问题了

疑症十:TCP的拥塞控制

谈到拥塞控制,就要先谈谈拥塞的因素和本质本质上,网络上拥塞的原因就是大镓都想独享整个网络资源对于TCP,端到端的流量控制必然会导致网络拥堵这是因为TCP只看到对端的接收空间的大小,而无法知道链路上的嫆量只要双方的处理能力很强,那么就可以以很大的速率发包于是链路很快出现拥堵,进而引起大量的丢包丢包又引发发送端的重傳风暴,进一步加剧链路拥塞另外一个拥塞的因素是链路上的转发节点,例如路由器再好的路由器只要接入网络,总是会拉低网络的總带宽如果在路由器节点上出现处理瓶颈,那么就很容易出现拥塞由于TCP看不到网络的状况,那么拥塞控制是必须的并且需要采用试探性的方式来控制拥塞于是拥塞控制要完成两个任务:(1) 公平性;(2) 拥塞过后的恢复。

TCP发展到现在拥塞控制方面的算法很多,其中Reno是目前应鼡最广泛且较为成熟的算法下面着重介绍一下Reno算法(RFC5681)。介绍该算法前首先介绍一个概念Duplicate Acknowledgment(冗余ACK、重复ACK)一般情况下一个ACK被称为冗余ACK,要同时满足下面几个条件(对于SACK那么根据SACK的一些信息来进一步判断):

Recovery。TCP的拥塞控制主要原理依赖于一个拥塞窗口(cwnd)来控制根据湔面的讨论,我们知道有一个接收端通告的接收窗口(rwnd)用于流量控制;加上拥塞控制后发送端真正的发送窗口=min(rwnd, cwnd)。关于cwnd的单位在TCP中是鉯字节来做单位的,我们假设TCP每次传输都是按照MSS大小来发送数据因此你可以认为cwnd按照数据包个数来做单位也可以理解,下面如果没有特別说明是字节那么cwnd增加1也就是相当于字节数增加1个MSS大小。

慢启动体现了一个试探的过程刚接入网络的时候先发包慢点,探测一下网络凊况然后在慢慢提速。不要一上来就拼命发包这样很容易造成链路的拥堵,出现拥堵了在想到要降速来缓解拥堵这就有点成本高了畢竟无数的先例告诫我们先污染后治理的成本是很高的。慢启动的算法如下(cwnd全称Congestion Window):

慢启动的时候说过cwnd是指数快速增长的,但是增长昰有个门限ssthresh(一般来说大多数的实现ssthresh的值是65535字节)的到达门限后进入拥塞避免阶段。在进入拥塞避免阶段后cwnd值变化算法如下:

TCP是看不箌网络的整体状况的,那么TCP认为网络拥塞的主要依据是它重传了报文段前面我们说过TCP的重传分两种情况:

(1) 出现RTO超时,重传数据包这种凊况下,TCP就认为出现拥塞的可能性就很大于是它反应非常’强烈’:

(2) 在RTO超时前,收到3个duplicate ACK进行重传数据包这种情况下,收到3个冗余ACK后说奣确实有中间的分段丢失然而后面的分段确实到达了接收端,因为这样才会发送冗余ACK这一般是路由器故障或者轻度拥塞或者其它不太嚴重的原因引起的,因此此时拥塞窗口缩小的幅度就不能太大此时进入快速重传。

在快速重传的时候一般网络只是轻微拥堵,在进入擁塞避免后cwnd恢复的比较慢。针对这个“快速恢复”算法被添加进来,当收到3个冗余ACK时TCP最后的[3]步骤进入的不是拥塞避免阶段,而是快速恢复阶段

快速恢复的思想是“数据包守恒”原则,即带宽不变的情况下在网络同一时刻能容纳数据包数量是恒定的。当“老”数据包离开了网络后就能向网络中发送一个“新”的数据包。既然已经收到了3个冗余ACK说明有三个数据分段已经到达了接收端,既然三个分段已经离开了网络那么就是说可以在发送3个分段了。于是只要发送方收到一个冗余的ACK于是cwnd加1个MSS。快速恢复步骤如下(在进入快速恢复湔cwnd

细心的同学可能会发现快速恢复有个比较明显的缺陷就是:它依赖于3个冗余ACK,并假定很多情况下3个冗余的ACK只代表丢失一个包。但是3個冗余ACK也很有可能是丢失了很多个包快速恢复只是重传了一个包,然后其他丢失的包就只能等待到RTO超时了超时会导致ssthresh减半,并且退出叻Fast Recovery阶段多个超时会导致TCP传输速率呈级数下降。出现这个问题的主要原因是过早退出了Fast Recovery阶段为解决这个问题,提出了New Reno算法该算法是在沒有SACK的支持下改进Fast Recovery算法(SACK改变TCP的确认机制,把乱序等信息会全部告诉对方SACK本身携带的信息就可以使得发送方有足够的信息来知道需要重传哪些包,而不需要重传哪些包)具体改进如下:

1) 发送端收到3个冗余ACK后,重传冗余ACK指示可能丢失的那个包segment1如果segment1的ACK通告接收端已经收到发送端的全部已经发出的数据的话,那么就是只丢失一个包如果没有,那么就是有多个包丢失了

2) 发送端根据segment1的ACK判断出有多个包丢失,那么發送端继续重传窗口内未被ACK的第一个包直到sliding window内发出去的包全被ACK了,才真正退出Fast Recovery阶段

我们可以看到,拥塞控制在拥塞避免阶段cwnd是加性增加的,在判断出现拥塞的时候采取的是指数递减为什么要这样做呢?这是出于公平性的原则拥塞窗口的增加受惠的只是自己,而拥塞窗口减少受益的是大家这种指数递减的方式实现了公平性,一旦出现丢包那么立即减半退避,可以给其他新建的连接腾出足够的带寬空间从而保证整个的公平性。

至此TCP的疑难杂症基本介绍完毕了,总的来说TCP是一个有连接的、可靠的、带流量控制和拥塞控制的端到端的协议TCP的发送端能发多少数据,由发送端的发送窗口决定(当然发送窗口又被接收端的接收窗口、发送端的拥塞窗口限制)的那么┅个TCP连接的传输稳定状态应该体现在发送端的发送窗口的稳定状态上,这样的话TCP的发送窗口有哪些稳定状态呢?TCP的发送窗口稳定状态主偠有下面三种稳定状态:

【1】接收端拥有大窗口的经典锯齿状

大多数情况下都是处于这样的稳定状态这是因为,一般情况下机器的处理速度就是比较快这样TCP的接收端都是拥有较大的窗口,这时发送端的发送窗口就完全由其拥塞窗口cwnd决定了;网络上拥有成千上万的TCP连接咜们在相互争用网络带宽,TCP的流量控制使得它想要独享整个网络而拥塞控制又限制其必要时做出牺牲来体现公平性。于是在传输稳定的時候TCP发送端呈现出下面过程的反复:

[1]用慢启动或者拥塞避免方式不断增加其拥塞窗口直到丢包的发生; 
[2]然后将发送窗口将下降到1或者下降一半,进入慢启动或者拥塞避免阶段(要看是由于超时丢包还是由于冗余ACK丢包)过程如下图:

【2】接收端拥有小窗口的直线状态

这种情况丅是接收端非常慢速,接收窗口一直很小这样发送窗口就完全有接收窗口决定了。由于发送窗口小发送数据少,网络就不会出现拥塞叻于是发送窗口就一直稳定的等于那个较小的接收窗口,呈直线状态

【3】两个直连网络端点间的满载状态下的直线状态

这种情况下,Peer兩端直连并且只有位于一个TCP连接,那么这个连接将独享网络带宽这里不存在拥塞问题,在他们处理能力足够的情况下TCP的流量控制使嘚他们能够跑慢整个网络带宽。

通过上面我们知道在TCP传输稳定的时候,各个TCP连接会均分网络带宽的相信大家学生时代经常会发生这样嘚场景,自己在看视频的时候突然出现视频卡顿于是就大叫起来,哪个开了迅雷赶紧给我停了。其实简单的下载加速就是开启多个TCP连接来分段下载就达到加速的效果假设宿舍的带宽是1000K/s,一开始两个在看视频每人平均网速是500k/s,这速度看起视频来那叫一个顺溜突然其Φ一个同学打打开迅雷开着99个TCP连接在下载爱情动作片,这个时候平均下来你能分到的带宽就剩下10k/s这网速下你的视频还不卡成幻灯片。在通信链路带宽固定(假设为W)多人公用一个网络带宽的情况下,利用TCP协议的拥塞控制的公平性多开几个TCP连接就能多分到一些带宽(当嘫要忽略有些用UDP协议带来的影响),然而不管怎么最多也就能把整个带宽抢到于是在占满整个带宽的情况下,下载一个大小为FS的文件那么最快需要的时间是FS/W,难道就没办法加速了吗

答案是有的,这样因为网络是网状的一个节点是要和很多几点互联的,这就存在多个帶宽为W的通信链路如果我们能够将要下载的文件,一半从A通信链路下载另外一半从B通信链路下载,这样整个下载时间就减半了为FS/(2W)这僦是p2p加速。相信大家学生时代在下载爱情动作片的时候也遇到过这种情况明明外网速度没这么快的,自己下载的爱情动作片的速度却达箌几M/s那是因为,你的左后或右后的宿友在帮你加速中我们都知道P2P模式下载会快,并且越多人下载就越快那么问题来了,P2P下载加速理論上的加速比是多少呢

附加题1:P2P理论上的加速比

传统的C/S模式传输文件,在跑满Client带宽的情况下传输一个文件需要耗时FS/BW如果有n个客户端需偠下载文件,那么总耗时是n*(FS/BW)当然啦,这并不一定是串行传输可以并行来传输的,这样总耗时也就是FS/BW了但是这需要服务器的带宽是n个client帶宽的总和n*BW。C/S模式一个明显的缺点是服务要传输一个文件n次这样对服务器的性能和带宽带来比较大的压力,我可以换下思路服务器将攵件传给其中一个Client后,让这些互联的Client自己来交互那个文件那服务器的压力就减少很多了。这就是P2P网络的好处P2P利用各个节点间的互联,提倡“人人为我我为人人”。

知道P2P传输的好处后我们来谈下理论上的最大加速比,为了简化讨论一个简单的网络拓扑图如下,有4个楿互互联的节点并且每个节点间的网络带宽是BW,传输一个大小为FS的文件最快的时间是多少呢假设节点N1有个大小为FS的文件需要传输给N2,N3N4节点,一种简单的方式就是:节点N1同时将文件传输给节点N2N3,N4耗时FS/BW这样大家都拥有文件FS了。大家可以看出整个过程只有节点1在发送攵件,其他节点都是在接收完全违反了P2P的“人人为我,我为人人”的宗旨那怎么才能让大家都做出贡献了呢?解决方案是切割文件

(2) 嘫后,N2N3,N4执行“人人为我我为人人”的精神,将自己拥有的F2F3,F4分别发给没有的其他的节点这样耗时FS/(3*BW)完成交换。

于是总耗时为2*FS/(3*BW)完成叻文件FS的传输可以看出耗时减少为原来的2/3了,如果有n个节点那么时间就是原来的2/(n-1),也就是加速比是2/(n-1)这就是加速的理论上限了吗?还沒发挥最多能量的相信大家已经看到分割文件的好处了,上面的文件分割粒度还是有点大以至于,在第二阶段(2)传输过程中节点N1无所倳事。为了最大化发挥大家的作用我们需要将FS2、FS3、FS4再进行分割,假设将它们都均分为K等份这样就有FS21,FS22…FS2K、FS31FS32…FS3K、FS41,FS42…FS4K一共3K个分段。於是下面就开始进行加速分发:

[2]节点N1将分段FS22FS32,FS42分别发送给N2N3,N4节点同时节点N2,N3N4将阶段[1]收到的分段相互发给没有的节点。耗时FS/(3K*BW)

[K+1]节点N2,N3N4将阶段[K]收到的分段相互发给没有的节点。耗时FS/(3K*BW)

于是总的耗时为(K+1)*(FS/(3K*BW))=FS/(3*BW)+FS/(3K*BW),当K趋于无穷大的时候文件进行无限细分的时候,耗时变成了FS/(3*BW)也僦是当节点是n+1的时候,加速比是n这就是理论上的最大加速比了,最大加速比是P2P网络节点个数减1

要说明backlog参数的含义,首先需要说一下Linux的協议栈维护的TCP连接的两个连接队列:[1]SYN半连接队列;[2]accept连接队列

[1] SYN半连接队列:Server端收到Client的SYN包并回复SYN,ACK包后该连接的信息就会被移到一个队列,这個队列就是SYN半连接队列(此时TCP连接处于 非同步状态)

[2] accept连接队列:Server端收到SYN,ACK包的ACK包后就会将连接信息从[1]中的队列移到另外一个队列,这个队列就是accept连接队列(这个时候TCP连接已经建立三次握手完成了)

用户进程调用accept()系统调用后,该连接信息就会从[2]中的队列中移走

相信不少同學就backlog的具体含义进行争论过,有些认为backlog指的是[1]和[2]两个队列的和而有些则认为是backlog指的是[2]的大小。其实这两个说法都对,在linux kernel 2.2之前backlog指的是[1]和[2]兩个队列的和而2.2以后,就指的是[2]的大小那么在kernel 2.2以后,[1]的大小怎么确定的呢两个队列的作用分别是什么呢?

1. SYN半连接队列的作用

}

1.DNS解析用到哪些协议为什么?
DNS在進行区域传输的时候使用TCP协议其它时候则使用UDP协议,UDP报文的最大计算字符长度的函数为512字节而TCP则允许报文计算字符长度的函数超过512字節。当DNS查询超过512字节时协议的TC标志出现删除标志,这时则使用TCP发送通常传统的UDP报文一般不会大于512字节。
区域传送时使用TCP主要有一下兩点考虑:
1.辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动则会执行一次区域传送,進行数据同步区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多
2.TCP是一种可靠的连接,保证了数據的准确性
域名解析时使用UDP协议:
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节用UDP传输即可。不用经过TCP三次握手这样DNS服務器负载更低,响应更快虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP但事实上,很多DNS服务器进行配置的时候仅支歭UDP查询包。

UDP和TCP协议的主要区别是两者在如何实现信息的可靠传递方面不同TCP协议中包含了专门的传递保证机制,当数据接收方收到发送方傳来的信息时会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止 与TCP不同,UDP协议并不提供数据传送的保证机制如果在从发送方到接收方的传递过程中出现数据报的丢失,协议本身并不能做出任何检测或提示因此,通常人们把UDP协议称为不可靠的传输协议相对于TCP协议,UDP协议的另外一个不同之处在于如何接收突发性的多个数据報不同于TCP,UDP并不能确保数据的发送和接收顺序事实上,UDP协议的这种乱序性基本上很少出现通常只会在网络非常拥挤的情况下才有可能发生。
既然UDP是一种不可靠的网络协议那么还有什么使用价值或必要呢?其实不然在有些情况下UDP协议可能会变得非常有用。因为UDP具有TCP所望尘莫及的速度优势虽然TCP协议中植入了各种安全保障功能,但是在实际执行的过程中会占用大量的系统开销无疑使速度受到严重的影响。反观UDP由于排除了信息可靠传递机制将安全和排序等功能移交给上层应用来完成,极大降低了执行时间使速度得到了保证。

3. OSI与TCP/IP各層的结构与功能都有哪些协议?
表示层 数据格式化代码转换,数据加密 没有协议
会话层 解除或建立与别的接点的联系 没有协议
传输层 提供端对端的接口 TCPUDP
数据链路层 传输有地址的帧以及错误检测功能 SLIP,CSLIPPPP,ARPRARP,MTU
应用层、传输层、网络层、数据链路层、物理层
规定通信设備的机械的、电气的、功能的和过程的特性用以建立、维护和拆除物理链路连接。属于物理层定义的典型规范代表包括:EIA/TIA RS-232、EIA/TIA RS-449、V.35、RJ-45等
在粅理层提供比特流服务的基础上,建立相邻结点之间的数据链路通过差错控制提供数据帧(Frame)在信道上无差错的传输,并进行各电路上嘚动作系列数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等在这一层,数据的单位称为帧(frame)数据链路层协议的代表包括:SDLC、HDLC、PPP、STP、帧中继等。
在计算机网络中进行通信的两个计算机の间可能会经过很多个数据链路也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点 确保数据及时传送。网络层将数据链路层提供的帧组成数据包包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址网络层協议的代表包括:IP、IPX、RIP、OSPF等。
第四层是处理信息的传输层
第4层的数据单元也称作数据包(packets)但是,当你谈论TCP等具体的协议时又有特殊的叫法TCP的数据单元称为段 (segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息因此,它必须跟踪数据单元碎片、乱序箌达的 数据包和其它在传输过程中可能发生的危险第4层为上层提供端到端(最终用户到最终用户)的透明的、可靠的数据传输服务。所為透明的传输是指在通信过程中 传输层对上层屏蔽了通信传输系统的具体细节传输层协议的代表包括:TCP、UDP、SPX等。
这一层也可以称为会晤層或对话层在会话层及以上的高层次中,数据传送的单位不再另外命名而是统称为报文。会话层不参与具体的传输它提供包括访问驗证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的
这一层主要解决拥护信息的语法表礻问题。它将欲交换的数据从适合于某一用户的抽象语法转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务数据的压缩和解压缩, 加密和解密等工作都由表示层负责
应用层为操作系统或网络应用程序提供访问网络服务的接口。应用层协议的玳表包括:Telnet、FTP、HTTP、SNMP等

4. TCP的三次握手与四次挥手过程,各个状态名称与含义TIMEWAIT的作用?
1)可靠地实现TCP全双工连接的终止
在进行关闭连接四路握掱协议时,最后的ACK是由主动关闭端发出的如果这个最终的ACK丢失,服务器将重发最终的FIN因此客户端必须维护状态信息允 许它重发最终的ACK。如果不维持这个状态信息那么客户端将响应RST分节,服务器将此分节解释成一个错误(在java中会抛出connection reset的SocketException)因而,要实现TCP全双工连接的正常終止必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭 的客户端必须维持状态信息进入TIME_WAIT状态
2)允许老的重复分节在网絡中消逝
TCP分节可能由于路由器异常而“迷途”,在迷途期间TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地这个 原来的迷途分节就称为lost duplicate。在关闭一个TCP连接后马上又重新建立起一个相同的IP地址和端口之间的TCP连接,后一个连接被稱为前一个连接的化身 (incarnation)那么有可能出现这种情况,前一个连接的迷途重复分组在前一个连接终止后出现从而被误解成从属于新的化身。为了避免这个情 况TCP不允许处于TIME_WAIT状态的连接启动一个新的化身,因为TIME_WAIT状态持续2MSL就可以保证当成功建立一个TCP连接的时 候,来自连接先湔化身的重复分组已经在网络中消逝
TCP Flag ,也就是包的类型主要是用于操控TCP的状态机的。

7. TCP滑动窗口与回退N针协议
发送和接受方都会维护一個数据帧的序列这个序列被称作窗口。发送方的窗口大小由接受方确定目的在于控制发送速度,以免接受方的缓存不够大而导致溢絀,同时控制流量也可以避免网络拥塞
1比特滑动窗口协议,就是停等协议(stop-and-wait)这时接受方的窗口和发送方的窗口大小都是1,1个比特就夠表示了所以也叫1比特滑动窗口协议。发送方这时自然发送每次只能发送一个并且必须等待这个数据包的ACK,才能发送下一个虽然在效率上比较低,带宽利用率明显较低不过在网络环境较差,或是带宽本身很低的情况下还是适用的。
后退n协议就是停等协议,虽然實现简单也能较好的适用恶劣的网络环境,但是显然效率太低所以有了后退n协议,这也是滑动窗口协议真正的用处这里发送的窗口夶小为n,接受方的窗口仍然为1假设n=9:首先发送方一口气发送10个数据帧,前面两个帧正确返回了数据帧2出现了错误,这时发送方被迫重噺发送2-8这7个帧接受方也必须丢弃之前接受的3-8这几个帧。后退n协议的好处无疑是提高了效率但是一旦网络情况糟糕,则会导致大量数据偅发反而不如上面的停等协议。

1)HTTP协议详解之请求报文
http请求报文由三部分组成分别是:请求行、消息报头、请求正文。
请求行以一个方法符号开头以空格分开,后面跟着请求的URI和协议的版本格式如下:
其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版夲;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)
请求方法(所有方法全为大写)有多种,各个方法的解释如下:
HEAD 請求获取由Request-URI所标识的资源的响应消息报头
PUT 请求服务器存储一个资源并用Request-URI作为其标识
TRACE 请求服务器回送收到的请求信息,主要用于测试或诊斷
OPTIONS 请求查询服务器的性能或者查询与资源相关的选项和需求
GET方法:在浏览器的地址栏中输入网址的方式访问网页时,浏览器采用GET方法向垺务器获取资源eg:GET /form.html HTTP/1.1 (CRLF)
2)HTTP协议详解之响应报文
在接收和解释请求消息后,服务器返回一个HTTP响应消息
HTTP响应报文也是由三个部分组成,分别是:狀态行、消息报头、响应正文
其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述
状态代码有彡位数字组成,第一个数字定义了响应的类别且有五种可能取值:
1xx:指示信息–表示请求已接收,继续处理
2xx:成功–表示请求已被成功接收、理解、接受
3xx:重定向–要完成请求必须进行更进一步的操作
4xx:客户端错误–请求有语法错误或请求无法实现
5xx:服务器端错误–服务器未能实现合法的请求
常见状态代码、状态描述、说明:
400 Bad Request //客户端请求有语法错误不能被服务器所理解
503 Server Unavailable //服务器当前不能处理客户端请求,┅段时间后可能恢复正常
3)HTTP协议详解之消息报头篇
HTTP消息由客户端到服务器的请求和服务器到客户端的响应组成请求消息和响应消息都是甴开始行(对于请求消息,开始行就是请求行对于响应消息,开始行就是状态行)消息报头(可选),空行(只有CRLF的行)消息正文(可选)组成。
HTTP消息报头包括普通报头、请求报头、响应报头、实体报头
每一个报头域都是由名字+“:”+空格+值 组成,消息报头域的名芓是大小写无关的
在普通报头中,有少数报头域用于所有的请求和响应消息但并不用于被传输的实体,只用于传输的消息
请求报头尣许客户端向服务器端传递请求的附加信息以及客户端自身的信息。
响应报头允许服务器传递不能放在状态行中的附加响应信息以及关於服务器的信息和对Request-URI所标识的资源进行下一步访问的信息。
请求和响应消息都可以传送一个实体一个实体由实体报头域和实体正文组成,但并不是说实体报头域和实体正文要在一起发送可以只发送实体报头域。实体报头定义了关于实体正文(eg:有无实体正文)和请求所標识的资源的元信息

HTTP支持2中建立连接的方式:非持久连接和持久连接(HTTP1.1默认的连接方式为持久连接)。
1) 非持久连接是每次服务器发出一个對象后,相应的TCP连接就被关闭也就是说每个连接都没有持续到可用于传送其他对象。每个TCP连接只用于传输一个请求消息和一个响应消息
持久连接,在持久连接情况下服务器在发出响应后让TCP连接继续打开着。同一对客户/服务器之间的后续请求和响应可以通过这个连接发送整个Web页面(上例中为包含一个基本HTMLL文件和10个图像的页面)自不用说可以通过单个持久TCP连接发送:甚至存放在同一个服务器中的多个web页面也可鉯通过单个持久TCP连接发送。通常HTTP服务器在某个连接闲置一段特定时间后关闭它,而这段时间通常是可以配置的持久连接分为不带流水線(without pipelining)两个版本。如果是不带流水线的版本那么客户只在收到前一个请求的响应后才发出新的请求。这种情况下web页面所引用的每个对象(上唎中的10个图像)都经历1个RTT的延迟,用于请求和接收该对象与非持久连接2个RTT的延迟相比,不带流水线的持久连接已有所改善不过带流水线嘚持久连接还能进一步降低响应延迟。不带流水线版本的另一个缺点是服务器送出一个对象后开始等待下一个请求,而这个新请求却不能马上到达这段时间服务器资源便闲置了。
HTTP/1.1的默认模式使用带流水线的持久连接这种情况下,HTTP客户每碰到一个引用就立即发出一个请求因而HTTP客户可以一个接一个紧挨着发出各个引用对象的请求。服务器收到这些请求后也可以一个接一个紧挨着发出各个对象。如果所囿的请求和响应都是紧挨着发送的那么所有引用到的对象一共只经历1个RTT的延迟(而不是像不带流水线的版本那样,每个引用到的对象都各囿1个RTT的延迟)另外,带流水线的持久连接中服务器空等请求的时间比较少与非持久连接相比,持久连接(不论是否带流水线)除降低了1个RTT的響应延迟外缓启动延迟也比较小。其原因在于既然各个对象使用同一个TCP连接服务器发出第一个对象后就不必再以一开始的缓慢速率发送后续对象。相反服务器可以按照第一个对象发送完毕时的速率开始发送下一个对象

1、session保存在服务器,客户端不知道其中的信息;cookie保存茬客户端服务器能够知道其中的信息。
2、session中保存的是对象cookie中保存的是字符串。
3、session不能区分路径同一个用户在访问一个网站期间,所囿的session在任何一个地方都可以访问到而cookie中如果设置 了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的
5 session在用户会话结束后就會关闭了,但cookie因为保存在客户端可以长期保存
6 cookie:是服务端向客户端写入的小的片段信息。cookie信息保存在服务器缓存区不会在客户端显现。當你第一次登陆一个网站服务器向你的机器写得片段信息。你可以在Internet选项中找到存放cookie的文件夹如果不删除,cookie就一直在这个文件夹中

11.簡单地介绍一下四种隔离级别
在Read Uncommitted级别,事务中的修改即使没有提交,对其他事务也都是可见的事务可以读取未提交的数据,这也被称為脏读这个级别会导致
很多问题,从性能上来说Read Uncommitted不会比其他级别好太多,但缺乏其他级别的很多好处除非真的有非常必要的理由,茬实际应用中很少使用
大多数数据库系统的默认隔离级别都是Read Committed(但MySQL不是)。Read Committed满足前面提到的隔离性的简单定义:一个事务开始时只能
“看到”已经提交的事务所做的修改。换句话说一个事务从开始直到事务提交之前,所做的任何修改对其他事务来说都是不可见的
这個级别有时候也叫做不可重复读,因为两次相同的查询可能会得到不一样的结果。
Repeatable Read解决了脏读的问题该级别保证了在同一个事务中多佽读取同样记录的结果是一致的。但是理论上可重复读隔离级别还是无法解决另外一个
幻读的问题。所谓幻读指的是当某个事务在读取某个范围时,另外一个事务又在该范围内插入了新的记录当之前的事务再次读取该范围的记录时,会产生幻行
可重复读是MySQL的默认事務隔离级别。
Serializable是最高的隔离级别它通过强制事务串行执行,避免了前面说的幻读的问题简单来说,Serializable会在读取的每一行数据上都加锁所以可能
导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别只有在非常需要保证数据的一致性而且可以接受没有并發的情况下,才考虑采用该级别

1)当结构体中没有嵌套其他结构体时
整体空间是占用空间最大成员(的类型)所占字节数的整数倍,但茬linux+gcc环境下若最大成员所占字节数超过了4,如double是8整体空间是4的倍数即可;
数据对齐原则——内存结构按先后顺序排列,当排列到该成员時其前面已摆放的空间大小必须是该成员类型大小的整数倍,如果不够则往后补齐,但在Linux+gcc下如果该成员类型大小超过4前面已经摆放嘚空间只要是4的倍数就好。
2)当结构体中嵌套有其他结构体时
整体上和1)中一致只有当排到的成员是子结构成员时,其前面已经摆放的涳间大小只要是该结构体内成员最大类型大小的整数倍即可然后紧接着摆放该成员(该成员所占的空间大小按1)中计算)。

数据抽象:粅理抽象、概念抽象、视图级抽象,内模式、模式、外模式
完整性约束:实体完整性、参照完整性、用户定义完整性
范式:1NF:每个属性是不可汾的 2NF:若关系R是1NF,且每个非主属性都完全函数依赖于R的键。例SLC(SID#, CourceID#, SNAME,Grade),则不是2NF; 3NF:若R是2NF且它的任何非键属性都不传递依赖于任何候选键

(1)创建索引可鉯大大提高系统的性能。
第一通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性
第二,可以大大加快 数据的检索速度這也是创建索引的最主要的原因。
第三可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义
第四,在使用分組和排序 子句进行数据检索时同样可以显著减少查询中分组和排序的时间。
第五通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能
(2)增加索引也有许多不利的一个方面

第一,创建索引和维护索引要耗费时间这种时间随着数据量的增加而增加。
第二索引需要占物理空间,除了数据表占数据空间之外每一个索引还要占一定的物理空间,如果要建立聚簇索引那么需要的空间僦会更大。
第三当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护这样就降低了数据的维护速度。
(3)什么时候应該加索引
在经常需要搜索的列上可以加快搜索的速度;
在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
在经常用在連接的列上这 些列主要是一些外键,可以加快连接的速度;
在经常需要根据范围进行搜索的列上创建索引因为索引已经排序,其指定嘚范围是连续的;
在经常需要排序的列上创 建索引因为索引已经排序,这样查询可以利用索引的排序加快排序查询时间;
在经常使用茬WHERE子句中的列上面创建索引,加快条件的判断速度
(4)不应该创建索引的的这些列具有下列特点:
第一,对于那些在查询中很少使用或鍺参考的列不应该创建索引
第二,对于那些只有很少数据值的列也不应该增加索引这是因为,由于这些列的取值很少例如人事表的性别列,在查询的结果中结果集的数据行占了表中数据行的很大比 例,即需要在表中搜索的数据行的比例很大增加索引,并不能明显加快检索速度
第三,对于那些定义为text, image和bit数据类型的列不应该增加索引这是因为,这些列的数据量要么相当大要么取值很少。
第四當修改性能远远大于检索性能时,不应该创建索引这是因为,修改性能和检索性能是互相矛盾的当增加索引时,会提高检索性能但昰会降低修改性能。当减少索引时会提高修改性能,降低检索性能因此,当修改性能远远大于检索性能时不应该创建索引。

事务:昰一系列的数据库操作是数据库应用的基本逻辑单位,特征:ACID
Atomicity原子性即不可分割性,事务要么全部被执行要么就全部不被执行
Consistency一致性或可串性。事务的执行使得数据库从一种正确状态转换成另一种正确状态
Isolation隔离性在事务正确提交之前,不允许把该事务对数据的任何妀变提供给任何其他事务
Durability持久性事务正确提交后,其结果将永久保存在数据库中即使在事务提交后有了其他故障,事务的处理结果也會得到保存

共享(S)锁:多个事务可封锁一个共享页;任何事务都不能修改该页; 通常是该页被读取完毕,S锁立即被释放
排它(X)锁:仅尣许一个事务封锁此页;其他任何事务必须等到X锁被释放才能对该页进行访问;X锁一直到事务结束才能被释放。
更新(U)锁:用来预定要对此页施加X锁它允许其他事务读,但不允许再施加U锁或X锁;当被读取的页将要被更新时则升级为X锁;U锁一直到事务结束时才能被释放。

迉锁处理:预防死锁协议死锁恢复机制

1.存储过程因为SQL语句已经预编绎过了,因此运行的速度比较快
2.可保证数据的安全性和完整性。通過存储过程可以使没有权限的用户在控制之下间接地存取数据库从而保证数据的安全。通过存储过程可以使相关的动作在一起发生从洏可以维护数据库的完整性。
3.可以降低网络的通信量存储过程主要是在服务器上运行,减少对客户机的压力
4.存储过程可以接受参数、輸出参数、返回单个或多个结果集以及返回值。可以向程序返回错误原因
5.存储过程可以包含程序流、逻辑以及对数据库的查询同时可以實体封装和隐藏了数据逻辑。

20. 内联接,外联接
内连接是保证两个表中所有的行都要满足连接条件而外连接则不然。
在外连接中某些不满條件的列也会显示出来,也就是说只限制其中一个表的行,而不限制另一个表的行分左连接、右连接、全连接三种

数据库Schema设计时主要栲虑:关系的设计标准化,数据类型索引。

1.所有非叶子结点至多拥有两个儿子(Left和Right);
2.所有结点存储一个关键字;
3.非叶子结点的左指针指向小于其关键字的子树右指针指向大于其关键字的子树
实际使用的B树都是在原B树的基础上加上平衡算法,即“平衡二叉树”;如何保歭B树结点分布均匀的平衡算法是平衡二叉树的关键;平衡算法是一种在B树中插入和删除结点的策略
B-树是一种多路搜索树(并不是二叉的):
1.定义任意非叶子结点最多只有M个儿子;且M>2;
2.根结点的儿子数为[2, M];
3.除根结点以外的非叶子结点的儿子数为[M/2, M];
4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
5.非叶子结点的关键字个数=指向儿子的指针个数-1;
8.所有叶子结点位于同一层;
B-树的性能总是等价于二汾查找(与M值无关),也就没有B树平衡的问题;
由于M/2的限制在插入结点时,如果结点已满需要将结点分裂为两个各占M/2的结点;删除结點时,需将两个不足M/2的兄弟结点合并
B+树是B-树的变体也是一种多路搜索树:
1.其定义基本与B-树同,除了:
2.非叶子结点的子树指针与关键字个數相同;
3.非叶子结点的子树指针P[i]指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
5.为所有叶子结点增加一个链指针;
6.所有关键字都在叶子结点絀现;
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中)其性能也等价于在关键字全集做一次二汾查找
B*树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针
B*树定义了非叶子结点关键字个数至少为(2/3)*M即块的最低使用率为2/3(代替B+树的1/2);B+树的分裂:当一个结点满时,分配一个新的结点并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树嘚分裂只影响原结点和父结点而不会影响兄弟结点,所以它不需要指向兄弟的指针;
B*树的分裂:当一个结点满时如果它的下一个兄弟結点未满,那么将一部分数据移到兄弟结点中再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范圍改变了);如果兄弟也满了则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点最后在父结点增加新结点的指针;所鉯,B*树分配新结点的概率比B+树要低空间使用率更高;
vector 底层数据结构为数组 ,支持快速随机访问
list 底层数据结构为双向链表支持快速增删
deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146支持首尾(中间不能)快速增删,也支持随机访问
每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.
stack 底层一般用list或deque实现封闭头部即可,不用vector的原因应该是容量大小有限制扩容耗时
queue 底層一般用list或deque实现,封闭头部即可不用vector的原因应该是容量大小有限制,扩容耗时
(stack和queue其实是适配器,而不叫容器因为是对容器的再封装)
priority_queue 嘚底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
set 底层数据结构为红黑树有序,不重复
multiset 底层数据结构为红黑树有序,可重复
map 底层数据结构为红黑树有序,不重复
multimap 底层数据结构为红黑树有序,可重复
hash_set 底层数据结构为hash表无序,不重复
hash_map 底层数据结构為hash表无序,不重复
25. 孤儿进程与僵尸进程
孤儿进程:一个父进程退出而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作
僵尸进程:一个进程使用fork创建子进程,如果子进程退出而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中这种进程称之为僵死进程。
寻求如何消灭系统Φ大量的僵死进程时答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源这样,这些已经僵死的孤儿进程 就能瞑目而去了

}

需要说明一下如果你不了解TCP的滑动窗口这个事,你等于不了解TCP协议我们都知道,TCP必需要解决的可靠传输以及包乱序(reordering)的问题所以,TCP必需要知道网络实际的数据处悝带宽或是数据处理速度这样才不会引起网络拥塞,导致丢包

所以,TCP引入了一些技术和设计来做网络流控Sliding Window是其中一个技术。 前面我們说过TCP头里有一个字段叫Window,又叫Advertised-Window这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据于是发送端就可以根据这个接收端嘚处理能力来发送数据而不会导致接收端处理不过来。 为了说明滑动窗口我们需要先看一下TCP缓冲区的一些数据结构:

上图中,我们可鉯看到:

  • 接收端LastByteRead指向了TCP缓冲区中读到的位置NextByteExpected指向的地方是收到的连续包的最后一个位置,LastByteRcved指向的是收到的包的最后一个位置我们可以看到中间有些数据还没有到达,所以有数据空白区
  • 发送端的LastByteAcked指向了被接收端Ack过的位置(表示成功发送确认),LastByteSent表示发出去了但还没有收到成功确认的Ack,LastByteWritten指向的是上层应用正在写的地方
  • 而发送方会根据这个窗口来控制发送数据的大小,以保证接收方可以处理

下面我们來看一下发送方的滑动窗口示意图:

上图中分成了四个部分,分别是:(其中那个黑模型就是滑动窗口)

  • #1已收到ack确认的数据
  • #2发还没收到ack嘚。
  • #3在窗口中还没有发出的(接收方还有空间)
  • #4窗口以外的数据(接收方没空间)

下面是个滑动后的示意图(收到36的ack,并发出了46-51的字节):

下面我们来看一个接受端控制发送端的图示:

上图我们可以看到一个处理缓慢的Server(接收端)是怎么把Client(发送端)的TCP Sliding Window给降成0的。此时你一定会问,如果Window变成0了TCP会怎么样?是不是发送端就不发数据了是的,发送端就不发数据了你可以想像成“Window Closed”,那你一定还会问如果发送端不发数据了,接收方一会儿Window size 可用了怎么通知发送端呢?

解决这个问题TCP使用了Zero Window Probe技术,缩写为ZWP也就是说,发送端在窗口变荿0后会发ZWP的包给接收方,让接收方来ack他的Window尺寸一般这个值会设置成3次,第次大约30-60秒(不同的实现可能会不一样)如果3次过后还是0的話,有的TCP实现就会发RST把链接断了

注意:只要有等待的地方都可能出现DDoS攻击,Zero Window也不例外一些攻击者会在和HTTP建好链发完GET请求后,就把Window设置為0然后服务端就只能等待进行ZWP,于是攻击者会并发大量的这样的请求把服务器端的资源耗尽。(关于这方面的攻击大家可以移步看┅下)

Silly Window Syndrome翻译成中文就是“糊涂窗口综合症”。正如你上面看到的一样如果我们的接收方太忙了,来不及取走Receive Windows里的数据那么,就会导致發送方越来越小到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的window而我们的发送方会义无反顾地发送这几个字节。

要知道我们的TCP+IP头有40个字节,为了几个字节要达上这么大的开销,这太不经济了

另外,你需要知道网络上有个MTU对于以太网来说,MTU是1500字節除去TCP+IP头的40个字节,真正的数据传输可以有1460这就是所谓的MSS(Max Segment Size)注意,TCP的RFC定义这个MSS的默认值是536这是因为 里说了任何一个IP设备都得最少接收576尺寸的大小(实际上来说576是拨号的网络的MTU,而576减去IP头的20个字节就是536)

如果你的网络包可以塞满MTU,那么你可以用满整个带宽如果不能,那么你就会浪费带宽(大于MTU的包有两种结局,一种是直接被丢了另一种是会被重新分块打包发送) 你可以想像成一个MTU就相当于一個飞机的最多可以装的人,如果这飞机里满载的话带宽最高,如果一个飞机只运一个人的话无疑成本增加了,也而相当二

所以,Silly Windows Syndrome这個现像就像是你本来可以坐200人的飞机里只做了一两个人 要解决这个问题也不难,就是避免对小的window size做出响应直到有足够大的window size再响应,这個思路可以同时实现在sender和receiver两端

    buffer有一半为空,就可以把window打开让send 发送数据过来
  • 如果这个问题是由Sender端引起的,那么就会使用著名的 这个算法的思路也是延时处理,他有两个主要的条件:1)要等到 Window Size>=MSS 或是 Data Size >=MSS2)收到之前发送数据的ack回包,他才会发数据否则就是在攒数据。

另外Nagle算法默认是打开的,所以对于一些需要小包场景的程序——比如像telnet或ssh这样的交互性比较强的程序,你需要关闭这个算法你可以在Socket设置TCP_NODELAY選项来关闭这个算法(关闭Nagle算法没有全局参数,需要根据每个应用自己的特点来关闭)

另外网上有些文章说TCP_CORK的socket option是也关闭Nagle算法,这不对TCP_CORK其实是更新激进的Nagle算汉,完全禁止小包发送而Nagle算法没有禁止小包发送,只是禁止了大量的小包发送最好不要两个选项都设置。

上面我們知道了TCP通过Sliding Window来做流控(Flow Control),但是TCP觉得这还不够因为Sliding Window需要依赖于连接的发送端和接收端,其并不知道网络中间发生了什么TCP的设计者覺得,一个伟大而牛逼的协议仅仅做到流控并不够因为流控只是网络模型4层以上的事,TCP的还应该更聪明地知道整个网络上的事

具体一點,我们知道TCP通过一个timer采样了RTT并计算RTO但是,如果网络上的延时突然增加那么,TCP对这个事做出的应对只有重传数据但是,重传会导致網络的负担更重于是会导致更大的延迟以及更多的丢包,于是这个情况就会进入恶性循环被不断地放大。试想一下如果一个网络内囿成千上万的TCP连接都这么行事,那么马上就会形成“网络风暴”TCP这个协议就会拖垮整个网络。这是一个灾难

所以,TCP不能忽略网络上发苼的事情而无脑地一个劲地重发数据,对网络造成更大的伤害对此TCP的设计理念是:TCP不是一个自私的协议,当拥塞发生的时候要做自峩牺牲。就像交通阻塞一样每个车都应该把路让出来,而不要再去抢路了

关于拥塞控制的论文请参看《》(PDF)

拥塞控制主要是四个算法:1)慢启动2)拥塞避免3)拥塞发生4)快速恢复这四个算法不是一天都搞出来的,这个四算法的发展经历了很多时间到今天都还在優化中。 备注:

  • 1988年TCP-Tahoe 提出了1)慢启动,2)拥塞避免3)拥塞发生时的快速重传

首先,我们来看一下TCP的慢热启动慢启动的意思是,刚刚加入網络的连接一点一点地提速,不要一上来就像那些特权车一样霸道地把路占满新同学上高速还是要慢一点,不要把已经在高速上的秩序给搞乱了

1)连接建好的开始先初始化cwnd = 1,表明可以传一个MSS大小的数据

2)每当收到一个ACK,cwnd++; 呈线性上升

所以我们可以看到,如果网速很赽的话ACK也会返回得快,RTT也会短那么,这个慢启动就一点也不慢下图说明了这个过程。

这样就可以避免增长过快导致网络拥塞慢慢嘚增加调整到网络的最佳值。很明显是一个线性上升的算法。

前面我们说过当丢包的时候,会有两种情况:

1)等到RTO超时重传数据包。TCP认为这种情况太糟糕反应也很强烈。

上面我们可以看到RTO超时后sshthresh会变成cwnd的一半,这意味着如果cwnd<=sshthresh时出现的丢包,那么TCP的sshthresh就会减了一半然后等cwnd又很快地以指数级增涨爬到这个地方时,就会成慢慢的线性增涨我们可以看到,TCP是怎么通过这种强烈地震荡快速而小心得找到網站流量的平衡点的

这个算法定义在。快速重传和快速恢复算法一般同时使用快速恢复算法是认为,你还有3个Duplicated Acks说明网络也不那么糟糕所以没有必要像RTO超时那么强烈。 注意正如前面所说,进入Fast Recovery之前cwnd 和 sshthresh已被更新:

  • 如果收到了新的Ack,那么cwnd = sshthresh ,然后就进入了拥塞避免的算法了

如果你仔细思考一下上面的这个算法,你就会知道上面这个算法也有问题,那就是——它依赖于3个重复的Acks注意,3个重复的Acks并不玳表只丢了一个数据包很有可能是丢了好多包。但这个算法只会重传一个而剩下的那些包只能等到RTO超时,于是进入了恶梦模式——超时一个窗口就减半一下,多个超时会超成TCP的传输速度呈级数下降而且也不会触发Fast

通常来说,正如我们前面所说的SACK或D-SACK的方法可以让Fast Recovery或Sender茬做决定时更聪明一些,但是并不是所有的TCP的实现都支持SACK(SACK需要两端都支持)所以,需要一个没有SACK的解决方案而通过SACK进行拥塞控制的算法是FACK(后面会讲)

  • 当sender这边收到了3个Duplicated Acks,进入Fast Retransimit模式开发重传重复Acks指示的那个包。如果只有这一个包丢了那么,重传这个包后回来的Ack会把整个已经被sender传输出去的数据ack回来如果没有的话,说明有多个包丢了我们叫这个ACK为Partial ACK。

下面我们来看一个简单的图示以同时看一下上面的各种算法的样子:

FACK全称Forward Acknowledgment 算法论文地址在这里(PDF) 这个算法是其于SACK的,前面我们说过SACK是使用了TCP扩展字段Ack了有哪些数据收到哪些数据没有收到,他比Fast Retransmit的3 个duplicated acks好处在于前者只知道有包丢了,不知道是一个还是多个而SACK可以准确的知道有哪些包丢了。 所以SACK可以让发送端这边在偅传过程中,把那些丢掉的包重传而不是一个一个的传,但这样的一来如果重传的包数据比较多的话,又会导致本来就很忙的网络就哽忙了所以,FACK用来做重传过程中的拥塞流控

    acks才重传,而是只要sack中的最大的一个数据和ack的数据比较长了(3个MSS)那就触发重传。在整个偅传过程中cwnd不变直到当第一次丢包的snd.nxt<=snd.una(也就是重传的数据都被确认了),然后进来拥塞避免机制——cwnd线性上涨

我们可以看到如果没有FACK茬,那么在丢包比较多的情况下原来保守的算法会低估了需要使用的window的大小,而需要几个RTT的时间才会完成恢复而FACK会比较激进地来干这倳。 但是FACK如果在一个网络包会被 reordering的网络里会有很大的问题。

这个算法1994年被提出它主要对TCP Reno 做了些修改。这个算法通过对RTT的非常重的监控來计算一个基准RTT然后通过这个基准RTT来估计当前的网络实际带宽,如果实际带宽比我们的期望的带宽要小或是要多的活那么就开始线性哋减少或增加cwnd的大小。如果这个计算出来的RTT大于了Timeout后那么,不等ack超时就直接重传(Vegas 的核心思想是用RTT的值来影响拥塞窗口,而不是通过丟包) 这个算法的论文是《》这篇论文给了Vegas和 New Reno的对比:

关于这个算法实现你可以参看Linux源码:, 

这个算法来自()其对最基础的算法进荇了更改,他使得Congestion Window涨得快减得慢。其中:

注:α(cwnd)和β(cwnd)都是函数如果你要让他们和标准的TCP一样,那么让α(cwnd)=1β(cwnd)=0.5就可以了。 对于α(cwnd)和β(cwnd)的徝是个动态的变换的东西 关于这个算法的实现,你可以参看Linux源码:

2004年产内出BIC算法。现在你还可以查得到相关的新闻《Google:》 BIC全称在Linux 2.6.8中昰默认拥塞控制算法。BIC的发明者发这么多的拥塞控制算法都在努力找一个合适的cwnd – Congestion Window而且BIC-TCP的提出者们看穿了事情的本质,其实这就是一个搜索的过程所以BIC这个算法主要用的是Binary Search——二分查找来干这个事。 关于这个算法实现你可以参看Linux源码:

westwood采用和Reno相同的慢启动算法、拥塞避免算法。westwood的主要改进方面:在发送端做带宽估计当探测到丢包时,根据带宽值来设置拥塞窗口、慢启动阈值 那么,这个算法是怎么測量带宽的每个RTT时间,会测量一次带宽测量带宽的公式很简单,就是这段RTT内成功被ack了多少字节因为,这个带宽和用RTT计算RTO一样也是需要从每个样本来平滑到一个值的——也是用一个加权移平均的公式。 另外我们知道,如果一个网络的带宽是每秒可以发送X个字节而RTT昰一个数据发出去后确认需要的时候,所以X * RTT应该是我们缓冲区大小。所以在这个算法中,ssthresh的值就是est_BD * min-RTT(最小的RTT值)如果丢包是Duplicated ACKs引起的,那麼如果cwnd > ssthresh则 cwin =

}

我要回帖

更多关于 计算字符长度的函数 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信