设想这样一个画面:山村的邮差爷爷,第一次给城里上大学的孙子打电话。电话接通前,两个人必须来回确认——“喂?听得见吗?”“哎爷爷,听得见!您听得见我说话吗?”“听得见听得见!”——三次对话之后,通话才真正开始。而挂断时,又是一番“我说完啦”“知道啦,我这儿也说完了”“好嘞,再见”,爷爷还要握着听筒多等两分钟,确认孙子没有别的话要说了,才放心放下。这段看似平常的通话流程,恰恰是 TCP 协议建立与断开连接的全部智慧:三次握手保证双方“听得见也说得出”,四次挥手让告别体面而安全,最后的等待则是给迟到的声音一个“过期作废”的时间。本文就用这条电话线,把 TCP 最核心的握手与挥手讲清楚——你不需要任何网络基础,只要会打电话,就能看懂。

岗位关联

对 AI 从业者而言,无论是部署分布式训练集群、排查推理服务连接超时,还是优化千卡通信效率,TCP 连接的生命周期管理都是绕不开的底层功。理解握手与挥手,等于拿到网络排障的第一把钥匙。

目录
  1. 可视化演示
  2. 三次握手机制
  3. 报文段字段
  4. 建立状态迁移
  5. 四次挥手过程
  6. 可靠性保障
  7. 疑难与调优
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示TCP连接建立与释放的完整过程,包含三次握手SYN/SYN-ACK/ACK和四次挥手FIN/ACK的报文交互动画。

本节配有交互动效(进入视口后自动加载,约 3KB)。

TCP三次握手 SYN同步序号建立双向收发能力 SYN+ACK合并确认减少报文往返 ESTABLISHED = SYN + SYN/ACK + ACK 三步握手确保连接可靠建立

三次握手机制

爷爷拨通孙子的电话,听筒里传来“嘟——”的长音,这就像客户端发出的第一个 SYN 报文:带着一个精心挑选的序列号 x,轻声问“喂,你在吗?”如果孙子接了电话说“哎爷爷,我在!您听得见我说话吗?”——这既是回应前一句(ACK),又是发起新的询问(SYN),对应服务端回送的 SYN+ACK 报文。最后爷爷说“听得见!”——这个 ACK 确认,让服务端确信爷爷也能听见自己。至此,两端才确认彼此的“耳朵”和“嘴巴”都正常工作,连接正式建立。

为什么必须是三次,而不是两次?想象爷爷在山上大喊“喂”,回声在山谷里回荡,结果爷爷把回声当成孙子的回应,以为电话通了,其实孙子根本没接。TCP 面临的正是这种“网络回声”:一个延迟了很久的旧 SYN 报文,可能被当作新请求。只有通过“询问-回应-再确认”三轮对话,让双方都确认对方收到了自己的初始序列号,才能把历史的、过期的报文挡在门外。两次握手只能证明孙子的“耳朵”好,却证明不了爷爷的“耳朵”好。

技术层面,三次握手的核心是交换并确认双方的初始序列号(ISN)。第一次握手客户端发送 SYN,seq=x;第二次握手服务端回应 SYN+ACK,seq=y,ack=x+1;第三次握手客户端回 ACK,ack=y+1。任何一个 ack 缺失或错位,连接都无法进入 ESTABLISHED。这三报文段的 ack 关系非常对称,是 TCP 可靠性的地基。

三次握手,本质上是两个陌生人完成“你能听见我、我能听见你、我们节奏对齐”的仪式。少了任何一次,总有一端心里没底——握手的“三”,不是巧合,是信息论意义上最少的次数。

