计算机网络--UDP和TCP协议详解

一、UDP协议

1.概念

UDP是OSI(Open System Interconnection)模型中的无连接、不可靠的传输层协议,全称是User Datagram Protocol ,用户数据报协议。

UDP的不可靠体现在:

    通信双方在通信之前没有建立连接,所以客户端在发送请求时无法知道服务器的状态。 尽最大努力传输数据,不保证数据的可靠性,没有超时重传。 由于没有建立连接,所以通信双方不知道对方的接收的能力,没有流量控制机制,不断发送UDP数据包,接收方的接收缓冲区可能会因为溢出造成数据丢失。 没有任何安全机制,由于某种原因没有发送成功,UDP也不会向应用层返回任何错误信息。

UDP面向数据报: 发送方对应用程序交付的服务,添加首部之后就向下交付给IP层。应用层交给UDP多长报文,UDP就原样发送,不会拆分,也不会合并。

缓冲区:

    UDP的数据直接是由内核处理的,将内核数据传输给网络层协议; 接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了,再到达的UDP数据就会被丢弃;

全双工: UDP的socket既能读又能写

2. 报文格式

    UDP长度是16位,表示整个数据报(UDP首部+UDP数据)的最大长度。 如果校验和出错,就会直接丢弃。

二、TCP协议

1.概念

TCP的全称是Transmission Control Protocol,传输控制协议,特点是有连接、面向字节流、可靠。

有连接: 双方在通信前需要建立连接,才能开始数据的读写。TCP是全双工的,数据的读写可以通过一个连接完成。双方通信结束之后,需要断开连接,释放系统资源。

面向字节流: 发送方连续多次写数据时,先将数据放入TCP缓冲区。等到TCP要开始发送数据的时候,TCP缓冲区中的数据会被封装成一个或者多个TCP报文段发出。因此,可以看到TCP的报文段个数和执行写操作的次数没有明显的数量关系。但是对于UDP而言,没有真正意义上的发送缓冲区,直接将数据交给内核处理。发送端每执行一次写操作,UDP就会将数据封装成一个UDP数据报并且发送,接收端需要及时读取,不然可能会造成丢包。同时接收端需要指定足够大的接收缓冲区,否则一旦缓冲区满了,之后的数据可能会被丢弃。

可靠:

    确认应答机制:接收端每收到一个TCP报文段之后,都要给发送方一个应答,这样发送方才会认为对方已经收到数据了。 超时重传机制:发送方发送数据之后,由于网络拥塞等原因,数据无法到达接收方;此时,发送方会等待一个特定的时间间隔内还没有收到应答,就会进行重发。但是,发送方没有收到接收方的应答,也可能是因为ACK丢失了。因此,接收方会收到很多重复的数据,TCP议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。 TCP会对乱序、重复的数据进行整理、重排和去重,可以使用序列号对数据进行去重并且解决乱序。 流量控制:TCP支持根据接收端的处理能力,来决定发送端的发送速度。

2.报文格式

    源/目的端口号:表示数据是从哪个进程来,到哪个进程去。 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是 15 * 4 = 60。 6位标志位 (1)URG:紧急指针是否有效 (2)ACK:确认号是否有效 (3)PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走 (4)RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段 (5)SYN:请求建立连接;我们把携带SYN标识的称为同步报文段 (6)FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段 16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不 光包含TCP首部,也包含TCP数据部分。 16位紧急指针:标识哪部分数据是紧急数据。

3.TCP连接和关闭

3.1 三次握手建立连接

    第一次握手:建立连接,客户端发送SYN包(SYN = j)到服务器,SYN是同步序列编号。 第二次握手:服务器收到SYN包,必须确认用户的SYN(ACK=j+1),代表j之前的数据都已经接收到了,同时自己也发送一个SYN包(SYN=k),也就是,SYN+ACK包。 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=k+1)。

经历3次握手之后,通信双方就可以知道各自的发送和接收能力都没有问题。

举个例子 情景:A同学与B同学打电话 (1)A问B:听的到吗? 此时,A要验证自己的麦克风和B的扬声器是否正常 (2)B回复:听的到 B收到A的消息,就可以知道A的麦克风和自己的扬声器都没有问题。 B需要验证自己的麦克风和A的扬声器是否正常。 (3)A回复:好的,我知道了。 此时,A 收到B的回复,知道自己的扬声器和B的麦克风没有问题。 同时,A再回复一句话。B收到A的回复后,也知道自己的麦克风和A的扬声器没有问题。

