使用 lwIP 协议栈进行 TCP 裸机编程
,其本质就是编写协议栈指定的各种回调函数。将你的应用逻辑封装成函数,注册到协议栈,在适当的时候,由协议栈自动调用,所以称为回调。
注:除非特别说明,以下内容针对 lwIP 2.0.0 及以上版本。
向协议栈注册回调函数有专门的接口,如下所示:
tcp_err(pcb, errf); //注册 TCP 接到 RST 标志或发生错误回调函数 errf
tcp_connect(pcb, ipaddr, port, connected); //注册 TCP 建立连接成功回调函数 connecter
tcp_accept(pcb, accept); //注册 TCP 处于 LISTEN 状态时,监听到有新的连接接入
tcp_recv(pcb, recv); //注册 TCP 接收到数据回调函数 recv
tcp_sent(pcb, sent); //注册 TCP 发送数据成功回调函数 sent
tcp_poll(pcb, poll, interval); //注册 TCP 周期性执行回调函数 poll
本节讲述 connected
、sent
、poll
回调函数。
connected
回调函数
在 TCP 控制块中,函数指针 connected
指向用户实现的函数,当一个 PCB 连接到远端主机时,由协议栈调用此函数。
函数指针 connected
的类型为 tcp_connected_fn
,该类型定义在 tcp.h 中:
/** Function prototype for tcp connected callback functions. Called when a pcb* is connected to the remote side after initiating a connection attempt by* calling tcp_connect().** @param arg Additional argument to pass to the callback function (@see tcp_arg())* @param tpcb The connection pcb which is connected* @param err An unused error code, always ERR_OK currently ;-) @todo!* Only return ERR_ABRT if you have called tcp_abort from within the* callback function!** @note When a connection attempt fails, the error callback is currently called!*/
typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *tpcb, err_t err);
协议栈通过宏 TCP_EVENT_CONNECTED(pcb,err,ret)
调用 pcb->connected
指向的函数,宏 TCP_EVENT_CONNECTED
定义在 tcp_priv.h
中:
#define TCP_EVENT_CONNECTED(pcb,err,ret) \do { \if((pcb)->connected != NULL) \(ret) = (pcb)->connected((pcb)->callback_arg,(pcb),(err)); \else (ret) = ERR_OK; \} while (0)
以关键字 TCP_EVENT_CONNECTED
搜索源码,可以搜索到 1 处使用:
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
这句代码在 tcp_process
函数中,PCB 控制块处于 SYN_SENT
状态的连接,收到 SYN + ACK
标志且序号正确,则调用宏 TCP_EVENT_CONNECTED
回调 connected
指向的函数,报告应用层连接
static err_t
tcp_process(struct tcp_pcb *pcb)
{/* Do different things depending on the TCP state. */switch (pcb->state) {case SYN_SENT:/* received SYN ACK with expected sequence number? */if ((flags & TCP_ACK) && (flags & TCP_SYN)&& (ackno == pcb->lastack + 1)) {// PCB 字段更新/* Call the user specified function to call when successfully connected. */TCP_EVENT_CONNECTED(pcb, ERR_OK, err);if (err == ERR_ABRT) {return ERR_ABRT;}tcp_ack_now(pcb);}break;}return ERR_OK;
}
sent
回调函数
在 TCP 控制块中,函数指针 sent
指向用户实现的函数,当成功发送数据后,由协议栈调用此函数,通知用户数据已成功发送。
函数指针 sent
的类型为 tcp_sent_fn
,该类型定义在 tcp.h 中:
/** Function prototype for tcp sent callback functions. Called when sent data has* been acknowledged by the remote side. Use it to free corresponding resources.* This also means that the pcb has now space available to send new data.** @param arg Additional argument to pass to the callback function (@see tcp_arg())* @param tpcb The connection pcb for which data has been acknowledged* @param len The amount of bytes acknowledged* @return ERR_OK: try to send some data by calling tcp_output* Only return ERR_ABRT if you have called tcp_abort from within the* callback function!*/
typedef err_t (*tcp_sent_fn)(void *arg, struct tcp_pcb *tpcb,u16_t len);
通过注释可以知道当数据成功发送后(收到远端主机 ACK
应答),调用 sent
回调函数,用于释放某些资源(如果用到的话)。这也意味着 PCB 现在有可以发送新的数据的空间了。
协议栈通过宏 TCP_EVENT_SENT(pcb,space,ret)
调用 pcb->sent
指向的函数。宏 TCP_EVENT_SENT
定义在 tcp_priv.h
中:
#define TCP_EVENT_SENT(pcb,space,ret) \do { \if((pcb)->sent != NULL) \(ret) = (pcb)->sent((pcb)->callback_arg,(pcb),(space)); \else (ret) = ERR_OK; \} while (0)
以关键字 TCP_EVENT_SENT
搜索源码,可以搜索到 1 处使用:
TCP_EVENT_SENT(pcb, (u16_t)acked16, err);
这是在 tcp_input
函数中,如果收到数据 ACK
应答,则回调 sent
函数,应答的数据字节数作为参数传到到回调函数。
void
tcp_input(struct pbuf *p, struct netif *inp)
{// 经过一系列检测,没有错误/* 在本地找到有效的控制块 pcb */if (pcb != NULL) {err = tcp_process(pcb);if (err != ERR_ABRT) {if (recv_flags & TF_RESET) {// 收到 RST 标志,回调 errf 函数TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);tcp_pcb_remove(&tcp_active_pcbs, pcb);tcp_free(pcb);} else {if (recv_acked > 0) {// 收到数据 ACK 应答,回调 sent 函数TCP_EVENT_SENT(pcb, (u16_t)acked16, err); // <--- 这里if (err == ERR_ABRT) {goto aborted;}recv_acked = 0;}if (recv_data != NULL) {// 收到有效数据, 回调 recv 函数TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if (err == ERR_ABRT) {goto aborted;}}if (recv_flags & TF_GOT_FIN) {// 收到 FIN 标志,回调 recv 函数,远端关闭连接TCP_EVENT_CLOSED(pcb, err); if (err == ERR_ABRT) {goto aborted;}}/* Try to send something out. */tcp_output(pcb);}}}
}
poll
回调函数
在 TCP 控制块中,函数指针 poll
指向用户实现的函数,协议栈周期性的调用此函数,“周期“由用户在注册回调函数时指定,最小为 TCP_SLOW_INTERVAL
毫秒(默认 500),用户层可以使用这个回调函数做一些周期性处理。
函数指针 poll
的类型为 tcp_poll_fn
,该类型定义在 tcp.h 中:
/** Function prototype for tcp poll callback functions. Called periodically as* specified by @see tcp_poll.** @param arg Additional argument to pass to the callback function (@see tcp_arg())* @param tpcb tcp pcb* @return ERR_OK: try to send some data by calling tcp_output* Only return ERR_ABRT if you have called tcp_abort from within the* callback function!*/
typedef err_t (*tcp_poll_fn)(void *arg, struct tcp_pcb *tpcb);
协议栈通过宏 TCP_EVENT_POLL(pcb,ret)
调用 pcb->poll
指向的函数。宏 TCP_EVENT_POLL
定义在 tcp_priv.h
中:
#define TCP_EVENT_POLL(pcb,ret) \do { \if((pcb)->poll != NULL) \(ret) = (pcb)->poll((pcb)->callback_arg,(pcb)); \else (ret) = ERR_OK; \} while (0)
以关键字 TCP_EVENT_POLL
搜索源码,可以搜索到 1 处使用:
TCP_EVENT_POLL(prev, err);
这是在 tcp_slowtmr
函数中,当达到设定的时间时,调用 poll
回调函数。简化后的代码为:
void
tcp_slowtmr(void)
{++prev->polltmr;if (prev->polltmr >= prev->pollinterval) {prev->polltmr = 0;TCP_EVENT_POLL(prev, err); // <-- 这里/* if err == ERR_ABRT, 'prev' is already deallocated */if (err == ERR_OK) {tcp_output(prev);}}}
}
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)