ACKserver=SEQclient+1=x+1\text{ACK}_{\text{server}} = \text{SEQ}_{\text{client}} + 1 = x + 1
服务端的确认号不是 x,而是 x+1——这多出来的 1,代表它已收下你的 SYN,正在期待下一个字节。
  1. 客户端初始序列号客户端随机挑一个数 x 作为自己的起点,这个数字在报文中记为 seq=x。
    SEQclient=x\text{SEQ}_{\text{client}} = x
  2. 服务端确认号服务端用 ack=x+1 告诉客户端“我收到了你的 x,接下来请从 x+1 开始发数据”。
    ACKserver=x+1\text{ACK}_{\text{server}} = x + 1
  3. 客户端二次确认客户端再回 ack=y+1,其中 y 是服务端的初始序列号,形成对称的确认闭环。
    ACKclient=y+1\text{ACK}_{\text{client}} = y + 1

报文段字段

爷爷和孙子通话时,每句话都有讲究:开头先说“我是爷爷,第 7 句话我想说……”,孙子听完会说“爷爷,您第 1 到 7 句话我都收到了,接下来您说第 8 句吧”。TCP 报文段里装着同样的“对话包装纸”——序列号 SEQ 告诉对方“这是我的第几个字节”,确认号 ACK 告诉对方“我已经收到你到第几个字节为止的全部内容”,而 SYN、FIN、ACK 这些标志位,则像电话里的语气词,标明这句话是“询问”“回答”还是“告别”。

序列号不是从 0 开始,而是从双方各自随机选的初始序列号(ISN)起步。这样做有什么好处?想象爷爷每次都从“第 1 句”开始数,那么一个迟到的旧电话录音可能被孙子误以为是新对话,从而串号。随机化 ISN 让黑客很难凭空猜出合法的序列号去伪造报文,也避免新旧连接之间因序号重叠而互相污染。

序列号的计算规律可以用一个简单的公式概括:每个报文段的序列号,等于上一个报文段的序列号加上它承载的数据字节数。也就是说,序号是“按字节计价”的,数据本身不靠“这是我第几包”来识别,而是靠“我从第几个字节开始、延续多少个字节”来唯一定位。确认号则是累计的——你只需要告诉对方“我到 1500 为止全都收到”,比逐条回执高效得多。

如果把数据流比作一条望不到头的公路,序列号就是路边的里程碑,确认号就是“我已开到第几公里”的回执。里程碑随机换起点,是为了让每个司机的里程本不被别人的旧记录干扰。

SEQn=SEQn−1+LEN(payloadn−1)\text{SEQ}_n = \text{SEQ}_{n-1} + \text{LEN}(\text{payload}_{n-1})
序列号顺着数据字节数递增;确认号永远表示“下一个期待字节”的位置。
  1. 上一段序列号第 n-1 个报文段的序列号 seq_{n-1},是字节流的起点位置。
    SEQn−1\text{SEQ}_{n-1}
  2. 载荷长度LEN(payload) 是该报文段携带数据的字节数,用于在字节流上“前进”。
    LEN(payloadn−1)\text{LEN}(\text{payload}_{n-1})
  3. 新序列号两者相加得到下一段起始序号,体现序号是字节流的连续坐标。
    SEQn=SEQn−1+LEN(payloadn−1)\text{SEQ}_n = \text{SEQ}_{n-1} + \text{LEN}(\text{payload}_{n-1})

建立状态迁移

爷爷打电话的全程,电话机其实一直处于某种“内部状态”:安静待机时是 CLOSED;拿起听筒开始拨号,手机进入 SYN_SENT(拨号中);孙子的手机在桌上充电,处于 LISTEN(等待来电);铃声响起、孙子接起前,服务器进入 SYN_RCVD(已应答待确认)——直到爷爷最后的“听得见”传来,双方同时跳入 ESTABLISHED,进入“通话中”。

状态迁移是 TCP 协议的“剧本”,每个报文段都是一个情节转折。以服务端为例:进程启动后先 LISTEN,收到客户端 SYN 后进入 SYN_RCVD,此时它已经发出 SYN+ACK,但还悬着一颗心——万一客户端最后的 ACK 丢了呢?只有收到 ack=y+1,服务端才放心进入 ESTABLISHED。而客户端发出 SYN 后从 CLOSED 直落 SYN_SENT,收到 SYN+ACK 后立刻跳到 ESTABLISHED,步伐比服务端先行一步。

