1.TCP连接的建立1.1 为什么建立连接需要三次握手12次握手不合适若只有2次握手服务器端需要在收到SYN发出SYNACK后就建立连接。服务器端无法确认客户端是否收到SYNACK若SYNACK丢失服务器端创建连接后收不到客户端的数据会造成服务器端资源浪费。协议栈自动重发的SYN报文会与原始SYN报文使用相同的序列号。但是应用程序自己实现的等待超时自动重新发起连接机制会迫使协议栈使用新序列号发出SYN报文。服务器端连接创建完成后又收到四元组相同但序列号不同的SYN报文会回复RST重置连接若网络不稳定可能需要反复重建连接无谓消耗资源。3次握手比2次握手更有利于旧SYN报文在网络上自然消亡。服务器端需在收到ACK后才创建连接更不易受旧SYN报文影响。只有2次握手服务器容易遭受攻击大量消耗资源。2三次握手足以保证连接的可靠建立不需要四次握手。1.2 连接创建过程当服务端接收到客户端的 SYN 报文时会将其放入协议栈的半连接队列SYN 队列接着发送SYN ACK 给客户端等待客户端回应 ACK 报文服务端接收到 ACK 报文后从半连接队列中取出放入全连接队列accept队列。服务器端应用层通过调用 accpet从全连接队列取出连接。客户端会在发出ACK报文后创建连接服务器端会在收到ACK后创建连接。服务器端在收到SYN后只会创建半连接协议栈不会为半连接分配收发数据的缓存所以前两次握手的SYN报文都不能携带数据第3次握手的ACK可以携带数据因为服务器端收到ACK后就会正式创建有效连接分配收发数据的缓存可以处理数据了。在服务器端收到第三次握手的ACK报文之前若客户端向服务器端发送数据服务器端会回复RST重置连接。1.3 为何不携带数据的ACK报文不消耗序列号但不携带数据的SYN和FIN报文仍要消耗一个序列号RFC 793 规定无应用层数据的TCP控制报文SYN/FIN消耗一个序列号(SEQ)是为了与数据报文共享相同的确认机制。SYN消耗一个序列号可以使其ACK确认报文的期望序列号在SYN报文序列号的基础上加1与对数据报文的确认机制相同代码实现可以共享。若SYN不消耗序列号就需要特殊处理不能与确认数据的ACK报文共享代码。FIN消耗一个序列号可以使其ACK确认报文的期望序列号在FIN报文序列号的基础上加1。若FIN不消耗序列号确认FIN报文的ACK序列号与确认最后一帧数据的ACK序列号相同正在等待确认FIN报文一端就无法区分收到的ACK是对FIN的确认还是对之前数据的确认。1.4 初始序列号ISN建立连接时双方使用的初始序列号ISN是随机数目的为了尽量避免历史报文被下一个相同四元组的连接当作有效报文接收同时也为了防止TCP被窃听和攻击。1.5 连接创建超时重传RFC 1122 建议连接创建超时至少重传 3 次。SYN和SYNACK报文在等待下一次握手超时后都会主动重传。第三次握手的ACK报文不会主动重传ACK报文的重传由SYNACK报文的重传被动触发。任何交互的最后一次确认报文都不会主动重传只能被前一次交互的重传报文被动触发重传。在 Linux 里SYN 报文最大重传次数由内核参数net.ipv4.tcp_syn_retries控制SYNACK 报文的最大重传次数由内核参数net.ipv4.tcp_synack_retries决定这两个参数都是可以自定义的默认值一般是5。Linux系统重传间隔为指数退避第一次超时重传是在 1秒后后续每次重传间隔是上次的2倍重传5次总耗时1248163263秒大约 1分钟。首次超时重传使用固定间隔是因为连接尚未建立无法估算时间。重传超时则放弃创建连接释放资源。1.6 服务器端连接创建拥堵如果服务器端短时间内收到大量SYN报文会导致SYN 队列被占满只能对新的 SYN 报文回复RST拒绝连接请求。如果服务器端应用层不能及时调用accept取出连接会导致全连接队列被占满。内核参数net.ipv4.tcp_max_syn_backlog整型控制SYN队列的最大个数。内核参数net.ipv4.tcp_abort_on_overflow布尔型配置当SYN 队列被占满时是否对新的 SYN 报文回复RST默认值是0配置为1则回复RST报文。一种应对连接创建拥堵的办法是启用cookie设置内核参数net.ipv4.tcp_syncookies 1。当SYN 队列满之后后续服务器收到 SYN 报文不放入SYN 队列而是计算出一个 cookie 值再以 SYN ACK 中的序列号返回客户端。服务端接收到客户端的ACK报文时会检查这个 ACK 报文的合法性如果合法直接放入全连接队列。2.TCP连接的关闭2.1连接的正常关闭连接的正常关闭需要经历四次挥手并由主动关闭的一端进入TIME_WAIT状态等待来确保另一端能可靠关闭同时也确保旧连接的数据包能在网络上消亡不会影响新连接。2.2 为什么关闭连接需要四次挥手1四次挥手可以使双方实现半关闭主动关闭端先关闭自己的发送被动关闭端仍可继续发送数据被动关闭端关闭自己的发送双方都不再发送数据之后才完全关闭连接这样有利于被动关闭端妥善处理已接收未处理的数据。2三次挥手不足以保证连接的可靠关闭因为若被动关闭端发出的第三次挥手报文丢失主动关闭端就无法正常关闭连接被动关闭端需要第四次挥手来判断是否需要重传第三次挥手报文确保主动关闭端正常关闭连接。若第四次挥手的ACK报文丢失被动关闭端会重传第三次挥手的FIN报文。2.3 连接关闭超时重传发出FIN报文后如果等不到对方确认的ACK报文会重传FIN。两次FIN挥手的重传次数由内核参数net.ipv4. tcp_orphan_retries 控制默认值为5重传超时直接关闭连接释放资源。Linux系统重传间隔为指数退避第一次超时重传是在1秒后后续每次重传间隔是上次的2倍重传5次总耗时1248163263秒大约1分钟。主动关闭端收到第二次挥手的ACK报文后等待第三次挥手FIN报文的时间由内核参数net.ipv4.tcp_fin_timeout 控制默认值是60秒。超时仍未收到第三次挥手的FIN报文就会直接关闭连接释放资源。2.4 连接的TIME_WAIT状态2.4.1 进入TIME_WAIT状态的是什么连接是一个IP四元组的通信实例连接有CLOSED、LISTEN、ESTABLISHED、SYN_SEND、SYN_RECV、FIN_WAIT1、CLOSE_WAIT、FIN_WAIT2、LAST_ACK、TIME_WAIT、CLOSING多种状态可以在各个状态之间转换。TIME_WAIT是连接的一种状态进入TIME_WAIT状态的是连接的主动关闭端。进入TIME_WAIT状态的不是IP和端口对IP和端口只是连接占用的资源这对IP和端口不能被新连接复用是因为其仍在被处于TIME_WAIT状态的旧连接占用协议栈拒绝绑定。进入TIME_WAIT状态的不是套接字套接字只是连接的标识协议栈把套接字和连接关联起来之后交给应用层使用使应用层可以通过套接字访问连接的资源、获取和改变连接的属性和状态。套接字和连接的关系就像文件描述符和文件的关系。2.4.2 什么情况下会进入TIME_WAIT状态经历四次挥手正常关闭流程的主动关闭端会进入TIME_WAIT状态。进入TIME_WAIT状态有两个前提条件一是主动关闭的端二是经历四次挥手的正常关闭流程。经历四次挥手正常关闭流程的被动关闭端不会进入TIME_WAIT状态。未完整经历四次挥手、异常关闭的连接双方都不会进入TIME_WAIT状态。同时关闭连接的两端都会进入TIME_WAIT状态因为两端都是主动关闭端。两端同时关闭即两端都主动发出FIN报文在收到对端的ACK报文前先收到对端的FIN报文(进入CLOSING状态)在回复ACK报文后进入TIME_WAIT状态。双方都进入TIME_WAIT状态的目的是为了确保自己回复的ACK报文能被对端收到若ACK丢失对端收不到会重传FIN。客户端经常会进入TIME_WAIT状态因为主动关闭连接经常由客户端发起。当网络出现异常、客户端应用层崩溃或者资源耗尽时服务器端需要主动关闭连接来释放内存和文件描述符资源进入TIME_WAIT状态。服务器端的监听套接字不会进入TIME_WAIT状态因为该套接字只监听不连接。服务器端accept返回的连接会进入TIME_WAIT状态其进入TIME_WAIT状态会导致监听套接字重新bind绑定IP和端口失败因为IP和端口对被其占用协议栈拒绝重新绑定。2.4.3 TIME_WAIT状态的持续时间TIME_WAIT持续的时间是2MSLlinux系统大约60秒。时间设为2MSL的原因是主动关闭端回复的ACK可能会丢失若ACK丢失被动关闭端会重传FIN从被动关闭端发出FIN到主动关闭端发出的ACK到达被动关闭端这段时间最大为约2MSL。若被动关闭端在ACK消亡之前重传FIN则此FIN到达主动关闭端的最大时间距ACK发出的时间小于2MSL这种情况主动关闭端会重传ACK。若被动关闭端在ACK消亡之后才重发FIN则此FIN到达主动关闭端的最大时间距ACK发出的时间大于2MSL这种情况主动关闭端已退出TIME_WAIT状态。主动关闭端发出ACK之后经过2MSL仍未收到被动关闭端重传的FIN说明对方已正常关闭若因网络异常导致重传的FIN丢失则不应继续等待下去继续等下去会导致资源不能及时释放弊大于利。2.4.4 TIME_WAIT状态会占用哪些资源处于TIME_WAIT状态的连接不会占用文件描述符调用close后连接已与文件描述符分离进入TIME_WAIT状态的连接不再关联文件描述符。处于TIME_WAIT状态的连接不会占用数据收发缓存因其不再处理数据收发缓存已被协议栈释放主动关闭端发出FIN前释放发送缓存收到对端的FIN后释放接收缓存。处于TIME_WAIT状态的连接会占用小片内存用于保存IP和端口四元组、定时器、序列号、时间戳若启用等相关信息大概几百字节。处于TIME_WAIT状态的连接会占用IP和端口重新绑定这对IP和端口会被协议栈拒绝。处于TIME_WAIT状态的连接占用IP和端口是为了准备接收和回复对端可能重传的FIN报文。2.4.5 TIME_WAIT状态的优点1主动关闭端在必要时重传ACK报文尽量确保最后一次挥手的ACK报文能被对方收到确保被动关闭端能被正常关闭。2防止旧连接的失效数据被相同四元组的新连接当作有效数据接收。被动关闭端最后一次发出的数据包会在FIN之前发出若在FIN之后到达会被丢弃因为主动关闭端协议栈收到FIN后会立即回复ACK进入TIME_WAIT状态释放接收缓存不再处理数据。被动关闭端最后一次发出的数据包达主动关闭端的最大时间是MSL此时主动关闭端尚未退出TIME_WAIT状态仍在占用旧连接的IP和端口无法创建新连接。新连接要在TIME_WAIT状态结束即2MSL之后才能创建此时被动关闭端最后一次发出的数据包已消亡或被丢弃不会影响新连接。2.4.6 TIME_WAIT状态的缺点TIME_WAIT状态会占用端口和少量内存资源。套接字进入TIME_WAIT状态后其收发缓存已被释放只需少量内存保存属性信息大概几百字节。1对客户端影响较小客户端产生的连接数量较少占用的端口和内存资源都较少。客户端主动关闭连接后无法使用相同的IP和端口重新发起连接因为旧IP和端口号可能正在被TIME_WAIT状态的旧连接占用无法被bind绑定。客户端可以使用不同的IP 和端口重新建立连接。2对服务器端影响较大因为服务端产生的连接数量较多占用内存资源较多。服务器端使用固定IP和端口对若有连接进入TIME_WAIT状态应用层重启后监听套接字无法重新绑定这个IP和端口。服务器端的监听套接字和accept返回的所有连接使用相同的本地IP和端口只要这些连接中有任何一个主动关闭并进入TIME_WAIT状态服务器端应用层重启后监听套接字就无法重新绑定这个IP和端口。服务器端减小TIME_WAIT状态影响的方法①减小net.ipv4.tcp_max_tw_buckets。②在bind绑定前对套接字使用SO_REUSEADDR选项。2.4.7 Linux系统与TIME_WAIT状态有关的内核参数1net.ipv4.tcp_max_tw_buckets整型默认值是180000表示系统允许产生TIME_WAIT状态连接的最大数目。如果超过此数再主动关闭连接就直接发送RST重置连接不再进入TIME_WAIT状态。2net.ipv4.tcp_tw_recycle布尔型默认值是0置1表示打开TIME_WAIT连接快速回收在资源不足时销毁最早进入TIME_WAIT状态的连接释放内存资源。3net.ipv4.tcp_tw_reuse布尔型默认值是0置1表示允许销毁处于TIME_WAIT状态旧连接把其占用的IP和端口重新用于新连接。2.5 连接异常关闭连接双方不经历完整的四次挥手关闭流程直接销毁连接释放资源属于异常关闭。2.5.1 异常关闭的原因1网络异常数据重传超时或连接关闭超时无法执行四次挥手的正常流程协议栈会直接销毁连接。2一端收到对端的RST报文说明对端连接出现错误无法执行四次挥手的正常流程协议栈会直接销毁连接。3一端资源不足应用层使用SO_LINGER选项调用close主动发起异常关闭释放资源。struct linger { int l_onoff; int l_linger; }l_onoff为0则选项关闭l_linger的值被忽略使用默认方式正常关闭连接。l_onoff非0l_linger非0正常关闭连接。l_onoff非0l_linger为0丢弃缓存中的数据立即销毁连接并向对端发送RST报文。struct linger opt {1, 0};setsockopt(s, SOL_SOCKET, SO_LINGER, opt, sizeof(opt));close(s);使用SO_LINGER选项关闭连接协议栈会立即丢弃发送缓存中的残留数据跳过四次挥手过程向对端发送RST报文重置连接。RST报文没有确认机制可能会丢失。2.5.2 连接关闭异常有什么坏处1收发缓存中的数据会丢失。2如果客户端使用相同的IP和端口重新建立连接旧连接的失效数据包可能会被新连接接收导致新连接数据被污染。异常关闭的连接双方都不会进入TIME_WAIT状态重新建立连接耗时较短新连接建立后旧连接的数据包可能尚未消亡。若旧连接的失效数据包的SEQ小于新连接的SEQ会被当做重传的数据包直接丢弃。若旧连接的失效数据包的SEQ大于新连接的SEQ接收端会认为中间SEQ的数据包尚未到达把该数据包当做有效数据接收。因此客户端每次发起连接时应使用系统随机分配的端口尽量避免使用相同的端口。3连接的一端异常关闭后如果没有交互另一端无法感知无法及时释放资源和重建连接。对端在产生数据交互重传数据超时或者收到数据后协议栈发现连接不存在才能感知。协议栈会发送RST报文重置连接。对端可以启用KEEP_ALIVE保持数据交互或者在应用层主动监控连接主动关闭长时间没有数据交互的连接。3.连接异常连接的一端应用层崩溃时系统内核在清理应用层占用的文件描述符时会接管未关闭的连接对这些连接逐个发送FIN报文按照正常关闭流程释放资源。连接会因为主动发送FIN报文关闭而进入TIME_WAIT状态。在退出TIME_WAIT状态之前收到对端发来的数据会直接丢弃。退出TIME_WAIT状态之后收到对端发来的数据协议栈发现连接不存在会发送RST重置连接。连接一端应用层或系统崩溃并重启后收到对端发来的数据协议栈发现连接不存在会发送RST重置连接。客户端应用层在关闭连接之前崩溃系统会清理应用层占用的文件描述符并调用协议栈主动关闭连接完成资源的释放。客户端在关闭连接之前主机系统崩溃或者网络断开会导致服务器端的连接不能被关闭产生死连接。服务器端应用层需要定时检查连接状态主动识别死连接关闭这些死连接释放资源。服务器端资源不足时应用层应该主动关闭部分连接先走正常关闭流程并进入TIME_WAIT状态正常关闭失败会直接销毁连接释放内存和文件描述符资源。客户端系统崩溃之后重启若使用相同IP和端口重新发起连接服务器端发现连接已存在会回复RST重置连接。若客户端使用不同的IP或端口重新发起连接服务器端会创建新连接不会发现旧连接存在异常这会导致死连接不能被关闭内存和文件描述符不能被释放浪费资源。4.TCP重传4.1 重传次数RFC 1122 建议至少重传 3 次。Linux系统与超时重传有关的内核参数1net.ipv4.tcp_retries1整型默认值是3。重传次数tcp_retries1只重传数据。重传次数超过tcp_retries1每次重传前触发IP层路由刷新、PMTU探测排查链路故障不会断开连接。2net.ipv4.tcp_retries2整型默认值为15。重传次数超过tcp_retries2协议栈会主动关闭连接。重传15 次总耗时13到30分钟。4.2 重传机制TCP的重传机制有超时重传、快速重传、选择性确认。1超时重传发送方为每个报文启动重传定时器RTO若在RTO内未收到ACK则重传该报文。RTO基于动态估算的RTT往返时间计算RTO SRTT 4×RTTd每次超时后RTO会指数退避如翻倍。2快速重传发送方连续收到3个重复ACK即视为丢包立即重传对应的报文无需等待RTO超时。3选择性确认选择性确认的优点是一端发出多个数据包另一端只收到后边部分数据包前边部分数据包丢失发送方只需重传前边丢失的部分。启用选择性确认SACK可使接收方告知发送方自己已接收数据块的范围使发送方仅重传未收到的数据避免重传已收到的数据。SACK需双方在创建TCP连接时使用TCP头部选项协商通过SACK_PERMITTED选项)。受限制于TCP选项长度接收方最多同时报告3~4个SACK块。重复选择性确认DSACK是SACK的扩展可以让接收方在收到重复数据时用SACK的第一个块告知发送方自己收到重复数据的范围。接收方收到了重复的数据包说明数据包并未丢失只是延迟到达通过DSACK可以让发送方知道当前网络虽然拥塞但并不严重若后续出现数据确认超时可不必立即重传以避免网络因重传而加重拥塞。5. TCP保活5.1 保活选项SO_KEEPALIVEint on 1;setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, on, sizeof(on));int idle 300; // 空闲多久开始探测int intv 30; // 探测间隔int cnt 3; // 重试次数setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, idle, sizeof(idle));setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, intv, sizeof(intv));setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, cnt, sizeof(cnt));5.2 Linux与保活有关的内核参数net.ipv4.tcp_keepalive_time7200net.ipv4.tcp_keepalive_intvl75net.ipv4.tcp_keepalive_probes9保活时间是 7200 秒也就 2 小时内如果没有任何连接相关的活动则会启动保活机制。每次检测间隔 75秒。连续检测9次无响应认为对方是不可达的主动关闭连接。也即最少需要经过 2 小时 11 分 15 秒才可以发现一个死亡连接。6. 数据传输6.1 报文大小目前网络交换机和路由器的最大传输单元MTU普遍为1500字节。最大报文段大小MSS 为MTU值减去IPv4 Header20 Byte和TCP header20 Byte即MSS 1500 - 20 - 20 1460。
TCP/IP总结
发布时间:2026/6/5 14:58:26
1.TCP连接的建立1.1 为什么建立连接需要三次握手12次握手不合适若只有2次握手服务器端需要在收到SYN发出SYNACK后就建立连接。服务器端无法确认客户端是否收到SYNACK若SYNACK丢失服务器端创建连接后收不到客户端的数据会造成服务器端资源浪费。协议栈自动重发的SYN报文会与原始SYN报文使用相同的序列号。但是应用程序自己实现的等待超时自动重新发起连接机制会迫使协议栈使用新序列号发出SYN报文。服务器端连接创建完成后又收到四元组相同但序列号不同的SYN报文会回复RST重置连接若网络不稳定可能需要反复重建连接无谓消耗资源。3次握手比2次握手更有利于旧SYN报文在网络上自然消亡。服务器端需在收到ACK后才创建连接更不易受旧SYN报文影响。只有2次握手服务器容易遭受攻击大量消耗资源。2三次握手足以保证连接的可靠建立不需要四次握手。1.2 连接创建过程当服务端接收到客户端的 SYN 报文时会将其放入协议栈的半连接队列SYN 队列接着发送SYN ACK 给客户端等待客户端回应 ACK 报文服务端接收到 ACK 报文后从半连接队列中取出放入全连接队列accept队列。服务器端应用层通过调用 accpet从全连接队列取出连接。客户端会在发出ACK报文后创建连接服务器端会在收到ACK后创建连接。服务器端在收到SYN后只会创建半连接协议栈不会为半连接分配收发数据的缓存所以前两次握手的SYN报文都不能携带数据第3次握手的ACK可以携带数据因为服务器端收到ACK后就会正式创建有效连接分配收发数据的缓存可以处理数据了。在服务器端收到第三次握手的ACK报文之前若客户端向服务器端发送数据服务器端会回复RST重置连接。1.3 为何不携带数据的ACK报文不消耗序列号但不携带数据的SYN和FIN报文仍要消耗一个序列号RFC 793 规定无应用层数据的TCP控制报文SYN/FIN消耗一个序列号(SEQ)是为了与数据报文共享相同的确认机制。SYN消耗一个序列号可以使其ACK确认报文的期望序列号在SYN报文序列号的基础上加1与对数据报文的确认机制相同代码实现可以共享。若SYN不消耗序列号就需要特殊处理不能与确认数据的ACK报文共享代码。FIN消耗一个序列号可以使其ACK确认报文的期望序列号在FIN报文序列号的基础上加1。若FIN不消耗序列号确认FIN报文的ACK序列号与确认最后一帧数据的ACK序列号相同正在等待确认FIN报文一端就无法区分收到的ACK是对FIN的确认还是对之前数据的确认。1.4 初始序列号ISN建立连接时双方使用的初始序列号ISN是随机数目的为了尽量避免历史报文被下一个相同四元组的连接当作有效报文接收同时也为了防止TCP被窃听和攻击。1.5 连接创建超时重传RFC 1122 建议连接创建超时至少重传 3 次。SYN和SYNACK报文在等待下一次握手超时后都会主动重传。第三次握手的ACK报文不会主动重传ACK报文的重传由SYNACK报文的重传被动触发。任何交互的最后一次确认报文都不会主动重传只能被前一次交互的重传报文被动触发重传。在 Linux 里SYN 报文最大重传次数由内核参数net.ipv4.tcp_syn_retries控制SYNACK 报文的最大重传次数由内核参数net.ipv4.tcp_synack_retries决定这两个参数都是可以自定义的默认值一般是5。Linux系统重传间隔为指数退避第一次超时重传是在 1秒后后续每次重传间隔是上次的2倍重传5次总耗时1248163263秒大约 1分钟。首次超时重传使用固定间隔是因为连接尚未建立无法估算时间。重传超时则放弃创建连接释放资源。1.6 服务器端连接创建拥堵如果服务器端短时间内收到大量SYN报文会导致SYN 队列被占满只能对新的 SYN 报文回复RST拒绝连接请求。如果服务器端应用层不能及时调用accept取出连接会导致全连接队列被占满。内核参数net.ipv4.tcp_max_syn_backlog整型控制SYN队列的最大个数。内核参数net.ipv4.tcp_abort_on_overflow布尔型配置当SYN 队列被占满时是否对新的 SYN 报文回复RST默认值是0配置为1则回复RST报文。一种应对连接创建拥堵的办法是启用cookie设置内核参数net.ipv4.tcp_syncookies 1。当SYN 队列满之后后续服务器收到 SYN 报文不放入SYN 队列而是计算出一个 cookie 值再以 SYN ACK 中的序列号返回客户端。服务端接收到客户端的ACK报文时会检查这个 ACK 报文的合法性如果合法直接放入全连接队列。2.TCP连接的关闭2.1连接的正常关闭连接的正常关闭需要经历四次挥手并由主动关闭的一端进入TIME_WAIT状态等待来确保另一端能可靠关闭同时也确保旧连接的数据包能在网络上消亡不会影响新连接。2.2 为什么关闭连接需要四次挥手1四次挥手可以使双方实现半关闭主动关闭端先关闭自己的发送被动关闭端仍可继续发送数据被动关闭端关闭自己的发送双方都不再发送数据之后才完全关闭连接这样有利于被动关闭端妥善处理已接收未处理的数据。2三次挥手不足以保证连接的可靠关闭因为若被动关闭端发出的第三次挥手报文丢失主动关闭端就无法正常关闭连接被动关闭端需要第四次挥手来判断是否需要重传第三次挥手报文确保主动关闭端正常关闭连接。若第四次挥手的ACK报文丢失被动关闭端会重传第三次挥手的FIN报文。2.3 连接关闭超时重传发出FIN报文后如果等不到对方确认的ACK报文会重传FIN。两次FIN挥手的重传次数由内核参数net.ipv4. tcp_orphan_retries 控制默认值为5重传超时直接关闭连接释放资源。Linux系统重传间隔为指数退避第一次超时重传是在1秒后后续每次重传间隔是上次的2倍重传5次总耗时1248163263秒大约1分钟。主动关闭端收到第二次挥手的ACK报文后等待第三次挥手FIN报文的时间由内核参数net.ipv4.tcp_fin_timeout 控制默认值是60秒。超时仍未收到第三次挥手的FIN报文就会直接关闭连接释放资源。2.4 连接的TIME_WAIT状态2.4.1 进入TIME_WAIT状态的是什么连接是一个IP四元组的通信实例连接有CLOSED、LISTEN、ESTABLISHED、SYN_SEND、SYN_RECV、FIN_WAIT1、CLOSE_WAIT、FIN_WAIT2、LAST_ACK、TIME_WAIT、CLOSING多种状态可以在各个状态之间转换。TIME_WAIT是连接的一种状态进入TIME_WAIT状态的是连接的主动关闭端。进入TIME_WAIT状态的不是IP和端口对IP和端口只是连接占用的资源这对IP和端口不能被新连接复用是因为其仍在被处于TIME_WAIT状态的旧连接占用协议栈拒绝绑定。进入TIME_WAIT状态的不是套接字套接字只是连接的标识协议栈把套接字和连接关联起来之后交给应用层使用使应用层可以通过套接字访问连接的资源、获取和改变连接的属性和状态。套接字和连接的关系就像文件描述符和文件的关系。2.4.2 什么情况下会进入TIME_WAIT状态经历四次挥手正常关闭流程的主动关闭端会进入TIME_WAIT状态。进入TIME_WAIT状态有两个前提条件一是主动关闭的端二是经历四次挥手的正常关闭流程。经历四次挥手正常关闭流程的被动关闭端不会进入TIME_WAIT状态。未完整经历四次挥手、异常关闭的连接双方都不会进入TIME_WAIT状态。同时关闭连接的两端都会进入TIME_WAIT状态因为两端都是主动关闭端。两端同时关闭即两端都主动发出FIN报文在收到对端的ACK报文前先收到对端的FIN报文(进入CLOSING状态)在回复ACK报文后进入TIME_WAIT状态。双方都进入TIME_WAIT状态的目的是为了确保自己回复的ACK报文能被对端收到若ACK丢失对端收不到会重传FIN。客户端经常会进入TIME_WAIT状态因为主动关闭连接经常由客户端发起。当网络出现异常、客户端应用层崩溃或者资源耗尽时服务器端需要主动关闭连接来释放内存和文件描述符资源进入TIME_WAIT状态。服务器端的监听套接字不会进入TIME_WAIT状态因为该套接字只监听不连接。服务器端accept返回的连接会进入TIME_WAIT状态其进入TIME_WAIT状态会导致监听套接字重新bind绑定IP和端口失败因为IP和端口对被其占用协议栈拒绝重新绑定。2.4.3 TIME_WAIT状态的持续时间TIME_WAIT持续的时间是2MSLlinux系统大约60秒。时间设为2MSL的原因是主动关闭端回复的ACK可能会丢失若ACK丢失被动关闭端会重传FIN从被动关闭端发出FIN到主动关闭端发出的ACK到达被动关闭端这段时间最大为约2MSL。若被动关闭端在ACK消亡之前重传FIN则此FIN到达主动关闭端的最大时间距ACK发出的时间小于2MSL这种情况主动关闭端会重传ACK。若被动关闭端在ACK消亡之后才重发FIN则此FIN到达主动关闭端的最大时间距ACK发出的时间大于2MSL这种情况主动关闭端已退出TIME_WAIT状态。主动关闭端发出ACK之后经过2MSL仍未收到被动关闭端重传的FIN说明对方已正常关闭若因网络异常导致重传的FIN丢失则不应继续等待下去继续等下去会导致资源不能及时释放弊大于利。2.4.4 TIME_WAIT状态会占用哪些资源处于TIME_WAIT状态的连接不会占用文件描述符调用close后连接已与文件描述符分离进入TIME_WAIT状态的连接不再关联文件描述符。处于TIME_WAIT状态的连接不会占用数据收发缓存因其不再处理数据收发缓存已被协议栈释放主动关闭端发出FIN前释放发送缓存收到对端的FIN后释放接收缓存。处于TIME_WAIT状态的连接会占用小片内存用于保存IP和端口四元组、定时器、序列号、时间戳若启用等相关信息大概几百字节。处于TIME_WAIT状态的连接会占用IP和端口重新绑定这对IP和端口会被协议栈拒绝。处于TIME_WAIT状态的连接占用IP和端口是为了准备接收和回复对端可能重传的FIN报文。2.4.5 TIME_WAIT状态的优点1主动关闭端在必要时重传ACK报文尽量确保最后一次挥手的ACK报文能被对方收到确保被动关闭端能被正常关闭。2防止旧连接的失效数据被相同四元组的新连接当作有效数据接收。被动关闭端最后一次发出的数据包会在FIN之前发出若在FIN之后到达会被丢弃因为主动关闭端协议栈收到FIN后会立即回复ACK进入TIME_WAIT状态释放接收缓存不再处理数据。被动关闭端最后一次发出的数据包达主动关闭端的最大时间是MSL此时主动关闭端尚未退出TIME_WAIT状态仍在占用旧连接的IP和端口无法创建新连接。新连接要在TIME_WAIT状态结束即2MSL之后才能创建此时被动关闭端最后一次发出的数据包已消亡或被丢弃不会影响新连接。2.4.6 TIME_WAIT状态的缺点TIME_WAIT状态会占用端口和少量内存资源。套接字进入TIME_WAIT状态后其收发缓存已被释放只需少量内存保存属性信息大概几百字节。1对客户端影响较小客户端产生的连接数量较少占用的端口和内存资源都较少。客户端主动关闭连接后无法使用相同的IP和端口重新发起连接因为旧IP和端口号可能正在被TIME_WAIT状态的旧连接占用无法被bind绑定。客户端可以使用不同的IP 和端口重新建立连接。2对服务器端影响较大因为服务端产生的连接数量较多占用内存资源较多。服务器端使用固定IP和端口对若有连接进入TIME_WAIT状态应用层重启后监听套接字无法重新绑定这个IP和端口。服务器端的监听套接字和accept返回的所有连接使用相同的本地IP和端口只要这些连接中有任何一个主动关闭并进入TIME_WAIT状态服务器端应用层重启后监听套接字就无法重新绑定这个IP和端口。服务器端减小TIME_WAIT状态影响的方法①减小net.ipv4.tcp_max_tw_buckets。②在bind绑定前对套接字使用SO_REUSEADDR选项。2.4.7 Linux系统与TIME_WAIT状态有关的内核参数1net.ipv4.tcp_max_tw_buckets整型默认值是180000表示系统允许产生TIME_WAIT状态连接的最大数目。如果超过此数再主动关闭连接就直接发送RST重置连接不再进入TIME_WAIT状态。2net.ipv4.tcp_tw_recycle布尔型默认值是0置1表示打开TIME_WAIT连接快速回收在资源不足时销毁最早进入TIME_WAIT状态的连接释放内存资源。3net.ipv4.tcp_tw_reuse布尔型默认值是0置1表示允许销毁处于TIME_WAIT状态旧连接把其占用的IP和端口重新用于新连接。2.5 连接异常关闭连接双方不经历完整的四次挥手关闭流程直接销毁连接释放资源属于异常关闭。2.5.1 异常关闭的原因1网络异常数据重传超时或连接关闭超时无法执行四次挥手的正常流程协议栈会直接销毁连接。2一端收到对端的RST报文说明对端连接出现错误无法执行四次挥手的正常流程协议栈会直接销毁连接。3一端资源不足应用层使用SO_LINGER选项调用close主动发起异常关闭释放资源。struct linger { int l_onoff; int l_linger; }l_onoff为0则选项关闭l_linger的值被忽略使用默认方式正常关闭连接。l_onoff非0l_linger非0正常关闭连接。l_onoff非0l_linger为0丢弃缓存中的数据立即销毁连接并向对端发送RST报文。struct linger opt {1, 0};setsockopt(s, SOL_SOCKET, SO_LINGER, opt, sizeof(opt));close(s);使用SO_LINGER选项关闭连接协议栈会立即丢弃发送缓存中的残留数据跳过四次挥手过程向对端发送RST报文重置连接。RST报文没有确认机制可能会丢失。2.5.2 连接关闭异常有什么坏处1收发缓存中的数据会丢失。2如果客户端使用相同的IP和端口重新建立连接旧连接的失效数据包可能会被新连接接收导致新连接数据被污染。异常关闭的连接双方都不会进入TIME_WAIT状态重新建立连接耗时较短新连接建立后旧连接的数据包可能尚未消亡。若旧连接的失效数据包的SEQ小于新连接的SEQ会被当做重传的数据包直接丢弃。若旧连接的失效数据包的SEQ大于新连接的SEQ接收端会认为中间SEQ的数据包尚未到达把该数据包当做有效数据接收。因此客户端每次发起连接时应使用系统随机分配的端口尽量避免使用相同的端口。3连接的一端异常关闭后如果没有交互另一端无法感知无法及时释放资源和重建连接。对端在产生数据交互重传数据超时或者收到数据后协议栈发现连接不存在才能感知。协议栈会发送RST报文重置连接。对端可以启用KEEP_ALIVE保持数据交互或者在应用层主动监控连接主动关闭长时间没有数据交互的连接。3.连接异常连接的一端应用层崩溃时系统内核在清理应用层占用的文件描述符时会接管未关闭的连接对这些连接逐个发送FIN报文按照正常关闭流程释放资源。连接会因为主动发送FIN报文关闭而进入TIME_WAIT状态。在退出TIME_WAIT状态之前收到对端发来的数据会直接丢弃。退出TIME_WAIT状态之后收到对端发来的数据协议栈发现连接不存在会发送RST重置连接。连接一端应用层或系统崩溃并重启后收到对端发来的数据协议栈发现连接不存在会发送RST重置连接。客户端应用层在关闭连接之前崩溃系统会清理应用层占用的文件描述符并调用协议栈主动关闭连接完成资源的释放。客户端在关闭连接之前主机系统崩溃或者网络断开会导致服务器端的连接不能被关闭产生死连接。服务器端应用层需要定时检查连接状态主动识别死连接关闭这些死连接释放资源。服务器端资源不足时应用层应该主动关闭部分连接先走正常关闭流程并进入TIME_WAIT状态正常关闭失败会直接销毁连接释放内存和文件描述符资源。客户端系统崩溃之后重启若使用相同IP和端口重新发起连接服务器端发现连接已存在会回复RST重置连接。若客户端使用不同的IP或端口重新发起连接服务器端会创建新连接不会发现旧连接存在异常这会导致死连接不能被关闭内存和文件描述符不能被释放浪费资源。4.TCP重传4.1 重传次数RFC 1122 建议至少重传 3 次。Linux系统与超时重传有关的内核参数1net.ipv4.tcp_retries1整型默认值是3。重传次数tcp_retries1只重传数据。重传次数超过tcp_retries1每次重传前触发IP层路由刷新、PMTU探测排查链路故障不会断开连接。2net.ipv4.tcp_retries2整型默认值为15。重传次数超过tcp_retries2协议栈会主动关闭连接。重传15 次总耗时13到30分钟。4.2 重传机制TCP的重传机制有超时重传、快速重传、选择性确认。1超时重传发送方为每个报文启动重传定时器RTO若在RTO内未收到ACK则重传该报文。RTO基于动态估算的RTT往返时间计算RTO SRTT 4×RTTd每次超时后RTO会指数退避如翻倍。2快速重传发送方连续收到3个重复ACK即视为丢包立即重传对应的报文无需等待RTO超时。3选择性确认选择性确认的优点是一端发出多个数据包另一端只收到后边部分数据包前边部分数据包丢失发送方只需重传前边丢失的部分。启用选择性确认SACK可使接收方告知发送方自己已接收数据块的范围使发送方仅重传未收到的数据避免重传已收到的数据。SACK需双方在创建TCP连接时使用TCP头部选项协商通过SACK_PERMITTED选项)。受限制于TCP选项长度接收方最多同时报告3~4个SACK块。重复选择性确认DSACK是SACK的扩展可以让接收方在收到重复数据时用SACK的第一个块告知发送方自己收到重复数据的范围。接收方收到了重复的数据包说明数据包并未丢失只是延迟到达通过DSACK可以让发送方知道当前网络虽然拥塞但并不严重若后续出现数据确认超时可不必立即重传以避免网络因重传而加重拥塞。5. TCP保活5.1 保活选项SO_KEEPALIVEint on 1;setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, on, sizeof(on));int idle 300; // 空闲多久开始探测int intv 30; // 探测间隔int cnt 3; // 重试次数setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, idle, sizeof(idle));setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, intv, sizeof(intv));setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, cnt, sizeof(cnt));5.2 Linux与保活有关的内核参数net.ipv4.tcp_keepalive_time7200net.ipv4.tcp_keepalive_intvl75net.ipv4.tcp_keepalive_probes9保活时间是 7200 秒也就 2 小时内如果没有任何连接相关的活动则会启动保活机制。每次检测间隔 75秒。连续检测9次无响应认为对方是不可达的主动关闭连接。也即最少需要经过 2 小时 11 分 15 秒才可以发现一个死亡连接。6. 数据传输6.1 报文大小目前网络交换机和路由器的最大传输单元MTU普遍为1500字节。最大报文段大小MSS 为MTU值减去IPv4 Header20 Byte和TCP header20 Byte即MSS 1500 - 20 - 20 1460。