TCP协议难点全景解析文档格式.docx
- 文档编号:16846554
- 上传时间:2022-11-26
- 格式:DOCX
- 页数:26
- 大小:118.65KB
TCP协议难点全景解析文档格式.docx
《TCP协议难点全景解析文档格式.docx》由会员分享,可在线阅读,更多相关《TCP协议难点全景解析文档格式.docx(26页珍藏版)》请在冰豆网上搜索。
2.TCP和IP协议
终止于IP协议,我们已经可以完成一个端到端的通信,为何还需要TCP协议?
这是一个问题,理解了这个问题,我们就能理解TCP协议为何成了现在这个样子,为何如此“复杂”,为何又如此简单。
正如其名字所展示的那样,TCP的作用是传输控制,也就是控制端到端的传输,那为何这种控制不在IP协议中实现的。
答案很简单,那就是这会增加IP协议的复杂性,而IP协议需要的就是简单。
这是什么原因造成的呢?
首先我们认识一下为何IP协议是沙漏的细腰部分。
它的下层是繁多的链路层协议,这些链路提供了相互截然不同且相差很远的语义,为了互联这些异构的网络,我们需要一个网络层协议起码要提供一些适配的功能,另外它必然不能提供太多的“保证性服务”,因为上层的保证性依赖下层的约束性更强的保证性,你永远无法在一个100M吞吐量的链路之上实现的IP协议保证1000M的吞吐量...
IP协议设计为分组转发协议,每一跳都要经过一个中间节点,路由的设计是TCP/IP网络的另一大创举,这样,IP协议就无需方向性,路由信息和协议本身不再强关联,它们仅仅通过IP地址来关联,因此,IP协议更加简单。
路由器作为中间节点也不能太复杂,这涉及到成本问题,因此路由器只负责选路以及转发数据包。
因此传输控制协议必然需要在端点实现。
在我们详谈TCP协议之前,首先要看一下它不能做什么,由于IP协议不提供保证,TCP也不能提供依赖于IP下层链路的这种保证,比如带宽,比如时延,这些都是链路层决定的,既然IP协议无法修补,TCP也不能,然而它却能修正始于IP层的一些“不可保证性质”,这些性质包括IP层的不可靠,IP层的不按顺序,IP层的无方向/无连接。
将该小节总结一下,TCP/IP模型从下往上,功能增加,需要实现的设备减少,然而设备的复杂性却在增加,这样保证了成本的最小化,至于性能或者因素,靠软件来调节吧,TCP协议就是这样的软件,实际上最开始的时候,TCP并不考虑性能,效率,公平性,正是考虑了这些,TCP协议才复杂了起来。
3.TCP协议
这是一个纯软件协议,为何将其设计上两个端点,参见上一小节,本节详述TCP协议,中间也穿插一些简短的论述。
3.1.TCP协议
确切的说,TCP协议有两重身份,作为网络协议,它弥补了IP协议尽力而为服务的不足,实现了有连接,可靠传输,报文按序到达。
作为一个主机软件,它和UDP以及左右的传输层协议隔离了主机服务和网络,它们可以被看做是一个多路复用/解复用器,将诸多的主机进程数据复用/解复用到IP层。
可以看出,不管从哪个角度,TCP都作为一个接口存在,作为网络协议,它和对端的TCP接口,实现TCP的控制逻辑,作为多路复用/解复用器,它和下层IP协议接口,实现协议栈的功能,而这正是分层网络协议模型的基本定义(两类接口,一类和下层接口,另一类和对等层接口)。
我们习惯于将TCP作为协议栈的最顶端,而不把应用层协议当成协议栈的一部分,这部分是因为应用层被TCP/UDP解复用了之后,呈现出了一种太复杂的局面,应用层协议用一种不同截然不同的方式被解释,应用层协议习惯于用类似ASN.1标准来封装,这正体现了TCP协议作为多路复用/解复用器的重要性,由于直接和应用接口,它可以很容易直接被应用控制,实现不同的传输控制策略,这也是TCP被设计到离应用不太远的地方的原因之一。
总之,TCP要点有四:
一曰有连接,二曰可靠传输,三曰数据按序到达,四曰端到端流量控制。
注意,TCP被设计时只保证这四点,此时它虽然也有些问题,然而很简单,然而更大的问题很快呈现出来,使之不得不考虑和IP网络相关的东西,比如公平性,效率,因此增加了拥塞控制,这样TCP就成了现在这个样子。
3.2.有连接,可靠传输,数据按序到达的TCP
IP协议是没有方向的,数据报传输能到达对端全靠路由,因此它是一跳一跳地到达对端的,只要有一跳没有到达对端的路由,那么数据传输将失败,其实路由也是互联网的核心之一,实际上IP层提供的核心基本功能有两点:
第一点是地址管理,第二点就是路由选路。
TCP利用了IP路由这个简单的功能,因此TCP不必考虑选路,这又一个它被设计成端到端协议的原因。
既然IP已经能尽力让单独的数据报到达对端,那么TCP就可以在这种尽力而为的网络上实现其它的更加严格的控制功能。
TCP给无连接的IP网络通信增加了连接性,确认了已经发送出去的数据的状态,并且保证了数据的顺序。
3.2.1.有连接
这是TCP的基本,因为后续的传输的可靠性以及数据顺序性都依赖于一条连接,这是最简单的实现方式,因此TCP被设计成一种基于流的协议,既然TCP需要事先建立连接,之后传输多少数据就无所谓了,只要是同一连接的数据能识别出来即可。
=====================================================================
疑难杂症1:
3次握手和4次挥手
TCP使用3次握手建立一条连接,该握手初始化了传输可靠性以及数据顺序性必要的信息,这些信息包括两个方向的初始序列号,确认号由初始序列号生成,使用3次握手是因为3次握手已经准备好了传输可靠性以及数据顺序性所必要的信息,该握手的第3次实际上并不是需要单独传输的,完全可以和数据一起传输。
TCP使用4次挥手拆除一条连接,为何需要4次呢?
因为TCP是一个全双工协议,必须单独拆除每一条信道。
注意,4次挥手和3次握手的意义是不同的,很多人都会问为何建立连接是3次握手,而拆除连接是4次挥手。
3次握手的目的很简单,就是分配资源,初始化序列号,这时还不涉及数据传输,3次就足够做到这个了,而4次挥手的目的是终止数据传输,并回收资源,此时两个端点两个方向的序列号已经没有了任何关系,必须等待两方向都没有数据传输时才能拆除虚链路,不像初始化时那么简单,发现SYN标志就初始化一个序列号并确认SYN的序列号。
因此必须单独分别在一个方向上终止该方向的数据传输。
另一种回答:
因为当处于LISTEN状态的服务器端SOCKET当收到SYN报文(客户端希望新建一个TCP连接)后,它可以把ACK(应答作用)和SYN(同步作用)放在同一个报文里来发送给客户端。
但在关闭TCP连接时,当收到对方的FIN报文时,对方仅仅表示对方没有数据发送给你了,但未必你的所有数据都已经全部发送给了对方,所以你大可不必马上关闭SOCKET(发送一个FIN报文),等你发送完剩余的数据给对方之后,再发送FIN报文给对方来表示你同意现在关闭连接了,所以通常情况下,这里的ACK报文和FIN报文都是分开发送的。
关闭TCP连接一定需要4次挥手吗?
不一定,4次挥手关闭TCP连接是最安全的做法。
但在有些时候,我们不喜欢TIME_WAIT状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。
但这并不是一个很好的主意,TIME_WAIT对于我们来说往往是有利的。
忽略掉TIME_WAIT。
如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
BOOLbDontLinger=FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(constchar*)&
bDontLinger,sizeof(BOOL));
疑难杂症2:
TIME_WAIT状态
为何要有这个状态,原因很简单:
那就是每次建立连接的时候序列号都是随机产生的,并且这个序列号是32位的,会回绕。
现在我来解释这和TIME_WAIT有什么关系。
任何的TCP分段都要在尽力而为的IP网络上传输,中间的路由器可能会随意的缓存任何的IP数据报,它并不管这个IP数据报上被承载的是什么数据,然而根据经验和互联网的大小,一个IP数据报最多存活MSL(这是根据地球表面积,电磁波在各种介质中的传输速率以及IP协议的TTL等综合推算出来的,如果在火星上,这个MSL会大得多...)。
现在我们考虑终止连接时的被动方发送了一个FIN,然后主动方回复了一个ACK,然而这个ACK可能会丢失,这会造成被动方重发FIN,这个FIN可能会在互联网上存活MSL。
如果没有TIME_WAIT的话,假设连接1已经断开,然而其被动方最后重发的那个FIN(或者FIN之前发送的任何TCP分段)还在网络上,然而连接2重用了连接1的所有的5元素(源IP,目的IP,TCP,源端口,目的端口),刚刚将建立好连接,连接1迟到的FIN到达了,这个FIN将以比较低但是确实可能的概率终止掉连接2.
为何说是概率比较低呢?
这涉及到一个匹配问题,迟到的FIN分段的序列号必须落在连接2的一方的期望序列号范围之内。
虽然这种巧合很少发生,但确实会发生,毕竟初始序列号是随机产生了。
因此终止连接的主动方必须在接受了被动方且回复了ACK之后等待2*MSL时间才能进入CLOSE状态,之所以乘以2是因为这是保守的算法,最坏情况下,针对被动方的ACK在以最长路线(经历一个MSL)经过互联网马上到达被动方时丢失。
为了应对这个问题,RFC793对初始序列号的生成有个建议,那就是设定一个基准,在这个基准之上搞随机,这个基准就是时间,我们知道时间是单调递增的。
然而这仍然有问题,那就是回绕问题,如果发生回绕,那么新的序列号将会落到一个很低的值。
因此最好的办法就是避开“重叠”,其含义就是基准之上的随机要设定一个范围。
要知道,很多人很不喜欢看到服务器上出现大量的TIME_WAIT状态的连接,因此他们将TIME_WAIT的值设置的很低,这虽然在大多数情况下可行,然而确实也是一种冒险行为。
最好的方式就是,不要重用一个连接。
备注:
在这四次握手状态中,有一个特别要注意的状态TIME_WAIT。
这个状态是主动关闭方在收到被关闭方的FIN后会处于并长期(2个MSL时间,根据具体的实现不同,这个值会不同,在RFC1122建议MSL=2分钟,但在Berkeley的实现上使用的值为30s,具体可以看,要是没有耐心去看英文的可以看这个网站里面有协议说明以及相应的源码,java源码中我没有发现这个值,我只能追踪到PlainSocketImpl.java这个类,再往下就是本地接口调用了,因此它是依赖本地操作系统的实现)处于的一个状态。
也就是大约1-4分钟,然后由操作系统自动回收并将TCP连接设为CLOSED初始状态。
TIME_WAIT状态存在有两个理由:
可靠地实现TCP全双工连接终止。
允许老的重复分组在网络中消失。
疑难杂症3:
重用一个连接和重用一个套接字
这是根本不同的,单独重用一个套接字一般不会有任何问题,因为TCP是基于连接的。
比如在服务器端出现了一个TIME_WAIT连接,那么该连接标识了一个五元素,只要客户端不使用相同的源端口,连接服务器是没有问题的,因为迟到的FIN永远不会到达这个连接。
记住,一个五元素标识了一个连接,而不是一个套接字(当然,对于BSD套接字而言,服务端的accept套接字确实标识了一个连接)。
Socket重用:
如果在已经处于ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
tcp_tw_reuseBOOLEANAllowtoreuseTIME-WAITsocketsfornewconnectionswhenitissafefromprotocolviewpoint.Defaultvalueis0.
Itshouldnotbechangedwithoutadvice/requestoftechnicalexperts.
tcp_tw_recycleBOOLEANEnablefastrecyclingTIME-WAITsockets.Defaultvalueis0.
tcp_fin_timeoutINTEGER默认值是60
对于本端断开的socket连接,TCP保持在FIN_WAIT_2状态的时间。
对方可能会断开连接或一直不结束连接或不可预料的进程死亡。
默认值为60秒。
过去在2.2版本的内核中是180秒。
您可以设置该值,但需要注意,如果您的机器为负载很重的web服务器,您可能要冒内存被大量无效数据报填满的风险,FIN-WAIT-2sockets的危险性低于FIN-WAIT-1,因为它们最多只吃1.5K的内存,但是它们存在时间更长。
echo"
1"
>
>
/proc/sys/net/ipv4/tcp_tw_reuse
/proc/sys/net/ipv4/tcp_tw_recycle
net.ipv4.tcp_fin_timeout=30"
/etc/sysctl.conf
net.ipv4.tcp_tw_reuse=1"
net.ipv4.tcp_tw_recycle=1"
地址重用:
BOOLbReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(constchar*)&
bReuseaddr,sizeof(BOOL));
SO_REUSEADDR可以用在以下四种情况下。
(摘自《Unix网络编程》卷一,即UNPv1)
1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。
2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。
但每个实例绑定的IP地址是不能相同的。
在有多块网卡或用IPAlias技术的机器可以测试这种情况。
3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。
这和2很相似,区别请看UNPv1。
4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。
但这只用于UDP的多播,不用于TCP。
3.2.2.传输可靠性
基本上传输可靠性是靠确认号实现的,也就是说,每发送一个分段,接下来接收端必然要发送一个确认,发送端收到确认后才可以发送下一个字节。
这个原则最简单不过了,教科书上的“停止-等待”协议就是这个原则的字节版本,只是TCP使用了滑动窗口机制使得每次不一定发送一个字节,但是这是后话,本节仅仅谈一下确认的超时机制。
怎么知道数据到达对端呢?
那就是对端发送一个确认,但是如果一直收不到对端的确认,发送端等多久呢?
如果一直等下去,那么将无法发现数据的丢失,协议将不可用,如果等待时间过短,可能确认还在路上,因此等待时间是个问题,另外如何去管理这个超时时间也是一个问题。
疑难杂症4:
超时时间的计算
绝对不能随意去揣测超时的时间,而应该给出一个精确的算法去计算。
毫无疑问,一个TCP分段的回复到达的时间就是一个数据报往返的时间,因此标准定义了一个新的名词RTT(Round-TripTime往返时延):
,代表一个TCP分段的往返时间。
然而我们知道,IP网络是尽力而为的,并且路由是动态的,且路由器会毫无先兆的缓存或者丢弃任何的数据报,因此这个RTT是需要动态测量的,也就是说起码每隔一段时间就要测量一次,如果每次都一样,万事大吉,然而世界并非如你所愿,因此我们需要找到的恰恰的一个“平均值”,而不是一个准确值。
这个平均值如果仅仅直接通过计算多次测量值取算术平均,那是不恰当的,因为对于数据传输延时,我们必须考虑的路径延迟的瞬间抖动,否则如果两次测量值分别为2和98,那么超时值将是50,这个值对于2而言,太大了,结果造成了数据的延迟过大(本该重传的等待了好久才重传),然而对于98而言,太小了,结果造成了过度重传(路途遥远,本该很慢,结果大量重传已经正确确认但是迟到的TCP分段)。
因此,除了考虑每两次测量值的偏差之外,其变化率也应该考虑在内,如果变化率过大,则通过以变化率为自变量的函数为主计算RTT(如果陡然增大,则取值为比较大的正数,如果陡然减小,则取值为比较小的负数,然后和平均值加权求和),反之如果变化率很小,则取测量平均值。
这是不言而喻的,这个算法至今仍然工作的很好。
重传超时时间(RTO,RetranmissionTimeOut)的测量依赖于RTT(round-triptime)的。
从RTT计算RTO的算法有两个版本。
第一个版本比较简单:
R=xR+(1-x)RTT#R表示历史的RTT内容,通常x=0.9,初始为0s
RTO=yR#通常y=2
但是Jacobson认为如果在RTT变化范围很大的时候,这种方式计算出的RTO并不能够很好地适应这种变化。
那么第二个版本就是:
E=RTT-R#R表示历史RTT,E表示偏差值
R=R+gE#通常g=0.125,然后修正历史RTT,初始为0s
D=D+h(abs(E)-D)#通常h=0.25,被平滑之后的偏差,初始为3s
RTO=R+4D
当TCP超时并且重传时,它不一定要重传相同的报文段。
相反,TCP允许进行重新分组而发送一个较大的报文段,这将有助于提高性能。
疑难杂症5:
超时计时器的管理-每连接单一计时器
很显然,对每一个TCP分段都生成一个计时器是最直接的方式,每个计时器在RTT时间后到期,如果没有收到确认,则重传。
然而这只是理论上的合理,对于大多数操作系统而言,这将带来巨大的内存开销和调度开销,因此采取每一个TCP连接单一计时器的设计则成了一个默认的选择。
可是单一的计时器怎么管理如此多的发出去的TCP分段呢?
又该如何来设计单一的计时器呢。
设计单一计时器有两个原则:
1.每一个报文在长期收不到确认都必须可以超时;
2.这个长期收不到中长期不能和测量的RTT相隔太远。
因此RFC2988定义一套很简单的原则:
a.发送TCP分段时,如果还没有重传定时器开启,那么开启它。
b.发送TCP分段时,如果已经有重传定时器开启,不再开启它。
c.收到一个非冗余ACK时,如果有数据在传输中,重新开启重传定时器。
d.收到一个非冗余ACK时,如果没有数据在传输中,则关闭重传定时器。
我们看看这4条规则是如何做到以上两点的,根据a和c(在c中,注意到ACK是非冗余的),任何TCP分段只要不被确认,超时定时器总会超时的。
然而为何需要c呢?
只有规则a存在的话,也可以做到原则1。
实际上确实是这样的,但是为了不会出现过早重传,才添加了规则c,如果没有规则c,那么万一在重传定时器到期前,发送了一些数据,这样在定时器到期后,除了很早发送的数据能收到ACK外,其它稍晚些发送的数据的ACK都将不会到来,因此这些数据都将被重传。
有了规则c之后,只要有分段ACK到来,则重置重传定时器,这很合理,因此大多数正常情况下,从数据的发出到ACK的到来这段时间以及计算得到的RTT以及重传定时器超时的时间这三者相差并不大,一个ACK到来后重置定时器可以保护后发的数据不被过早重传。
这里面还有一些细节需要说明。
一个ACK到来了,说明后续的ACK很可能会依次到来,也就是说丢失的可能性并不大,另外,即使真的有后发的TCP分段丢失现象发生,也会在最多2倍定时器超时时间的范围内被重传(假设该报文是第一个报文发出启动定时器之后马上发出的,丢失了,第一个报文的ACK到来后又重启了定时器,又经过了一个超时时间才会被重传)。
虽然这里还没有涉及拥塞控制,但是可见网络拥塞会引起丢包,丢包会引起重传,过度重传反过来加重网络拥塞,设置规则c的结果可以缓解过多的重传,毕竟将启动定时器之后发送的数据的重传超时时间拉长了最多一倍左右。
最多一倍左右的超时偏差做到了原则2,即“这个长期收不到中长期不能和测量的RTT相隔太远”。
还有一点,如果是一个发送序列的最后一个分段丢失了,后面就不会收到冗余ACK,这样就只能等到超时了,并且超时时间几乎是肯定会比定时器超时时间更长。
如果这个分段是在发送序列的靠后的时间发送的且和前面的发送时间相隔时间较远,则其超时时间不会很大,反之就会比较大。
疑难杂症6:
何时测量RTT
目前很多TCP实现了时间戳,这样就方便多了,发送端再也不需要保存发送分段的时间了,只需要将其放入协议头的时间戳字段,然后接收端将其回显在ACK即可,然后发送端收到ACK后,取出时间戳,和当前时间做算术差,即可完成一次RTT的测量。
3.2.3.数据顺序性
基本上传输可靠性是靠序列号实现的。
疑难杂症7:
确认号和超时重传
确认号是一个很诡异
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- TCP 协议 难点 全景 解析