当前位置: 首页 > news >正文

LWIP的TCP协议

1. 输入主路径:ethernet_input → ip4_input → tcp_input → tcp_process → tcp_receive

在这里插入图片描述

  • 入口与校验

    • ip4_input() 识别协议为 TCP 后,交给 tcp_input(struct pbuf* p, struct netif*).
    • tcp_input 做三件事:
      1. 首部合法性与校验和检查(TCP_HLEN、伪首部校验)。
      2. 基于四元组定位 PCB(active -> timewait -> listen 顺序)。
      3. 将 p->payload 指向 TCP 数据区,并计算 tcplen/flags/seqno/ackno 等临时全局。
  • 状态机分发(对应你的“流程方框:tcp_input→tcp_process→tcp_receive()”)

    • 找到活动 PCB 后,inseg 结构指向当前段,随后调用 tcp_process(pcb) 执行“TCP FSM”:
      • SYN_SENT:处理对端的 SYN/ACK,建立连接,初始化 cwnd/mss/窗口后进入 ESTABLISHED。
      • SYN_RCVD:等待对端 ACK,转入 ESTABLISHED(或错误时 RST)。
      • ESTABLISHED/CLOSE_WAIT/FIN_WAIT_x/…:统一走 tcp_receive(pcb) 处理 ACK、数据、窗口更新、重排等。
    • 未找到 PCB:tcp_input 发送 RST(tcp_rst_netif)。
  • 数据接收与乱序重排(和你图中“recv_data/应用回调/邮箱投递”等一致)

    • tcp_receive(pcb) 核心逻辑:
      • ACK 处理:移动 unacked/unsent 队列(tcp_free_acked_segments),更新 cwnd/ssthresh(慢启动/拥塞避免)、rto/rtt 等。
      • 窗口与重复 ACK:
        • 重复 ACK 计数≥3 触发快速重传 tcp_rexmit_fast();同时调整拥塞窗口。
      • 就绪数据与乱序:
        • 就绪(seq==rcv_nxt):rcv_nxt 前进、rcv_wnd 递减,必要时拼接 ooseq 变为有序,再设置 recv_data。
        • 乱序:插入 pcb->ooseq(tcp_oos_insert_segment),必要时裁剪重叠;可选生成 SACK。
      • 向上递交:TCP_EVENT_RECV(pcb, recv_data) 交给上层,若应用暂时接不走,存 pcb->refused_data。
      • ACK:针对收到的数据/窗口移动调用 tcp_ack()/tcp_send_empty_ack()。
  • 零窗口探测(Zero-Window Probe)

    • 对端通告窗口为 0 且仍有未发送数据:由慢计时器进入“persist 计时”,tcp_zero_window_probe() 每次发 1 字节数据或者 FIN 探测。

2. 输出主路径:应用 → tcp_write → (分段/入队) → tcp_output → tcp_output_segment → ip_output_if

  • tcp_write(pcb, buf, len, apiflags)

    • 三阶段分段与入队:
      1. OVERSIZE 尾部直接追加(减少 pbuf 分配/拷贝)。
      2. 复用/拼接到最后一个 unsent 段(concat_p/extendlen)。
      3. 创建新段 tcp_create_segment(构建 TCP 头但不填 ack/wnd/chksum),串到 pcb->unsent。
    • PSH 设置:若未带 TCP_WRITE_FLAG_MORE,则给最后一个段置 PSH。
    • 典型“粘包”来源就在 1)+2) 阶段:多次小写入极易被合并为更少的段(见第 3 节)。
  • tcp_output(pcb)

    • 依据有效发送窗 wnd = min(snd_wnd, cwnd) 和 Nagle 算法决定是否发送。
    • 逐段发送 unsent 链表中满足窗的段:
      • 设置 ACK 标志(非 SYN_SENT)。
      • 通过 tcp_output_segment(seg, pcb, netif) 下发到 IP。
      • 发送后移到 unacked(仅数据/FIN/SYN计序的段)。
  • tcp_output_segment

    • 回填首部字段:ackno=rcv_nxt,wnd=本端通告窗口(考虑窗口缩放),可选写时间戳/窗口扩大/SACK_PERM。
    • 计算 TCP 校验和(支持“拷贝时校验”优化 TCP_CHECKSUM_ON_COPY)。
    • 调 ip_output_if(p, src, dst, ttl, tos, IP_PROTO_TCP, netif)。
  • 拥塞控制与重传

    • RTO 触发:tcp_slowtmr() 中 rtime≥rto → tcp_rexmit_rto_prepare/commit,rto 指数回退、cwnd=1*MSS。
    • 快速重传:见 tcp_receive 的 dupack≥3 分支调用 tcp_rexmit_fast(),并调整 ssthresh/cwnd。

