1. UDP协议
1.1 UDP协议格式
系统内的UDP协议结构体:
注1:UDP协议的报头大小是确定的,为8字节
注2:可以通过报头中,UDP长度将UDP协议的报头和有效载荷分离,有效载荷将存储到接收缓冲区中等待上层解析。
注3:UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议后进行后续传输动作
注4:UDP具有接收缓冲区,该缓冲区不能保证收到UDP报文的顺序和发送UDP报文的顺序一致,如果缓冲区满了,再到达的UDP数据会被丢弃。
认知:操作系统会接收各种协议的大量的报文,因此os需要对报文做管理,而每个报文都是一个结构体(类),该类保存了报头和有效载荷等其他相关属性信息,该结构体可以通过某些数据结构统一管理起来。
注1:一个struct sk_buff对应一块内存空间记录报文数据,这和先前学习 struct file_struct 也会指向对应file是类似的
注2:网络通信,每一层都是报头+有效载荷的结构。
head和end标志整个缓冲区的开始和结束,假设数据从应用层一直递交到网络层,开始时data指向应用层数据开始地址处,tail指向尾部,每一层加入新的报头时,data往上添加
2. TCP协议
2.1 TCP协议格式
2.2 序号和确认序号
认知:TCP协议是面向字节流的协议,序号和确认序号是报文中有效载荷在整个数据流中的编号,如果将字节流抽象成一个一维数组,那么编号就是数组下标,即序号确定了报文有效载荷的开始与结束
注:SYN和FIN标志位会占用序号,其他标志位不会占用序号
2.3 四位首部长度
认知:用于记录TCP报头长度,基本单位是4字节,TCP协议规定了报头的长度范围为20~60字节,因此四位报头长度的数值应为[5,15]。
2.4 窗口大小
现象:TCP协议,通信的双方存在各自的接收缓冲区,实际通信过程中,如果多个客户端向服务端发起通信请求,那么可能会导致服务端的接收缓冲区占满。占满之后,多余的客户端请求会被丢弃,导致资源浪费和低效。
认知:为了解决上述问题,窗口大小的目的是为了流量控制,是为了告诉对方,我的接收缓冲区剩余空间大小,避免资源浪费和低效。当客户端知道了服务端的窗口大小,会控制自己的发送速度。
2.5 6/8位标志位
问:为什么TCP报头中要有标志位?
答:接收方会收到不同类型的TCP报文,针对不同的报文类型,接收方要有不同的做法,因此需要有标志报文不同类型的字段,所以就需要这个标志位。
注:实际是位段,属于哪个类型就哪个位置一,否则置零
2.5.1 SYN标志位
作用:同步、建立连接,在握手过程中使用的标志位
2.5.2 ACK标志位
作用:表明是一个应答报文
注:大多数情况下,ACK标志位常置为1,只有第一次握手时为0,后续讨论。
2.5.3 FIN标志位
作用:表明是一个关闭连接的报文
2.5.4 PSH标志位
作用:催促接收方尽快将接收缓冲区内的数据交给上层
2.5.5 RST标志位
作用:连接重置报文,双方连接出现任何问题时,都可以进行重置
3. TCP协议中的三次握手☆☆☆
3.1 第一次握手
解释:
• SYN = 1:
SYS标志位置1,SYN标志位是用来建立连接的,因此,当客户端第一次向服务端发起握手请求时,报文中的SYN标志位会被置1
• Seq:
是客户端OS随机生成的一个序列号,用于建立连接
• ACK = 0:
第一次发送请求时,不会包含应答,所以ACK置0
细节1:双方在握手过程中,实际发送的是报文!
细节2:第一次握手时,报文中只包含报头数据,不包含有效载荷
3.2 第二次握手
服务端在接收到客户端发来的报文时,服务端也会将SYN置1,同时发送一个随机序号y,同时将确认序号置为:Ack = x + 1,告知客户端,下一次从x + 1序号位置发送数据。
细节:第二次握手时,报文中依然只有报头,没有有效载荷
3.3 第三次握手
客户端接收到服务端发送来的应答,表明x+1之前的序号已经连接成功,第一次握手成功,此时客户端向服务端发送应答 Ack = y + 1,告知服务端,下次从y+1开始的序号发送数据。
当服务端再次收到客户端的应答时,双方都建立了可靠的通信。
☆☆☆细节:ACK不占用序号,所以在双方三次握手完毕,建立起连接后,客户端的确认需要就为seq = x + 1;客户端在后续发送带有有效载荷的数据时,就从该序号开始,这样双方在第一次通信时,就能够保证发送方的起始序号和接收方的确认序号能够一致
3.4 细节问题
问:前面我们说了,序号是用来形容有效载荷的,而且第一次握手和第二次握手中,不包含有效载荷,那么第一次握手和第二次握手中的序号Seq是什么?
答:SYN和FIN标志位会被计入到序号中,只占1位
问:序号和确认序号究竟是什么?有什么作用?
答:
• 序号:每个字节在数据流中的开始位置,告知对方这次发送的数据是从该序号开始的,每个字节都有唯一序号,在数据发送过程中不断递增。
• 确认序号:告诉接收方期望收到的下一个字节的序号,并告知对方我已经成功收到了你从开始序号到该序号为止的数据
举两个例子:
1.三次握手(假设握手成功):
• 客户端向服务端发送序号Seq = 100,有效载荷的大小和SYN标志位决定了序号的范围,即1
• 服务端接收客户端发来的序号 Seq = 100,服务端OS解析TCP报文长度,得知序号范围为100~101,所以Ack = Seq + 1 = 101,同时随机设置一个序号Seq = 200,发送给客户端。
• 客户端接收到服务端的应答,表明从100~101的序号服务端已经全部接收完毕,客户端OS会解析服务端发送来的报文,得知序号范围为200~201,所以应答 Ack = Seq + 1 = 201,发送给服务端。
2.握手成功后正常通信(假设标志位全为0,双方有效载荷的大小为50字节):
• 客户端向服务端发送序号Seq = 100
• 服务端接收客户端发来的报文,告知客户端下次从150序号开始发送数据,同时回复Ack = 150,并随机发送自己的序列号 1000
• 客户端接收服务端的应答,客户端的数据从150序号开始发送,同时告知服务端下次数据从1050序号开始发送。
问:为什么三次握手前两次需要有SYN标志位?
答:SYN是用于建立连接的标志位,TCP通信是全双工通信,需要确认双方可以互通
问:为什么要三次握手?
答:1.验证服务端和客户端能进行全双工通信的最短的方式,本质是验证网络畅通;2.以最小成本100%确认双方通信意愿
问:假设第三次握手时,服务端没有收到来自客户端的回应?
答:那么握手就失败了,客户端觉得,只要把ACK发出,他就认为三次握手完成,但实际情况是:服务端没有接收到客户端的回应,就会导致出现一个建立是否成功认知不一致的问题,即客户端认为连接建立成功,而服务器认为连接建立失败。
此时若客户端向服务端发送报文时,服务器就会识别到当前没有成功建立连接,就会向客户端发送一个带RST标志位的报文,要求重新建立连接。
细节1:connect发起握手请求,三次握手的过程由 client OS 和 client OS 完成,accpet不参与三次握手的过程
细节2:丢包问题,当发送方收不到应答 && 接收Ack超时时
问:这个时间有多长?
答:根据网络情况定长短,以500ms为基准,第二次传以500*2为基准 ,第三次传以500*4为基准,如果500*4成功了,下次就以500*4为基准
细节3:TCP在握手时,向对方传递的不仅仅是SYN、ACK标志位,而是一整个报文,保温内部有对方的窗口大小信息,因此在握手过程中,双方就确认了对方的窗口大小信息,以便控制自己滑动窗口的大小进行流量控制
4. TCP协议中的四次挥手☆☆☆
4.1 四次挥手的过程(假设客户端主动断开连接)
第一次挥手:
客户端调用close(fd); 关闭套接字,发送带有FIN标志位的报文,表示要关闭客户端,客户端进入FIN_WAIT_1状态;
第二次挥手:
服务端接收客户端发来的FIN报文,返回带有ACK的报文,表示服务端知道客户端要关闭了,此时服务端进入CLOSED_WAIT状态
第三次挥手:
服务器处理完剩余数据后,主动发送带有FIN标志位的报文,表示要关闭服务端,服务端进入LAST_ACK状态
第四次挥手:
客户端收到服务端的FIN后,发送ACK表示客户端知道服务端关闭了,此时客户端进入TIME_WAIT状态,大约两个MSL时间后,客户端完全关闭进入CLOSED状态
细节:上述是客户端主动断开连接的情况,如果是服务端主动断开连接,对应状态变化只需要互换即可。
4.2 细节问题
问:如果客户端关闭/退出,服务端不关会出现什么情况?
答:服务端会一直处于close_wait状态,依旧占用fd,连接没有释放 → 服务端文件描述符泄漏问题
注:客户端关闭时,服务端也需要调用close(),关闭对应套接字,即发起第三次挥手过程。
问:MSL是什么?
答:MSL:TCP协议通信双方互发的历史报文中,存活时间最大的报文。
问:为什么要等待两倍的MSL?在回答这个问题之前,需要对另一个现象做一下描述。
现象:当发送方发送一个数据时,发送方判断超时,认为该数据已经丢失,但实际情况可能是该数据还在路由器的等待队列当中,即还存在于网络当中。此时如果断开的服务器立马重启,那么在建立连接的过程中,网络中的数据可能会被重新接收,从而影响建立连接的过程
答:为此发起断开的那一方在最后一次发送ACK时,需要处于TIME_WAIT状态同时等待两倍的MSL时间,为的就是网络中两个方向上残存的报文消散,避免引起下一次建立通信连接时出错。
注1:这也就是为什么主动断开的那一方为什么无法立刻重启服务器,因为他处于TIME_WAIT的状态,对应的端口号无法被使用,得换一个端口号才能重启。历史上被丢弃的报文就不会和新的连接配得上(源ip port 目的 ip port),双方os就能够甄别出来这是陈旧报文
注2:如果需要让服务端又要处于TIME_WAIT状态,又要立即重启,需要重新设置套接字:
5. 滑动窗口
滑动窗口的作用:用于流量控制,是流量控制的具体实现方案
滑动窗口的大小:无需等待确认应答而可以继续发送数据的最大值
滑动窗口是发送缓冲区的一部分:窗口左边是已经发送完毕的数据,会被清空;窗口内部是待发送的数据;窗口右边是尚未发送的数据。
问:滑动窗口的大小有谁决定?
答:滑动窗口的大小由对方接收能力决定,简单点的话,滑动窗口的start = 报文确认序号
问:滑动窗口可以向左滑动吗?
答:不可以
问:滑动窗口可以变小、变大、不变以及为零吗?
答:可以
问:如果对端的窗口大小满了呢?
答:滑动窗口的大小设置为0,
1. 发送端周期性的进行窗口探测(发送只带报头的报文)来获取对端窗口大小属性。
2. 对端窗口更新时也会向发送端发送窗口更新的报文(报头)
问:如果滑动窗口内的数据丢失了怎么办?
丢失分为三种:左侧、中间以及右侧丢失,后两种丢失都可以变为左侧丢失,因此只对左侧丢失做分析
答:假设发送方发送的序号为1001~2000、2001~3000、3001~4000、4001~5000。
①. 如果现在发送方在发送时,1001~2000的数据丢失了,而2000后的数据接收方正常接收。
这里需要注意一点:那就是发送方发过来的起始序号必须和接收方的确认序号是完全相同的。接收方的确认序号为1001,那么发送方发过来数据的起始序号必须为1001,如果1001~2000的数据丢失了,后续所有接收方收到的报文的确认序号都为1001,发送方在接收接收方的应答时,就还是从1001序号开始发送报文,如果发送方收到了多个相同确认序号,就会触发快重传,否则就是慢重传。
再来复习一下确认序号的定义:假设确认序号为x,告知对方,前x个序号已经被成功接收,下一次从x+1开始的序号发送数据。
上述情况,如果1001~2000的数据接收成功,那么接收方的确认序号会被重置为seq = 2000+1 = 2001,作为应答传给发送方,告知前2001个序号接收成功,下一个数据从2001序号开始,但是如果接收方接收失败,后续收到的报文的起始序号和当前接收方的确认序号不一致,那么接收方会以1001作为确认序号来应答。此时滑动窗口的大小不会更新!
②. 如果1001~2000数据的应答丢失,发送方收到了3001的确认序号,说明3001序号之前的数据已经接收完毕,此时可以更新滑动窗口的大小了,更新到3001序号处。
6. 拥塞控制
6.1 相关概念
拥塞窗口(cwnd):决定了发送端发送缓冲区滑动窗口的大小
滑动窗口大小 = min(拥塞窗口,对端接收缓冲区剩余空间)
注:网络情况是实时变化的,这决定了拥塞窗口也是实时更正的,拥塞窗口大,客户端的滑动窗口可能越大,能够发送的数据越多。
ssthresh:慢启动阈值
网络拥塞:在TCP网络通信过程中,出现大面积的丢包问题。
6.2 拥塞控制策略
具体方式:
1. 慢启动阶段
拥塞窗口指数增长,开始增长慢,探测网络情况,后续增长快,尽快占用带宽
2. 拥塞避免阶段
当拥塞窗口超过慢增长阈值时,开始线性增长,避免过快增长导致出现网络拥塞
3. 小面积丢包触发快/慢重传
当发生丢包时,接收方会发送重复的ACK,发送方可以快速重传丢失的数据包,
数据恢复后,拥塞窗口较小至原来的一半,并通过线性增长逐步恢复
4. 大面积丢包重新进入慢启动阶段
如果发生网络拥塞,慢开始阈值变为拥塞窗口的一半,拥塞窗口从1开始重新进入慢开始阶段
注:当发生网络拥塞时,此时客户端不能立即重传数据,因为成千上亿的用户如果同时重传,会导致网络负担进一步加大
7. 延时应答
概念:接收端在接收到对方报文,可以先等一下再答复,等待期间上层可能已经报这条报文处理完了,或者先前的报文处理完了,这样窗口的大小可能不变或者变大,再应答给发送端,这样发送端的窗口有概率会增大,发送更多数据
注:这个等待时间不会超过发送方的等待时间