这张“剧本”可以用状态转移式来表达:给定当前状态 S_n 和到达的事件 E,下一状态 S_{n+1} 由转移函数 f 决定。TCP 的状态机本质就是一张巨大的 f 查询表,每个真实报文段都会触发一次状态刷新。理解这张表,是排查“卡在 SYN_RCVD 出不来”之类的连接故障的基础。

状态迁移讲述的是“谁在什么时候等什么”的纪律。爷爷不会在拨号没接通时就自顾自说话,TCP 也不会在任何一环未确认时就贸然传数据——一切皆按剧本推进。

SYN_RCVD→ACK(y+1)ESTABLISHED\text{SYN\_RCVD} \xrightarrow{\text{ACK}(y+1)} \text{ESTABLISHED}
状态迁移由事件驱动:一个合法确认号,让半开连接变为全双工连接。
  1. 起始状态 SYN_RCVD服务端已收到 SYN 并回送 SYN+ACK,处于“等最终确认”的悬空状态。
    SYN_RCVD\text{SYN\_RCVD}
  2. 触发事件收到客户端 ACK 报文,且确认号恰好为 y+1,事件条件成立。
    ACK(y+1)\text{ACK}(y+1)
  3. 到达状态 ESTABLISHED条件满足后服务端迁移到稳定通话态,可以承载数据。
    ESTABLISHED\text{ESTABLISHED}

四次挥手过程

电话那头,爷爷说完了正事:“孙子,我就说这些,挂了吧?”——这是第一个 FIN。孙子回答:“知道啦爷爷,我听着呢。”——这是对 FIN 的 ACK。注意,此时通话并未彻底中断:爷爷的嘴巴闭上了,但耳朵还竖着,孙子还能继续补充叮嘱——这个“一方说完、另一方还能说”的状态,就是半关闭(CLOSE_WAIT 阶段)。等孙子把话都说尽:“爷爷我也说完了!”——第二个 FIN 传来。爷爷最后回一句:“好嘞,再见!”——最后的 ACK。

为什么需要四次而不是两次?因为 TCP 是全双工的,两条数据通道各自独立。爷爷的“我说完了”(FIN)和孙子的“我也说完了”(FIN)天然是两件事,中间还隔着孙子的补充内容。如果孙子刚好没有任何补充,第二个 FIN 可以和第一个 ACK 拼在一个报文里,挥手就会“压缩”成三次——这也是线上抓包有时只见三个挥手报文的原因。

爷爷挂断电话后并没有立刻放下听筒,而是多等了一个“两分钟”(2MSL)。这个等待被称为 TIME_WAIT,它的意义在于:万一自己最后的 ACK 在半路丢了,孙子会重发 FIN,爷爷好及时补一个 ACK;同时,让这段通话里所有还在网络上游荡的“迟到的字节残片”彻底过期,避免它们混进下一次通话。这个等待时长有规范依据:

四次挥手是一场体面的告别:每句话都有回应,每条通道都单独说再见,最后还要留一段“守灵期”确认没有遗言迟到。TCP 的严谨,在告别时刻体现得淋漓尽致。

TTIME_WAIT=2×TMSLT_{\text{TIME\_WAIT}} = 2 \times T_{\text{MSL}}
MSL 是一个报文在网络中能存活的最长时间。等待 2 倍 MSL,足以让本次连接的所有残留报文“死透”。
  1. MSL 定义报文最大生存时间,常见取值 30 秒到 2 分钟,过期后报文被网络丢弃。
    TMSLT_{\text{MSL}}
  2. 双向各一次数据从客户端到服务端、再从服务端到客户端各需一个 MSL 消散,故乘 2。
    2×TMSL2 \times T_{\text{MSL}}
  3. 总等待时长主动关闭方在 TIME_WAIT 状态停留 2MSL,之后才彻底关闭。
    TTIME_WAIT=2×TMSLT_{\text{TIME\_WAIT}} = 2 \times T_{\text{MSL}}

