TCP是面向连接的传输层层协议,可以为应用层提供可靠的数据传输服务。所谓的面向连接并不是真正意思上的连接,只不过是在发送数据之前,首先得相互握手,也就是说接收方知道你要发数据给它了。而UDP是面向无连接的传输层协议,并不提供可靠的数据传输。有一个很恰当的比喻:UDP传输就类似于写信,接收方事先并不知道你要写信给他;而TCP传输就像是打电话,必须等对方按了接听键你才能更他通话。
那么TCP又是如何来实现面向连接和可靠性服务的??在讨论TCP的可靠数据传输之前,我们先看看最简单的传输层服务UDP。
1、UDP
源端口号/目的端口号:同TCP首部中端口号的作用相同
首部长度:报文段中的字节数(首部加数据)。
校验和:差错检测,用于确定当UDP报文段从源到达目地移动时,其中的比特是否发生了变化。
检验和如何计算??
包括三部分:UDP伪首部、UDP首部、UDP数据部分。伪首部如下所示:
其中,协议字段:TCP为6,UDP为17,UDP长度即为UDP(包括UDP头和数据部分)的总长度。
- 首先把UDP伪首部添加到UDP的前面,然后把UDP首部中的检验和字段填0,把所有的位划分成16位的字
- 把所有16位的字相加,如果遇到进位,则将高于16字节的进位部分的值加到最低位上,如:
- 1011 1011 0101 1110 + 1111 1100 1110 1100 = 1 1011 1000 0100 1010
- 那么把1 1011 1000 0100 1011最高位的1加到最低位上得1011 1000 0100 1011
- 将所有字相加得到的结果为一个16位的数,将该数取反即为检验和字段
从UDP的首部我们就可以看到,UDP是一个很简陋的传输层协议,只负责从发送端的应用层接收数据,封装层UDP报文段,然后交给下层发送到接收端;在接收端,UDP从下层接收数据,然后送达应用层。在该传输过程中,UDP之提供一个基本的差错检测服务,如果检测没有错误,就直接交给应用层;否则直接丢弃。
下面我们来看一下TCP提供的可靠传输服务:
2、TCP
源端口号/目的端口号:用于多路复用/分解来自或送到上层应用的数据。什么意思呢?处于应用层的进程可能有很多,每个进程都有可能通过传输层发送数据到因特网或者通过传输层从因特网中接收数据。那么当传输层从因特网中接收到数据应该发送给应用层中的哪个进程?或者如何知道从应用层收到的数据是属于应用层中的哪个服务??其实这些的实现都是通过端口号的标识的。应用层中的每个网络服务都对应着一个端口号,通过端口号来标识对应的服务。所以说端口号是将传输层绑定到应用层的粘合剂。
序号和确认号:被用来实现可靠数据传输服务。
接收窗口字段:指示接收方接收缓冲区剩余大小,用于流量控制。
首部长度字段:TCP首部中有一个选项字段的存在,也就是说TCP首部的长度是可变的,所以需要指明首部的长度。
选项字段:用于发送方与接收方协商最大报文段长度(MSS)时,或在高速网络环境下用作窗口调节因子时使用。还定义了一个时间戳选项。
RST、SYN、FIN比特:用于连接的建立和拆除。
PSH比特:当PSH比特被设置时,表明接收方应该立即将数据交给上层。
URG比特和紧急数据指针:URG比特指示报文段里存在着被发送端的上层实体置为“紧急”的数据;紧急数据的最后一个字节由16bit的紧急数据指针字段指出。当紧急数据存在并给出紧急数据尾的时候,TCP必须立即通知接收端的上层实体。
检验和字段:同UDP检验和,提供差错检测。
TCP 如何保证数据传输的可靠性??
(1) 在发送数据之前,进行三次握手,保证与接收端相互可靠通信。下面来讲一个三次握手的过程:
初始状态客户端和服务器都为CLOSED状态,服务器打开listen监听客户连接进入LISTEN状态;然后客户端发送一个SYN包,序列号为j,此时客户端进入SYN_SENT状态;当服务器接收到SYN包时,服务器进入SYN_RECV状态,并且发送一个带SYN的ACK,确认号为j+1,序列号为k;当客户端收到这个带SYN的ACK时,客户端进入ESTABLISHED状态,对于客户端来说,已经确认可以与服务器通信了,所以客户端就可以发数据给服务器了,此时客户端发一个ACK(ACK中可以包含数据信息)到服务器,确认号为k+1;在服务器接收到ACK之前,三次握手还没有完成,虽然客户端可以发数据给服务器,但是只能包含在ACK中,而服务器并不能发数据到客户端,只有当收到ACK后,服务器端进入状态ESTABLISHED状态。自此,三次握手完成,客户端可以与服务器端已经建立了连接,可以互相发送数据。
一定要进行三次握手么,不能只进行两次或者四次??
其实这个问题的本质是因特网中信道不可靠, 但是要在这个不可靠的信道上可靠地传输数据,三次握手是最小的理论值。
如果只进行两次握手,那么当客户端发送一个SYN分组后,会发生两种情况:
情况一:服务器接收到了这个SYN并返回ACK,无论客户端是否接收到了ACK,服务器都认为已经与客户端建立连接了,于是就开始向客户端发送数据。但是如果客户段没有收到ACK,那么客户端会认为与服务器没有建立连接,就不会接收服务器发来的数据,也就是说直接丢弃服务器发来的数据,服务器发出的消息超时了,就重复发送数据,这就产生了死锁。
情况二:客户端发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达服务器。本来这是一个早已失效的报文段。但服务器收到此失效的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求。于是就向客户端发送ACK,但是此时客户端没有发出请求,所以并不会理睬这个ACK,而服务器又开始发数据给客户端了,这时候,客户端又把这些数据都丢弃了,而服务器发出的消息超时了,就重复发送数据,也产生了死锁。
(2) 通过确认和重传机制来保证数据的完整性和按序交付
TCP把数据看成是无结构和有序的字节流,所以上面所说的报文段的序列号是该报文段首字节的字节流编号,而报文段中的确认号是主机期望从客户端收到的下一个字节的序号。我们来举个例子:
假设TCP从应用层接收到3000个字节长度的数据,而TCP最大报文长度MSS为1460,那么就要对数据进行分段,第一段数据为0~1459字节,第二段为1460~2919字节,第三段为2920~2999字节,那么这三个报文段的序列号分别为0、1460、2920。
假如服务器端接收到客户端发过来的第一个报文段0~1459字节,那么它期望收到的下一个字节的序列号为1460,那么在返回给客户端的ACK中确认号即为1460,然后服务器又收到客户端发来的2920~2999字节的报文,但是未收到1460~2919字节,那么服务器端继续期望下一个接收字节为1460,所以返回的ACK中的确认号依旧为1460。TCP只确认直到第一个未收到字节之前的字节,所以TCP提供的是累积确认。接收方保留失序的字节,同时等待缺少的字节来填补间隔。
当然在如此错综复杂的网络中,即使三次握手建立连接了,也不可能每次发送数据都能成功到达目的地。客户端每次向网络中发送一个报文号,其实还会继续缓存该报文,指导收到服务器端发过来的ACK确认服务器收到了该报文,然后才会丢弃。但是当发送的报文段在网络中发生丢包了或者产生了比特出错又或者服务器返回的ACK丢失了,那么客户端将都收不到ACK。那么怎么办?总不能一直等着吧?
客户端通过一个定时器超时机制来保证客户端不会无限制地等待。也就是当发送一个报文段后,就启动定时器,当发生超时了还未收到服务器发来的ACK时,客户端就重新发送该报文段。但是设定多长时间呢??从客户端发送一个报文到接收到ACK相当于一个来回,我们用往返时间RTT来表示,设定的这个定时器时间至少得大于RTT吧。如果是ACK丢失了,那么服务器如果收到了这个重发的报文,那么数据不就重复了么??服务器通过序列号来保证数据的无冗余,当服务器收到了这个重复的数据包时,便知道客户端没有收到ACK超时了,就直接把它丢弃,然后返回给客户端一个最新的ACK。
(3) TCP提供了流量控制和拥塞控制
流量控制其实是一个速度匹配服务,也就是说发送方发送数据的速率要与接收方应用程序读取速率相匹配,以消除接收端缓冲区溢出的可能性。在TCP首部中有一个字段叫做接收窗口字段,它就是用来通知发送端服务器上剩余的缓冲区的大小(rwnd)的。
TCP提供的拥塞控制并不是网络辅助的拥塞控制,而是端到端的拥塞控制,因为IP层并不向端系统提供显式的网络拥塞反馈。那么TCP发送方如何限制它的发送速率?发送方又如何知道路径上是否拥塞?
上面提到当数据包在网络中丢失时就可能发生超时,而服务器段可能收到冗余的数据包,当然客户端也不例外,也可能收到冗余的ACK。所以我们把丢包事件定义为:要么出现超时,要么收到来自接收端的3个冗余的ACK。当丢包事件发生了,客户端就知道链路上存在拥塞。
发送端维护着一个拥塞窗口(cwnd),一个发送方的缓冲区中未被求确认的数据量不会超过cwnd和rwnd(流量控制中接收窗口字段,服务器上剩余的缓冲区的大小)的最小值。这个约束限制了发送方未被确认的数据量,也就间接限制了发送速率。
其实TCP是按照如下原则来设置发送速率:
- 一个丢失的报文段意味着拥塞,因此当丢失报文段时应降低TCP发送方的速率。
- 一个确认报文段指示该网络正在向接收方交付发送方的报文段,所以,当对先前未确认报文段的确认到达时,能够增加发送方的速率。
- 因为IP层并不向上层提供显式的网络拥塞反馈,所以TCP是通过ACK和丢包事件来充当隐式信号进行带宽探测。
那么问题又来了cwnd的值又该如何设置呢??
通过TCP拥塞控制算法:慢启动、拥塞避免、快速恢复。
关于拥塞控制算法具体实现过程说来话长,下一篇博文持续更新,敬请关注博主。
版权所有,欢迎转载,转载请注明出处。