RAW API 的 TCP 总结2

主要内容参照17. 使用RAW API接口编程 --- [野火]LwIP应用开发实战指南---基于野火STM32 文档,整理出来自用。

RAW API 的 TCP 编程本质是对TCP 控制块(PCB) 的全生命周期操作,涵盖控制块的创建、绑定、监听、连接管理、数据收发及异常处理等核心流程。

1. 新建 TCP 控制块:tcp_new()

  • 核心功能:分配并初始化一个新的 TCP 控制块,为后续 TCP 连接奠定基础。

  • 实现逻辑

    1. 调用tcp_alloc(TCP_PRIO_NORMAL)分配 TCP 控制块结构,优先级为 "正常";
    2. 若空间不足,tcp_alloc()会释放低优先级 / 非关键状态的控制块(如TIME_WAITCLOSING状态),优先满足新控制块分配;
    3. 分配成功后,内核自动初始化控制块的各个字段(如端口、IP、状态等)。
  • 源码核心

    复制代码
    struct tcp_pcb *tcp_new(void) { return tcp_alloc(TCP_PRIO_NORMAL); }

2. 绑定控制块:tcp_bind()

  • 核心功能:将本地 IP 地址、端口号与 TCP 控制块绑定(仅服务器端常用),确保端口不冲突。
  • 实现逻辑
    1. 处理默认参数:若 IP 为NULL,自动绑定IP4_ADDR_ANY(任意本地 IP);若端口为 0,调用tcp_new_port()分配随机可用端口;
    2. 端口冲突检测:遍历 LwIP 的 4 条 TCP 控制块链表(tcp_pcb_lists),检查目标 IP + 端口是否已被占用,若占用返回ERR_USE
    3. 绑定与挂载:设置控制块的local_iplocal_port,并将其插入tcp_bound_pcbs(绑定状态链表)。
  • 关键返回值ERR_OK(成功)、ERR_USE(端口冲突)、ERR_BUF(端口分配失败)。

3. 监听连接:tcp_listen()

  • 核心功能:将绑定后的控制块转为 "监听状态",等待客户端连接(仅服务器端),并优化内存占用。
  • 核心设计 :LwIP 为监听状态单独设计tcp_pcb_listen(精简版控制块),仅包含监听所需字段(如本地 IP / 端口、回调参数),比完整控制块更节省内存(适配嵌入式场景)。
  • 实现逻辑
    1. 状态检查:若控制块已为LISTEN状态,直接返回并提示ERR_ALREADY
    2. 分配精简控制块:调用memp_malloc(MEMP_TCP_PCB_LISTEN)分配tcp_pcb_listen,拷贝完整控制块的关键字段(如local_portprio);
    3. 状态切换与资源清理:将完整控制块从tcp_bound_pcbs移除并释放,设置精简控制块状态为LISTEN,插入tcp_listen_pcbs(监听状态链表);
    4. 客户端连接处理:当收到客户端SYN报文时,内核遍历tcp_listen_pcbs找到匹配控制块,新建完整控制块(拷贝精简块字段),填写客户端 IP / 端口,挂载到tcp_active_pcbs(活跃连接链表),精简块保留以继续监听其他连接。

4. 处理客户端连接:tcp_accept()

  • 核心功能:为监听状态的控制块注册 "连接回调函数",当客户端连接请求被接受时,内核自动触发该回调。

  • 实现逻辑

    1. 仅对LISTEN状态的控制块生效,将tcp_accept_fn类型的回调函数赋值给tcp_pcb_listenaccept字段;
    2. 回调函数参数:newpcb(新连接对应的完整控制块,需用户后续处理)、arg(自定义参数)、err(错误码)。
  • 源码核心

    复制代码
    typedef err_t (*tcp_accept_fn)(void *arg, struct tcp_pcb *newpcb, err_t err);
    void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept) {
        if (pcb && pcb->state == LISTEN) ((struct tcp_pcb_listen *)pcb)->accept = accept;
    }

5. 建立客户端连接:tcp_connect()

  • 核心功能:客户端主动向服务器发起 TCP 连接(三次握手的起点),将控制块从 "绑定状态" 转为 "活跃状态"。
  • 实现逻辑
    1. 配置连接参数:设置服务器 IP(remote_ip)和端口(remote_port),通过ip_route()确定发送报文的网卡(netif);
    2. 本地参数补全:若未绑定本地 IP / 端口,自动绑定网卡的本地 IP 和随机端口;
    3. 初始化窗口与序号:设置发送序号(iss)、接收窗口(rcv_wnd)、拥塞窗口(cwnd=1)等 TCP 核心参数;
    4. 发起连接:调用tcp_enqueue_flags(pcb, TCP_SYN)构造SYN报文,控制块状态改为SYN_SENT,从tcp_bound_pcbs移除并插入tcp_active_pcbs,调用tcp_output()发送SYN报文;
    5. 连接回调:需传入tcp_connected_fn回调函数,连接建立(三次握手完成)后内核触发该回调。