可靠性保障

电话信号不好时,爷爷常说“你刚说啥?没听清,再说一遍”。TCP 的可靠性同样依赖这种“重听”机制:发送方发出数据后启动一个定时器,如果在规定时间(RTO,超时重传时间)内没收到对方的确认,就把数据重发一遍。但这儿有个讲究——RTO 定得太短,正常延迟的数据会被冤枉重发,造成网络拥堵;定得太长,丢包后要干等都是大眼瞪小眼。所以 TCP 要动态窥探网络的心跳。

窥探的方法是 RTT 采样。RTT 是报文从发出到收到确认所经历的时间,好比爷爷掐表算“我说完到孙子回话用了多久”——山路上信号差时久一点,城里信号好时快一点。TCP 对多次 RTT 做加权平滑,得出一个“平均反应时间”,再乘一个安全系数,就是下次超时的判定线。网络越快,超时线越紧;网络越慢,超时线越松。

序号机制同样撑起可靠性的大梁。因为每个字节都有唯一序号,接收方收到乱序数据时能精确判断“缺了哪些字节”,重传只补缺失的片段,不必整段重说。累积确认则让接收方有机会“批量盖章”:一口气确认到第 1500 字节,比逐字节回执节省大量带宽。这三者——序号定位、累积确认、超时重传——是 TCP“绝不说谎”的信用体系。

爷爷反复确认孙子有没有听清,靠的是耐心和计时;TCP 则靠序号、确认和自适应超时,在毫秒级尺度上复现同一种“不抛弃、不放弃”的可靠精神。

RTO=2×RTT‾\text{RTO} = 2 \times \overline{\text{RTT}}
超时重传时间约等于两倍平滑 RTT,既不过分激进,也不过分保守。
  1. RTT 测量每次数据发送到收到 ACK 的时间差,就是一次 RTT 样本。
    RTT\text{RTT}
  2. 平滑平均多个样本按指数加权平均得到 \overline{RTT},抹平网络抖动。
    RTT‾\overline{\text{RTT}}
  3. 超时判定取两倍均值作为 RTO,作为“还没收到回执就重发”的触发线。
    RTO=2×RTT‾\text{RTO} = 2 \times \overline{\text{RTT}}

疑难与调优

有一天,电话局突然被大量“只响铃不出声”的骚扰电话淹没——拨通即挂、或者接通后沉默。爷爷家的电话一直响,但接起来没人说话,导致想打电话的人打不进来。对 TCP 而言,这种攻击叫 SYN 洪泛:攻击者疯狂发送伪造源地址的 SYN,服务端老实巴交地回 SYN+ACK 并在半连接队列里记下“待确认”条目,却永远等不到最后的 ACK。

半连接队列是服务端在 SYN_RCVD 状态的“暂存区”,容量有限。攻击者把队列塞满后,正常用户的 SYN 请求全部溢出被丢弃——连接建立直接瘫痪。防御思路主要有三:一是开大 backlog 参数,拓宽暂存区;二是启用 SYN Cookie,干脆不占队列,把状态信息编码进回执里的序号中,等真正收到 ACK 时再现场还原;三是调短 SYN_RCVD 超时,让迟迟不确认的“僵尸条目”快速过期。攻击者的 SYN 重传也遵守指数退避规则——越传越慢:

TIME_WAIT 的调优同样值得关注。高并发服务器上大量短连接会让 TIME_WAIT 堆积,占据端口资源。合理做法是开启“TIME_WAIT 重用”或调整 net.ipv4.tcp_fin_timeout,但必须谨慎——缩减 2MSL 可能牺牲防止报文串扰的安全边界。就像爷爷不再等满两分钟就挂电话,省了时间,却也可能漏掉孙子迟到的叮嘱。调优的本质,是在资源效率与安全冗余之间找平衡。

