河北省建设厅官方网站 官网,aso投放平台,房地产开发公司排名,专业网站seo推广大部分内容源于网络加之个人理解#xff5e;巨人的肩膀有多大决定你可以看得多远#xff5e; 文章目录 1. 三次握手说一下三次握手的过程为什么是三次握手 2. 四次挥手说一下四次挥手的过程为什么需要四次挥手有可能出现三次挥手吗#xff0c;什么时候会出现呢#xff1f;为… 大部分内容源于网络加之个人理解巨人的肩膀有多大决定你可以看得多远 文章目录 1. 三次握手说一下三次握手的过程为什么是三次握手 2. 四次挥手说一下四次挥手的过程为什么需要四次挥手有可能出现三次挥手吗什么时候会出现呢为什么 TIME_WAIT 等待的时间是 2MSL为什么需要 TIME_WAIT 状态TIME_WAIT 过多有什么危害如何优化 TIME_WAIT 3. 报文丢失会怎么样第一次握手报文丢失第二次握手报文丢失第三次握手报文丢失第一次挥手报文丢失第二次挥手报文丢失第三次挥手报文丢失第四次挥手报文丢失报文丢失重传达到最大次数之后会发生什么 4. 为什么每次建立 TCP 连接时初始化的序列号都要求不一样呢5. 初始序列号 ISN 是如何随机产生的6. 什么是 SYN 攻击如何避免 SYN 攻击7. 如果已经建立了连接但是客户端突然出现故障了怎么办8. 如果已经建立了连接但是服务端的进程崩溃会发生什么9. TCP 连接一端崩溃或者宕机会发生什么7. 使用 TCP 一定不会造成丢包吗8. TCP 中的数据结构9. Socket 编程针对 TCP 应该如何 Socket 编程 10. TCP 可靠传输的工作原理1. 为了解决数据差错问题TCP 的校验数据部分包含首部和数据而 ip 层只校验首部。2. 为了解决数据丢失问题TCP 数据时会设置一个超时计时器超时没有收到确认报文则会重传当前报文。3. 为了解决数据重复问题TCP 有确认迟到的机制也就是说当 TCP 收到重复报文的同时还会发送确认报文。4. 为了解决数据乱序问题 TCP 首部存在每个报文唯一的4字节序号。 11. TCP 可靠传输的实现12. 聊聊 tcp 的流量控制13. tcp 的拥塞控制的常用方法14. TCP 与 UDP 的分片发生在哪里 1. 三次握手 说一下三次握手的过程 第 1 次握手建立连接时客户端向服务器发送 SYN 报文SEQxSYN1并进入 SYN_SENT 状态等待服务器确认。 第 2 次握手实际上是分两部分来完成的即 SYNACK请求和确认报文。 ● 服务器收到了客户端的请求向客户端回复一个确认信息ACKx1。 ● 服务器再向客户端发送一个 SYN 包SEQy建立连接的请求此时服务器进入 SYN_RECV 状态。 第 3 次握手是客户端收到服务器的回复SYNACK 报文。 此时客户端也要向服务器发送确认包ACK。此包发送完毕客户端和服务器进入 ESTABLISHED 状态完成 3 次握手。 而且值得注意的是只有报文中的大ACK1确认 ACK ACKnowledgment小ack才有效。
为什么是三次握手
这个问题的本质原因就是,信道不可靠, 因此通信双发需要就某个问题达成一致。 而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值。所以三次握手是为了满足 “在不可靠信道上进行可靠传输” 这一需求所导致的。 ● 「两次握手」无法防止历史连接的建立会造成双方资源的浪费也无法可靠的同步双方序列号 ● 「四次握手」三次握手就已经理论上最少可靠连接建立所以不需要使用更多的通信次数。 为什么不能是两次 如果只有两次为了双方都能进入连接态只能丢弃 SYN_RCVD中间状态。这样的话当堵塞于网络中的旧初始化连接请求到来时服务端会误认为旧连接是正常连接而进入 established连接状态导致套接字空等造成资源浪费。 2. 四次挥手 说一下四次挥手的过程
假设存在 client 跟 server 端挥手请求由 client发起。 第一次挥手 Client 端发起挥手请求向 Server 端发送标志位是FIN报文段设置序列号 seq此时Client 端进入FIN_WAIT_1状态。表示Client端没有数据要发送给Server端了。 第二次挥手Server 端收到了 Client 端发送的 FIN 报文段向 Client 端返回一个标志位是 ACK 的报文段ack 设为 seq 加1Client 端进入FIN_WAIT_2状态。表示 Server 端知道 Clent 端关闭连接。 第三次挥手 Server 端向 Client 端发送标志位是 FIN 的报文段请求关闭连接同时 Client 端进入LAST_ACK状态。表示 Server 端也没有数据要发送给 Client 了。 第四次挥手 Client 端收到 Server 端发送的 FIN 报文段向 Server 端发送确认报文然后Client端进入TIME_WAIT状态。Server端收到Client端的ACK报文段以后就关闭连接。Client端等待 2MSL 的时间后依然没有收到回复则证明Server端已正常关闭Client 端关闭连接。 为什么需要四次挥手
因为 TCP 是一个全双工的通信协议也就是说双方可以同时传输数据。而断开连接需要有人发起有人确认所以双方都断开连接就需要四次挥手了。
有可能出现三次挥手吗什么时候会出现呢
在 Linux 环境开发的时候通过 tcpdump 进行抓包可以发现经常会出现 第二次挥手跟第三次挥手合并成为一个报文发送的情况。主要是因为现在 TCP 有延迟确认的机制且是默认开启的。
为什么 TIME_WAIT 等待的时间是 2MSL
主要有两个原因 第一为了保证最后一个断开连接的报文( 包括超时重传的 ) 可以到达对端。 第二防止已失效的连接请求报文出现在本连接。因为发送完最后一个 ACK 报文后再经过 2MSL 理论上就可以使得本链接所产生的所有报文都从网络中消失。 MSL(Maximum Segment Lifetime)最长报文段寿命。也就是说 MSL 时间内报文没有到达对端就认为报文已丢失。 2MSL 可以保证可以接收到重传报文。
为什么需要 TIME_WAIT 状态
主动发起关闭连接的一方才会有 TIME-WAIT 状态。 需要 TIME-WAIT 状态主要是两个原因 防止历史连接中的数据被后面相同四元组的连接错误的接收 保证「被动关闭连接」的一方能被正确的关闭 TIME_WAIT 过多有什么危害
注意: 只有先发起 Fin 报文的端点才会有 TIME_WAIT 状态。 过多的 TIME-WAIT 状态主要的危害有两种 第一是占用系统资源比如文件描述符、内存资源、CPU 资源、线程资源等 第二是占用端口资源端口资源也是有限的一般可以开启的端口为 3276861000也可以通过 net.ipv4.ip_local_port_range参数指定范围。 客户端和服务端 TIME_WAIT 过多造成的影响是不同的。 如果客户端主动发起关闭连接方的 TIME_WAIT 状态过多占满了所有端口资源那么就无法对「目的 IP 目的 PORT」都一样的服务器发起连接了但是被使用的端口还是可以继续对另外一个服务器发起连接的。具体可以看我这篇文章客户端的端口可以重复使用吗(opens new window) 因此客户端发起连接方都是和「目的 IP 目的 PORT 」都一样的服务器建立连接的话当客户端的 TIME_WAIT 状态连接过多的话就会受端口资源限制如果占满了所有端口资源那么就无法再跟「目的 IP 目的 PORT」都一样的服务器建立连接了。 不过即使是在这种场景下只要连接的是不同的服务器端口是可以重复使用的所以客户端还是可以向其他服务器发起连接的这是因为内核在定位一个连接的时候是通过四元组源IP、源端口、目的IP、目的端口信息来定位的并不会因为客户端的端口一样而导致连接冲突。 如果服务端主动发起关闭连接方的 TIME_WAIT 状态过多并不会导致端口资源受限因为服务端只监听一个端口而且由于一个四元组唯一确定一个 TCP 连接因此理论上服务端可以建立很多连接但是 TCP 连接过多会占用系统资源比如文件描述符、内存资源、CPU 资源、线程资源等。
如何优化 TIME_WAIT
这里给出优化 TIME-WAIT 的几个方式都是有利有弊 ● 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项 ● net.ipv4.tcp_max_tw_buckets ● 程序中使用 SO_LINGER 应用强制使用 RST 关闭。 方式一net.ipv4.tcp_tw_reuse 和 tcp_timestamps 如下的 Linux 内核参数开启后则可以复用处于 TIME_WAIT 的 socket 为新的连接所用。 有一点需要注意的是tcp_tw_reuse 功能只能用客户端连接发起方因为开启了该功能在调用 connect() 函数时内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用。
net.ipv4.tcp_tw_reuse 1
使用这个选项还有一个前提需要打开对 TCP 时间戳的支持即 net.ipv4.tcp_timestamps1 默认即为 1
这个时间戳的字段是在 TCP 头部的「选项」里它由一共 8 个字节表示时间戳其中第一个 4 字节字段用来保存发送该数据包的时间第二个 4 字节字段用来保存最近一次接收对方发送到达数据的时间。 由于引入了时间戳我们在前面提到的 2MSL 问题就不复存在了因为重复的数据包会因为时间戳过期被自然丢弃。 方式二net.ipv4.tcp_max_tw_buckets 这个值默认为 18000当系统中处于 TIME_WAIT 的连接一旦超过这个值时系统就会将后面的 TIME_WAIT 连接状态重置这个方法比较暴力。 方式三程序中使用 SO_LINGER 我们可以通过设置 socket 选项来设置调用 close 关闭连接行为。
struct linger so_linger;
so_linger.l_onoff 1;
so_linger.l_linger 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, so_linger,sizeof(so_linger));
如果l_onoff为非 0 且l_linger值为 0那么调用close后会立该发送一个RST标志给对端该 TCP 连接将跳过四次挥手也就跳过了TIME_WAIT状态直接关闭。 但这为跨越TIME_WAIT状态提供了一个可能不过是一个非常危险的行为不值得提倡。 前面介绍的方法都是试图越过 TIME_WAIT状态的这样其实不太好。虽然 TIME_WAIT 状态持续的时间是有一点长显得很不友好但是它被设计来就是用来避免发生乱七八糟的事情。 《UNIX网络编程》一书中却说道TIME_WAIT 是我们的朋友它是有助于我们的不要试图避免这个状态而是应该弄清楚它。 如果服务端要避免过多的 TIME_WAIT 状态的连接就永远不要主动断开连接让客户端去断开由分布在各处的客户端去承受 TIME_WAIT。 3. 报文丢失会怎么样 源于 [小林coding] 第一次握手报文丢失
当客户端想和服务端建立 TCP 连接的时候首先第一个发的就是 SYN 报文然后进入到 SYN_SENT 状态。 在这之后如果客户端迟迟收不到服务端的 SYN-ACK 报文第二次握手就会触发「超时重传」机制重传 SYN 报文而且重传的 SYN 报文的序列号都是一样的。 不同版本的操作系统可能超时时间不同有的 1 秒的也有 3 秒的这个超时时间是写死在内核里的如果想要更改则需要重新编译内核比较麻烦。 当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后客户端就会重发 SYN 报文那到底重发几次呢 在 Linux 里客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制这个参数是可以自定义的默认值一般是 5。
# cat /proc/sys/net/ipv4/tcp_syn_retriescentos 7.x 是6 通常第一次超时重传是在 1 秒后第二次超时重传是在 2 秒第三次超时重传是在 4 秒后第四次超时重传是在 8 秒后第五次是在超时重传 16 秒后。没错每次超时的时间是上一次的 2 倍。 当第五次超时重传后会继续等待 32 秒如果服务端仍然没有回应 ACK客户端就不再发送 SYN 包然后断开 TCP 连接。 所以总耗时是 1248163263 秒大约 1 分钟左右。
第二次握手报文丢失
当服务端收到客户端的第一次握手后就会回 SYN-ACK 报文给客户端这个就是第二次握手此时服务端会进入 SYN_RCVD 状态。 第二次握手的 SYN-ACK 报文其实有两个目的 ● 第二次握手里的 ACK 是对第一次握手的确认报文 ● 第二次握手里的 SYN是服务端发起建立 TCP 连接的报文 所以如果第二次握手丢了就会发生比较有意思的事情具体会怎么样呢 因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文所以如果客户端迟迟没有收到第二次握手那么客户端就觉得可能自己的 SYN 报文第一次握手丢失了于是客户端就会触发超时重传机制重传 SYN 报文。 然后因为第二次握手中包含服务端的 SYN 报文所以当客户端收到后需要给服务端发送 ACK 确认报文第三次握手服务端才会认为该 SYN 报文被客户端收到了。 那么如果第二次握手丢失了服务端就收不到第三次握手于是服务端这边会触发超时重传机制重传 SYN-ACK 报文。 在 Linux 下SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定默认值是 5。
# cat /proc/sys/net/ipv4/tcp_synack_retries
因此当第二次握手丢失了客户端和服务端都会重传 ● 客户端会重传 SYN 报文也就是第一次握手最大重传次数由 tcp_syn_retries内核参数决定 ● 服务端会重传 SYN-ACK 报文也就是第二次握手最大重传次数由 tcp_synack_retries 内核参数决定。 注tcp_syn_retries 一般比 tcp_synack_retries稍微大一点。
第三次握手报文丢失
客户端收到服务端的 SYN-ACK 报文后就会给服务端回一个 ACK 报文也就是第三次握手此时客户端状态进入到 ESTABLISH 状态。 因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文所以当第三次握手丢失了如果服务端那一方迟迟收不到这个确认报文就会触发超时重传机制重传 SYN-ACK 报文直到收到第三次握手或者达到最大重传次数。 注意ACK 报文是不会有重传的当 ACK 丢失了就由对方重传对应的报文。
第一次挥手报文丢失
当客户端主动关闭方调用 close 函数后就会向服务端发送 FIN 报文试图与服务端断开连接此时客户端的连接进入到 FIN_WAIT_1 状态。 正常情况下如果能及时收到服务端被动关闭方的 ACK则会很快变为 FIN_WAIT_2 状态。 如果第一次挥手丢失了那么客户端迟迟收不到被动方的 ACK 的话也就会触发超时重传机制重传 FIN 报文重发次数由 tcp_orphan_retries 参数控制。
# cat /proc/sys/net/ipv4/tcp_orphan_retries
当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后就不再发送 FIN 报文则会在等待一段时间时间为上一次超时时间的 2 倍如果还是没能收到第二次挥手那么直接进入到 close 状态。
第二次挥手报文丢失
当服务端收到客户端的第一次挥手后就会先回一个 ACK 确认报文此时服务端的连接进入CLOSE_WAIT 状态。 在前面我们也提了ACK 报文是不会重传的所以如果服务端的第二次挥手丢失了客户端就会触发超时重传机制重传 FIN 报文直到收到服务端的第二次挥手或者达到最大的重传次数。
第三次挥手报文丢失
当服务端被动关闭方收到客户端主动关闭方的 FIN 报文后内核会自动回复 ACK同时连接处于 CLOSE_WAIT 状态顾名思义它表示等待应用进程调用 close 函数关闭连接。 此时内核是没有权利替代进程关闭连接必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。 服务端处于 CLOSE_WAIT 状态时调用了 close 函数内核就会发出 FIN 报文同时连接进入 LAST_ACK 状态等待客户端返回 ACK 来确认连接关闭。 如果迟迟收不到这个 ACK服务端就会重发 FIN 报文重发次数仍然由 tcp_orphan_retries 参数控制这与客户端重发 FIN 报文的重传次数控制方式是一样的。
第四次挥手报文丢失
当客户端收到服务端的第三次挥手的 FIN 报文后就会回 ACK 报文也就是第四次挥手此时客户端连接进入 TIME_WAIT 状态。 在 Linux 系统TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。 然后服务端被动关闭方没有收到 ACK 报文前还是处于 LAST_ACK 状态。 如果第四次挥手的 ACK 报文没有到达服务端服务端就会重发 FIN 报文重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。
报文丢失重传达到最大次数之后会发生什么
假设重传的最大次数是3. 如果重传三次后还未收到该报文的确认那么就不再尝试重传直接发送 reset 报文重置该TCP连接但有些要求很高的业务应用系统则会不断的重传被丢弃的报文以尽最大可能保证业务数据的正常交互。
4. 为什么每次建立 TCP 连接时初始化的序列号都要求不一样呢
主要原因有两个方面 ● 为了防止历史报文被下一个相同四元组的连接接收主要方面 ● 为了安全性防止黑客伪造的相同序列号的 TCP 报文被对方接收
5. 初始序列号 ISN 是如何随机产生的
起始 ISN 是基于时钟的每 4 微秒 1转一圈要 4.55 个小时。 RFC793 提到初始化序列号 ISN 随机生成算法ISN M F(localhost, localport, remotehost, remoteport)。 ● M 是一个计时器这个计时器每隔 4 微秒加 1。 ● F 是一个 Hash 算法根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出用 MD5 算法是一个比较好的选择。 可以看到随机数是会基于时钟计时器递增的基本不可能会随机成一样的初始化序列号。
6. 什么是 SYN 攻击如何避免 SYN 攻击 我们都知道 TCP 连接建立是需要三次握手假设攻击者短时间伪造不同 IP 地址的 SYN 报文服务端每接收到一个 SYN 报文就进入SYN_RCVD 状态但服务端发送出去的 ACK SYN 报文无法得到未知 IP 主机的 ACK 应答久而久之就会占满服务端的半连接队列使得服务器不能为正常用户服务。
避免 SYN 攻击方式可以有以下四种方法 ● 调大 netdev_max_backlog ● 增大 TCP 半连接队列 ● 开启 tcp_syncookies ● 减少 SYNACK 重传次数 方式一调大 netdev_max_backlog 当网卡接收数据包的速度大于内核处理的速度时会有一个队列保存这些数据包。控制该队列的最大值如下参数默认值是 1000我们要适当调大该参数的值比如设置为 10000 net.core.netdev_max_backlog 10000 方式二增大 TCP 半连接队列 增大 TCP 半连接队列要同时增大下面这三个参数 ● 增大 net.ipv4.tcp_max_syn_backlog ● 增大 listen() 函数中的 backlog ● 增大 net.core.somaxconn 具体为什么是三个参数决定 TCP 半连接队列的大小可以看这篇可以看这篇TCP 半连接队列和全连接队列满了会发生什么又该如何应对(opens new window) 方式三开启 net.ipv4.tcp_syncookies 开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接相当于绕过了 SYN 半连接来建立连接。
具体过程 ● 当 「 SYN 队列」满之后后续服务器收到 SYN 包不会丢弃而是根据算法计算出一个 cookie 值 ● 将 cookie 值放到第二次握手报文的「序列号」里然后服务端回第二次握手给客户端 ● 服务端接收到客户端的应答报文时服务器会检查这个 ACK 包的合法性。如果合法将该连接对象放入到「 Accept 队列」。 ● 最后应用程序通过调用 accpet() 接口从「 Accept 队列」取出的连接。 可以看到当开启了 tcp_syncookies 了即使受到 SYN 攻击而导致 SYN 队列满时也能保证正常的连接成功建立。 net.ipv4.tcp_syncookies 参数主要有以下三个值 ● 0 值表示关闭该功能 ● 1 值表示仅当 SYN 半连接队列放不下时再启用它 ● 2 值表示无条件开启功能 那么在应对 SYN 攻击时只需要设置为 1 即可。 $ echo 1 /proc/sys/net/ipv4/tcp_syncookies 方式四减少 SYNACK 重传次数 当服务端受到 SYN 攻击时就会有大量处于 SYN_REVC 状态的 TCP 连接处于这个状态的 TCP 会重传 SYNACK 当重传超过次数达到上限后就会断开连接。 那么针对 SYN 攻击的场景我们可以减少 SYN-ACK 的重传次数以加快处于 SYN_REVC 状态的 TCP 连接断开。 SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定默认值是 5 次比如将 tcp_synack_retries 减少到 2 次 $ echo 2 /proc/sys/net/ipv4/tcp_synack_retries
7. 如果已经建立了连接但是客户端突然出现故障了怎么办
TCP 有一个机制是保活机制。这个机制的原理是这样的 定义一个时间段在这个时间段内如果没有任何连接相关的活动TCP 保活机制会开始作用每隔一个时间间隔发送一个探测报文该探测报文包含的数据非常少如果连续几个探测报文都没有得到响应则认为当前的 TCP 连接已经死亡系统内核将错误信息通知给上层应用程序。 注意 我们也可以像websocket 通信那样自己实现心跳机制来实现。 在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔以下都为默认值 ● tcp_keepalive_time7200表示保活时间是 7200 秒2小时也就 2 小时内如果没有任何连接相关的活动则会启动保活机制 ● tcp_keepalive_intvl75表示每次检测间隔 75 秒 ● tcp_keepalive_probes9表示检测 9 次无响应认为对方是不可达的从而中断本次的连接。 注意应用程序若想使用 TCP 保活机制需要通过 socket 接口设置 SO_KEEPALIVE 选项才能够生效如果没有设置那么就无法使用 TCP 保活机制。 如果开启了 TCP 保活需要考虑以下几种情况 ● 第一种对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应这样 TCP 保活时间会被重置等待下一个 TCP 保活时间的到来。 ● 第二种对端程序崩溃并重启。当 TCP 保活的探测报文发送给对端后对端是可以响应的但由于没有该连接的有效信息会产生一个 RST 报文这样很快就会发现 TCP 连接已经被重置。 ● 第三种是对端程序崩溃或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后石沉大海没有响应连续几次达到保活探测次数后TCP 会报告该 TCP 连接已经死亡。
8. 如果已经建立了连接但是服务端的进程崩溃会发生什么
TCP 的连接信息是由内核维护的所以当服务端的进程崩溃后内核需要回收该进程的所有 TCP 连接资源于是内核会发送第一次挥手 FIN 报文后续的挥手过程也都是在内核完成并不需要进程的参与所以即使服务端的进程退出了还是能与客户端完成 TCP四次挥手的过程。 我自己做了个实验使用 kill -9 来模拟进程崩溃的情况发现在 kill 掉进程后服务端会发送 FIN 报文与客户端进行四次挥手。
9. TCP 连接一端崩溃或者宕机会发生什么
如果「客户端进程崩溃」客户端的进程在发生崩溃的时候内核会发送 FIN 报文与服务端进行四次挥手。 但是「客户端主机宕机」那么是不会发生四次挥手的具体后续会发生什么还要看服务端会不会发送数据 ● 如果服务端会发送数据由于客户端已经不存在收不到数据报文的响应报文服务端的数据报文会超时重传当重传总间隔时长达到一定阈值内核会根据 tcp_retries2 设置的值计算出一个阈值后会断开 TCP 连接 ● 如果服务端一直不会发送数据再看服务端有没有开启 TCP keepalive 机制 ○ 如果有开启服务端在一段时间没有进行数据交互时会触发 TCP keepalive 机制探测对方是否存在如果探测到对方已经消亡则会断开自身的 TCP 连接 ○ 如果没有开启服务端的 TCP 连接会一直存在并且一直保持在 ESTABLISHED 状态。
7. 使用 TCP 一定不会造成丢包吗
● 数据从发送端到接收端链路很长任何一个地方都可能发生丢包几乎可以说丢包不可避免。 ● 平时没事也不用关注丢包大部分时候TCP的重传机制保证了消息可靠性。 ● 当你发现服务异常的时候比如接口延时很高总是失败的时候可以用ping或者mtr命令看下是不是中间链路发生了丢包。 ● TCP只保证传输层的消息可靠性并不保证应用层的消息可靠性。如果我们还想保证应用层的消息可靠性就需要应用层自己去实现逻辑做保证。
8. TCP 中的数据结构
队列 半连接和全连接队列 在 TCP 三次握手的时候Linux 内核会维护两个队列分别是 ● 半连接队列也称 SYN 队列 ● 全连接队列也称 accept 队列 服务端收到客户端发起的 SYN 请求后内核会把该连接存储到半连接队列并向客户端响应 SYNACK接着客户端会返回 ACK服务端收到第三次握手的 ACK 后内核会把连接从半连接队列移除然后创建新的完全的连接并将其添加到 accept 队列等待进程调用 accept 函数时把连接取出来。
不管是半连接队列还是全连接队列都有最大长度限制超过限制时内核会直接丢弃或返回 RST 包。
链表 双向链表 https://blog.51cto.com/u_13045346/4553142
9. Socket 编程 针对 TCP 应该如何 Socket 编程
● 服务端和客户端初始化 socket得到文件描述符 ● 服务端调用 bind将 socket 绑定在指定的 IP 地址和端口; ● 服务端调用 listen进行监听 ● 服务端调用 accept等待客户端连接 ● 客户端调用 connect向服务器端的地址和端口发起连接请求 ● 服务端 accept 返回用于传输的 socket 的文件描述符 ● 客户端调用 write 写入数据服务端调用 read 读取数据 ● 客户端断开连接时会调用 close那么服务端 read 读取数据的时候就会读取到了 EOF待处理完数据后服务端调用 close表示连接关闭。 这里需要注意的是服务端调用 accept 时连接成功了会返回一个已完成连接的 socket后续用来传输数据。 所以监听的 socket 和真正用来传送数据的 socket是「两个」 socket一个叫作监听 socket一个叫作已完成连接 socket。 成功连接建立之后双方开始通过 read 和 write 函数来读写数据就像往一个文件流里面写东西一样。 listen 的时候参数 backlog 的意义 Linux内核中会维护两个队列 ● 半连接队列SYN 队列接收到一个 SYN 建立连接请求处于 SYN_RCVD 状态 ● 全连接队列Accpet 队列已完成 TCP 三次握手过程处于 ESTABLISHED 状态
int listen (int socketfd, int backlog) ● 参数一 socketfd 为 socketfd 文件描述符 ● 参数二 backlog这参数在历史版本有一定的变化 在早期 Linux 内核 backlog 是 SYN 队列大小也就是未完成的队列大小。 在 Linux 内核 2.2 之后backlog 变成 accept 队列也就是已完成连接建立的队列长度所以现在通常认为 backlog 是 accept 队列。 但是上限值是内核参数 somaxconn 的大小也就说 accpet 队列长度 min(backlog, somaxconn)。 accept 发生在三次握手的哪一步 我们先看看客户端连接服务端时发送了什么
● 客户端的协议栈向服务器端发送了 SYN 包并告诉服务器端当前发送序列号 client_isn客户端进入 SYN_SENT 状态 ● 服务器端的协议栈收到这个包之后和客户端进行 ACK 应答应答的值为 client_isn1表示对 SYN 包 client_isn 的确认同时服务器也发送一个 SYN 包告诉客户端当前我的发送序列号为 server_isn服务器端进入 SYN_RCVD 状态 ● 客户端协议栈收到 ACK 之后使得应用程序从 connect 调用返回表示客户端到服务器端的单向连接建立成功客户端的状态为 ESTABLISHED同时客户端协议栈也会对服务器端的 SYN 包进行应答应答数据为 server_isn1 ● ACK 应答包到达服务器端后服务器端的 TCP 连接进入 ESTABLISHED 状态同时服务器端协议栈使得 accept 阻塞调用返回这个时候服务器端到客户端的单向连接也建立成功。至此客户端与服务端两个方向的连接都建立成功。 从上面的描述过程我们可以得知客户端 connect 成功返回是在第二次握手服务端 accept 成功返回是在三次握手成功之后。 客户端调用 close 了连接是断开的流程是什么 我们看看客户端主动调用了 close会发生什么
● 客户端调用 close表明客户端没有数据需要发送了则此时会向服务端发送 FIN 报文进入 FIN_WAIT_1 状态 ● 服务端接收到了 FIN 报文TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后这就意味着服务端需要处理这种异常情况因为 EOF 表示在该连接上再无额外数据到达。此时服务端进入 CLOSE_WAIT 状态 ● 接着当处理完数据后自然就会读到 EOF于是也调用 close 关闭它的套接字这会使得服务端发出一个 FIN 包之后处于 LAST_ACK 状态 ● 客户端接收到服务端的 FIN 包并发送 ACK 确认包给服务端此时客户端将进入 TIME_WAIT 状态 ● 服务端收到 ACK 确认包后就进入了最后的 CLOSE 状态 ● 客户端经过 2MSL 时间之后也进入 CLOSE 状态
10. TCP 可靠传输的工作原理
tcp 发送的报文段是交给 ip 层传送的但是 IP 层只能提供尽最大努力服务不可靠。 首先可靠传输意味着 无差错不丢失不重复不乱序。那么接下来我也会从这几个方面来介绍可靠传输的工作原理。
1. 为了解决数据差错问题TCP 的校验数据部分包含首部和数据而 ip 层只校验首部。
2. 为了解决数据丢失问题TCP 数据时会设置一个超时计时器超时没有收到确认报文则会重传当前报文。
3. 为了解决数据重复问题TCP 有确认迟到的机制也就是说当 TCP 收到重复报文的同时还会发送确认报文。
4. 为了解决数据乱序问题 TCP 首部存在每个报文唯一的4字节序号。 总的来说就是 TCP 采用了 数据报唯一编号确认重传 的机制在不可靠的信道上利用人为的算法协议确保可靠传输。 而在这基本思想的发展上tcp 出现了一系列的可靠传输协议比如说停止等待协议连续 ARQ 协议。 停止等待”就是每发送完一个分组就停止发送等待对方的确认。在收到确认后再发送下一个分组。 连续 ARQ 采用的是滑动窗口的算法思想达到累积确认的目的。
11. TCP 可靠传输的实现
数据报的唯一编号是基于操作系统的时钟生成的。 以字节为单位的滑动窗口 超时重传的时间选择 选择确认(SACK)(可选的需要双方商定)。
12. 聊聊 tcp 的流量控制
流量控制属于点对点问题在 tcp 主要是利用滑动窗口的思想双方就窗口的大小进行协商避免数据包传输过快或过慢。
13. tcp 的拥塞控制的常用方法
首先拥塞代表着对资源的总需求量大于可用资源而且拥塞导致丢包引起的重传会加剧拥塞程度拥塞控制的目的就是避免过多的数据包注入到网络中。而TCP中为了减少拥塞带来的损失采用以下四种方法。 ● 1. 慢开始 每个传输轮次加倍 ● 2. 拥塞避免 传输轮次1 ● 3. 快重传 发送方连续收到三个某个报文的确认就认为该报文没有被接收方收到立即进行重传。 ● 4. 快恢复 缩小拥塞窗口门限后立即执行拥塞避免。
14. TCP 与 UDP 的分片发生在哪里
首先提到分片我们需要聊一聊 MTU它是链路层中的网络对数据帧的一个限制以以太网为例MTU为1500个字节。一个IP数据报在以太网中传输如果它的长度大于该MTU值就要进行分片传输使得每片数据报的长度小于MTU。分片传输的IP数据报不一定按序到达但IP首部中的信息能让这些数据报片按序组装。IP数据报的分片与重组是在网络层进完成的。 UDP数据报由于UDP数据报不会自己进行分片当UDP数据长度超过了MTU时会在网络层进行IP分片。 对于TCP来说它是尽量避免分片的为什么因为如果在IP层进行分片了话如果其中的某片的数据丢失了对于保证可靠性的TCP协议来说会增大重传数据包的机率而且只能重传整个TCP分组。因此当TCP数据大于MTU时会在传输层进行分段然后再进入网络层。 TCP 报文里面有个长度可变的选项字段其最初只有一个 MSS(Maxiumum Segment Size)是每个 TCP 报文段里的数据字段的最大长度。