问题:为什么不是两次握手呢? 从上述例子可以看出,如果两次的话,B无法确认自己的麦克风和扬声器是否正常。所以,A、B无法确认自己是否具有正常的收发能力。

三次挥手的目的就是投石问路,确认当前网络环境比较畅通,进行可靠传输。

3.1 四次挥手断开连接

终止连接需要四次挥手是和TCP的半关闭有关,所谓半关闭就是指,A结束了对数据的发送后,还能接受来自B的消息。

    第一次挥手: 客户端发送一个 FIN 报文,报文中会指定一个序列号。 第二次挥手: 服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了。 第三次挥手: 服务端完成数据传输,也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。 第四次挥手: 客户端收到 FIN 之后,会发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值。 注意: 此时TCP还没有释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。

问题1:为什么是4次挥手呢? 当服务器端收到来自客户端的FIN报文,由于服务器端的数据可能并没有发送结束,所以只是回复了一个ACK。等到数据发送完毕,再发送FIN报文。

问题二:2、3挥手都是服务端发起的,可以合并吗? 答案是:不可以。 接着第一个问题,因为服务器发送给客户的ACK是由内核负责的,发送的FIN是用户态负责的(服务器这端的代码调用了socket.close(),才会触发FIN)。当服务器端收到客户端的FIN后,会立即回复一个ACK。但是,FIN何时触发取决于用户代码是如何设计的,所以这两个操作不是同一时机的。

问题一、二是相互联系的,可以结合起来理解~

但是,3次握手中,当服务端收到客户端的SYN连接请求报文后,服务端可以直接发送SYN+ACK报文。服务器端发送的ACK和SYN是同一时机的,都是由内核负责的。

问题三:为什么要等待2MSL? MSL: 表示网络上任意两点之间,传输需要的最大时间。 假如最后一个ACK丢了,服务器端没有收到。对于服务器端来说,不清楚是FIN没有发送成功还是ACK丢了。所以服务器端就假设FIN丢了,就是重传FIN(超时重传)。假设客户端在发了ACK后就断开连接,那重传的FIN到了之后,就无法应答这个FIN报文。

因此,需要一段等待时间。时间到了,再关闭连接。

4. 滑动窗口

滑动窗口的意义是:在确保可靠传输的基础上,提高效率。 在刚才提到的确认应答机制中,对于每一个数据段,都要给一个ACK的确认应答。收到ACK之后,继续发送数据段。这样的缺点就是效率低,尤其是往返时间较长时。

这样一发一收的性能比较低,可以一次发多条数据,性能就会明显提高,其实就是将多段等待时间重叠在一起了。

使用滑动窗口,滑动窗口的本质就是一次批量发送一波数据,然后等待一波ACK。

第一组数据发送结束,等待ACK 当前等待1001 2001 3001 4001,等收到10001后,就可以接着发送下一组数据了(4001-5000)。 此时等待的ACK就是2001 3001 4001 5001,如果2001到了之后,就可以再发一组数据(5001-6000)。 此时等待的ACK就是3001 4001 5001 6001,以此类推。
    滑动的意思是:在等待过程中,等到一份ACK,就可以接着发下一组数据了,并不是等到所有的ACK再发送数据。 一次批量发送数据N,然后等待一波ACK,这里的N称为窗口大小。 在上图中,一次发了4组数据。在发送四组数据过程中不需要等待ACK。发送完成之后,统一等。 一份等待时间,等待多份ACK,相当于把多份等待ACK的时间压缩成了一份。 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉。

窗口示例图:

    主机A收到2001的ACK,代表1001-2000这个数据都已经收到了,可以发送下一组数据了。 发送下一组数据后,要等待的ACK范围也会发生变化。 随着ACK的到来、下一组数据的发送,这个窗口就会不断地向后移动。 当窗口越大,发送数据的效率就会越高。一份等待时间里,等待的ACK就会越多,总的等待时间就会变少。 但是,滑动窗口有一个问题就是:丢包。丢包之后如何进行重传呢,分两种情况讨论。

情况一:数据到了,ACK没到 图中有6组ACK,丢了3组。 这种情况ACK丢了不要紧,可以通过后续的ACK确认

    在发送4001之前,收到了2001。此时代表,2001之前的数据都已经收到了,所以有没有收到1001已经不重要了 在发送6001之前,收到了5001.此时代表,5001之前的数据都已经收到了,所以3001,4001没有收到已经不重要了。 情况二:数据包直接丢失了 由于1001-2000这个数据丢失了,所以B一直向A索要这段数据。即使A发送后面的数据,B依旧会索要1001到2000。 B发送3次重复的确认应答之后,A明白是数据丢失了,触发重传,就重发1001-2000这个数据段。