Tk+1=2×TkT_{k+1} = 2 \times T_k
SYN 重传间隔按指数翻倍,避免网络雪崩;调低超时阈值可加速清除半连接残影。
  1. 首次重传间隔第一个没有被确认的 SYN,1 秒后重传一次。
    T1=1sT_1 = 1\text{s}
  2. 指数翻倍每次重传间隔翻倍,第 k+1 次等待是第 k 次的 2 倍。
    Tk+1=2×TkT_{k+1} = 2 \times T_k
  3. 重传上限约 5-6 次后放弃,避免无限重试拖垮网络。

AI 应用场景

场景一:分布式训练中的连接超时排障

阶段:分布式训练集群网络调优

解决的问题:大模型训练时常出现节点间“连接建立失败”或“训练中断”。传统排障靠肉眼盯日志,效率低下。理解三次握手后,可直接通过 tcpdump 抓包判断卡在哪一步:只有 SYN 没回 SYN+ACK,多半是服务端端口未监听或被防火墙丢弃;SYN+ACK 发了但 ACK 没回来,可能是半连接队列溢出或客户端丢包。

具体案例:某团队训练 70B 模型时,每跑 20 分钟就有一个 worker 掉线。抓包发现大量 SYN 重传后超时,检查发现网卡多队列中断不均导致丢包,调整 RSS 队列后训练稳定运行。

场景二:高并发推理服务抵御 SYN 洪泛

阶段:推理服务上线前的稳定性加固

解决的问题:AI 服务的对外 API 网关一旦遭受 SYN 洪泛,用户将全部无法连接。理解半连接队列与 SYN Cookie 机制后,运维可提前调优:增大 net.core.somaxconn、net.ipv4.tcp_max_syn_backlog、开启 tcp_syncookies,并设置合理的 SYN_RCVD 超时(tcp_synack_retries),让攻击流量快速过期。

具体案例:某 LLM API 服务遭遇突发流量导致大量 connection timed out,监控显示 SYN_RCVD 堆积到上限。开启 SYN Cookie 并调短 synack_retries 后,正常请求恢复,服务可用性从 97.2% 回升至 99.95%。

场景三:AI 集群健康检查与连接池优化

阶段:推理服务连接池管理

解决的问题:AI 服务间频繁建立短连接会导致 TIME_WAIT 大量堆积,端口被占满,新连接无法建立。理解 2MSL 原理后,可设置连接池复用长连接,或用 net.ipv4.tcp_tw_reuse 安全重用 TIME_WAIT 端口,显著降低握手/挥手开销,提升服务响应速度。

具体案例:某向量数据库服务调用 LLM 网关时,短连接每秒建连 1200 次,服务端 TIME_WAIT 达 3 万,连接超时率 4%。改用 gRPC 长连接池后,超时率降至 0.01%,P99 延迟下降 40%。

Python 演示