在这里插入图片描述
在这里插入图片描述

  • unsent:已构建但尚未通过 ip 发送的 tcp_seg 链(发前/被退回重发时也会放回这里)。

  • unacked:已发送、等待对端确认的 tcp_seg 链(RTO、快速重传操作针对该链)。

  • ooseq:接收端保存的乱序段链,等待前序到达并合并后上交给应用。

  • 关键序列/窗口:snd_nxt(下一个要发送的序号)、lastack(最后被确认的序号)、snd_lbb(拥塞控制基准)、rcv_nxt(下一个期望接收序号)、snd_wnd/cwnd/ssthresh(发送窗/拥塞窗/阈值)。
    在这里插入图片描述
    状态机与队列行为对应

  • LISTEN

    • 不发送数据;收到 SYN 时会为新连接分配 PCB(SYN_RCVD)并把 SYN|ACK 放入 unsent/unacked(视是否已发送)。
  • SYN_SENT

    • 主动发送 SYN(SYN 段在 unsent -> 发送后移入 unacked,等待 SYN|ACK 的 ACK)。超时重传作用于 unacked。
  • SYN_RCVD

    • 被动接受 SYN 后发 SYN|ACK(SYN|ACK 在 unacked),收到对端 ACK 后进入 ESTABLISHED 并释放与 ACK 相关的 unacked 段。
  • ESTABLISHED

    • 发送:tcp_write 将数据构造为 tcp_seg 串入 unsent;tcp_output 按 wnd/cwnd/Nagle 将满足条件的段从 unsent 发送并移入 unacked。
    • 接收:按 seq 与 rcv_nxt 判断,按序的数据直接交付(rcv_nxt 前移);乱序插入 ooseq,后续拼接后交付。
    • 重传:dup-ACK 快速重传或 RTO(基于 unacked)把需要重发的段移回 unsent 或直接发送。
  • FIN_WAIT1 / LAST_ACK

    • 发送 FIN 时 FIN 段作为一个 tcp_seg 放入 unsent -> 发送后在 unacked 等待对端的 ACK。收到 ACK 后根据状态转为 FIN_WAIT2 或 CLOSED。
  • FIN_WAIT2 / CLOSING

    • 如果先收到对端 FIN,而自身 FIN 仍未被 ACK,状态变为 CLOSING(两端都已发 FIN,等待对方或 ACK)。unacked 仍可能包含本端的 FIN 段。
  • TIME_WAIT

    • 双方完成四次挥手且本端最后发 ACK 后进入 TIME_WAIT;通常 unacked 已被确认(无待确认段),PCB 被放到 timewait 列表等待 2MSL 后释放。
  • CLOSE_WAIT

    • 被动收到 FIN,交给应用(p==NULL 表示 EOF),应用要调用 tcp_close/tcp_shutdown 触发 FIN 发送(形成 LAST_ACK)。
  • 异常(RST/abort)

    • 调用 tcp_abort 会立即发 RST 并释放所有队列(unacked/unsent/ooseq 被丢弃并释放 pbuf)。
  • “tcp_seg + pbuf 结构图”:正是 unsent/unacked/ooseq 三条链的节点形态。
    在这里插入图片描述

  • “滑动窗口图(snd_wnd/lastack/snd_nxt)”:变量名与图一一对应(lastack、snd_nxt、snd_lbb、snd_wnd)。


