文章目录
- 一、UDP协议
-
- 二、TCP协议
-
- 1.概念
- 2.报文格式
- 3.TCP连接和关闭
-
- 3.1 三次握手建立连接
- 3.1 四次挥手断开连接
- 4. 滑动窗口
- 5. 流量控制
- 6. 拥塞控制
-
- 7. 延迟应答
- 8. 捎带应答
- 9 粘包问题
-
- 10. TCP小结
- 三、TCP和UDP的比较
一、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的比较
- TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;由于TCP有确认应答机制,所以效率相对于UDP来说,没有那么高。
- UDP用于对高速传输和实时性要求较高的通信领域,例如,早期的QQ,视频传输等。另外,UDP可以用于广播。