python90 行
import random  class TcpEndpoint:    """简化版TCP端点, 模拟三次握手与四次挥手的状态迁移"""     def __init__(self, name):        self.name = name        self.seq = random.randint(1000, 9999)        self.ack = 0        self.state = "CLOSED"     def send_syn(self):        self.state = "SYN_SENT"        return self.seq     def recv_syn_ack(self, remote_seq, ack):        if ack != self.seq + 1:            raise ValueError("确认号不匹配, 连接失败")        self.ack = remote_seq + 1        self.state = "ESTABLISHED"        return self.ack     def recv_syn(self, remote_seq):        ack = remote_seq + 1        self.state = "SYN_RCVD"        return self.seq, ack     def recv_ack(self, ack):        if ack != self.seq + 1:            raise ValueError("确认号不匹配")        self.state = "ESTABLISHED"        return ack     def send_fin(self):        self.state = "FIN_WAIT_1"        fin_seq = self.seq        self.seq += 1        return fin_seq     def recv_fin(self, remote_seq):        ack = remote_seq + 1        if self.state == "FIN_WAIT_1":            self.state = "CLOSING"        else:            self.state = "CLOSE_WAIT"        return ack     def recv_fin_ack(self, ack):        if ack != self.seq:            raise ValueError("确认号不匹配")        self.state = "TIME_WAIT"        return self.seq  def simulate():    client = TcpEndpoint("客户端")    server = TcpEndpoint("服务端")    server.state = "LISTEN"     print("=== 三次握手 ===")    syn_c = client.send_syn()    print("1. 客户端->服务端: SYN seq=%d [%s]" % (syn_c, client.state))     syn_s, ack_s = server.recv_syn(syn_c)    print("2. 服务端->客户端: SYN+ACK seq=%d ack=%d [%s]"          % (syn_s, ack_s, server.state))     ack_c = client.recv_syn_ack(syn_s, ack_s)    server.recv_ack(ack_c)    print("3. 客户端->服务端: ACK ack=%d [%s]" % (ack_c, client.state))    print("连接建立: client=%s, server=%s" % (client.state, server.state))     print("\n=== 四次挥手 ===")    fin_c = client.send_fin()    print("4. 客户端->服务端: FIN seq=%d [%s]" % (fin_c, client.state))     ack1 = server.recv_fin(fin_c)    print("5. 服务端->客户端: ACK ack=%d [%s]" % (ack1, server.state))     fin_s = server.send_fin()    print("6. 服务端->客户端: FIN seq=%d [%s]" % (fin_s, server.state))     ack_last = client.recv_fin_ack(fin_s)    print("7. 客户端->服务端: ACK ack=%d [%s]" % (ack_last, client.state))    print("连接释放: client 在 TIME_WAIT 等待 2MSL 后 CLOSED")  if __name__ == "__main__":    simulate()
目的:用两个类模拟 TCP 端点的状态机,完整演练三次握手的确认号递增规律与四次挥手的半关闭过程,每个关键步骤打印 seq/ack 和状态名。
  1. random.randint(1000, 9999)模拟初始序列号 ISN 随机化,避免新旧连接序号串扰。
  2. self.ack = remote_seq + 1确认号恒为“对方序列号+1”,体现对 SYN/FIN 占位字节的确认。
  3. recv_syn_ack` 中的 `if ack != self.seq + 1校验服务端确认号,任何错位直接抛异常,说明握手对账的严谨。
  4. server.recv_ack(ack_c)服务端验明最终确认后才从 SYN_RCVD 迁入 ESTABLISHED。
  5. send_fin` 中的 `self.seq += 1FIN 占据一个序列号空间,为后续确认号对账埋下伏笔。

常见误区

误区握手不就是互相打个招呼确认对方还活着吗?那两次就够了,为什么非要三次?

正解握手确认的不是“在不在线”,而是“收发能力是否同时就绪”以及“双方初始序列号是否对齐”。两次握手只能让服务端确认客户端能收,却无法让客户端确认服务端也懂自己的编号规则。更关键的是,两次握手无法识别迟到的旧 SYN——爷爷把山谷回声当成孙子回应,就会在没接通时开始说话。三次确认恰好是让双方建立共同编号体系的最小交互次数。

为何常见:日常交流中“确认对方在线”只需一次回应,人们把生活经验投射到协议设计上,忽略了 TCP 还要协商序列号、防止旧报文串扰的深层需求。

误区攻击者发海量 SYN,服务器忙着创建连接、算校验和,CPU 直接烧满。

正解服务器收到 SYN 后只做了少量计算,真正的瓶颈是半连接队列——一个记录 SYN_RCVD 状态的有限容量等待区。队列塞满后,正常请求的 SYN 直接丢弃,服务表现为“完全连不上”。SYN Cookie 能把状态编码进回执序号、不占队列,几乎零成本化解洪泛,这反过来印证了瓶颈在队列而非 CPU。