3. “粘包/拆包”在 lwIP 中如何产生与规避

  • 产生位置

    • 发送端:tcp_write 的“OVERSIZE 追加/拼接”与 Nagle 算法;网卡/内核的分段合并(TSO/GSO)也会使多个小写合并。
    • 接收端:TCP 是字节流,tcp_receive 可能一次回调给你多个应用“消息”的字节;也可能拆成多次回调。
  • 与 IP 分片无关

    • IP 分片发生在 IP 层,目的端重组后才进入 TCP,TCP 仍是连续字节流,不提供消息边界。
  • 正确做法

    • 应用协议自带帧:固定长度 / 定界符 / 长度前缀(最常见)。
    • 降低合并概率(并不能恢复“消息边界”):TCP_NODELAY 关闭 Nagle;配合“批量写”或 MSG_MORE/TCP_CORK 类策略。
  • 在 lwIP 的开关

    • 关闭 Nagle:tcp_nagle_disable(pcb) 或 socket 侧 TCP_NODELAY。
    • 大包/MSS:合理设置 TCP_MSS 与路径 MTU,减少 IP 分片,提高效率。

4. 与抓包字段对齐

  • TCP 头字段(你图中“20B首部+标志+窗口”)

    • 源/目的端口:tcphdr->src/dest
    • 序号/确认号:tcphdr->seqno/ackno
    • 首部长度与标志:TCPH_HDRLEN_FLAGS_SET / TCPH_FLAGS()
    • 窗口:tcphdr->wnd(收到时经窗口缩放变更为 snd_wnd/rcv_wnd)
    • 校验和:tcphdr->chksum(ip_chksum_pseudo 计算)
  • Wireshark“Len=1452/Seq/Ack/Flags/Win”

    • Len≈MSS,Seq/NextSeq 对应 lwip_ntohl(seg->tcphdr->seqno) 与 TCP_TCPLEN(seg)。
    • Window size value 与 Window size scaling factor 对应 TF_WND_SCALE/snd_scale/rcv_scale。
      在这里插入图片描述

5. 连接建立/关闭:“三次握手/四次挥手图”

  • 主动打开(tcp_connect)

    • 填 remote/local, 分配本地端口、初始 ISS、窗口、MSS。
    • 通过 tcp_enqueue_flags(TCP_SYN) 发送 SYN;进入 SYN_SENT。
  • 被动打开(tcp_listen_input)

    • LISTEN 收到 SYN:分配新 pcb(SYN_RCVD),记录对端 wnd/mss,入队 SYN|ACK,tcp_output 发送。
      在这里插入图片描述
  • 三次握手达成

    • SYN_SENT 收到 SYN|ACK 且 ack 正确 → ESTABLISHED,回 ACK。
    • SYN_RCVD 收到 ACK(ack 范围正确)→ ESTABLISHED,触发 accept 回调。
      在这里插入图片描述
  • 关闭(四次挥手与 TIME_WAIT)

    • tcp_close()/tcp_shutdown() 发送 FIN(tcp_send_fin/tcp_enqueue_flags),状态递进:ESTABLISHED→FIN_WAIT_1→…→TIME_WAIT。
    • 被动关闭收到 FIN:置 TF_GOT_FIN,回 ACK,交给应用 EOF(recv 回调 p==NULL)。
    • TIME_WAIT 定时在 tcp_slowtmr 中清理(2MSL)。
      在这里插入图片描述

实际上用wireshark抓包发现有时候挥手也是三次,四次是为了避免数据没有完全发完罢了。

  • 异常/放弃
    • tcp_abort()/tcp_abandon(reset=1):立刻发 RST 并释放 PCB。
    • 超时与重试次数:SYNMAXRTX / MAXRTX 受限后由慢计时器清理并可选复位。

6. 窗口、拥塞与定时器

