• 【计算机网络】 总结复习(2)


    tcp

    tcp 工作在传输层可靠的数据传输服务,确保传输数据是无损坏,无间隔,非冗余按序

    一些知识点
    1. 服务端最大并发 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
    2. TCP 和 UDP 可以使用同一个端口。
      数据链路层中,通过 MAC 地址来寻找局域网中的主机。在网际层中,通过 IP 地址来寻找网络中互连的主机或路由器。在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的不同应用程序。
      传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。
      根据IP 包头的「协议号」字段知道该数据包是 TCP/UDP,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理

    三次握手

    在这里插入图片描述
    三次握手原因:

    1. 阻止重复历史连接初始化
      网络阻塞的情况下可能与历史报文进行连接
    2. 同步双方的序号
    3. 避免资源浪费

    每次建立连接初始化序列号均不同,采用随机数+当前时间作为种子的随机数,避免历史连接和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应答会占满半连接队列,导致服务端无法建立连接

    避免方法:

    1. 增大tcp半连接队列阈值(增大 net.ipv4.tcp_max_syn_backlog,增大 net.core.somaxconn)
    2. 开启 net.ipv4.tcp_syncookies: 开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接,相当于绕过了 SYN 半连接来建立连接
    3. 减少 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 服务端的流程:

    1. 创建服务端 socket,bind 绑定端口、listen 监听端口
    2. 将服务端 socket 注册到 epoll
    3. epoll_wait 等待连接到来,连接到来时,调用 accpet 获取已连接的 socket
    4. 将已连接的 socket 注册到 epoll
    5. epoll_wait 等待事件发生
    6. 对方连接关闭时,我方调用 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 ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
    小窗口问题:
    当窗口 当发送方发送小数据,使用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”** (这是个内核线程),它在收到软中断后就会立马去链表里把消息取出,然后顺着数据链路层、网络层等层层往上传递最后给到应用程序。

    结束!
    终于整理完了

  • 相关阅读:
    【微信小程序】接口生成自定义首页二维码
    二维数组详解
    关于quartus 13.1出现的问题的一些总结
    【wxWidgets实现透明wxPanel_核心实现_原创思想】
    信安软考——第七章 访问控制技术原理与应用
    Codeforces Round #810 (Div. 2)
    Go语言的100个错误使用场景(55-60)|并发基础
    感觉自己好傻
    redis学习七redis的集群:主从复制、CAP、PAXOS、Cluster分片集群(二)
    MySQL面试题入门:四大范式、SQL生命周期、SQL六大语言、索引、最左匹配原则....
  • 原文地址:https://blog.csdn.net/ltd0924/article/details/130504470