为何常见:人们习惯用“高并发 = 高 CPU”的直觉归因,而忽略了协议栈中空间型瓶颈(队列耗尽)比计算型瓶颈更常见。

误区高并发服务被成千上万个 TIME_WAIT 占用端口,干脆把它关掉或设为 0 秒,效率最高。

正解TIME_WAIT 承担两个安全职责:一是等 2MSL,确保迟到的旧报文全部过期,不会污染同端口的新连接;二是兜底重传自己最后的 ACK(它若丢失,对方会重发 FIN)。直接清零等于让“残影”混入新通话,可能导致数据错乱。安全调优应使用 tcp_tw_reuse(仅在客户端安全复用)和 tcp_fin_timeout(适度缩小),而不是一刀切关闭。

为何常见:性能优化时总想删掉一切“看似等待”的开销,却忽略了协议层的安全边界是拿历史的报错代价换来的。

误区教科书上画了四个箭头,实际抓包也必须严格看到四次交互,少一次就是不正常。

正解四次挥手的前提是双方在挥手前都没有要发的数据。若服务端收到 FIN 后立刻无话可说,它的 ACK 和 FIN 可以合并进同一个报文段,线路上只出现三个报文。另外,若双方同时发起关闭,两个 FIN 与两个 ACK 交叉,依旧满足语义。挥手次数本就是“至少四个语义动作”,物理报文数取决于时序巧合。

为何常见:教材把典型过程画成定式,读者误把“默认时序”当成“唯一标准”,忽略了 TCP 的设计目标只是状态语义正确而非报文数量整齐。

面试题

Q1. 三次握手中,第二次握手报文段的标志位组合是?

单选题 · easy

  • A.仅 SYN
  • B.仅 ACK
  • C.FIN + ACK
  • D.SYN + ACK← 正确答案

答案:D。第二次握手是服务端对客户端 SYN 的确认(ACK),同时发起自己一侧的同步(SYN),所以标志位为 SYN+ACK。它回答“我听见你了”的同时问“你听见我吗”,A、B 各只说了一半,C 是挥手阶段的标志,故选 D。

Q2. 客户端发出 SYN 报文后,其 TCP 状态进入?

单选题 · easy

  • A.LISTEN
  • B.SYN_SENT← 正确答案
  • C.SYN_RCVD
  • D.ESTABLISHED

答案:B。客户端主动拨号后处于 SYN_SENT(拨号中),等待服务端回 SYN+ACK。LISTEN 是服务端等待连接的常态,SYN_RCVD 是服务端收到 SYN 后的状态,ESTABLISHED 要等第三次握手完成后才进入,故选 B。

Q3. 服务端收到合法的第三次握手 ACK 后,状态变为?

单选题 · easy

  • A.SYN_SENT
  • B.SYN_RCVD
  • C.ESTABLISHED← 正确答案
  • D.CLOSED

答案:C。服务端在 SYN_RCVD 状态等到客户端最终确认(ack=y+1),证明客户端也具备收发能力,状态迁入 ESTABLISHED,连接正式进入数据传输阶段。A、D 不是服务端此阶段可达状态,B 是迁移前的状态,故选 C。

Q4. 初始序列号(ISN)随机化的主要目的是?

单选题 · mid

  • A.提高安全性,防止外界伪造序列号劫持连接← 正确答案
  • B.节省服务器内存开销
  • C.加速 TCP 握手流程
  • D.减少数据重传次数

答案:A。ISN 随机化让攻击者无法预知合法序列号,难以伪造 RST/ACK 报文实施攻击;同时也能区分新旧连接,防止旧连接残留报文干扰新连接。它不省内存、不加速握手、也不影响重传策略,故选 A。

Q5. TIME_WAIT 状态持续时间取 2MSL 的根本原因是?