在这里插入图片描述

  • 发送窗口与拥塞窗口

    • 发送有效窗 wnd=min(snd_wnd, cwnd)。
    • 慢启动/拥塞避免:
      • 初始 cwnd=LWIP_TCP_CALC_INITIAL_CWND(mss)。
      • cwnd<ssthresh:每 ACK 按已确认字节线性增长(每 RTT 近似翻倍)。
      • cwnd≥ssthresh:拥塞避免,按 acked 累计到 1cwnd 时再加 1MSS。
  • 快速重传/恢复

    • dupacks≥3:tcp_rexmit_fast(); ssthresh=max(min(cwnd,snd_wnd)/2, 2MSS),cwnd=ssthresh+3MSS,TF_INFR 进入恢复。
  • RTO 重传

    • rtime≥rto:将 unacked 迁回 unsent,cwnd=1*MSS,rto 指数回退,tcp_output 再发。
  • 关键计时器

    • tcp_fasttmr() 250ms:发送延迟 ACK(TF_ACK_DELAY);重试挂起 FIN(TF_CLOSEPEND);处理 refused_data。
    • tcp_slowtmr() 500ms:
      • RTO/Persist/Keepalive/超时清理(SYN_RCVD/LAST_ACK/TIME_WAIT 等)。
      • 零窗口探测(persist):tcp_zero_window_probe。
      • ooseq 超时丢弃,防内存占用。

7. 选项与扩展:MSS/WS/TS/SACK/KEEPALIVE

  • 解析(接收):tcp_parseopt
    • MSS(LWIP_TCP_OPT_MSS)、窗口扩大(WS)、时间戳(TS)、SACK_PERM。
  • 发送(控制段/数据段):tcp_output_segment / tcp_output_fill_options
    • 按 flags 决定是否携带 TS/WS/SACK,SACK 的实际块由接收端的 ooseq 情况决定。
  • 保活:tcp_keepalive() 在 tcp_slowtmr 驱动,超出 keep_idle+keep_cnt*keep_intvl 仍无响应则复位连接。

8. 典型问题

  • 小包粘包:查看发送侧是否启用 Nagle(TF_NODELAY),观察 tcp_write 阶段 1/2 是否合并,抓包验证 PSH/段大小。
  • 吞吐上不去:确认 MSS/MTU、路径 MTU、窗口扩大(TF_WND_SCALE)、rcv_wnd/snd_wnd/cwnd 的约束。
  • 丢包/重传多:看 tcp_slowtmr 中 RTO 是否频繁,dupacks 与快速重传是否发生;定位链路问题或过小的缓冲。
  • 收到数据但应用没取:pcb->refused_data 非空;检查上层回调是否及时 tcp_recved()。
  • 零窗口死锁:persist 机制是否工作(persist_backoff/pcb->persist_probe),确认对端确实通告 0 窗口。
  • 关闭卡住:状态若停在 FIN_WAIT_2/LAST_ACK,结合 tmr 差值(tcp_ticks-pcb->tmr)看是否超时清理。

9. 参数与调优(lwipopts 相关)

  • 性能
    • TCP_SND_BUF / TCP_SND_QUEUELEN:发送缓冲/队列深度。
    • TCP_WND / 窗口扩大(LWIP_WND_SCALE、TCP_RCV_SCALE)。
    • TCP_MSS 与路径 MTU(tcp_eff_send_mss_netif)。
    • TCP_OVERSIZE/TCP_CHECKSUM_ON_COPY:降低 copy/计算开销。
  • 可靠性/特性
    • LWIP_TCP_SACK_OUT / LWIP_TCP_TIMESTAMPS / KEEPALIVE。
    • LWIP_NETIF_TX_SINGLE_PBUF:传输出单 pbuf,提升 DMA 友好性。
  • 行为
    • Nagle(默认开):需要低时延小包时关闭 TCP_NODELAY。
    • 端口复用:SO_REUSEADDR(被动监听/主动连接的 5 元组判重逻辑不同)。

10. 小结

  • 输入链路:tcp_input → tcp_process → tcp_receive 决定 ACK/重排/交付;配合 fast/slow 计时器实现“延迟 ACK、快速重传、RTO、persist、keepalive”。
  • 输出链路:tcp_write 分段入队 + tcp_output 受窗口/Nagle 约束下发到 IP;unsent/unacked 队列维护在途数据。
  • 粘包/拆包是 TCP 字节流的自然行为,需在应用层做帧化;与 IP 分片无关。

