您现在的位置是:首页 >学无止境 >【计算机网络】 总结复习(2)网站首页学无止境
【计算机网络】 总结复习(2)
tcp
tcp 工作在传输层可靠的数据传输服务,确保传输数据是无损坏,无间隔,非冗余按序
一些知识点
-
服务端最大并发 TCP 连接数远不能达到理论上限,会受以下因素影响:
- 文件描述符限制,每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。Linux 对可打开的文件描述符的数量分别作了三个方面的限制:
- 系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看;
- 用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看;
- 进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看;
- 内存限制,每个 TCP 连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生 OOM
- 文件描述符限制,每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。Linux 对可打开的文件描述符的数量分别作了三个方面的限制:
-
TCP 和 UDP 可以使用同一个端口。
在数据链路层中,通过 MAC 地址来寻找局域网中的主机。在网际层中,通过 IP 地址来寻找网络中互连的主机或路由器。在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的不同应用程序。
传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。
根据IP 包头的「协议号」字段知道该数据包是 TCP/UDP,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理
三次握手
三次握手原因:
- 阻止重复历史连接初始化
网络阻塞的情况下可能与历史报文进行连接 - 同步双方的序号
- 避免资源浪费
每次建立连接初始化序列号均不同,采用随机数+当前时间作为种子的随机数,避免历史连接和syn 伪造
第1次握手丢失
tcp_syn_retries 为 3
当syn 重传次数达到最大重传次数,再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。
第2次握手丢失
tcp_synack_retries 为 5
客户端与服务端均会重传
客户端syn 未收到ack ,情况同第一次握手
服务端报文中包含syn 也需要重传,重传次数由tcp_synack_retries决定
第3次握手丢失
tcp_synack_retries 为 5
服务端会重传
服务端报文中包含syn 也需要重传,重传次数由tcp_synack_retries决定,直至超过重传次数或收到确认
syn 攻击
伪造不同ip的syn 报文,服务端收到会发送synack,将连接添加半连接队列,但由于收不到ack应答会占满半连接队列,导致服务端无法建立连接
避免方法:
- 增大tcp半连接队列阈值(增大 net.ipv4.tcp_max_syn_backlog,增大 net.core.somaxconn)
- 开启 net.ipv4.tcp_syncookies: 开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接,相当于绕过了 SYN 半连接来建立连接
- 减少 SYN+ACK 重传次数(tcp_synack_retries)
已经建立连接收到syn
如果四元组不同,服务端会认为是一个新的连接进行建立,旧连接由于长时间无应答开启keep alive 探测 关闭。
四元组相同,由于seq 与期待ack 不符,会回复rst
没有accept可以建立连接吗
- 每一个socket执行listen时,内核都会自动创建一个半连接队列和全连接队列。
- 第三次握手前,TCP连接会放在半连接队列中,直到第三次握手到来,才会被放到全连接队列中。
- accept方法只是为了从全连接队列中拿出一条连接,本身跟三次握手几乎毫无关系。
- 出于效率考虑,虽然都叫队列,但半连接队列其实被设计成了哈希表,而全连接队列本质是链表。
- 全连接队列满了,再来第三次握手也会丢弃,此时如果tcp_abort_on_overflow=1,还会直接发RST给客户端。
半连接队列满了,可能是因为受到了SYN Flood攻击,可以设置tcp_syncookies,绕开半连接队列。 - 客户端没有半连接队列和全连接队列,但有一个全局hash,可以通过它实现自连接或TCP同时打开。
四次挥手
- 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
- 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
- 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
- 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
- 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
- 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
- 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。
第1次挥手丢失
重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制,如果超过重传次数,客户端直接进入close 状态
第2次挥手丢失
客户端变成 fin_wait_1,服务端变成close_wait。客户端重传fin 报文(tcp_orphan_retries),如果超过重传次数,客户端直接进入close 状态
第3次挥手丢失
服务端变成last_ack,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。如果超过重传次数,服务端会断开连接
客户端变成fin_wait_2 ,如果在tcp_fin_timeout 时间没有收到第三次挥手,客户端会断开连接
第4次挥手丢失
linux 系统time_wait 会持续2msl进入关闭状态
第四次的ack 丢失,服务端状态为last_ack,客户端为time_wait 。服务端会不断重发fin 报文次数由 tcp_orphan_retries控制。当客户端收到fin报文2msl 会重新计时,直至超过重传次数然后2msl后客户端关闭,服务端关闭
收到乱序fin 如何处理
在 FIN_WAIT_2 状态时,如果收到乱序的 FIN 报文,那么就被会加入到「乱序队列」,并不会进入到 TIME_WAIT 状态。
根据seq序号来判断,等再次收到前面被网络延迟的数据包时,会判断乱序队列有没有数据,然后会检测乱序队列中是否有可用的数据,如果能在乱序队列中找到与当前报文的序列号保持的顺序的报文,就会看该报文是否有 FIN 标志,如果发现有 FIN 标志,这时才会进入 TIME_WAIT 状态。
time_wait
time_wait 2msl 原因:
msl 是报文的最大生存时间,msl 确保历史报文已经自然消亡。设置2msl 是网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。而且2msl 相当于允许ack 丢失一次,fin 再次发送。
time_wait 状态存在的原因:防止历史连接被接受,2msl 确保历史报文已经自动消亡。确保被动关闭方正确关闭。
time_wait 过多会导致,系统资源占用,和端口资源占用
客户端主动发起,无法再与相同四元组建立连接
服务端主动发起,不会导致端口资源受限,它只监视一个端口,但是过多连接会占用系统资源比如fd,内存,cpu等
服务端time_wait 过多:
http(只要有一端没有使用) 没有使用长连接,服务端会主动关闭
http 长连接超时:nginx 就会启动一个「定时器」,如果客户端在完后一个 HTTP 请求后,在一定时间内都没有再发起新的请求,定时器的时间一到,nginx 就会触发回调函数来关闭该连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。
http长连接连接请求达到上限,nginx 会主动关闭长连接
服务端close_wait 过多:
意味着服务端作为被动关闭方没有调用close函数
一个普通的 TCP 服务端的流程:
- 创建服务端 socket,bind 绑定端口、listen 监听端口
- 将服务端 socket 注册到 epoll
- epoll_wait 等待连接到来,连接到来时,调用 accpet 获取已连接的 socket
- 将已连接的 socket 注册到 epoll
- epoll_wait 等待事件发生
- 对方连接关闭时,我方调用 close
若没有调用close 大概率是代码写的有问题
time_wait 后收到syn
如果双方开启了时间戳机制:
- 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要大,并且SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要大。那么就会重用该四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。
- 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要小,或者SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要小。那么就会再回复一个第四次挥手的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号,就回 RST 报文给服务端。
在 TIME_WAIT 状态,收到 RST 会断开连接吗?
- 如果 net.ipv4.tcp_rfc1337 参数为 0,则提前结束 TIME_WAIT 状态,释放连接。
- 如果 net.ipv4.tcp_rfc1337 参数为 1,则会丢掉该 RST 报文。
tcp_tw_reuse 为什么默认是关闭
net.ipv4.tcp_tw_reuse,如果开启该选项的话,客户端(连接发起方) 在调用 connect() 函数时,如果内核选择到的端口,已经被相同四元组的连接占用的时候,就会判断该连接是否处于 TIME_WAIT 状态,如果该连接处于 TIME_WAIT 状态并且 TIME_WAIT 状态持续的时间超过了 1 秒,那么就会重用这个连接,然后就可以正常使用该端口了。所以该选项只适用于连接发起方。
net.ipv4.tcp_tw_recycle,如果开启该选项的话,允许处于 TIME_WAIT 状态的连接被快速回收,该参数在 NAT 的网络下是不安全的。因为内网和外网,复用同一个ip 相同的四元祖,会影响其他连接
- 历史 RST 报文可能会终止后面相同四元组的连接,因为 PAWS 检查到即使 RST 是过期的,也不会丢弃。
- 如果第四次挥手的 ACK 报文丢失了,有可能被动关闭连接的一方不能被正常的关闭;
关闭函数
关闭函数有两种close 和 shutdown
close 同时socket关闭发送方和接受方。如果多个线程共享同一个socket 当引用次数为0 ,发送fin 报文
shutdown 函数,指定关闭发送方向不关闭读取,若多个线程共享同一个socket ,shutdown不管引用技术直接使socket不可用,发出fin 报文
指定关闭读取不关闭发送,不会fin报文
四次挥手可以变成三次吗
当被关闭方没有数据要发送,且开启延迟确认的机制,那么ack 和 fin 就会合并发送,出现三次挥手。
延迟确认:
解决ack 传输效率过低的问题,linux 系统中打开tcp_quickack
tcp 重传,滑动窗口,流量控制,拥塞控制
tcp 是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的。
tcp 重传
当对端在指定时间内没有收到确认报文,便会进行超时重传
sack 选择性重传,收到三次相同的ack 便会重发
滑动窗口+流量控制
一种累积应答方式
根据可用窗口进行调整发送速率
零窗口情况:
接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的。
TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。
如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
小窗口问题:
当窗口<min(mss,缓存空间/2) ,会发送零窗口
当发送方发送小数据,使用nagle 算法,思路是延时处理,只有满足下面两个条件中的任意一个条件,才可以发送数据:
条件一:要等到窗口大小 >= MSS 并且 数据大小 >= MSS;
条件二:收到之前发送数据的 ack 回包;
拥塞控制
对网络流量进行控制,避免发送方的数据填充整个网络
通过控制拥塞窗口控制网络的流量,♟策略包括慢启动,拥塞避免,快速重传,超时重传
tcp 快速建立连接
打开fast open ,会产生cookie,再次连接利用cookie 进行连接(cookie 未过期)
一些故障
进程崩溃
TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP四次挥手的过程
已经连接客户端故障
如果没有开启tcp keepalive 机制,服务端一直处于established 状态占用资源
开启keep alive ,一段时间后服务端会发送探测报文
若对端正常,回复报文,保活时间会重置,重新计时
若对端故障重启,能够相应但是信息不匹配,会产生rst 报文重置连接
若故障,无法接受报文,超过探测次数tcp会宣告死亡
查看网络状态的一些命令
ss 查看tcp 全连接队列的情况
listen 状态:
Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接;
Send-Q:当前全连接最大队列长度;
established:
Recv-Q:已收到但未被应用进程读取的字节数;
Send-Q:已发送但未收到确认的字节数;
netstat -s | grep “SYNs to LISTEN”
查询tcp 因半连接队列溢出而丢弃
用tcp协议数据一定不会丢吗
存在建立连接丢包,半连接队列满了,全连接队列满了,直接丢包
流量控制丢包,网卡的流控队列长度
网卡丢包,ringbuffer 过小导致丢包,性能不足
接收缓冲区丢包
ip
tcp 发数据与ping 之间的区别
从应用层到传输层再到网络层。这段路径跟ping外网的时候是几乎是一样的。到了网络层,系统会根据目的IP,在路由表中获取对应的路由信息,而这其中就包含选择哪个网卡把消息发出。
当发现目标IP是外网IP时,会从"真网卡"发出。
当发现目标IP是回环地址时,就会选择本地网卡。
本地网卡,其实就是个**“假网卡”,它不像"真网卡"那样有个ring buffer什么的,"假网卡"会把数据推到一个叫 input_pkt_queue 的 链表 中。这个链表,其实是所有网卡共享的,上面挂着发给本机的各种消息。消息被发送到这个链表后,会再触发一个软中断**。
专门处理软中断的工具人**“ksoftirqd”** (这是个内核线程),它在收到软中断后就会立马去链表里把消息取出,然后顺着数据链路层、网络层等层层往上传递最后给到应用程序。
结束!
终于整理完了