TCP和UDP详解

秋天到了,注意保暖。

请注意,本文编写于 774 天前,最后修改于 100 天前,其中某些信息可能已经过时。

TCP协议:

传输层协议,提供可靠的字节流服务。而TCP则是在传输字节流服务的时候可以确认数据是否送达对方,并且因为传输的时候是有序的,更有利于数据分割。而提供可靠和有序的传输则是依靠TCP的三次握手和四次挥手来达到的。

TCP报头:


每一行是32位也就是4个字节,5行就是20个字节,所以TCP报头固定拥有20个字节的长度。

源端口(16)/目的端口(16):用于标识由那个应用程序发送的数据。里面对应的就是数据的端口号

序号(32):序号范围是[0,2^32-1次方],用于标记报文第一个字节的序号,例如:一开始发送的第一个字节序号是11,携带了10字节的数据,说明最后一个字节是20,则下一个报文的序号就是21。

确认号(32):用于收到报文后回复对方下一个报文需要发送的序列号,例如上面的21就是他期望收到的下一个报文的序列号。而不是20。

首部长度/数据偏移(4):因为TCP报头长度不确定,所以需要用这个选项要标识报文长度,不过最小肯定是20,这个选项有4个字节所以最大为1111(15)*4=60,也就是说报头的最大长度为60字节。为什么也叫数据偏移。是因为这个实际上表示了数据在报文段中的起始偏移值。

保留字段(6):如名字所说,做保留使用,暂时无作用,但是一般情况下都是置0

控制位(6):每个标志各占1比特,1表示有效,0表示无效

URG:紧急指针是否有效。有效则会告诉系统此时有重要数据需要尽快进行传输

ACK:标识确认序号是否有效,握手成功后所有进行传输的报文ACK都置位1

PSH:推送指针,指的接收方需要尽快将这个数据移交应用层,而不是等待缓存区满后上交

RST:复位,要求对方重新建立连接,当RST=1的时候,则代表TCP连接出现严重差错,必须释放连接后再重新建立连接

SYN:同步序列号,请求建立连接,在连接请求过程的时候SYN=1 ACK=0的数据带是未带确认的连接请求报文,SYN=1 ACK=1则是带了确认的请求连接报文

FIN:用于释放连接,FIN=1的时候就已经代表发送方没有数据可以进行发送了,关闭本方数据流

窗口(16):滑动窗口大小,用来告知发送端和接收端的缓存大小,以控制发送端发送数据的速率,从而达到流量控制的效果,最大为65535(2^16-1)

校验和(16):覆盖整个TCP报文段:TCP首部和TCP携带的数据,因为TCP是可靠传输协议,所以这个字段就是用于检测数据的完整性和真实性,是可靠性保证的重要手段。

紧急指针(16):只有URG=1才有效,紧急指针是一个正的偏移量,和序号字段中的值相加标识紧急数据最后一个报文段,是发送端向另一端发送紧急数据的一种方式,紧急指针指出报文段中紧急数据的字节,长度16bit

选项字段(0-40):可选,长度在0-40字节,TCP头部中非必须的字段,用于实现各类高级功能

填充字段:选项中的一部分,主要是用于填充报文段,使其成为2的整数倍便于传输

TCP连接的建立与断开:

三次握手(建立连接):

还有人做了动图,i了i了。更好理解了

过程分析:

就以动图进行分析,左边是A,右边是B

服务器TCB(传输控制模块)时刻处于开启状态,方便接受连接

A主动发起TCP连接,创建TCB,发送一个SYN置位1为1,序列号seq=x的报文

B收到TCP连接后,处理这个报文,并且发送,SYN=1,ACK=1,seq=y,ack=x+1的报文

A收到B的报文后发送ACK=1 seq=x+1 ack=y+1 的报文回去

此时TCP三次握手结束连接建立完成

SYN和ACK是指头部中控制为的指针

seq和ack则是头部中的序号和确认号,这两个理解清楚三次握手就很容易理解了。

为什么不是2次或者4次

2次,是为了防止一些错误的请求在传输的过程中丢失后又被服务器接收,然后多次建立连接。三次则不会出现这种情况。就算是在外漂泊的报文被服务器接收,服务器发送连接过来,因为客户端已经知道这个报文失去效力就不会继续建立连接。避免了浪费和不必要的错误

4次,就太过多余了,3次就已经可以完全确认双方连接正常,四次多此一举。


四次挥手(连接的断开)

如果能理解三次握手,四次挥手的原理就是类似的

双方数据交换完成后,客户机主动关闭连接,服务器被动断开连接

客户机发送FIN=1 ack=u(这个u是指的之前数据传输的时候,最后一个报文的数据序列号+1),然后