6. 终止连接:tcp_close()(依赖tcp_close_shutdown()

  • 核心功能:主动终止 TCP 连接,根据控制块当前状态执行不同清理逻辑(需结合 TCP 状态转移图理解)。

  • 状态化处理逻辑

    控制块状态 处理方式
    CLOSED tcp_bound_pcbs移除,释放控制块内存
    LISTEN tcp_listen_pcbs移除,调用tcp_free_listen()释放精简控制块
    SYN_SENT tcp_active_pcbs移除,释放控制块,更新mib2统计(连接尝试失败)
    其他状态(如ESTABLISHED 调用tcp_close_shutdown_fin()发送FIN报文,启动关闭流程(四次挥手)
  • 特殊逻辑 :若存在未确认数据,可能发送RST报文强制关闭连接,避免数据残留。

7. 接收数据:tcp_recv()

  • 核心功能:为 TCP 控制块注册 "数据接收回调函数",内核收到数据后触发回调,将数据递交给应用层。
  • 实现逻辑
    1. 注册tcp_recv_fn类型回调函数到控制块的recv字段;
    2. 回调触发场景:
      • 正常接收数据:ppbuf指针,指向接收数据)非空,应用层需处理数据;
      • 对方关闭连接:p为空,应用层需调用tcp_close()关闭本地连接;
    3. 调用时机:通常在tcp_connected_fn(连接建立回调)中注册,确保连接稳定后可接收数据。

8. 发送确认回调:tcp_sent()

  • 核心功能 :注册 "发送确认回调函数",当发送的数据被对方确认(收到ACK)时,内核触发回调。
  • 作用:告知应用层 "数据已送达",可用于清理发送缓冲区、触发下一次数据发送。
  • 回调参数len(被确认的数据长度)、tpcb(对应的 TCP 控制块)。

9. 异常处理:tcp_err()

  • 核心功能:注册 "异常处理回调函数",当 TCP 连接发生异常(如连接超时、对方重置)时,内核触发回调。

  • 用户职责:在回调中实现异常恢复逻辑(如释放控制块、重新连接)。

  • 源码核心

    复制代码
    typedef void (*tcp_err_fn)(void *arg, err_t err);
    void tcp_err(struct tcp_pcb *pcb, tcp_err_fn err) { if (pcb) pcb->errf = err; }

10. 周期性回调:tcp_poll()

  • 核心功能 :注册 "周期性回调函数",内核每interval * 0.5s(内核定时器周期为 0.5s)触发一次,解决裸机编程中周期性任务的实现难题。
  • 典型用途:周期性检测连接状态、定时发送心跳包、清理超时数据。
  • 参数说明interval(周期系数,如interval=2表示每 1s 触发一次)、tcp_poll_fn(周期性回调函数)。

11. 构建 TCP 报文段:tcp_write()

  • 核心功能 :构建 TCP 报文段并缓存到控制块的unsent字段(仅 RAW API 需直接调用,上层 API 已封装)。
  • 关键细节
    1. 不直接发送数据:仅完成报文段构建和缓存,真正发送需依赖内核超时机制调用tcp_output(),或用户手动调用tcp_output()立即发送;
    2. 参数说明:
      • dataptr:待发送数据的指针;
      • len:数据长度(字节);
      • apiflags:额外操作标志(如是否拷贝数据、是否设置PSH标志);
    3. 前置检查:调用tcp_write_checks()验证数据是否可挂载到发送缓冲区,避免溢出。

12. 更新接收窗口:tcp_recved()

  • 核心功能:告知内核 "应用层已处理接收数据",更新接收窗口大小,避免发送方因窗口为 0 而停止发送。
  • 实现逻辑
    1. 接收窗口增加len(应用层已处理的数据长度),若窗口超过最大值则截断为TCP_WND_MAX
    2. 调用tcp_update_rcv_ann_wnd()更新窗口通告,若窗口增量≥TCP_WND_UPDATE_THRESHOLD(通常为窗口的 1/4),立即发送ACK告知发送方新窗口;
  • 注意事项:若不调用此函数,接收窗口会逐渐变为 0,导致后续数据无法接收(常见排障点)。

核心总结:RAW API TCP 编程流程

服务器端流程

  1. tcp_new() → 新建控制块;
  2. tcp_bind() → 绑定本地 IP + 端口;
  3. tcp_listen() → 转为监听状态,插入tcp_listen_pcbs
  4. tcp_accept() → 注册连接回调,等待客户端连接;
  5. 连接建立后(回调触发):tcp_recv()(注册接收回调)、tcp_sent()(注册发送确认回调);
  6. 数据交互:tcp_write()+tcp_output()(发送数据)、tcp_recved()(更新接收窗口);
  7. 关闭连接:tcp_close()

客户端流程

  1. tcp_new() → 新建控制块;
  2. tcp_bind()(可选)→ 绑定本地 IP + 端口(不绑定则自动分配);
  3. tcp_connect() → 发起连接,注册连接回调;
  4. 连接建立后(回调触发):tcp_recv()tcp_sent()
  5. 数据交互:同服务器端;
  6. 关闭连接:tcp_close()
相关推荐
AAA修煤气灶刘哥9 分钟前
网络编程原来这么好懂?TCP 三次握手像约会,UDP 像发朋友圈
后端·python·网络协议
NewCarRen12 分钟前
汽车盲点检测系统的网络安全分析和设计
网络·安全·汽车网络安全
GalaxyPokemon2 小时前
TCP和HTTP的keep-alive的区别
网络协议·tcp/ip·http
BioRunYiXue2 小时前
FRET、PLA、Co-IP和GST pull-down有何区别? 应该如何选择?
java·服务器·网络·人工智能·网络协议·tcp/ip·eclipse
taxunjishu2 小时前
ProfiNet 转 Ethernet/IP 柔性产线构建方案:网关技术保护新能源企业现有设备投资
网络·网络协议·tcp/ip
起个昵称吧2 小时前
TCP并发服务器构建
服务器·数据库·tcp/ip
Coision.2 小时前
linux 网络:并发服务器及IO多路复用
linux·服务器·网络
qqxhb3 小时前
系统架构设计师备考第9天——网络基础&通信技术
网络·tcp/ip·系统架构·tdm·香农公式·cdm·fdm
拾荒的小海螺3 小时前
JAVA:Nginx 事件驱动模型的技术指南
java·网络·nginx
用户47949283569153 小时前
面试官:Host、Referer、Origin三者有什么区别?
网络协议