主要内容参照17. 使用RAW API接口编程 --- [野火]LwIP应用开发实战指南---基于野火STM32 文档,整理出来自用。
RAW API 的 TCP 编程本质是对TCP 控制块(PCB) 的全生命周期操作,涵盖控制块的创建、绑定、监听、连接管理、数据收发及异常处理等核心流程。
1. 新建 TCP 控制块:tcp_new()
-
核心功能:分配并初始化一个新的 TCP 控制块,为后续 TCP 连接奠定基础。
-
实现逻辑 :
- 调用
tcp_alloc(TCP_PRIO_NORMAL)
分配 TCP 控制块结构,优先级为 "正常"; - 若空间不足,
tcp_alloc()
会释放低优先级 / 非关键状态的控制块(如TIME_WAIT
、CLOSING
状态),优先满足新控制块分配; - 分配成功后,内核自动初始化控制块的各个字段(如端口、IP、状态等)。
- 调用
-
源码核心 :
struct tcp_pcb *tcp_new(void) { return tcp_alloc(TCP_PRIO_NORMAL); }
2. 绑定控制块:tcp_bind()
- 核心功能:将本地 IP 地址、端口号与 TCP 控制块绑定(仅服务器端常用),确保端口不冲突。
- 实现逻辑 :
- 处理默认参数:若 IP 为
NULL
,自动绑定IP4_ADDR_ANY
(任意本地 IP);若端口为 0,调用tcp_new_port()
分配随机可用端口; - 端口冲突检测:遍历 LwIP 的 4 条 TCP 控制块链表(
tcp_pcb_lists
),检查目标 IP + 端口是否已被占用,若占用返回ERR_USE
; - 绑定与挂载:设置控制块的
local_ip
和local_port
,并将其插入tcp_bound_pcbs
(绑定状态链表)。
- 处理默认参数:若 IP 为
- 关键返回值 :
ERR_OK
(成功)、ERR_USE
(端口冲突)、ERR_BUF
(端口分配失败)。
3. 监听连接:tcp_listen()
- 核心功能:将绑定后的控制块转为 "监听状态",等待客户端连接(仅服务器端),并优化内存占用。
- 核心设计 :LwIP 为监听状态单独设计
tcp_pcb_listen
(精简版控制块),仅包含监听所需字段(如本地 IP / 端口、回调参数),比完整控制块更节省内存(适配嵌入式场景)。 - 实现逻辑 :
- 状态检查:若控制块已为
LISTEN
状态,直接返回并提示ERR_ALREADY
; - 分配精简控制块:调用
memp_malloc(MEMP_TCP_PCB_LISTEN)
分配tcp_pcb_listen
,拷贝完整控制块的关键字段(如local_port
、prio
); - 状态切换与资源清理:将完整控制块从
tcp_bound_pcbs
移除并释放,设置精简控制块状态为LISTEN
,插入tcp_listen_pcbs
(监听状态链表); - 客户端连接处理:当收到客户端
SYN
报文时,内核遍历tcp_listen_pcbs
找到匹配控制块,新建完整控制块(拷贝精简块字段),填写客户端 IP / 端口,挂载到tcp_active_pcbs
(活跃连接链表),精简块保留以继续监听其他连接。
- 状态检查:若控制块已为
4. 处理客户端连接:tcp_accept()
-
核心功能:为监听状态的控制块注册 "连接回调函数",当客户端连接请求被接受时,内核自动触发该回调。
-
实现逻辑 :
- 仅对
LISTEN
状态的控制块生效,将tcp_accept_fn
类型的回调函数赋值给tcp_pcb_listen
的accept
字段; - 回调函数参数:
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 连接(三次握手的起点),将控制块从 "绑定状态" 转为 "活跃状态"。
- 实现逻辑 :
- 配置连接参数:设置服务器 IP(
remote_ip
)和端口(remote_port
),通过ip_route()
确定发送报文的网卡(netif
); - 本地参数补全:若未绑定本地 IP / 端口,自动绑定网卡的本地 IP 和随机端口;
- 初始化窗口与序号:设置发送序号(
iss
)、接收窗口(rcv_wnd
)、拥塞窗口(cwnd=1
)等 TCP 核心参数; - 发起连接:调用
tcp_enqueue_flags(pcb, TCP_SYN)
构造SYN
报文,控制块状态改为SYN_SENT
,从tcp_bound_pcbs
移除并插入tcp_active_pcbs
,调用tcp_output()
发送SYN
报文; - 连接回调:需传入
tcp_connected_fn
回调函数,连接建立(三次握手完成)后内核触发该回调。
- 配置连接参数:设置服务器 IP(
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 控制块注册 "数据接收回调函数",内核收到数据后触发回调,将数据递交给应用层。
- 实现逻辑 :
- 注册
tcp_recv_fn
类型回调函数到控制块的recv
字段; - 回调触发场景:
- 正常接收数据:
p
(pbuf
指针,指向接收数据)非空,应用层需处理数据; - 对方关闭连接:
p
为空,应用层需调用tcp_close()
关闭本地连接;
- 正常接收数据:
- 调用时机:通常在
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 已封装)。 - 关键细节 :
- 不直接发送数据:仅完成报文段构建和缓存,真正发送需依赖内核超时机制调用
tcp_output()
,或用户手动调用tcp_output()
立即发送; - 参数说明:
dataptr
:待发送数据的指针;len
:数据长度(字节);apiflags
:额外操作标志(如是否拷贝数据、是否设置PSH
标志);
- 前置检查:调用
tcp_write_checks()
验证数据是否可挂载到发送缓冲区,避免溢出。
- 不直接发送数据:仅完成报文段构建和缓存,真正发送需依赖内核超时机制调用
12. 更新接收窗口:tcp_recved()
- 核心功能:告知内核 "应用层已处理接收数据",更新接收窗口大小,避免发送方因窗口为 0 而停止发送。
- 实现逻辑 :
- 接收窗口增加
len
(应用层已处理的数据长度),若窗口超过最大值则截断为TCP_WND_MAX
; - 调用
tcp_update_rcv_ann_wnd()
更新窗口通告,若窗口增量≥TCP_WND_UPDATE_THRESHOLD
(通常为窗口的 1/4),立即发送ACK
告知发送方新窗口;
- 接收窗口增加
- 注意事项:若不调用此函数,接收窗口会逐渐变为 0,导致后续数据无法接收(常见排障点)。
核心总结:RAW API TCP 编程流程
服务器端流程
tcp_new()
→ 新建控制块;tcp_bind()
→ 绑定本地 IP + 端口;tcp_listen()
→ 转为监听状态,插入tcp_listen_pcbs
;tcp_accept()
→ 注册连接回调,等待客户端连接;- 连接建立后(回调触发):
tcp_recv()
(注册接收回调)、tcp_sent()
(注册发送确认回调); - 数据交互:
tcp_write()
+tcp_output()
(发送数据)、tcp_recved()
(更新接收窗口); - 关闭连接:
tcp_close()
。
客户端流程
tcp_new()
→ 新建控制块;tcp_bind()
(可选)→ 绑定本地 IP + 端口(不绑定则自动分配);tcp_connect()
→ 发起连接,注册连接回调;- 连接建立后(回调触发):
tcp_recv()
、tcp_sent()
; - 数据交互:同服务器端;
- 关闭连接:
tcp_close()
。