带上自己的序列号seq=v,服务器发送到后就进入FIN-WAIT-1(终止等待1)状态。TCP规定FIN即使不携带数据也会消耗一个序列号

服务器收到连接释放报文后,发送ACK=1代表自己已经确认收到报文了,并且确认序号ack=u+1 并且携带自己的seq=v,这时候服务器进入关闭等待状态(CLOSE-WAIT)

客户机收到后进入(FIN-WAIT-2)终止等待2状态,等待接收服务器的断开请求和剩下未发送完的报文数据

这时候,TCP服务器通知上层应用已经处于半关闭状态,即客户机已经没有数据需要发送。但是这个时候如果服务器还要数据需要发送,客户机也还是会接收。这个时候的等待时间就是CLOSE-WAIT的持续时间

服务器发送完剩余需要发送的数据后,发送FIN=1 ACK=1 seq=w(假设服务器在关闭等待发送了一部分没发完的报文,此时序列号为w) ack=v+1的报文,服务器发送这个报文后就进入LAST-ACK(最后确认状态),等待客户端确认

客户端收到后,发出确认ACK=1 确认序列ack=w+1 自己的序列号为u+1,发送后进入TIME-WAIT状态(时间等待状态),这时候TCP没有释放,需要等待两个最长报文段寿命(2*MSL)后,客户端才会撤销相应的TCB进入CLOSED状态

而服务器不一样,在收到客户端的确认后,立刻进入CLOSED状态,并且撤销TCB后就结束了TCP连接,所以TCP连接的时候,服务端结束的时间会更早一些。

为什么服务器可以直接进入CLOSED,而客户端需要等待呢

MSL(最大报文段寿命),TCP允许实现可以设置不同的报文MSL值

  1. 保证客户端发送的最后一个ACK报文可以到达服务器。如果第一个客户端发送的结束报文丢失了,那么服务器在等待了一个MSL后,会重新请求断开,那么客户端就会在2MSL的时间内收到这个服务器重发出来的再次请求断开的报文。并且重置2MSL的计时器。
  2. 为了防止出现三次握手中失效的连接报文重新出现在连接中,客户端发送完最后一个确认报文,在2MSL时间中,就可以使得本连接持续时间内产生的所有报文段都在这个网络中消失,使得,后续建立的新连接中不会出现旧的报文段。

为什么建立连接是三次握手,而关闭是四次挥手呢

因为建立连接的时候,服务器一直处于Listen状态。在收到连接后就直接吧ACK和SYN一起发送给对端。

而关闭的时候,服务器收到FIN,只是表示对方不再继续发送,并不代表他不继续接受。而且自己的数据也不一定那么刚刚好发完。所以在收到的时候有两种情况,1.继续发送没发完的然后关闭。2.已经发完,直接请求关闭。也就是CLOSE-TIME那段时间的长短。所以在关闭的时候双方的FIN和ACK都会分开发送,就多了一次通讯过程。

如果建立连接后发生故障?该怎么办?

TCP有保活计时器,服务器收到客户端的请求后就会刷新这个时间。通常是2H。如果2H后没有收到消息,服务器就发送探测报文段,没75min发送一次,如果10个探测报文后还是没反应,服务器端就断开连接。

TCP机制:

确认应答机制(ACK)

TCP的每个数据字节都有编号,而在对端收到后会将下次期望收到的编号发给对端,期望的编号一般就是已收到的数据+1,这样就可以确认数据是否到达对端。

超时重传机制

在数据发送的过程,如果出现丢包,无法到达对端,那么等待设定的重传时间到达后,依然没收到对端的确认应答报文,则A会重新发送数据包。

所以这个时候网络中会出现很多丢包的数据到处飘荡,而使用序列号就可以对重复的数据进行区分

超时时间设定:

如果时间设置不当,太长或者太短,都会影响传输的效果,有可能会频繁发送重复的包。

所以TCP为了保证任何条件下都有高效的通讯会进行动态计算来确定这个最大超时时间。

滑动窗口:

如果一直用ACK确认回答确认回答,一次也只能确认一个数据。其实效率十分低下,如果对端没确认就无法进行后续数据的发送。

滑动窗口的出现就解决了这个问题,可以实现一次性发送多个数据段来达到快速传输的效果

滑动窗口,肯定就是两个部分

滑动和窗口

窗口:指的是设定的不需要对端确认就可以继续发送的数据的最大值。

滑动:确认数据后,缓冲区去掉已经应答的数据的行为。

