lwIP 细节之六:connected、sent、poll 回调函数是何时调用的

使用 lwIP 协议栈进行 TCP 裸机编程,其本质就是编写协议栈指定的各种回调函数 。将你的应用逻辑封装成函数,注册到协议栈,在适当的时候,由协议栈自动调用 ,所以称为回调

注:除非特别说明,以下内容针对 lwIP 2.0.0 及以上版本。

向协议栈注册回调函数有专门的接口,如下所示:

c 复制代码
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

本节讲述 connectedsentpoll 回调函数。

connected 回调函数

在 TCP 控制块中,函数指针 connected 指向用户实现的函数,当一个 PCB 连接到远端主机时,由协议栈调用此函数。

函数指针 connected 的类型为 tcp_connected_fn,该类型定义在 tcp.h 中:

c 复制代码
/** 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 中:

c 复制代码
#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 处使用:

c 复制代码
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);

这句代码在 tcp_process 函数中,PCB 控制块处于 SYN_SENT 状态的连接,收到 SYN + ACK 标志且序号正确,则调用宏 TCP_EVENT_CONNECTED 回调 connected 指向的函数,报告应用层连接

c 复制代码
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 中:

c 复制代码
/** 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 中:

c 复制代码
#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 处使用:

c 复制代码
TCP_EVENT_SENT(pcb, (u16_t)acked16, err);

这是在 tcp_input 函数中,如果收到数据 ACK 应答,则回调 sent 函数,应答的数据字节数作为参数传到到回调函数。

c 复制代码
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 中:

c 复制代码
/** 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 中:

c 复制代码
#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 处使用:

c 复制代码
TCP_EVENT_POLL(prev, err);

这是在 tcp_slowtmr 函数中,当达到设定的时间时,调用 poll 回调函数。简化后的代码为:

c 复制代码
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);
      }
    }
  }
}

读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃'▽'〃)

相关推荐
无所谓จุ๊บ36 分钟前
树莓派开发相关知识十 -小试服务器
服务器·网络·树莓派
道法自然04021 小时前
Ethernet 系列(8)-- 基础学习::ARP
网络·学习·智能路由器
EasyCVR2 小时前
萤石设备视频接入平台EasyCVR多品牌摄像机视频平台海康ehome平台(ISUP)接入EasyCVR不在线如何排查?
运维·服务器·网络·人工智能·ffmpeg·音视频
明月看潮生2 小时前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
龙哥说跨境3 小时前
如何利用指纹浏览器爬虫绕过Cloudflare的防护?
服务器·网络·python·网络爬虫
懒大王就是我4 小时前
C语言网络编程 -- TCP/iP协议
c语言·网络·tcp/ip
Elaine2023914 小时前
06 网络编程基础
java·网络
海绵波波1075 小时前
Webserver(4.3)TCP通信实现
服务器·网络·tcp/ip
热爱跑步的恒川8 小时前
【论文复现】基于图卷积网络的轻量化推荐模型
网络·人工智能·开源·aigc·ai编程
云飞云共享云桌面9 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络