一、概述
- TCP 进行可靠传输,丢包之后数据会重传,所以TCP可以保证数据的完整性。
- 使用QQ传文件、访问网站都是用的TCP协议。
- TCP传输的协议数据单元是TCP报文段。
二、TCP的主要特点
(1)TCP是面向连接的传输层协议
就是说,应用程序在使用TCP协议之前,必须先建立TCP连接。在数据传送完毕之后,要释放已经建立的连接。就好比打电话的时候,要电话先拨通了,双方都说了一句“喂”,才开始说正事一样。
(2)每一条TCP连接只能有两个端点
这个端点叫做套接字或插口,简单来说就是把端口号和IP地址连接起来就叫做套接字。IP地址是用来标记把数据发送到哪个计算机去,而端口号是用来表示当前的数据要发送给哪个应用程序。比如一台计算机的微信消息是要发送给另一台计算机的微信程序去的,而不能发送到QQ上,这样就乱套了。
(3)TCP提供可靠交付的服务
也就是说,通过TCP传输的数据,无差错、不丢失、不重复、并且按顺序到达。也就是发送的是什么样的数据,收到的也是什么样的数据,如果传送的是一个文件,如果这个文件中间有一些东西丢失了,那么收到的这个不完整的文件可能就打不开了。
(4)TCP提供全双工通信
TCP允许通信双方的应用进程在任何时候都可以发送数据。TCP连接的两端都有发送缓存和接收缓存,用来临时存放数据。在发送的时候,应用程序把数据给TCP的发送缓存后,就可以做自己的事情。TCP选择一个合适的时间把缓存中的数据发送出去。接收时候也是一样,TCP的接收缓存用来接收数据,上层的应用进程在合适的时机读取缓存中的数据即可。
(5)面向字节流
应用程序和TCP交互是一次一个数据块,这个数据块的大小每次是不一定相等的。TCP把应用程序发给他的数据看作是一串字节流。如果应用程序给了TCP共计12个大小为1的数据块,TCP在发送的时候可能先发送了一个数据块,大小为3,又发送了一个数据块,大小为5,再发送了一个数据块,大小为4。也就是说TCP不会做到应用程序怎么给它的数据块他也怎么发给另一台计算机。但是TCP会保证应用程序给他的数据块共计大小为12,它发送出去的也是12。
三、TCP实现可靠传输的原理
首先,理想的传输条件有两个特点:
1.传输的数据不产生差错
2.接收方来的及接收
所以为了保证这两点,TCP实现了,如果发送的数据出错了,就让对方重新发送;在接收方来不及处理收到的数据的时候,告诉发送方适当的减速。
要实现这两点,首先来看一下停止等待协议:
3.1 停止等待协议
全双工通信的双方即时发送方,也是接收方,为了讨论问题方便,现在只考虑A发送数据,B接收数据。
(1)无差错情况
A发送数据1给B,然后A等待B的回复,B收到数据后回复给A“数据1我收到了,你可以继续发数据了。”
然后数据A再给B发送数据2,继续上述过程,直到数据发送结束,这是无差错的情况。
(2)出现差错
A发送数据1给B,然后A等待B的回复。
这时候B有两种情况,一是B收到了数据,但是B检验一下,发现数据在传输过程中出错了,那B就会将数据丢弃,然后什么也不做。
第二种情况是,数据在发送的途中丢失了,B没收到数据,B自然也什么都不做。
这时候A等的时间太长了,这个等待时间实际上是A发送出数据,就开始计时了,超过某个时间,还没有收到B的回复,就会重新发送数据给B,这个过程叫超时重传。
有三点需要注意:
第一,A在第一次给B发送完数据以后,并没有马上把这个数据删除,因为万一出错,还要重传这个数据。
第二,数据是有编号的。发送的时候和回复的时候都要用到这个编号,收发的双方才知道哪个数据发送了,哪个数据接收到了。
第三,A发送完数据的等待时间是A发送数据的时候设置了一个超时计时器来计时的,而且这个超时时间设置的要比传输数据的平均往返时间长一些。如果这个时间设置的太短,就会产生不必要的重传,如果这个时间设置的太长,通信的效率就会很低。但是在实际网络中,数据到底经过哪些网络,以及会产生多少延时是不确定的,所以这个时间设置的具体实现其实很复杂,现在只能大概理解为,比平均传递时间长一些。
(3)确认丢失和确认迟到
如果A给B发送数据1,B收到了,所以回复了A,但是这个回复信息在网络中丢失了,那么A过了超时时间就会再给B发送一遍数据1,这时B又收到了数据1。
这时B应当采取两个措施,一是第二次收到的这个数据1,B不会交给上层。二是再给发送一遍回复消息,告诉A已经收到数据1了。
使用(1)(2)(3)的确认和重传机制,就可以在不可靠的传输网络上实现可靠的通信。
上述的这种协议通常被称为自动重传请求ARQ(Automatic Repeat reQuest)。
(4)信道利用率
停止等待协议的优点是简单,但是缺点是信道利用率太低。
如图,A只在TD的时间里使用了信道,剩下都是等待时间和数据处理的时间,信道在大多数时间都是空闲的。
为了提高传输效率,发送方就可以不使用停止等待协议,而是采用流水线传输,也就是A在发送数据的时候就一直发数据,如图所示:
image.png
当使用流水线传输时,就要使用连续ARQ协议和滑动窗口协议。
3.2 连续ARQ协议
就是发送方维护一个窗口:
image.png
发送方A维护一个大小为5的窗口,这个窗口的大小设置要根据网络情况和数据接收情况设置,可能会有动态变化。
一开始窗口大小是5,A把1~5的数据都发送出去,这时候B回复的消息也不挨个回复,而是如果B收到了1,2,3,那么他就回复给A“3之前的我都收到了”,如果B收到的是1,2,4,那么B回复给A的就是“2之前的我都收到了,从3开始发就行”,说明3在网络中延迟了,或者丢失了,A就从3再开始发。
如果A发送了1~5的数据,此时B收到了1,然后B给A发消息说,1之前的我都收到了,这时候A收到回复消息,知道了1这个数据B已经收到了,那么A就会把窗口向后移,移到2的位置。
这样做有优点也有缺点,优点是容易实现,即使确认丢失了也不必重传。比如B发送了两条回复消息“2之前的都收到了”“5之前的都收到了”,如果第一条回复消息在网络中丢失了,那么A也不会重传1,2的数据,因为A收到了第二条消息,就知道1~5的数据B都收到了。
缺点就是接收方不能告诉发送方所有的数据接收情况,如果B收到了1,2,5,6,那么B会告诉A:1,2我都收到了,但是A并不知道5,6这两个数据B也收到了,所以A会重新发送3,4,5,6。
四、TCP的首段报文格式
格式如下:
image.png
固定的长度是首部的20个字节,一行是资格字节,那么20字节就是5行。
TCP抓包:
image.png
从这里可以看到,目标端口号是多少,源端口号是多少:
image.png
分析一下各个字段的含义:
image.png
- 源端口号和目的端口:
传输层和应用层之间的关系如下:
http=TCP+80
https=TCP+443
ftp=TCP+21
SMTP=TCP+25
POP3=TCP+110
RCP=TCP+3389
共享文件夹=TCP+445
SQL=TCP+1433
DNS=UDP+53 or TCP+53
所以443说明是上层是https协议。 - 序列号Seq:
如果一段报文的序列号是301,一共携带了100字节的数据,那就说明,当前报文段的第一个数据是301,最后一个数据是400。如果还有下一个报文段,下一个报文段的第一个字节应该是401,序列号就应该是401。 - 确认号ACK:
是期望收到对方下一个报文段的第一个数据字节的序号。例如,如果B收到了A发送来的数据,而且这个数据是正确的,如果当前数据的序列号是501,数据长度是200字节,说明B收到了A发送过来的数据,到700为止都收到了,所以B期望收到A的下一个数据的序列号是701,所以B在给A的确认报文段中,把确认号置为701,当前报文的确认号也是701。 - 数据偏移(Header Length)
实际上这个字段记录了TCP首部的长度,因为TCP首部有可变长度,所以这个字段就是必要的。这个字段占4位,4位二进制能表示的最大的十进制数字是15,15*4字节,所以TCP首部的最大长度为60字节,去掉固定长度部分,TCP首部的可变部分最长就是40字节。 - 保留
保留为今后使用,当前的保留位都为0。 - Flags(6个控制位)
看一下抓包的情况:
image.png
URG:当URG=1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送,而不要按原来的排队顺序来传送。当URG置1时,发送应用进程就告诉发送方的TCP有紧急数据要传送。于是发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。这时要与首部中紧急指针( Urgent Pointer)字段配合使用。
ACK:仅当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。
PSH:当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,TCP就可以使用推送(push操作。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地(即“推送”向前)交付给接收应用进程,而不再等到整个缓存都填满了后再向上交付。虽然应用程序可以选择推送操作,但推送操作还很少使用。
RST:当RST=1时,表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。RST置1还用来拒绝一个非法的报文段或拒绝打开一个连接。RST也可称为重建位或重置位。
SYN:在连接建立时用来同步序号。当SYN=1而ACK0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使sYN=1和ACK=1。因此,SYN置为1就表示这是一个连接请求或连接接受报文。
FIN:用来释放一个连接。当FIN=1时,表明此报文段的发送方的数据已发送完毕,并要求释放连接。 - 窗口
窗口指的是发送本报文段的方的接收窗口。例如,设确认号是701,窗口字段是1000.这就表明,从701号算起,发送此报文段的一方还有接收1000个字节数据(字节序号是701~1700)的接收缓存空间。窗口字段明确指出了现在允许对方发送的数据量。窗口值是经常在动态变化着。 - 校验和
用来计算接收的数据是否有误。 - 紧急指针
紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。即使窗口为零时也可发送紧急数据。
五、选择确认SACK
了解过TCP首部之后,有一个问题:前面说到如果A给B发送了数据1,2,3,4,5,如果B收到了1,2,4,5,就会告诉A:我收到了1,2,这样A就还要给B发送3,4,5,所以选择确认实现了,让B可以告诉A:1,2,4,5我都收到了。
TCP首部没有哪个字段可以实现这个功能,所以,如果AB要使用 选择确认,那么在建立TCP连接时,就要在TCP首部的选项中加上“允许SACK”的选项,原来的确认号字段用法不变。
六、TCP的拥塞控制
流量控制是点对点的,比如A给B发送数据,一次性发送了100个,B如果接收不过来,就会告诉A,你慢点发,我接收不过来,这就是流量控制。
而拥塞控制是防止过多的数据注入到网络当中,是一个全局性过程。比如这个网络当中有100台计算机,这个网络可以处理的数据量为1000,A给B发送100个数据,B能接收,但是如果100台计算机同时发送出去100个数据,对方计算机即使能接收,这个网络也处理不过来。
如图:
image.png
随着提供的负载的增大,网络吞吐量的增长速率逐渐减小。在网络吞吐量还未达到饱和时,就已经有一部分的数据包被丢弃了。当网络的吞吐量明显地小于理想的吞吐量时,网络就进入了轻度拥塞的状态。当提供的负载达到某一数值时,网络的吞吐量反而随提供的负载的增大而下降,这时网络就进入了拥塞状态。当提供的负载继续增大到某一数值时,网络的吞吐量就下降到零,网络已无法工作。这就叫死锁。
几种拥塞控制方法
慢开始、拥塞避免、快重传、快恢复。
(1)慢开始和拥塞避免
A给B发送数据,A维持一个拥塞窗口(cwnd)的值,让自己的发送窗口等于拥塞窗口。A控制拥塞窗口的原则是,如果当前网络没有拥塞,就把拥塞窗口扩大一点,如果发生了拥塞,就把这个拥塞窗口缩小一些。
A知道是否发生了拥塞实际上是猜测,如果A给B发送数据,B及时的回复确认收到了,就假设当前网络没有拥塞,如果B的确认超时到达或者没有到达,就假设当前网络发生了拥塞。
还有一个概念是一个最大报文段:MSS。
最大报文段长度(MSS)是TCP协议的一个选项,用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度(不包括文段头)。
慢开始是这样的,在最开始不知道网络情况的时候,先发送几个数据试试网络情况,如果没有拥塞再逐渐扩大拥塞窗口。所以在一开始没有发送数据的时候,拥塞窗口的值设置为一个最大报文段(cwnd=1),可以理解为可以发一个数据包。
最开始cwnd=1,然后A发送一个报文段,收到B的回复后,A把cwnd的值2,cwnd=2,这时候A再发送两个报文段,如果按时收到了B的回复,再把cwnd2,这时候cwnd=4。
这个cwnd的值是有一个上限的,这个上限叫慢开始门限(ssthresh)。
当cwnd < ssthresh时,使用慢开始算法。
当cwnd > ssthresh时,使用拥塞避免算法。
当cwnd = ssthresh时,使用两种算法都可以。
慢开始是每次把cwnd的值乘2,避免拥塞是每经过一个往返时间RTT就把发送方的cwnd+1,这样cwnd就按线性规律缓慢增长。
无论在开始阶段还是在拥塞避免阶段,只要发送方觉得出现了网络拥塞,也就是没有及时收到确认,就把慢开始门限的ssthresh设置为当前出现拥塞时的窗口值的一半(ssthresh不能小于2),然后把拥塞窗口cwnd重新设置为1,执行慢开始算法,当cwnd增长到ssthresh的值的时候,再使用拥塞避免算法。整个过程如图所示:
image.png
(2)快重传和块恢复
快重传算法要求接收方每收到一个失序的报文段后,就立即发出确认。
例如,A给B发送数据,A发送了1,2,3,4,5,五个报文段,这时候B接收数据,B收到1的时候,发送给A“1收到了”,接收2的时候,发送给A“2收到了”,3在网络中延迟或丢失了,B又收到了4,就发送给A“2收到了”,B又收到了5,就发送给A“2收到了”。这样A就收到了三个“2收到了”,快重传算法规定,发送方只要一连收到三个重复确认,就尽早重传未被确认的报文段,也就是A现在收到了三个2的确认,就尽早给B发送数据3。
然后不使用慢开始算法,因为A收到了三个确认,所以现在的网络很可能没有发生拥塞。(因为如果发生了拥塞,4和5可能都到达不了)所以这时候A把sstresh的值减半,如果原来是32,现在减为16。然后开始使用拥塞避免算法。这个过程叫快恢复。
image.png在上述过程中,都没有考虑B的接收能力,如果B一次只能接收5个数据,也就是接收窗口的大小为5,那么A的发送窗口越大,数据传输的效率只能越小,所以A的发送窗口还要考虑B的接收窗口的大小。
也就是发送窗口的值 = Min(拥塞窗口,接收窗口)
。
七、TCP连接(连接建立)
- TCP的连接分为三个阶段:连接建立、数据传送、连接释放。
- TCP连接的建立都是客户端发起的。
- 主动发起建立连接的应用进程叫客户端,被动等待建立连接的应用进程叫服务器。
TCP的三次握手
A为客户端,B为服务端。
一开始的时候,A和B的TCP都处于关闭状态CLOSED。
B的TCP服务器进程先创建传输控制块TCB,准备接受连接请求,然后B的服务器进程就处于LISTEN(收听)状态,在LISTEN状态的时候,如果有客户端发送来连接请求,就做出响应。
然后A的客户端进程也先创建一个传输控制模块TCB,然后向B发送请求报文段,这时候TCP报文首部的SYN=1,同时选择一个初始序列号seq=x。(TCP规定,SYN=1的报文段不能携带数据,但要消耗一个序列号seq),这时候客户端A就进入SYN-SEND状态,也就是同步已发送状态。
B收到A发送来的请求后,如果同意建立连接,就发送给A一个确认报文段。这个报文段的SYN和ACK都为1,ack=x+1,同时也为自己选择一个初始序列号seq=y。这个报文也不能携带数据,但是也要消耗一个序列号。这时候B的TCP进程进入SYN-RCVD状态,就是同步收到状态。
A的TCP进程收到B的确认后,还要再给B发送一个确认,ACK为1,ack=y+1,自己的序列号为x+1,即seq=x+1。(这时候就没有同步号SYN了,也就是SYN就是0了)这时候TCP的连接就建立了,A的TCP进程进入ESTABLISHED状态,也就是连接已建立状态。
B的TCP进程收到A的确认后,也进入连接已建立状态。
这个过程叫TCP的三次握手。
如图所示:
image.png
所以过程就是A给B发送建立连接的请求,B收到之后回复给A,可以建立连接。然后A再给B发送一个,好的我建立连接了。这时候三次握手的过程才完成。
但是为什么在B已经回复A可以建立之后,A要再发送一次数据报呢?
如果A不发送第二次确认的话,考虑下面的情况:
A给B发送一个建立连接的请求,如果这个请求报文因为网络的原因延迟了,B暂时没收到。超时了之后A就会再给B发送一个请求建立连接的报文,这时候B收到了,就发送给A确认连接,A和B数据传输完毕之后,断开了连接。
这时候B收到了A在网络中延迟的那个请求,就以为是A要再次建立连接,就给A发送确认连接的数据报。A收到这个数据报就看一下自己并没有要建立连接啊,就没有理睬这个数据报。如果不需要A发送第二次确认,那么这时候B已经建立连接了,那他就会等着A给他发送数据。这样B的许多资源就浪费了。
所以为了避免上述情况,TCP握手要三次。
TCP的四次挥手(连接释放)
还是假设A给B发送数据,当数据传输结束后,A和B都可以释放连接。
现在A和B都处于Established的状态,如果是A的TCP进程先发送说要断开连接,这个报文首部的FIN会置为1,序列号seq=u,这个序列号等于它前面已经传送过的数据的最后一个字节再加一,这时A进入FIN-WAIT-1状态,也就是终止等待1状态,等待B的确认。TCP规定FIN报文段即使不携带数据,也要消耗掉一个序列号。
B收到A要断开连接的请求之后会给A一个回复,确认号是u+1,报文的序列号是自己的序列号v,这个v是B前面已经传送过的数据的最后一个字节的序列号+1。这时候B就进入CLOSE-WAIT状态,关闭等待状态。
B的TCP服务器进程这时候会通知高层的应用,A已经没有要发送的数据了。这时候TCP连接处于半关闭状态。但是如果B要发送给A数据,A仍要接收。
A收到来自B的确认后,就会进入FIN-WAIT-2状态,也就是终止等待2的状态。等待B说断开连接。
如果B没有要发送的数据,就会给A发送数据报文段。这个数据报文段的FIN=1,如果B在半关闭状态又发送了一些数据,那么当前这个数据报文段的序列号就为w,确认号为上次的确认号ack=u+1,这时候B进入LAST-ACK状态,最后确认状态。
A在收到B要断开连接的请求后,发送确认报文段。这个报文段的ACK=1,ack=w+1,自己的序列号是seq=u+1,然后进入TIME-WAIT状态,时间等待状态。现在TCP连接还没有释放,必须等待2MSL后,A才进入CLOSED状态。这个MSL叫做,最长报文段寿命,FRC793建议MSL设置为2分钟,也就是说如果按照标准,A要等待4分钟才断开连接,才能开始建立下一个连接。(TCP允许根据实际将MSL设置为更小的值)
以上是一个完整的四次挥手的过程,可参考图片:
image.png
为什么A还要等待2MSL的时间再关闭?
一是如果A的确认报文在网络中延迟或丢失了,B就永远也收不到A的确认,也就是说B就永远在LAST-ACK状态了,就永远关闭不了了。
二是防止之前TCP建立连接三次握手时候的“已失效的连接请求报文段”的问题,A在发送完最后一个ACK报文段后,再经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
B只要收到了A发岀的确认,就进入 CLOSED状态。同样,B在撤销相应的传输控制块TCB后,就结束了这次的TP连接。所以,B结束TCP连接的时间要比A早一些。
除了计时MSL以外,TCP还设有一个保活计时器。有这样的情况:客户端A与服务器B已经建立了TCP连接。但后来客户端A的主机突然出故障。所以服务器B以后就不能再收到客户A发来的数据。因此,应当有措施使服务器B不要再白白等待下去。这就是使用保活计时器。服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没有收到客户的数据,服务器就发送一个探测报文段,以后则每隔75分钟发送一次。若一连发送10个探测报文段后仍无客户的响应,服务器就认为客户端出了故障,接着就关闭这个连接。
网友评论