例如设定4000,那么你1000发送出去没有收到对端确认,可以继续发送2000、3000、4000直到你窗口被填满后对端依然没有回复,那么你就停止发送,如果你发送1000、2000,在发送3000的时候收到了1000的回复那么就会进行滑动,1000就会从缓冲区被去掉,那么如果后续2000没有被确认,那么就可以一直发送到5000。

滑动窗口的丢包情况分析:

  1. 对端收到数据包,B发送ACK的时候ACK丢了。

按上图进行分析,B已经收到数据包,但是1001的数据包丢了。但是2001的ACK发给对端,因为滑动窗口的原因,前面数据没被确认是不会确认后面的数据的,那么2001已经确认就可以退出1001也已经确认,所以对端即使没有收到1001的ACK也可以通过2001的ACK得知,1001已经被收到,所以不会影响后续的数据发送。

  1. A发送数据的时候B还没收到就丢了

图上也浅显易懂,没被确认之后的数据也会存入缓存区,直到前面的数据被确认后,缓存区的数据才会被确认,并且,缺少的数据会一直发送ACK回去,三次后触发重发(快重传机制,高速重发控制)

如果上图1000 和 3000字段的都丢了

那么会先一直发1000的,然后1000确认后,窗口滑动,2000已经收到就不会确认,直接一直发2001-3000的没收到的ack,然后收到后直接确认到7000。(不知道表达会不会太乱)

流量控制

每个人的设备不一样性能也不一样,所以接收端和发送端也是如此。如果发送端发的太快,接收方处理不来就会导致缓冲区被填满,继续发送的包会因为缓冲区无法存放而被丢弃,从而导致一系列反应

所以就出现了TCP协商支持接收端和发送端的处理能力来决定发送速度。

如果接收端在收到数据的时候发现自己的缓冲区快满了,会讲窗口大小设置成一个更小的值给对端,对端就会减慢自己的发送速度。如果缓冲区满了窗口会直接设置成0.

这时候发送方就会停止发送数据。但是需要定期发送窗口探测数据段,让接收端把窗口大小再告诉发送端。

传输两端的窗口协商是通过报文内的窗口字段来确认的。

16位,所以是有65536,但是窗口最大远不如此。

因为TCP首部选项中还有一个窗口扩大因子M,实际上的大小是窗口字段的值左移M位(左移一位相当于乘2)

所以如果窗口是1000,因子为2,那么大小就是4000(1000*4)

拥塞控制:

虽然有滑动窗口这个东西,但是如果一开始就发送大量数据还是有其他的问题。

所以在不清楚网络状态的情况下发送大量数据会导致网络的拥堵,可能加重网络的负担。

因此TCP有慢启动机制,通过不断地加大和修改数据发送的量,摸清楚网络的拥塞状况后在确认一个速度进行传输。

拥塞窗口:

一开始为1,收到一个ACK就加1

发送的时候拥塞窗口和对端窗口进行比较,取小的值发送。

可以看到,拥塞窗口的增长其实是很快的,呈指数型

1--2--4--8--16

所以就引入一个慢启动的阈值,到达阈值后,开始按线性方式增长

触发拥塞就会导致阈值变成最大值的一半,然后拥塞窗口重置为1,直到达到阈值后变成线性增长,直到下次拥塞后减半阈值重置窗口,如此往复

拥塞控制,还是为了TCP传输效率的最大化,又不愿意造成太大的网络压力的折中方案。

TCP小结:

为什么复杂:

因为要保证可靠性传输还要保证高性能

保证可靠性的机制:

校验和
序列号
确认应答
超时重传
连接管理
流量控制
拥塞控制

提高性能的机制:

滑动窗口
快速重传
延迟应答(我没理解)
捎带应答(我没理解)

定时器:

超时重传计时器
保活计时器
TIME_WAIT计时器

参考Blog

下面还有UDP的部分,等我摸一会了写,一摸就是一个月。惭愧。下面补全下UDP缺失的内容

UDP协议

UDP与TCP相比,最大的区别就是安全性了。

少了三次握手和四次挥手,使得数据不是那么的可靠,但是也降低了时延,所以就常常用于对时间要求非常高的地点,并且数据安全性没那么重要的地方。

例如QQ聊天啊,手机视频,网络视频这种地方。

所以TCP UDP二者有优有缺,总的来说还是根据实际场景来进行应用。

UDP的头部

image-20201128130151437.png
image-20201128130151437.png

和TCP相比,这个头部不要太简单。

源端口(16):对方回信的时候使用,不需要的时候全为0

目的端口(16):在终点交互报文的时候会用到。

UDP长度(16):指的UDP报头+数据一共占用的长度,最小长度可能是8字节,因为头部报头就占用了8字节。最长则不超过65535

校验和(16):覆盖头部和数据的校验和。确认数据完整性

召唤看板娘