TCP(Transmission Control Protocol 传输控制协议)是一种面向连接、可靠的、基于字节流的传输层协议。TCP在传送数据之前会先相互发送一些预备报文段协商一些参数,比如序号等等,TCP将用户数据打包成报文段,发送数据后启动一个定时器,另一端对收到的数据进行确认,对失序的数据重新排序,丢弃重复数据,TCP提供端到端的流量控制,并计算和验证一个强制性的端到端校验和。在OSI七层模型中位于第四层,在TCP/IP四层模型中位于第三层。
使用TCP的应用程序:Telnet、Rlogin、FTP、SMTP
(1)应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度将保持不变。由TCP传递给IP的信息单位称为报文段或段(segment)
(2)当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文 段。
(3)当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒
(4)TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。
(5)既然TCP报文段作为IP数据报来传输,而I P数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
(6)既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据
(7)TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
(1)Client独有:SYS_SENT, FIN_WAIT1, FIN_WAIT2, TIME_WAIT, CLOSING
(2)Sever独有:LINSTEN, SYN_RECD, CLOSE_WAIT, LAST_ACK
(3)共有:CLOSED, ESTABLISHED,
(1)客户端、服务器端初始转态CLOSED, 服务器端创建SOCKET开始监听,服务器端转态变为LINSTEN,客户端发送SYN进行建立连接的请求,客户端转态变为SYS_SENT
(2)服务端收到客户端发来的SYN后,向客户端发送ACK确认及SYN,服务器端转态变为SYN_RECD
(3)客户端收到服务器端发来的ACK及SYN后,向服务器端发送确认ACK,客户端转态变为ESTABLISHED
(4)服务器端收到客户端发来的ACK后,转态变为ESTABLISHED,建立连接完成
TCP建立连接需要三次握手的原因:
防止已经过期的连接再次传送到被连接的主机
client端向server发送第一个连接请求报文段并没有丢失而是在网络中某个节点滞留到client和server连接结束后的某个时间点到达了server,若不采用三次握手,server发送ACK此时server端认为连接就已经建立,但是client端并没有发送连接请求因此对server发来的ACK不予理睬,这样server就一直等到客户端发送数据,浪费了服务器端的资源。
(1)此时客户端和服务器端转态都为ESTABLISHED,客户端数据传输完毕请求断开连接,客户端发送FIN给服务器端,客户端转态变为FIN_WAIT1
(2)服务器端收到客户端发来的FIN后对其进行确认发送ACK,服务器端转态变为CLOSE_WAIT
(3)客户端收到服务器端发来的ACK后转态变为FIN_WAIT2,此时客户端到服务器端的连接关闭,但是服务器到客户端的连接还么有关闭,服务器还可以向客户端发送数据
(4)当服务器端没有数据要给客户端发送时,服务端向客户端发送FIN,服务器端转态变为LAST_ACK
(5)客户端收到服务器端的FIN后向其发送ACK确认,客户端进入TIME_WAIT转态,客户端等待2MSL时间后进入CLOSED转态
(6)服务器端收到客户端的ACK后,转态变为CLOSED,此时TCP连接完全关闭
以上是TCP断开连接不出错的情况下的步骤,其中还有一种客户端的转态CLOSING没有提到,这种情况发生在客户端向服务器端发送FIN后,客户端没有接收到服务器发来的ACK却接收到服务器发来的FIN时,客户端转态从FIN_WATI1直接进入CLOSING转态,当接收到服务器的ACK后再从CLOSING转态进入TIME_WAIT转态。
TCP结束连接需要四次握手原因:
当客户端没有数据向服务器端传输时发送FIN要求结束客户端到服务器端的连接,但此时可能服务器端还有数据向客户端发送因此,服务器端仅向客户端发送ACK(并没有一起发送ACK与FIN)结束客户端到服务端的连接,但是此时服务器端到客户端的连接还是有效的。当服务器端没有数据向客户端发送时服务器端再发送FIN,然后客户端ACK对其确认。
结束连接时从TIME_WAIT到CLOSED等待2MSL的原因:
(1)因为收到服务器发来的FIN客户端发送ACK确认,但是ACK可能丢失,在客户端收到服务器端发来的FIN(在CLOSING转态下接收到的是ACK)后直接进入CLOSED这种情况下,若客户端发给服务器端的ACK丢失,服务器端没有接收到ACK超时就重发FIN,但是客户端已经关闭,服务器端就会不断超时重发FIN,无法关闭服务器端到客户端的连接。若设置了TIME_WAIT转态,客户端发送ACK后等待2MSL时间,若此时间内再次收到服务器端的FIN,客户端就重发ACK,并从新等待2MSL;若此时间内没有再次收到服务器端的FIN,客户端就推测服务器端已经接收到ACK,连接已经断开,进入CLOSED转态。(MSL(Maximum Segment Lifetime)指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间)
(2)TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口,服务器的IP地址和端口)不能再被使用,这个连接只能在2MSL结束后才能再被使用。在连接处于2MSL等待期间,任何迟到的报文段将被丢弃,这就防止了将他认为为使用相同插口的新的连接的一部分。
TCP存在半关闭的原因:
客户端向服务器端发送FIN请求关闭客户端端服务器端的连接,服务器端向客户端发送ACK确认,此时客户端处于FIN_WAIT2状态,服务器端处于CLOSE_WAIT状态,此时称为半关闭状态。比如客户端向服务器端发送数据,要在服务器端进行排序,服务器端必须全部接收到要排序的数据后才可以开始进行排序工作,那么服务器端是如何知道客户端发送数据完毕了呢?就是通过客户端向服务器端发送FIN请求关闭客户端到服务器端的连接知道数据已经接收完毕了,开始排序并把排序后的数据从服务器端发送到客户端。(当然也可以建立两个TCP连接,一个用于客户端向服务器端发送数据,一个用于服务器端向客户端发送数据,但这个消耗大费时)
(1)16位源端口号:标记TCP报文段来自上层那个端口(用于寻找响应的应用进程),多路复用来自上层应用的数据。
(2)16位目的端口号:标记TCP报文段应该送至上层那个端口(用于寻找响应的应用进程),多路分用送至上层应用。
(3)32位序号:发送方发送数据报文段的序号(首字节序号),比如第一个报文段的序号是0,有500个字节(0-499),那么下一个报文段的序号就是500,长度为500字节(500-999),第三个报文段序号为1000,长度500字节(1000-1499)。
(4)32位确认序号:对已经接收的连续的数据进行确认,比如接收方顺利接收到(3)中的第一个报文段则接收方向发送方发送的确认序号为500,表示500以前的所有数据都已正确接收,下次期待接收序号500的数据;若是先接收到第一个报文段然后接收到第三个报文段,但是第二个报文段没有接收到此时接收方会重复发送确认序号500(累积确认),这种情况下有两种处理方式,一是把失序的第三个报文段丢弃,而是保持第三个报文段等待第二个报文段到来填充间隔。
(5)4位首部长度:表示以32bit的字为单位的TCP首部长度,因为有“选项”字段因此TCP首部长度是可变的,但是“选项”字段通常为空。
(6)保留(6位):保留未用字段6bit
(7)URG:紧急字段1bit,置为1 表示报文段中有被上层置为“紧急”的数据,后面第15字段“16位紧急指针”有效且指出了报文段中“紧急”数据的最后一个字节。使用时置为1,用来处理避免TCP数据流中断。(实际中未被使用)
(8)ACK:置为1表明“32位确认序号”字段是有效的。
(9)PSH:置为1时表示接收方尽快将这个报文段交给应用层,接收方接收到数据后不必等到数据缓冲区满后才传送给应用程序而是接收后直接传送给应用程序。(实际中未使用)
(10)RST:用于复位由于主机崩溃或其他原因出现的错误连接,也可以用于拒绝错误的数据报和非法连接,常见的一种情况是当连接请求到达时目的端口没有进程监听,TCP产生RST,也可异常终止一个连接而不是使用FIN,RST也可以用于检测半打开连接。
(11)SYN:同步序号,用于TCP连接的建立(参考TCP连接图示)
(12)FIN:用于断开TCP连接(参考TCP断开连接图示)
(13)16位窗口大小:窗口大小为字节数,表明接收端期望接收的字节数,TCP连接两端的窗口大小用于进行TCP流量控制。
(14)16位校验和:TCP校验和覆盖整个TCP报文段:TCP首部和TCP数据部分,由发送端计算存储,由接收端验证。(使用到伪首部)
(15)16位紧急指针:URG位置为1时有效。紧急指针是一个正偏移量,与32位序号字段相加获得紧急数据最后一个字节的序号。
(16)选项:一般TCP连接的两端在通信的第一个报文段中在这个字段指出本端能接收的最大报文段大小MSS(Maximum Segment Size),一般这个字段为空。
(17)数据:可选,在连接建立和终止时这个字段一般为空,仅有TCP首部。
TCP在建立是会在SYN报文(且只会出现在SYN报文中)中告知对方自己的最大报文段。这样双方在发送数据时发送的报文段一般会小于双方最大报文段的最小值,这样是为了尽量避免报文在传输过程中分段。若一方没有设置MSS值则采用默认值536字节(再加上20字节的TCP首部,20字节的IP首部可以得到最大576字节的IP数据报)。MSS值一般设置为MTU值减去20字节TCP首部再减去20字节IP首部。这样并不能完全避免分段,比如主机A,B都连接到以太网上最大MSS都使用536字节或1460字节,但是中间网络有的最大MTU为296字节,那么A,B发送的数据在经过这部分网络时还是会分段的。下图说明了A,B主机如何告知对方自己允许的MSS,和网络中在哪分段:
拥塞避免算法是用于处理丢失分组的方法,一般在网络上由于分组损坏而丢失分组的概率非常小,因此一般认为分组丢失是由于源主机到目的主机之间的某处网络发生了拥塞,观察拥塞的方式有两种:一是超时,二是连续接收到3个重复ACK。
为什么是3个连续的ACK:由于我们不知道一个重复的ACK是由一个丢失的报文引起的,还是由于仅仅出现了几个报文段的重新排序引起的,因此我们等待少量的重复的ACK到来。因为若只是一些报文段的重新排序引起的,一般在重新排序报文段完成并产生一个新的ACK之前只可能产生1-2个重复的ACK。
拥塞的不同处理方式:对于由于超时重传认为的拥塞,我们一般是重传报文段,然后进入慢启动(下文5.1);对于由于接收到3个重复的ACK认为的拥塞,我们一般是立即重传报文段,然后进入拥塞避免(下文5.2)。这样处理的原因是因为当因为定时器超时,此时网络中可能已经很拥塞,数据确认的ACK已经无法发送回来,因此我们立即减少注入网络中的数据,使用慢启动cwnd减小为1;而对于收到,3个重复的ACK说明还有其他的报文段到达了目的地(因为接收方只有在收到失序的报文段时才会产生重复的ACK而且还有重复的ACK发送回来),也即收发两端还有数据的流动,因此我们不必使用慢启动突然减少注入网络的数据。
注:(1)以下为了便于讨论假设接收方的接收窗口足够大,实际中发送窗口取接收窗口和拥塞窗口的最小值,因假设接收窗口足够大,因此发送窗口就等于拥塞窗口(cwnd);设慢启动门限为ssthresh。(2)为了便于讨论窗口大小的单位都是用报文段(实际中是以字节为单位)。
(1)初始化cwnd=1, ssthresh=16,开始慢启动
(2)0-4往返时间内cwnd < ssthresh执行的是慢启动算法,cwnd以指数的方式增加,每接收一个ACK,cwnd就增加一个报文段大小。第一个RTT内发送一个报文段,接收到一个ACK,cwnd变为2,第二个RTT发送2个报文段,接收两个ACK,cwnd变为4,以此类推….
(3)当cwnd==ssthresh时既可以执行慢启动也可以执行拥塞避免
(4)当cwnd>ssthresh时开始执行拥塞避免算法,cwnd呈线性增长,每收到一个ACK,cwnd就增加1/cwnd大小,比如4-5RTT,发送了16个报文段,每接收一个ACK,cwnd就增加1/16,16个接收完也即一个传输轮次(RTT)cwnd增加1,4-12都如此执行。
(5)若在12-13时,发生了超时重传分组,就把ssthresh设置为此时cwnd的一半大小,cwnd大小重新设为1,再次开始执行慢启动,如上图。
当因收到三个及三个以上的重复ACK时,使用如下拥塞控制方法:
(1)收到3个重复的ACK,将ssthresh值设为当前cwnd窗口大小的一半,以图为例在cwnd=24时收到三个重复的ACK,把ssthresh设为12。
(2)此时立即重传丢失的报文不用等到定时器超时,此为快重传。
(3)此后开始执行拥塞避免而不是慢启动,此为快恢复。
注:在TCP的具体的实现中在执行”乘法减小”的过程中可能还有一些隐含的操作(不同版本实现可能不相同,一般的教科书讲述的会像是以上两幅图描述的那样),当收到3个重复的ACK时,把ssthresh设置为当前cwnd值得一般,把cwnd值设为ssthresh+3(因为收到三个重复的ACK说明有三个报文段离开了网络),若再收到重复的ACK,cwnd就再加1,直到收到新数据的ACK,此时把cwnd设置为ssthresh(或ssthresh+1),然后开始执行拥塞避免。(在收到重复的ACK期间继续发不发新的报文段,要比较cwnd的值和未被确认的数据的值)。下面是《TCP详解:卷一》第21章中的一幅图,说明了上图连续收到三个重复ACK时cwnd和ssthresh的具体变化(图中单位都是字节,报文段大小256字节,在接收到第三个重复ACK时当前窗口大小为2426字节,取其一半然后四舍五入到报文段整数倍大小即为1024赋值给ssthresh,注意这里实际的实现和理论的差异哦,不要太刻板哦):
对TCP两种检测到丢包的方式(超时和三次重复ACK)产生的相应操作总结如下图:
对于这种ICMP差错,设置拥塞窗口cwnd为1,发起慢启动,但是慢启动的门限ssthresh不变化。
对于这两种ICMP差错,都可以忽略,因为这两种差错被认为是短暂现象,有可能是由于中间路由器关闭而导致选路协议要花费数分钟才能稳定到另一个替换路由。TCP还是会试图发送引起差错的数据,最终数据可能发送成功,也可能多次重传超过重传最大次数而失败。
TCP超时并重传时,并不一定重传相同的报文段,TCP允许重新分组发送一个较大的报文段,如:我们首先键入第一行”first line”发送成功,然后断开网络,键入第二行”second line”发送,此时发送失败,我们在重新发送第二行之前键入第三行”third line”,然后接上网络,这样TCP就有可能把第二行数据和第三行数据放在同一个报文段中发送。
原因:TCP是提供的是可靠的传输,它通过对发送的报文段进行确认来保证,若发送一个报文后在规定的时间内没有收到相应的确认,就需要重传报文段,超时时间的设定就是通过重传定时器实现的。
工作方法:发送一个报文段在重传定时器规定的时间内没有收到相应的确认,就重新发送这个报文段。
算法:使用指数退避算法进行重传,比如第一次1.5s重传,若还没有收到相应的确认第二次3s重传,第三次6s重传,依次类推,直到超时重传的上限,在上限可能尝试多次,若还是发送不成功则放弃。
原因:当接收方通告其接收窗口为0时,发送方停止发送,然后接收方发送更新窗口报文通告发送方,若窗口更新报文丢失,那么接收方认为成功一直等待发送方发送数据,而发送方一直等待接收方的更新窗口报文,这就形成了死锁,这就需要一个persist定时器使发送方能够周期性的询问接收方的窗口大小,破除可能的死锁状态。
工作方法:当发送方收到一个通告接收窗口为0的报文时就触发发送方的persist定时器,然后发送方若在一定时间内没有
接收到相应的窗口更新报文,就在规定的时间间隔到来时发送一个报文段(窗口探测报文window probe)询问接收方的窗口大小,接收方对这个报文确认并给出窗口大小。
算法:采用类似指数退避的算法来的发送窗口探测报文,和超时重传不同的一点是TCP从不放弃发送window probe报文,
直到窗口打开或连接被终止。
原因:当客户端收到服务器的FIN后发送ACK,但是这个ACK可能在传输中丢失,因此服务器方可能超时重发FIN,若客户端直接进入CLOSED状态那么就无法处理服务器重发的FIN,那么服务器就无法正常关闭。
工作方法:当客户端收到服务器发来的FIN后发送ACK,然后客户端进入2MSL的TIME_WAIT状态,若在2MSL时间内没有再收到服务器的FIN,客户端就假设服务器已经正确接收了ACK已经关闭,客户端也转为CLOSED状态;若在2MSL内再次接收到服务器端的FIN,那么客户端就重发ACK,并把定时器重新设2MSL。
算法:每重发一次就重新把定时器设置为2MSL,从新等待2MSL时间。
注:以上也可能服务器重发的FIN丢失或被延迟,等服务器发的FIN到达客户端时定时器已经到2MSL,客户端进入CLOSED状态了,这种情况下服务器没有正常关闭,但这种情况极少发生,我们可使用下一个保活定时器处理这种情况。
原因:检测空闲连接的另一端何时停止或重启。
工作方法:给定的连接在规定的时间内(一般2小时)没有任何动作,则服务器向客户端发送一个探查报文段,当客户端处于不同情况有不同的处理方法。以下分四种讨论:
(1)客户端依然正常运行,并从服务器可达。服务器会在两小时后把保活定时器复位,若在两小时之内有数据交互,那么保活定时器在交互数据后的两小时再复位。
(2)客户端崩溃,并且关闭或者正在重新启动。这样服务器发出的保活报文无法得到客户端的回复,若探查超时服务器会发送一定次数的这样的保活报文,若都没有得到应答,则服务器认为客户端已经关闭并终止连接。
(3)客户端崩溃并已经重新启动。此时服务器能收到客户端的响应,但是客户端刚刚重新启动不认识这个连接,因此响应是一个复位报文,使得服务器终止这个连接。
(4)客户端端正常,但是不可达。同样是不能得到应答,服务器无法区分(2)与(4)。
算法:针对不同情况已在工作方法中提到。
不主张使用保活(keepalive)定时器的理由:
(1)在出现短暂差错情况下,可能释放掉一个很好的连接(比如两端中间网络出现临时故障,如中间路由崩溃并重启,若此时发送了保活探查一端会认为另一端已经崩溃)
(2)消耗不必要的带宽
(3)若是按分组收费,在互联网上的费用增加
注:不管是客户端还是服务器端都可以使用保活功能,但是我们一般是在服务器端使用,比如NFS两端都使用了此功能,而Telnet和Rlogin仅在服务器端使用了此功能。保活定时器应该有运输层提供还是应用层提供说法不一,保活功能主要是为服务器的应用程序提供的,服务器应用程序希望知道客户端是否崩溃,从而可以代表客户端使用资源。
注:未完待续
原文地址:http://blog.csdn.net/corcplusplusorjava/article/details/46954433