前言
网络协议是每个前端工程师都必须要掌握的知识,TCP/IP 中有两个具有代表性的传输层协议。
UDP
UDP 协议全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种无连接的协议。在 OSI 模型中,在第四层——传输层,处于 IP 协议的上一层。UDP 有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。
特点
1.UDP是面向无连接的
在发送端,传输层的数据传给UDP协议时,UDP协议只会给其加上一个标识,然后就传递给网络层了;在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作。
2.UDP传输不可靠
UDP通信建立不需要连接,数据想发就发,也不保证其正确性,再加上由于网络环境的因素,可能时好时坏,不能保障数据到达的准确性,而且UDP也没有相应的手段去确保数据的连续性。
3.UDP是面向报文传输的
发送方的 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。
4.UDP的首部开销很小(只有8个字节)
UDP 头部包含了以下几个数据:
- 两个十六位的端口号,分别为源端口(可选字段)和目标端口
- 整个数据报文的长度
- 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误
因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
5.UDP支持单播,多播,广播传输
TCP
TCP 协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水管中的水流。
特点
1.TCP是面向连接的协议
面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。
2.TCP是点到点传输的(单播)
每条 TCP 传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。
3.TCP是面向字节流传输的
4.TCP是可靠传输的协议
5.TCP是一个全双工的协议
TCP头部
tcp的头部如图所示
源端口和目的端口
确定点到点传输的发送方端口和接收方端口
序号
表示在这个报文段中的第一个数据字节序号
确认序号
仅当 ACK 标志为 1 时有效。TCP 使用确认号来告知对方下一个期望接受的序列号,小于此确认号的所有字节都已经收到。
TCP标记
- URG:紧急位 URG=1表示紧急数据
- ACK:确认位 ACK=1确认号生效
- PSH:推送位 PSH=1 尽快将数据交付给应用层
- RST:重置位 RST=1 重新建立连接’
- SYN:同步位 SYN=1 表示连接请求数据报文
- FIN:终止位 FIN=1 表示释放连接
窗口
占16位(最大为65535字节),窗口指明允许对方发送的数据量。为了让窗口再大一些,TCP 协议引入了 “TCP 窗口缩放”选项作为窗口缩放的调控因子。调控因子的范围:0-14。
紧急指针
紧急数据URG = 1时才使用,表示紧急数据再报文中的位置。
TCP的可靠传输
- TCP的可靠传输基于滑动窗口协议
- TCP的滑动窗口以字节为单位
滑动窗口协议
维持发送方/接收方缓冲区 缓冲区是 用来解决网络之间数据不可靠的问题,例如丢包,重复包,出错,乱序
如上图,4到10的区间里就是一个滑动窗口,他的大小为7个数据包,在4以前的数据包都是已经发送并且已经确认了的,滑动窗口内有两种情况的数据包,一种是黄色区域里的已发送未确认的序列包,这些序列包已经发送给接收端,但是还没有得到接收方的确认,而绿色部分则是还没有进行发送的,这里并不是说他们要等前面的数据确认完才可以进行发送,只是暂时还没有进行发送而已。总之就是在滑动窗口内部的数据包都是可以进行发送的。而滑动窗口后面的数据包是不能允许发送的。
每个数据包其实都是数据字节的序号范围,这个序号范围保存在了
TCP选项里面。
数据包必须要按顺序确认才能向后滑动,比如图中的4号数据包确认完成后,窗口会向后移动一个位置,此时11号数据包也会进入滑动窗口中。如果数据包的确认不是顺序的话,比如在4号数据包还没有确认时,5号数据包确认了,此时窗口也是不会向后滑动的。
如果我们这个 Ack确认 始终不来怎么办呢?
TCP有一个 超时重传 的机制,每次发送一个数据包都会设置一个计时器(超时计时器),在一定时间内没有收到确认的ACK,则会进行超时重传,对该数据包进行重新发送。注意此时如果上一次的数据包发送接收到了,也是会被废弃的。
TCP协议的流量控制
发送方不能无脑的发数据给接收方,要考虑接收方处理能力。
如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。
为了解决这种现象发生,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。
- 流量控制是控制发送方的发送速率
- 流量控制是基于滑动窗口实现的
- 流量控制是控制点到点之间的传输
如上图所示,TCP 通过让接收方指明希望从发送方接收的数据大小即
rwnd(接收窗口大小)来进行流量控制,如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。
窗口关闭潜在的危险
这会导致发送方一直等待接收方的非 0 窗口通知,接收方也一直等待发送方的数据,如不不采取措施,这种相互等待的过程,会造成了死锁的现象。
那么TCP是怎么解决这一问题的呢?
- 当发送方收到窗口大小为零的时候,则会启动坚持定时器
- 坚持定时器会每隔一段时间发送一个窗口探测报文
窗口探测
- 如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;
- 如果接收窗口不是 0,那么死锁的局面就可以被打破了。
窗口探查探测的次数一般为 3 此次,每次次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。
拥塞控制
前面的流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么。
一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大….
于是,就有了拥塞控制 ,控制的目的就是避免「发送方」的数据填满整个网络。
为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口 」的概念。
我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于入了拥塞窗口的概念后,此时发送窗口的值是 swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。
拥塞窗口 cwnd 变化的规则:
- 只要网络中没有出现拥塞,
cwnd就会增大; - 但网络中出现了拥塞,
cwnd就减少;
那么怎样判断网络发生了拥塞呢?
其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了用拥塞。
拥塞控制的主要的两种算法
慢启动算法:
慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,就拥塞窗口 cwnd 的大小就会加 1
比如一开始 cwnd 的大小为1,在经过
这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,下面举个例子:
- 连接建立完成后,一开始初始化
cwnd = 1,表示可以传一个MSS大小的数据。 - 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个
- 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发 2 个,所以这一次能够发送 4 个
- 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个。
从图中可以看出慢启动算法是呈一个指数级增长的,那么他增长到什么时候是个头呢?增长到头之后呢?
有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。
- 当
cwnd < ssthresh时,使用慢启动算法。 - 当
cwnd >= ssthresh时,就会使用「拥塞避免算法」。
拥塞避免算法
前面说道,当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。
一般来说 ssthresh 的大小是 65535 字节。
那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。
接上前面的慢启动的栗子,现假定 ssthresh 为 8:
- 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个
MSS大小的数据,变成了线性增长。
所以,我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。
就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。
TCP三次握手
在了解TCP三次握手之前,先来回顾一下之前讲到的TCP标记
- URG:紧急位 URG=1表示紧急数据
- ACK:确认位 ACK=1确认号生效
- PSH:推送位 PSH=1 尽快将数据交付给应用层
- RST:重置位 RST=1 重新建立连接’
- SYN:同步位 SYN=1 表示连接请求数据报文
- FIN:终止位 FIN=1 表示释放连接
如上图,就是整个三次握手的整个流程
第一次握手:
由客户端发起,客户端会初始化序列号,并将其放入TCP首部的序号中,同时将SYN标志为1,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,此时服务端被迫打开进入LISTEN 状态,之后客户端处于 SYN-SENT 状态。
第二次握手:
服务端收到客户端的SYN保温之后,也会初始化序列号,并将其放入TCP首部序号中,其次是获取之前客户端的TCP请求的首部的序号,并将确认号ack设置为其加一,并将其放入TCP首部的确认号中。接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
第三次握手:
客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次确认后ack字段填入 服务端的seq + 1,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于ESTABLISHED 状态。
经过三次握手的过程,客户端和服务端之间的确定连接正常,接下来进入 ESTABLISHED 状态,服务端和客户端就可以快乐地通信了。
为什么要三次握手?
我们可以设想一下,如果只是两次握手的话,第一次客户端想要与服务端建立连接,服务端收到客户端的SYN报文,服务端之后发出确认报文,客户端接收到,此时完成连接,而服务端在他发出确认报文后完成连接。若此时确认报文丢失,而服务端已经发送了,服务端认为已经建立了连接,此时便会服务端就占用了一个无用的资源,因为客户端没有进行连接,并不会进行数据传输。
在假设一下,客户端发送的连接请求如果超时,客户端又重发一次,之后服务端发送确认报文,与客户端进行连接,之后,原本超时的报文发送到服务端,服务端又与其建立一次连接。数据发送可能会因此而发生错误。
TCP四次挥手
双方 都可以主动断开连接,断开连接后主机中的「资源」将被释放。
上图是客户端主动关闭连接 :
第一次挥手
客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
第二次挥手
服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
第三次挥手
客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
第四次挥手
- 客户端收到服务端的
FIN报文后,回一个ACK应答报文,之后进入TIME_WAIT状态 - 服务器收到了
ACK应答报文后,就进入了CLOSED状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL一段时间后,自动进入CLOSED状态,至此客户端也完成连接的关闭。
为什么要四次挥手
因为第一挥手是客户端发送起的,此时是客户端的数据停止了传输,但是客户端还能再接收数据,服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。此时还有最后一次握手也很关键,因为这次握手是确保服务端发送关闭连接的请求数据报文并没有丢失。
客户端发送确认报文后还要等待2MSL再关闭连接
当服务端收到关闭连接的确认报文后,就可以立即关闭连接了,而客户端还需要等待一个2MSL后再关闭连接,这是因为此时要确保服务端收到了这个确认请求报文。
MSL 指的是 Maximum Segment Lifetime:一段 TCP 报文在传输过程中的最大生命周期。
2MSL 即是服务器端发出为 FIN 报文和客户端发出的 ACK 确认报文所能保持有效的最大时长。
- 如果客户端在 2MSL 内,再次收到了来自服务器端的 FIN 报文,说明服务器端由于各种原因没有接收到客户端发出的 ACK 确认报文。
客户端再次向服务器端发出 ACK 确认报文,计时器重置,重新开始 2MSL 的计时。
- 否则客户端在 2MSL 内没有再次收到来自服务器端的 FIN 报文,说明服务器端正常接收了 ACK 确认报文,客户端可以进入 CLOSED 阶段,完成“四次挥手”。
UDP和TCP的比较
| UDP | TCP | |
|---|---|---|
| 是否面向连接 | 无连接 | 面向连接 |
| 传输是否可靠 | 不可靠传输 | 可靠传输 |
| 传输对象 | 可以一对多,多对多,多对一 | 只能是单播,端对端连接 |
| 首部开销 | 首部开销比较小,只有8个字节 | 首部最小 20 字节,最大 60 字节 |
| 适用场景 | 适用于实时通信等对速率要求高,不要求数据可靠传输的 | 适用于可靠传输 |
总结
TCP和UDP的知识肯定是不止这么点,这里只是对一些基础的知识进行一些总结。
参考文章:
你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?
声明
本文首发于个人博客:blog.codedogs.top