全链路调用顺序如下。
输入链路:NIC_ISR (中断/DMA) → ethernetif_input (low_level_input) → netif->input (tcpip_input) → tcpip_thread (message) → ethernet_input → ip4_input → tcp_input → tcp_process → tcp_receive

输出链路:APP(socket/netconn/raw)→ tcp_write() → 构造 tcp_seg 并追加到 pcb->unsent → tcp_output()(检查 wnd/cwnd/Nagle)→ 填写 TCP/IP 首部(tcp_output_segment)→ ip_output_if()/ip4_output_if() → netif->output(如 ethernetif_output / low_level_output)→ 启动 NIC DMA 发送 → NIC 硬件发帧 → NIC TX 完成中断(NIC_ISR)→ ethernetif 的 TX 完成回调/任务处理(可选通知 tcpip_thread)。

NIC 中断 / DMAethernetif_input 任务tcpip_inputtcpip_thread (lwIP core)ethernet_inputip4_inputtcp_inputtcp_process / tcp_receive应用xSemaphoreGiveFromISR(s_xSemaphore) // 发信号量唤醒接收任务low_level_input() ->> 构造 pbufnetif->>input(p) (通常为 tcpip_input)投递消息到 tcpip_thread(mailbox)ethernet_input(p, netif)pbuf_remove_header()ip4_input(p)dispatch 到 tcp_input(p)tcp_process / tcp_receive (状态机/交付)上层回调 / netconn/socket 收到数据tcp_recved(pcb,len) // 应用消费后告知 lwIP应用调用 tcp_write/sendcreate tcp_seg ->> append 到 pcb->>unsenttcp_output ->> 填写头部 ->> ip_output_ifnetif low_level_output (DMA)NIC 触发 TX 完成中断(可选)NIC 中断 / DMAethernetif_input 任务tcpip_inputtcpip_thread (lwIP core)ethernet_inputip4_inputtcp_inputtcp_process / tcp_receive应用

http://www.lryc.cn/news/626658.html

相关文章:

  • 在 Golang 中复用 HTTP 连接
  • 26_基于深度学习的茶叶等级检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • CTFshow系列——命令执行web38-40
  • Qt音乐播放器项目实践:本地持久化与边角问题处理
  • 小红书账号隔离:解决IP关联问题方案
  • 网络工程师考试重点:OSI七层模型TCP/IP四层模型解析
  • 【北京迅为】iTOP-4412精英版使用手册-第三十二章 网络通信-TCP套字节
  • yolo_RK3588系列(三)
  • 5.4 4pnpm 使用介绍
  • FreeRTOS---进阶知识1---列表的创建
  • SQL 中大于小于号的表示方法总结
  • Claude Code NPM 包发布命令
  • 内网安全——出网协议端口探测
  • Java开源工具Apache PDFBox(强大的处理 PDF文档工具:创建、读取、修改、解析和提取 PDF)
  • Apache ShenYu和Nacos之间的通信原理
  • 【Tech Arch】Apache Pig大数据处理的高效利器
  • Spring Boot 日志体系详解:配置与实战
  • 三、k8s 1.29 之 资源清单
  • 网络编程5(HTTPS)
  • 【考研408数据结构-08】 图论基础:存储结构与遍历算法
  • Linux的奇妙冒险——进程pcb第二讲
  • 云原生俱乐部-k8s知识点归纳(5)
  • SpringTask入门
  • 关于多个el-input的自动聚焦,每输入完一个el-input,自动聚焦到下一个
  • Rust并发编程:解锁高性能系统的密钥
  • 第12课_Rust项目实战
  • 批处理指令常见问题
  • 软考高级--系统架构设计师--案例分析真题解析
  • 【clion】cmake脚本1:调试脚本并构建Fargo项目win32版本
  • 无需驱动!单文件实现键盘按键禁用的技术方案