单选题 · mid

  • A.让服务端有时间处理完缓存数据
  • B.确保本次连接的所有残留报文在网络中过期消失← 正确答案
  • C.客户端处理最后一次 ACK 需要时间
  • D.给用户预留取消挂断的缓冲期

答案:B。2MSL 保证两条方向上所有迟到报文都能过期,避免残影串扰接下来的新连接;同时为丢失的最终 ACK 预留重传时间。它与服务端缓存、客户端处理速度无关,故选 B。

Q6. 关于半关闭机制,下列说法正确的有?

多选题 · mid

  • A.一方发送 FIN 后,仍可接收对方数据← 正确答案
  • B.半关闭后连接立即双向禁止
  • C.半关闭状态是四次挥手的前提← 正确答案
  • D.收到 FIN 的一端可以继续发送数据← 正确答案

答案:ACD。TCP 是全双工协议,每一方向独立关闭。一方发 FIN 只关闭自己的发送通道,仍可接收数据,故 A 对、B 错。半关闭让“对方补充叮嘱”成为可能,是四次挥手与三次挥手区分的结构前提,C 对;收到 FIN 的一端处于 CLOSE_WAIT,仍能继续发数据,D 对。故选 ACD。

Q7. SYN 洪泛攻击让服务端陷入瘫痪的直接原因是?

单选题 · mid

  • A.CPU 被握手计算占满
  • B.半连接队列被未完成的连接占满← 正确答案
  • C.内存被 SYN 报文挤爆
  • D.磁盘 IO 写满日志

答案:B。攻击者发送大量不回应的 SYN,让服务端在 SYN_RCVD 状态堆积半连接条目,直至半连接队列溢出,正常用户的 SYN 请求全部被丢弃。握手本身计算量很小,内存开销也可控,真正的瓶颈是有限容量的等待队列,故选 B。

Q8. 下列措施中,可以有效缓解 SYN 洪泛影响的有?

多选题 · hard

  • A.增大半连接队列长度 backlog← 正确答案
  • B.开启 SYN Cookie 机制← 正确答案
  • C.缩短 SYN_RCVD 等待超时时间← 正确答案
  • D.将 TIME_WAIT 时间设置为 0

答案:ABC。增大 backlog 能拓宽暂存区,SYN Cookie 不占队列即可完成防伪验证,缩短超时能让僵尸半连接快速过期,三者都是常见防御手段。TIME_WAIT 是主动关闭方处理已建连接的机制,与 SYN 洪泛无关,设为 0 反而引入安全性问题,故选 ABC。

Q9. 客户端收到确认号 ACK=1500,且数据流此前只发到第 1499 字节,这表示?

单选题 · hard

  • A.服务端已收到前 1499 字节,期待第 1500 字节← 正确答案
  • B.服务端已收到前 1500 字节
  • C.服务端要求客户端从 1500 字节重新传
  • D.服务端已丢弃 1500 字节数据

答案:A。TCP 确认号表示“下一个期待收到的字节序号”,即已收到的字节是 1-1499。A 准确描述了累积确认语义;B 把期待号当成已收号,C、D 混淆了重传与确认机制,故选 A。

Q10. 关于 TIME_WAIT 状态,理解正确的有?

多选题 · extreme

  • A.只有主动关闭连接的一端会进入← 正确答案
  • B.被动关闭连接的一端也会进入
  • C.它可以防止旧连接的延迟报文污染新连接← 正确答案
  • D.它保证了连接端口可以被立即复用

答案:AC。TIME_WAIT 仅出现在先发 FIN 的主动关闭方,被动方通常直接 CLOSED,故 A 对 B 错。等待 2MSL 的核心目的就是让旧报文过期,防止序列号与端口组合出现歧义,C 对。恰好相反,TIME_WAIT 阻止端口立即复用,必须等 2MSL 或显式开启重用选项,D 错。故选 AC。