A在给B重发数据之前B的接收缓冲区如下图所示:

    A没有重传数据之前,1001-2000这里是有一个空缺。 A重传1001-2000且B收到之后,再次返回的ACK就是7001,因为2001 - 7000接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中,不用重传。 已经到了的数据不需要重传,只需要重传缺失的,这样效率还是比较高的,所以也成为了快重传。

5. 流量控制

接收端处理数据的速度是有限的。 如果发送端发的太快,导致接收端的缓冲区满了,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。

流量控制:TCP可以根据接收端的处理能力,来控制发送端的发送速度。

    可以把B的接收缓冲区理解为一个阻塞队列,当数据满了,就会阻塞。如果缓冲区继续发送,就会造成丢包。 因为B的接收缓冲区是有一定大小的,随着数据越来越多,空间就会变小。 随着B处理的数据越多,那么接收缓冲区的空间又会变大一些。 如果B的剩余空间大,可以让A发送快一点 如果B剩余的空间不够多了,就让A发送慢一点

举个例子~ 情景:一个固定大小的水桶,注水、出水

水装满水桶之后,接着注水,只会让水流失、浪费。 这个时候,就需要打开出水阀门。让水出去一部分,水桶有了剩余空间,又可以注水了。
水桶相当于B的接收缓冲区 发送端A不断地发送数据就相当于在注水 接收端B从缓冲区读取数据处理的过程,相当于出水

那么,问题来了:接收端如何把窗口大小告诉发送端呢?

在TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息

6. 拥塞控制

6.1 概念

虽然TCP有了滑动窗口,提高了效率。但是,在刚开始就发送大量的数据,也容易引发问题。TCP拥塞控制,限制了滑动窗口的发送速率。

发送端发送数据的快慢,不仅和接收端的处理能力有关,也和中间的链路处理能力有关。

拥塞控制的处理方案就是:

发送方一开始以较小的速率发送数据,如果数据可以很流畅的到达,那逐渐增大窗口。 当窗口增大到一定程度时,出现丢包(丢包就是链路拥堵),这个时候减小窗口。 通过窗口反复的增大/减小,找到一个合适的范围,使得拥塞窗口在这个范围内变化,达到动态平衡。

6.2 拥塞窗口的具体变化

    通过这个来限制滑动窗口的大小 滑动窗口的大小 = min(拥塞窗口,流量控制窗口) 最开始的时候,取初始窗口大小,这个值是非常小的。 慢开始只是指初始慢,增长速度非常快,指数增长 设置一个阈值,等慢开始到这个值的时候,开始线性增长 每次丢包重发的时候,慢开始的阈值就会变为原来的一般,并且拥塞窗口回归初始值。一般少量丢包,仅触发超时重传。大量丢包,认为是网络拥堵。

TCP开始通信之后,网络的吞吐量逐渐上升;等到发生拥堵的时候,吞吐量立刻下降。

拥塞控制的意义就在于TCP在提高效率的同时,又不想造成网络拥堵,从而提出的一种折中方案。

7. 延迟应答

如果接收方收到数据后立刻应答,这时候返回的窗口就比较小。如果窗口越大,网络的吞吐量就会越大,传输效率就会越高。

举个例子~

假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K; 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了; 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来; 如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;

Q:所有的包都可以延时应答吗? A:答案显然不是!

    数量限制:每隔N个包就要应答一次 时间限制:超过最大延时时间,就应答一次。

8. 捎带应答

在延时应答的基础上,我们可以发现,接受方和发送方都是“一发一收”,所以,我们在发送数据的时候,我们把ACK搭顺风车的方式发送给对方了。

9 粘包问题

9.1 概念

粘包问题中的 “包” ,是指的应用层的数据包。 TCP的协议头中,没有如同UDP一样的 “报文长度” 这样的字段,但是有一个序号这样的字段。 在传输层的角度,TCP是一个一个报文过来的,按照序号排好序放在缓冲区中。 在应用层的角度,看到的只是一串连续的字节数据。 当应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。

9.2 如何解决粘包问题

    对于定长的包,保证每次都按固定大小读取即可 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置 对于变长的包,还可以在包和包之间使用明确的分隔符

10. TCP小结

可靠性:

    校验和 序列号(按序到达) 确认应答 超时重发 连接管理 流量控制 拥塞控制

提高性能

    滑动窗口 快速重传 捎带应答 延迟应答

三、TCP和UDP的比较

经验分享 程序员 微信小程序 职场和发展