# 概述
运输层向它上面的应用层提供通信服务。
网络层的已经能够实现两台主机之间的通信,为什么还需要运输层呢?
两台主机间的通信其实是一台主机的一个进程与另一个主机中的一个进程的通信,因此严格来说,两台主机间的通信其实是两台主机中的应用进程的互相通信,IP协议虽然能够把分组送到目的主机,但是这个分组还停留在主机的网络层而没有交付主机中的应用进程。
运输层有一个很重要的功能,复用和分用,这里的复用是指发送方不同的应用进程都可以使用同一个运输层协议传送数据(当然需要加上适当的首部),而分用是指接收方的运输层在剥去报文首部后能够把这些数据正确交付目的应用进程。
运输层提供应用进程间的逻辑通信。逻辑通信的意思是:从应用层来看,只要把应用层报文交给下面的运输层,运输层就可以把这报文传送到对方的运输层,好像这种通信就是沿着水平方向直接传送数据,但事实上这两个运输层之间并没有一条水平方向的物理连接。
# UDP
特点
- UDP是无连接的,即发送数据之前不需要建立连接,减少了开销和发送数据之前的时延
- UDP使用尽最大努力交付,即不保证可靠交付
- UDP是面向报文的,发送方的UDP对应用程序交下来的报文,直接添加首部后就交付给IP层。
- UDP没有拥塞控制,因此即使网络出现拥塞也不会使源主机的发送速率降低,这对某些实时应用很重要,如实时视频会议等,允许网络拥塞时丢失一些数据,但是不允许数据有太大的时延。
- UDP支持一对一、一对多、多对一和多对多的交互通信
- UDP的首部开销很小,只有8个字节,比TCP的20个字节的首部要短
UDP的首部格式
- 源端口
- 目的端口
- 长度,UDP用户数据报的长度,其最小值是8(仅有首部)
- 检验和,检测UDP用户数据包在传输中是否有错,有错就丢弃
发送方检验和的计算
- 先添加12字节的伪首部
- 检验和字段设置为全0
- 将伪首部和整个UDP数据报以16位一组,如果最后不够16位则补零,按照二进制反码求和计算出这些16位字的和,将此和的二进制反码写入检验和字段,就发送这样的UDP用户数据报,当然伪首部和填充部分需要去掉。
二进制反码求和:0和0相加是0,但要产生一个进位1,0和1相加是1,1和1相加是0。若最高位相加后产生进位,则最后得到的结果要加1。
接受方检验
把收到的UDP用户数据包连同伪首部(以及可能的填充全零字节)一起,按照二进制反码求这些16位字的和,如果和的位全为1表明没有差错,否者有差错。
伪首部第一字段为源IP地址,第二字段为目的地址,第三字段全0,第4是IP首部中的协议字段的值,第5字段是UDP用户数据报的长度。
# TCP
# 特点
- TCP是面向连接的传输层协议,进程通信前需要三次握手建立连接,通信结束后需要四次挥手释放连接。
- TCP提供可靠交付的服务
- 每一条TCP连接只能是点对点的(一对一),每一条TCP连接只能有两个端点
- TCP提供全双工通信
- 面向字节流,TCP把应用程序交下来的数据仅仅看成是一连串无结构的字节流
# TCP的连接
每一个TCP连接有两个端点,这个端点就是套接字(socket),套接字 socket = (IP地址:端口号)。
每一条TCP连接唯一地被通信两端的两个端点(即两个套接字)所确定,即
TCP连接={socket1,socket2}={(IP1:port1),(IP2:port2)}
# TCP报文段的首部格式
TCP报文段包括TCP首部和TCP报文段数据部分,TCP报文段首部的前20个字节是固定的,后面4n字节是根据需要而增加的。
TCP首部:
- 源端口和目的端口
- 序号:是本报文段所发送的数据的第一个字节的序号,序号用来对字节流进行编号的,例如此时序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
- **确认号 **:期望收到的下一个报文段的序号。例如,服务端收到了客户端发送过来的报文,其序列号字段是501,而数据长度是200字节,这表明服务端正确的收到了客户端发送的到序号700为止的数据。因此,服务端期望收到客户端的下一个数据序号是701,于是服务端在发送给客户端的确认报文段中把确认号置为701;(若确认号 = N,代表直到序号 N-1 的所有数据都已正确收到。)
- 数据偏移 :指的是数据部分距离报文段起始处的偏移量,单位是字(一个字表示四个字节),实际上指的是首部的长度,因此TCP首部最大长度是4*15=60字节,减去固定的20字节,选项部分最多40字节。
- 确认 ACK :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
- 同步 SYN :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
- 终止 FIN :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
- 窗口 :窗口值作为接收方让发送方设置其发送窗口的依据。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量,单位是字节。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
- 检验和:检验范围包括首部和数据两部分。计算检验和前需要在 TCP 报文段前面加上 12 字节的伪首部,格式与 UDP 伪首部一样,但应把第 4 个字段中的 17 改为 6 (TCP 协议号为 6 ),把第 5 个字段中 UDP 长度改为 TCP 长度。
# 可靠传输的工作原理
TCP发送的报文段是交给IP层的,但IP层只能提供最大尽最大努力服务,TCP下面的网络所提供的是不可靠传输,因此TCP必须采用适当的措施才能使得两个运输层之间的通信变得可靠。
# 停止等待协议
# 无差错情况
A发送分组M1,发送完就暂停发送,等待B的确认,B收到M1就向A发送确认,A在收到对M1的确认后,再发送下一个分组M2。同样,在收到B对M2的确认后,再发送M3。
# 出现差错
在每发送完一个分组时设置一个超时计时器,在超时计时器到期后A没有收到确认,就要重传前面发送过的分组,这叫超时重传。
注意三点
- A在发送完一个分组后,必须暂时保留已发送的分组副本,以便在发生超时重传时使用
- 分组和确认分组都必须编号,这样才能明确是哪一个发送出去的分组收到了确认,哪一个分组没有收到确认
- 超时计时器设置的重传时间应当比数据在分组传输的平均往返时间更长一些
# 确认丢失和确认迟到
若B收到M1向A发出确认,但是这个确认丢失了,A在超时计时器到期后就要重传M1,假定B又收到了重传的分组M1,这是B应该采取两个行动
- 第一,丢弃这个重复的分组M1,不向上层交付
- 第二,再向A发送确认
若传输过程没有出错,只是B对分组M1的确认迟到了,A还是会在超时计时器到期后重传M1,B收到M1就丢弃,而之后A会收到两个重复的对M1确认,对重复的确认什么都不做。
像这样的可靠传输协议称为自动重传请求ARQ,意思是重传的请求是自动进行的,接收方不需要请求发送方重传某个出错的分组。
停止等待协议的缺点就是信道利用率太低,为了提高传输效率,采用流水线传输,因此TCP用到的是连续ARQ协议和滑动窗口协议。
# 连续ARQ协议
发送方维持一个发送窗口,窗口内的分组都可以连续发送出去,而不需要等待对方的确认,这样信道利用率提高了。
接收方采用累积确认的方式,接收方不需要对收到的分组逐个发送确认,只需要对按序到达的最后一个分组发送确认,这就表示到这个分组为止的所有分组都已正确收到。
# 滑动窗口协议
TCP的滑动窗口是以字节为单位的,现假定A收到了B发来的确认报文段,其中窗口是20字节,而确认号是31(这表明B期望收到的下一个序号是31,而需要30为止的数据已经收到了),根据这两个数据,A就构造出自己的发送窗口
发送窗口由前沿和后沿共同确定。发送窗口通常不断向前移动,也有可能不动(一是没有收到新的确认;二是收到了新的确认但对方通知窗口缩小了,因此只有后沿缩小,前沿正好不动)。发送窗口前沿也有可能向后收缩,发生在对方通知的窗口缩小了,但是TCP标准强烈不赞成这么做。
描述一个发送窗口状态需要三个指针:P1,P2和P3,指针都指向字节的序号
- 小于P1的是已发送并已收到确认的部分,而大于P3的是不允许发送的部分
- P3-P1=A的发送窗口
- P2-P1=已发送但尚未收到确认的字节数
- P3-P2=允许发送但当前尚未发送的字节数
B的接受窗口大小是20,在接受窗口外面,到30为止的数据是已经发送过确认,并且已经交付主机了,因此B可以不用保留这些数据了,接受窗口内的序号(31~50)是允许接受的,在下图中,B收到了序号为32和33的数据,这些数据没有按序到达,因为序号31的数据没有收到,因此B只能对按序收到的数据中的最高序号给出确认,所以B发送的确认报文段中的确认号仍然是31(即期望收到的序号)。
现假定B收到了序号为31的数据,并把序号为 31~33的数据交付主机,接着把接受窗口移动了3个序号,同时给A发送确认,其中窗口值仍为20,但确认号是34,表明B已经收到了序号33为止的数据。A收到了B的确认后,就可以把发送窗口向前滑动3个序号,但指针P2不动。
A在继续发送完序号42~53的数据后,发送窗口的序号都已经用完,但没有收到确认,此时A的发送窗口已满,必须停止发送,B可能没有收到分组或者发送的确认滞留在网络中了,为了保证可靠传输,A在超时计时器到期后就重传这部分数据,并重新设置超时计时器,直到收到B的确认为止。
# 超时重传时间的选择
重传时间的选择是TCP最复杂的问题之一
- 设置太短,引起很多报文段的不必要的重传,是网络负荷增大
- 设置太长,又会使网络空闲时间增大,降低传输效率
TCP采用了一种自适应算法,保留了RTT的一个加权平均往返时间RTTs,RTT是一个报文段发出的时间与收到相应的确认的时间之差。每测量到一个新的RTT样本,就以下面公式更新一次RTTs:
新的RTTs=(1−a)∗(旧的RTTS)+a∗(新的RTT样本)
0 <= a < 1,若a很接近0,表示新的RTTs值和旧的RTTs值相比变化不大,RTT更新较慢。若选择的a接近1,则表示新的RTTs值受新的RTT样本的影响较大,RTT值更新较快。RFC6289建议的a值是1/8。
超时重传时间RTO(RetransmissionTime-Out)应该略大于上面得出的加权平均往返时间RTTs,RFC6289建议使用下式计算RTO:
RTO=RTTS+4∗RTTD
RTT_D是RTT的偏差的加权平均值,第一次测量时RTT_D为RTT的一半,在以后的测量中,使用下式计算:
新的RTTD=(1−β)∗(RTTD)+β∣RTTS−新的RTT样本∣
β的推荐值是1/4。
重传样本问题
若发送出一个报文段,设定的重传时间到了还没有收到确认,因此重传,经过一段时间后收到了确认。此时无法判断:此确认是对第一次报文段的确认,还是第二次报文段的确认?
修正的Karn算法:报文段每重传一次,就把超时重传时间RTO增大为原来的2倍,当不再发生重传时,再根据上面的公式计算RTO。
选择确认SACK
假设收到的报文段无差错,只是未按序号,中间还缺少一些序号的数据,那么能否设法只传送缺少的数据而不重传已经正确到达接受放的数据,如下图所示,给发送方返回的确认号是1001,如何不重传1501到3000和3501到4500的数据?选择确认是一个解决方法。
要使用选择确认SACK,那么在建立TCP连接时,就要在TCP首部的选项中加上“允许SACK”的选项。
首部选项长度最多 40 字节,指定一个边界需要用掉 4 字节(因为序号是 4 字节),因此选项中最多可以指明 4 个字节块的边界信息(8个边界共用掉 32 字节,还有 2 字节分别用来指明 SACK 选项和这个选项要占用多少字节)。
# TCP的流量控制
利用滑动窗口实现流量控制
所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收,流量控制是接收端抑制发送端发送数据的速率问题。
在上图中,接受方一共改变了三次窗口的大小来进行流量控制,建立连接时的A的发送窗口大小为400,B在返回确认信息的时候把窗口减小到300,然后100,最后减小到0。当A的发送窗口为0时,表明A需要暂时停止发送。
A的发送窗口为零,如何重新发送数据呢?TCP为每一个连接设有一个持续计时器,只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,若持续计时器设置的时间到期,就发送一个零窗口探测报文段,而对方就在确认这个探测报文段时给出了现在的窗口值,如果窗口仍然是零,那么收到这个报文段的一方就重新设置持续计时器。
TCP规定,即使设置为零窗口,也必须接受以下几种报文段:零窗口探测报文段、确认报文段和携带紧急数据的报文段。
# 拥塞控制
流量控制是接收端抑制发送端发送数据的速率问题,而拥塞控制就是防止过多的数据注入到网络中,防止网络中的路由器或链路不致过载,它是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。
# 慢开始
发送方维持一个拥塞控制窗口cwnd(congestion window),当主机开始发送数据时,由于不清楚网络负荷情况,由小到大逐渐增大发送窗口。
开始时设置拥塞控制窗口cwnd=1,每经过一个传输轮次,拥塞窗口cwnd就加倍,一个传播轮次就是把拥塞窗口cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认的过程。
一个传播轮次所经历的时间就是一个往返时间RTT,例如拥塞控制窗口cwnd的大小是4个报文段,那么这是的往返时间RTT就是发送方连续发送4个报文段,并收到这4个报文段的确认,总共经历的时间(跟超时重传处的RTT定义不同?)。
# 拥塞避免
拥塞避免就是让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT(或一个传输轮次)就把发送方的拥塞窗口cwnd加1。
为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh,当
- cwnd < ssthresh时,使用慢开始算法,cwnd > ssthresh时,使用拥塞避免算法
- 刚开始设置拥塞窗口cwnd=1,使用慢开始算法,每经过一个传输轮次将窗口加大两倍,当窗口大小达到慢开始门限,进入拥塞避免阶段。网络出现超时,发送方判断网络拥塞,于是调整门限值 ssthresh = cwnd / 2,cwnd = 1,并重新进入慢开始阶段;
# 快重传
问题:有时,个别报文段会在网络中丢失,但实际上网络并未发生阻塞,如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞,这就导致发送方错误地启动慢开始,把拥塞窗口swnd有设置为1,因而降低了传输效率。
使用快重传算法可以让发送方尽早知道发生了个别报文段的丢失,不会导致超时重传,导致重新启动慢开始。过程如下
接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。
如下图所示,发送方只要一连收到 3 个重复确认,就知道接收方没有收到 M3 ,而立即重传 M3 。
# 快恢复
当接收到连续 3 个重复确认时,知道只是丢失了个别报文段,于是不启动慢开始,而是执行快恢复。
图5-25中 4 发生了报文段的丢失,此时调整门限值ssthresh为拥塞窗口cwnd的一半,同时设置cwnd=ssthresh,并开始执行拥塞避免算法。
# 拥塞控制流程
在拥塞控制方法中,假定了发送窗口是由网络拥塞程度来控制的,但实际上接收方的缓存空间有限,还应考虑接收方发送过来的接受窗口值rwnd,因此窗口大小应为:
发送方窗口的上限值=Min[rwnd,cwnd]
该式指出
- 当rwnd < cwnd时,是接受方的接受能力限制发送方窗口的最大值
- 当rwnd > cwnd时,则是网络的拥塞程序限制发送方窗口的最大值
也就是说,rwnd和cwnd中数值较小的一个,控制了发送方发送数据的速率
# TCP的运输连接管理
# 三次握手
首先服务端处于监听状态,等待客户端的连接请求。
客户端向服务端发送连接请求报文,这个报文中SYN=1,ACK=0,选择一个初始的序号 x。
服务端收到连接请求报文,如果同意建立连接,则向客户端发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。
客户端收到服务端的连接确认报文后,还要向服务端发出确认,ACK=1, 确认号为 y+1,序号为 x+1。
服务端收到客户端的确认后,连接建立。
TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。 ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
为什么要三次握手?
主要防止已经失效的连接请求报文突然又传送到了服务器,造成不必要的资源浪费。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
# 四次挥手
- 客户端发送连接释放报文,FIN=1,序号为u(等于前面已经传送过去的数据的最后一个字节的序号加1)。客户端进入 FIN-WAIT-1(终止等待1)状态
- 服务端收到之后发出确认报文,ACK=1,序号为v,确认号为u+1,服务端进入CLOSE-WAIT(关闭等待)状态,客户端收到确认进入 FIN-WAIT-2(终止等待2)状态。此时 TCP 属于半关闭状态,服务端能向客户端发送数据但是客户端不能向服务端发送数据。
- 当服务端不再需要连接时,发送连接释放报文,FIN=1,确认号是u+1,序号为w(等于v+第二次挥手后服务器向客户端发送的数据的字节数),服务端进入LAST-ACK(最后确认)状态。
- 客户端收到后发出确认报文,ACK=1,序号为u+1,确认号为w+1,客户端进入TIME-WAIT(时间等待)状态,等待 2 MSL(最大报文存活时间)后释放连接(即进入CLOSED状态)。
- 服务端收到客户端的确认后释放连接(也进入CLOSED状态)。
MSL(Maximum Segment Lifetime),最大报文存活时间,TCP允许不同的实现可以设置不同的MSL值。
为什么客户端最后还要等待2MSL?
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
为什么要进行4次挥手?
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
保活计时器
设想这样一个场景,客户已主动与服务器建立了TCP连接,但是后来客户端的主机突然故障了,服务端不能白白等待下去浪费资源,服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两个小时,若两个小时都没有收到客户的数据,服务器就发送一个探测报文段,以后每隔75秒中就发送一次,若一连发送10个探测报文段都没有响应,服务器就认为客户端出了故障,接着就关闭这个连接。
# TCP粘包问题
TCP是面向字节流的,它会把应用层交下来的报文拆分成报文段然后发送出去,这可能会导致两个报文的部分数据粘合到一起。给每个包的头部加一个包长,接收方按照包长区分每个包。
# TCP和UDP的区别
用户数据报协议UDP: 它提供无连接的、尽最大努力的数据传输服务;无拥塞控制;支持一对一、一对多、多对一、多对多的交互通信;
传输控制协议TCP: 提供面向连接、可靠的数据传输服务;有流量控制、拥塞控制;提供全双工通信,面向字节流;每条TCP连接只能一对一。
最主要的区别就是一个是无连接的一个是面向连接的,通过这两个区别就可以延申到可靠性不同,开销不同,应用场景不同。
TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、拥塞控制
确认有两种方式,一是校验和的方式,二是它有两个协议:ARQ协议和滑动窗口协议,发送方维护了一个窗口,窗口内的数据是可以发送的,接收方接收到数据之后会对按序到达的最后一个分组发出确认,发送方收到确认后就将窗口往后移动,如果某个分组在超时重传之内没有收到确认,则会重新将这个分组发送。
拥塞控制:慢开始、拥塞避免、快重传、快恢复
各自的应用
只要是应用层设计到通信的地方基本都用TCP和UDP,UDP发送数据前不需要建立连接,发送速度较快,但是不可靠,因此可以用在实时性要求高但是不需要保证可靠的场景下使用,如视频聊天、直播
TCP发送数据前需要建立连接,发送速度较慢,但是可靠,只能一对一传输,因此用在保证可靠但对实时性和要求不高的场景下使用,如文件传输(FTP),电子邮件(SMTP),远程登录(TELNET),浏览的网页是基于HTTP协议的,而HTTP协议是基于TCP。
# 使用UDP和TCP的各种应用和应用层协议
应用协议 | 中文名 | 运输层协议 | 端口号 |
---|---|---|---|
FTP | 文件传送协议 | TCP | 21 |
TELNET | 远程终端协议 | TCP | 23 |
SMTP | 简单邮件传输协议 | TCP | 25 |
HTTP | 超文本传输协议 | TCP | 80 |
HTTPS | 超文本传输安全协议 | TCP | 443 |
DNS | 域名系统 | UDP | 53 |
TFTP | 简单文件传送协议 | UDP | 69 |