使用 lwIP 协议栈进行 TCP 裸机编程 ,其本质就是编写协议栈指定的各种回调函数。将你的应用逻辑封装成函数,注册到协议栈,在适当的时候,由协议栈自动调用,所以称为回调。
注:除非特别说明,以下内容针对 lwIP 2.0.0 及以上版本。
向协议栈注册回调函数有专门的接口,如下所示:
tcp_err(pcb, errf);
tcp_connect(pcb, ipaddr, port, connected);
tcp_accept(pcb, accept);
tcp_recv(pcb, recv);
tcp_sent(pcb, sent);
tcp_poll(pcb, poll, interval);
errf 回调函数
在 TCP 控制块中,函数指针 errf 指向用户实现的 TCP 错误处理函数,当 TCP 连接发送错误时,由协议栈调用此函数。 函数指针 errf 的类型为 tcp_err_fn ,该类型定义在 tcp.h 中:
typedef void (*tcp_err_fn)(void *arg, err_t err);
从注释得知,错误处理函数在接收到 RST 标志,或者连接意外关闭时,由协议栈调用。 注意,当这个函数调用的时候,TCP 控制块已经释放掉了。
协议栈通过宏 TCP_EVENT_ERR(last_state,errf,arg,err) 调用 errf 指向的错误处理函数,宏 TCP_EVENT_ERR 定义在 tcp_priv.h 中:
#define TCP_EVENT_ERR(last_state,errf,arg,err) \
do { \
LWIP_UNUSED_ARG(last_state); \
if((errf) != NULL) \
(errf)((arg),(err)); \
} while (0)
可以看到这个宏的第 4 个参数就是传递给错误处理函数的错误码。 以关键字 TCP_EVENT_ERR 搜索源码,可以搜索到 4 处使用:
TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);
TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_CLSD);
TCP_EVENT_ERR(last_state, errf, errf_arg, ERR_ABRT);
TCP_EVENT_ERR(last_state, err_fn, err_arg, ERR_ABRT);
用到了 3 个错误码:ERR_RST 、ERR_CLSD 和 ERR_ABRT ,分别表示连接复位、连接关闭和连接异常。
1.连接复位
当远端连接发送 RST 标志,并且报文序号正确是,调用错误类型为 ERR_RST 的错误处理回调函数,这一过程在 tcp_input 函数中执行。
void
tcp_input(struct pbuf *p, struct netif *inp)
{
flags = TCPH_FLAGS(tcphdr);
if (pcb != NULL) {
tcp_input_pcb = pcb;
err = tcp_process(pcb);
if (err != ERR_ABRT) {
if (recv_flags & TF_RESET) {
TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);
tcp_pcb_remove(&tcp_active_pcbs, pcb);
tcp_free(pcb);
}
}
}
return;
}
tcp_process 函数中关于 RST 标志的判断代码:
static err_t
tcp_process(struct tcp_pcb *pcb)
{
if (flags & TCP_RST) {
if (pcb->state == SYN_SENT) {
if (ackno == pcb->snd_nxt) {
acceptable = 1;
}
} else {
if (seqno == pcb->rcv_nxt) {
acceptable = 1;
} else if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
pcb->rcv_nxt + pcb->rcv_wnd)) {
tcp_ack_now(pcb);
}
}
if (acceptable) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
recv_flags |= TF_RESET;
tcp_clear_flags(pcb, TF_ACK_DELAY);
return ERR_RST;
}
}
}
2.连接关闭
这部分代码没有理解清楚,暂时保留
3.连接异常
3.1 由 tcp_abandon 函数调用
tcp_abandon 函数会调用错误类型为 ERR_ABRT 的错误回调函数,简化后的代码为:
void
tcp_abandon(struct tcp_pcb *pcb, int reset)
{
if (pcb->state == TIME_WAIT) {
tcp_pcb_remove(&tcp_tw_pcbs, pcb);
tcp_free(pcb);
} else {
TCP_EVENT_ERR(last_state, errf, errf_arg, ERR_ABRT);
}
}
tcp_abandon 函数又是谁在调用呢?
3.1.1 tcp_listen_input 函数中
在 tcp_listen_input 函数中,检测接收到 SYN 标志报文,则创建新的 TCP_PCB,然后发送 SYN|ACK 标志报文。在这一过程中,若发送 SYN|ACK 标志报文失败,则调用 tcp_abandon 函数放弃这个连接,在 tcp_abandon 函数内部会调用错误类型为 ERR_ABRT 的错误处理回调函数。简化后的代码为:
static void
tcp_listen_input(struct tcp_pcb_listen *pcb)
{
if (flags & TCP_SYN) {
npcb = tcp_alloc(pcb->prio);
npcb->state = SYN_RCVD;
rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);
if (rc != ERR_OK) {
tcp_abandon(npcb, 0);
return;
}
tcp_output(npcb);
}
return;
}
3.1.2 tcp_kill_state 函数中 在《lwIP 细节之二:协议栈什么情况下发送 RST 标志》博文中,有提到 tcp_alloc 函数,tcp_alloc 函数设计原则是尽一切可能返回一个有效的 TCP_PCB 控制块,因此,当 TCP_PCB 不足时,函数可能 “杀死”(kill)正在使用的连接,以释放 TCP_PCB 控制块! 具体就是:
- 先调用
tcp_kill_timewait 函数,试图找到 TIME_WAIT 状态下生存时间最长的连接,如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志,以便通知远端释放连接; - 如果第 1 步失败了,则调用
tcp_kill_state 函数,试图找到 LAST_ACK 和 CLOSING 状态下生存时间最长的连接,如果找到符合条件的控制块 pcb ,则调用 tcp_abandon(pcb, 0) 函数 “杀” 掉这个连接,注意这个函数并不会发送 RST 标志,处于这两种状态的连接都是等到对方发送的 ACK 就会结束连接,不会有数据丢失; - 如果第 2 步也失败了,则调用
tcp_kill_prio(prio) 函数,试图找到小于指定优先级(prio)的最低优先级且生存时间最长的有效(active)连接!如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志。
这里的第 2 步,调用 tcp_abandon(pcb, 0) 函数 “杀” 掉这个连接时,会调用 tcp_abandon 函数放弃这个连接,在 tcp_abandon 函数内部会调用错误类型为 ERR_ABRT 的错误处理回调函数。简化后的代码为:
static void
tcp_kill_state(enum tcp_state state)
{
inactivity = 0;
inactive = NULL;
for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->state == state) {
if ((u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {
inactivity = tcp_ticks - pcb->tmr;
inactive = pcb;
}
}
}
if (inactive != NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_kill_closing: killing oldest %s PCB %p (%"S32_F")\n",
tcp_state_str[state], (void *)inactive, inactivity));
tcp_abandon(inactive, 0);
}
}
3.1.3 tcp_abort 函数中 tcp_abort 函数终止一个连接,会向远端主机发送一个 RST 标志。这个函数只能在某个 TCP 回调函数中调用,并返回 ERR_ABRT 错误码(其它情况绝不要返回 ERR_ABRT 错误码,否则可能会有内存泄漏的风险或者访问已经释放的内存!) tcp_abort 函数代码简单,原始无简化代码为:
void
tcp_abort(struct tcp_pcb *pcb)
{
tcp_abandon(pcb, 1);
}
3.2 由 tcp_slowtmr 函数调用
tcp_slowtmr 函数中完成重传和超时处理,当重传达到设定次数,或者超时达到设定时间,则调用错误类型为 ERR_ABRT 的错误处理回调函数。
重传和超时事件有:
- PCB 控制块处于
SYN_SENT 状态,重传次数达到 TCP_SYNMAXRTX 次(默认 6 次) - PCB 控制块处于其它状态,重传次数达到
TCP_MAXRTX 次(默认 12 次) - 坚持定时器探查窗口达到
TCP_MAXRTX 次(默认 12 次) - PCB 控制块处于
FIN_WAIT_2 状态,超时达到 TCP_FIN_WAIT_TIMEOUT 秒(默认 20 秒) - PCB 控制块处于
SYN_RCVD 状态,超时达到 TCP_SYN_RCVD_TIMEOUT 秒(默认 20 秒) - PCB 控制块处于
LAST_ACK 状态,超时达到 2 * TCP_MSL 秒(默认 120 秒) - 使能保活、PCB 控制块处于
ESTABLISHED 或 CLOSE_WAIT 状态,超时达到 pcb->keep_idle + TCP_KEEP_DUR(pcb) 秒(默认 2 小时 10 分 48 秒)
tcp_abort 函数简化后的代码为:
void
tcp_slowtmr(void)
{
while (pcb != NULL) {
LWIP_ASSERT("tcp_slowtmr: active pcb->state != CLOSED\n", pcb->state != CLOSED);
LWIP_ASSERT("tcp_slowtmr: active pcb->state != LISTEN\n", pcb->state != LISTEN);
LWIP_ASSERT("tcp_slowtmr: active pcb->state != TIME-WAIT\n", pcb->state != TIME_WAIT);
if (pcb->state == SYN_SENT && pcb->nrtx >= TCP_SYNMAXRTX) {
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));
} else if (pcb->nrtx >= TCP_MAXRTX) {
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));
} else {
if (pcb->persist_backoff > 0) {
if (pcb->persist_probe >= TCP_MAXRTX) {
++pcb_remove;
}
}
if (pcb->state == FIN_WAIT_2) {
if (pcb->flags & TF_RXCLOSED) {
if ((u32_t)(tcp_ticks - pcb->tmr) >
TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {
++pcb_remove;
}
}
}
if (ip_get_option(pcb, SOF_KEEPALIVE) &&
((pcb->state == ESTABLISHED) ||
(pcb->state == CLOSE_WAIT))) {
if ((u32_t)(tcp_ticks - pcb->tmr) >
(pcb->keep_idle + TCP_KEEP_DUR(pcb)) / TCP_SLOW_INTERVAL) {
++pcb_remove;
++pcb_reset;
}
}
if (pcb->state == SYN_RCVD) {
if ((u32_t)(tcp_ticks - pcb->tmr) >
TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {
++pcb_remove;
}
}
if (pcb->state == LAST_ACK) {
if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
++pcb_remove;
}
}
if (pcb_remove) {
tcp_pcb_purge(pcb);
if (prev != NULL) {
prev->next = pcb->next;
} else {
tcp_active_pcbs = pcb->next;
}
if (pcb_reset) {
tcp_rst(pcb, pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
pcb->local_port, pcb->remote_port);
}
tcp_free(pcb2);
TCP_EVENT_ERR(last_state, err_fn, err_arg, ERR_ABRT);
}
}
}
connected 回调函数
在 TCP 控制块中,函数指针 connected 指向用户实现的函数,当 TCP 建立连接成功时,由协议栈调用此函数。 函数指针 connected 的类型为 tcp_connected_fn ,该类型定义在 tcp.h 中:
typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *tpcb, err_t err);
当一个 PCB 连接到远端主机时调用。 协议栈通过宏 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)
{
switch (pcb->state) {
case SYN_SENT:
if ((flags & TCP_ACK) && (flags & TCP_SYN)
&& (ackno == pcb->lastack + 1)) {
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
if (err == ERR_ABRT) {
return ERR_ABRT;
}
tcp_ack_now(pcb);
}
break;
}
return ERR_OK;
}
|