紧跟Linux_网络_传输层协议 TCP/UDP继续补充
文章目录
1. TCP通信时滑动窗口(效率)
TCP在通信时存在确认应答机制,如果收到ACK后再发送下一个报文。类似串行发送,效率太低。
TCP在发送消息时,一次发送多条数据就可以大大提高性能,这就是TCP滑动窗口。(多条发送时间重叠在一起,类似多线程发送)
注意:TCP在发送数据时不能将数据一次全部发给对端,因为需要考虑对端接受缓冲区的大小
滑动窗口:
发送方不用等待ACK一次发送数据的最大量,滑动窗口大小与对端接受缓冲区的大小有关。
注意:
-
窗口大小:表示的对方接受能力,衡量的是接受缓冲区的剩余大小
-
滑动窗口:在自己的发送缓冲区限定一块空间可以直接发送不需要ACK。当发送的数据收到响应且对方接受缓冲区还有空间时,窗口向后滑动。
-
窗口一定是向右移动或者不移动。不可能向左移动,因为窗口左边一定是已经接受到ACK的数据
-
滑动窗口实际是环形缓冲区,窗口外的数据可以被覆盖,窗口内的数据要保存起来,为了防止丢包等重传问题。
但是TCP报文到达对端不一定是有序的,可能窗口内的报文先ACK或者窗口内的报文丢包
当窗口丢包时:
- 可能是响应报文丢包,此时影响不大,因为可以根据窗口其他ACK来确认
根据TCP报头中的确认序号(发送报文的末端序列号+1,告知对方发送报文的末端序列号前的数据已经收到,下次发送从发送报文的末端序列号+1处开始发送)
eg:
因为窗口报文一起发给对端,所以发送端虽然只接收到一次报文,但根据第二次响应ACK的2001可以推断出0-1000对端已经收到了。直接从2001序号开始发送新的报文,同时窗口开始移动。
- 丢包还可能存在在报文发送时丢包的情况
- 当某段报文丢包时例如1001-2000,此时发送端会一直收到ACK:1001
- 当收到三次ACK:1001时发送端会补发1001-2000段数据
- 设ACK:1001收到了三次, 当这次补发报文收到时ACK5001,因为5000前接收端已经收到(接受缓冲区)(快重传机制)
注意:当收到ACK 1001次数少与3次则无法触发快重传机制,就使用超时重传机制。
2. 流量控制(可靠性)
数据传输过程中,如果发送方发送数据的速度太快,接受方的接受缓冲区会满。如果不加处理,会因为接受缓冲区被打满而导致丢包。
TCP支持根据接受缓冲区的大小来决定发送数据的速度,这个机制称为流量控制。
- 接受端将接收缓冲区大小放入TCP报头中的 窗口大小字段上,通过ACK告诉发送端。
- 窗口大小字段越大,代表网络吞吐量越大。
- 当发送端收到ACK,其中窗口大小比较小时,会减少发送报文速度。(通过减少滑动窗口大小来减少速度)
- 如果ACK报头中窗口大小为0,会停止发送报文,但会定期发送一个窗口探测数据段来获取接受端的缓冲区大小。
16位窗口大小为65535字节,但TCP报头上还存在选项字段(扩大因子M),实际窗口大小=16位窗口大小<<M
注意:在握手阶段,两端就已经协商了双方的接受缓冲区的大小。防止第一次数据通信时缓冲区越界。
3. 拥塞控制(慢启动)
当网络发送数据时出现大量丢包时,不是正常现象。可能是网络出现问题,可能网络太拥堵。
此时如果再进行丢包重传,只会加重网络的负担,不可取。
拥塞控制相关策略:
此时网络中的所有TCP采用慢启动机制,发送端会尽量不发送数据,一段时间后再发送少量数据。等到网络流畅后再进行正常发送。
拥塞控制这里又引入拥塞窗口:
- 窗口大小:对端接受缓冲区剩余大小。
- 滑动窗口:发送缓冲区限定一块空间可以直接发送不需要ACK
- 拥塞窗口:一次发送端发送的数据量大于拥塞窗口值时,可能会引发网络拥堵。
发送开始时拥塞窗口大小定义为1,每次收到ACK应答时拥塞窗口会增大(指数增长)。每次接收端ACK报文中的窗口大小将于拥塞窗口数值进行比较,取较小的值作为发送端发送数据的大小。(实现慢启动)指数级增长为了尽快恢复通信。
指数增长到一定程度(慢启动阈值)后改为线性增长
慢启动阈值与上一次发生网络阻塞时阻塞窗口的大小有关(一半阻塞窗口的大小)
滑动窗口大小=min(对端接受能力,拥塞窗口大小)
4. 延迟应答
接收端在接收到对端发送的报文后,根据确认响应机制ACK报文有窗口大小字段。
窗口大小字段越大代表网络吞吐量越大,所以接受端为了提高窗口大小字段,会延迟发送响应报文,等待应用层将缓冲区数据尽可能读取完毕在进行响应。(不会导致超时导致重传)
注意:
- 不是所有报文都会进行延迟应答,每隔N个报文进行一次延迟应答
- 具体N的大小以及超时时间根据操作系统而定。
5. 捎带应答(提高通信效率)
确认应答机制,接受端在ACK时可以携带要给发送端发送的数据。
ACK报文既有确认应答作用,还有发送数据的能力。提高通信效率
6. TCP常见问题
字节流
创建TCP的sock时,在内核创建发送和接受缓冲区。
发送端上层调用write。数据写入发送缓冲区,但写入缓冲区没有格式要求,可以一次写入100字节,也可以循环多次写入100字节数据。
接收端调用read函数阻塞等待接受缓冲区数据就绪。读取缓冲区数据没有格式要求,可以循环多次读取100字节,也可以一次读取100字节。
这就是面向字节流
粘包
当发送方将报文发送到对端接受缓冲区后,对端应用层按流式读取 接受缓冲区时读取方式和协议类型有关(eg:http协议按行读取),但如果上层不按照对应协议方式读取,可能应用层读取报文时不是完整的报文。这种现象就叫做粘包。
解决方法:明确报文与报文之间的边界
- 定长报文
- 特殊字符作为报文边界(http协议‘\n’)
- 自描述+定长/特殊字符(http协议’\n’+Content-Length字段)
UDP是定长报头+自描述(8字节报头长,16位UDP长度)所以不存在粘包问题
但基于TCP的应用层可能存在粘包问题:因为TCP面向字节流,TCP不维护报文之间的边界,报文与报文的分离需要应用层来做。
TCP异常
进程终止:进程终止后会释放文件描述符会关闭,可以发送FIN报文,正常进行四次挥手,正常退出连接。
机器重启:操作系统会终止所有进行,与进程退出一样,自动进行四次挥手。
拔网线:服务器无法迅速得知客户端掉线,客户端长时间不访问服务器,服务器向客户端发送数据,尝试多次后断开连接。(基于保活定时器的心跳机制,一般由应用层代码实现)
7. TCP总结
提高效率方面:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
可靠性方面
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
8. 半连接队列与全连接队列
针对的是监听listen函数的第二个参数LISTNUM
TCP是有连接拒绝策略,listen第二个参数为TCP底层可以连接的个数(全连接个数)
全连接队列(accept队列):用来保存已经建立好连接(ESTABLISHED状态),但是应用层没有调用accept函数将连接取走
全连接队列的长度由listen函数的第二个参数而定(全队列长度=listen第二个参数数值+1)
如果全连接队列满,如果此时又建立新连接,此时连接不会建立成功,而是处于半连接状态(SYN_RECV状态)
半连接队列:用来保存SYN_RECV状态和SYN_SEND状态的连接请求。长度由系统控制(半连接长度算法)。
系统维护全连接和半连接队列为了保证服务器可以最大限度的工作。
但是队列的长度不能太长,因为维护队列需要成本,同时客户端等待时间太长,维护长连接不如将资源提高服务器处理速度。
连接建立过程:
SYN洪水攻击使服务器无法正常提供服务
通过向服务器大量发送SYN报文,服务器SYN+ACK后,发送方不再ACK回去。导致半连接无法转移到全连接队列,最后半连接队列满,正常客户端无法再向服务器发送连接建立请求报文,因为半连接队列已满。使服务器无法提供服务。