lwIP 细节之三:TCP 回调函数是何时调用的
使用 lwIP 協(xié)議棧進(jìn)行 TCP 裸機(jī)編程,其本質(zhì)就是編寫(xiě)協(xié)議棧指定的各種回調(diào)函數(shù)。將你的應(yīng)用邏輯封裝成函數(shù),注冊(cè)到協(xié)議棧,在適當(dāng)?shù)臅r(shí)候,由協(xié)議棧自動(dòng)調(diào)用,所以稱為回調(diào)。
注:除非特別說(shuō)明,以下內(nèi)容針對(duì) lwIP 2.0.0 及以上版本。
向協(xié)議棧注冊(cè)回調(diào)函數(shù)有專門(mén)的接口,如下所示:
tcp_err(pcb, errf); //注冊(cè) TCP 接到 RST 標(biāo)志或發(fā)生錯(cuò)誤回調(diào)函數(shù) errf tcp_connect(pcb, ipaddr, port, connected); //注冊(cè) TCP 建立連接成功回調(diào)函數(shù) connecter tcp_accept(pcb, accept); //注冊(cè) TCP 處于 LISTEN 狀態(tài)時(shí),監(jiān)聽(tīng)到有新的連接接入 tcp_recv(pcb, recv); //注冊(cè) TCP 接收到數(shù)據(jù)回調(diào)函數(shù) recv tcp_sent(pcb, sent); //注冊(cè) TCP 發(fā)送數(shù)據(jù)成功回調(diào)函數(shù) sent tcp_poll(pcb, poll, interval); //注冊(cè) TCP 周期性執(zhí)行回調(diào)函數(shù) pollerrf 回調(diào)函數(shù)
在 TCP 控制塊中,函數(shù)指針 errf 指向用戶實(shí)現(xiàn)的 TCP 錯(cuò)誤處理函數(shù),當(dāng) TCP 連接發(fā)送錯(cuò)誤時(shí),由協(xié)議棧調(diào)用此函數(shù)。
函數(shù)指針 errf 的類型為 tcp_err_fn ,該類型定義在 tcp.h 中:
從注釋得知,錯(cuò)誤處理函數(shù)在接收到 RST 標(biāo)志,或者連接意外關(guān)閉時(shí),由協(xié)議棧調(diào)用。
注意,當(dāng)這個(gè)函數(shù)調(diào)用的時(shí)候,TCP 控制塊已經(jīng)釋放掉了。
協(xié)議棧通過(guò)宏 TCP_EVENT_ERR(last_state,errf,arg,err) 調(diào)用 errf 指向的錯(cuò)誤處理函數(shù),宏 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)可以看到這個(gè)宏的第 4 個(gè)參數(shù)就是傳遞給錯(cuò)誤處理函數(shù)的錯(cuò)誤碼。
以關(guān)鍵字 TCP_EVENT_ERR 搜索源碼,可以搜索到 4 處使用:
用到了 3 個(gè)錯(cuò)誤碼:ERR_RST、ERR_CLSD 和 ERR_ABRT ,分別表示連接復(fù)位、連接關(guān)閉和連接異常。
1.連接復(fù)位
當(dāng)遠(yuǎn)端連接發(fā)送 RST 標(biāo)志,并且報(bào)文序號(hào)正確是,調(diào)用錯(cuò)誤類型為 ERR_RST 的錯(cuò)誤處理回調(diào)函數(shù),這一過(guò)程在 tcp_input 函數(shù)中執(zhí)行。
void tcp_input(struct pbuf *p, struct netif *inp) {// 經(jīng)過(guò)一系列檢測(cè),沒(méi)有錯(cuò)誤flags = TCPH_FLAGS(tcphdr); // 這里獲取數(shù)據(jù)包的 [標(biāo)志] 字段/* 在本地找到有效的控制塊 pcb */if (pcb != NULL) {tcp_input_pcb = pcb;err = tcp_process(pcb); // [標(biāo)志]中有 RST, 且報(bào)文序號(hào)正確:recv_flags |= TF_RESET/* A return value of ERR_ABRT means that tcp_abort() was calledand that the pcb has been freed. If so, we don't do anything. */if (err != ERR_ABRT) {if (recv_flags & TF_RESET) {/* TF_RESET means that the connection was reset by the otherend. We then call the error callback to inform theapplication that the connection is dead before wedeallocate the PCB. */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 函數(shù)中關(guān)于 RST 標(biāo)志的判斷代碼:
static err_t tcp_process(struct tcp_pcb *pcb) {/* Process incoming RST segments. */if (flags & TCP_RST) { // flags 保存數(shù)據(jù)包的 [標(biāo)志] 字段,在 tcp_input 函數(shù)中取得 /* First, determine if the reset is acceptable. */if (pcb->state == SYN_SENT) {/* "In the SYN-SENT state (a RST received in response to an initial SYN),the RST is acceptable if the ACK field acknowledges the SYN." */if (ackno == pcb->snd_nxt) {acceptable = 1;}} else {/* "In all states except SYN-SENT, all reset (RST) segments are validatedby checking their SEQ-fields." */if (seqno == pcb->rcv_nxt) {acceptable = 1;} else if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,pcb->rcv_nxt + pcb->rcv_wnd)) {/* If the sequence number is inside the window, we send a challenge ACKand wait for a re-send with matching sequence number.This follows RFC 5961 section 3.2 and addresses CVE-2004-0230(RST spoofing attack), which is present in RFC 793 RST handling. */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.連接關(guān)閉
這部分代碼沒(méi)有理解清楚,暫時(shí)保留
3.連接異常
3.1 由 tcp_abandon 函數(shù)調(diào)用
tcp_abandon 函數(shù)會(huì)調(diào)用錯(cuò)誤類型為 ERR_ABRT 的錯(cuò)誤回調(diào)函數(shù),簡(jiǎn)化后的代碼為:
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_PCB// 按需釋放[未應(yīng)答]、[未發(fā)送]、[失序]報(bào)文內(nèi)存// 按需發(fā)送 RST 標(biāo)志// 釋放 TCP_PCB :tcp_free(pcb)TCP_EVENT_ERR(last_state, errf, errf_arg, ERR_ABRT); // <-- 這里} }tcp_abandon 函數(shù)又是誰(shuí)在調(diào)用呢?
3.1.1 tcp_listen_input 函數(shù)中
在 tcp_listen_input 函數(shù)中,檢測(cè)接收到 SYN 標(biāo)志報(bào)文,則創(chuàng)建新的 TCP_PCB,然后發(fā)送 SYN|ACK 標(biāo)志報(bào)文。在這一過(guò)程中,若發(fā)送 SYN|ACK 標(biāo)志報(bào)文失敗,則調(diào)用 tcp_abandon 函數(shù)放棄這個(gè)連接,在 tcp_abandon 函數(shù)內(nèi)部會(huì)調(diào)用錯(cuò)誤類型為 ERR_ABRT 的錯(cuò)誤處理回調(diào)函數(shù)。簡(jiǎn)化后的代碼為:
static void tcp_listen_input(struct tcp_pcb_listen *pcb) {/* In the LISTEN state, we check for incoming SYN segments,creates a new PCB, and responds with a SYN|ACK. */if (flags & TCP_SYN) {npcb = tcp_alloc(pcb->prio);/* 這里 TCP PCB 申請(qǐng)成功,初始化新的 PCB*/// ...npcb->state = SYN_RCVD;// .../* Send a SYN|ACK together with the MSS option. */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 函數(shù)中
在《lwIP 細(xì)節(jié)之二:協(xié)議棧什么情況下發(fā)送 RST 標(biāo)志》博文中,有提到 tcp_alloc 函數(shù),tcp_alloc 函數(shù)設(shè)計(jì)原則是盡一切可能返回一個(gè)有效的 TCP_PCB 控制塊,因此,當(dāng) TCP_PCB 不足時(shí),函數(shù)可能 “殺死”(kill)正在使用的連接,以釋放 TCP_PCB 控制塊!
具體就是:
這里的第 2 步,調(diào)用 tcp_abandon(pcb, 0) 函數(shù) “殺” 掉這個(gè)連接時(shí),會(huì)調(diào)用 tcp_abandon 函數(shù)放棄這個(gè)連接,在 tcp_abandon 函數(shù)內(nèi)部會(huì)調(diào)用錯(cuò)誤類型為 ERR_ABRT 的錯(cuò)誤處理回調(diào)函數(shù)。簡(jiǎn)化后的代碼為:
static void tcp_kill_state(enum tcp_state state) {inactivity = 0;inactive = NULL;/* Go through the list of active pcbs and get the oldest pcb that is in stateCLOSING/LAST_ACK. */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));/* Don't send a RST, since no data is lost. */tcp_abandon(inactive, 0);} }3.1.3 tcp_abort 函數(shù)中
tcp_abort 函數(shù)終止一個(gè)連接,會(huì)向遠(yuǎn)端主機(jī)發(fā)送一個(gè) RST 標(biāo)志。這個(gè)函數(shù)只能在某個(gè) TCP 回調(diào)函數(shù)中調(diào)用,并返回 ERR_ABRT 錯(cuò)誤碼(其它情況絕不要返回 ERR_ABRT 錯(cuò)誤碼,否則可能會(huì)有內(nèi)存泄漏的風(fēng)險(xiǎn)或者訪問(wèn)已經(jīng)釋放的內(nèi)存!)
tcp_abort 函數(shù)代碼簡(jiǎn)單,原始無(wú)簡(jiǎn)化代碼為:
3.2 由 tcp_slowtmr 函數(shù)調(diào)用
tcp_slowtmr 函數(shù)中完成重傳和超時(shí)處理,當(dāng)重傳達(dá)到設(shè)定次數(shù),或者超時(shí)達(dá)到設(shè)定時(shí)間,則調(diào)用錯(cuò)誤類型為 ERR_ABRT 的錯(cuò)誤處理回調(diào)函數(shù)。
重傳和超時(shí)事件有:
- PCB 控制塊處于 SYN_SENT 狀態(tài),重傳次數(shù)達(dá)到 TCP_SYNMAXRTX 次(默認(rèn) 6 次)
- PCB 控制塊處于其它狀態(tài),重傳次數(shù)達(dá)到 TCP_MAXRTX 次(默認(rèn) 12 次)
- 堅(jiān)持定時(shí)器探查窗口達(dá)到 TCP_MAXRTX 次(默認(rèn) 12 次)
- PCB 控制塊處于 FIN_WAIT_2 狀態(tài),超時(shí)達(dá)到 TCP_FIN_WAIT_TIMEOUT 秒(默認(rèn) 20 秒)
- PCB 控制塊處于 SYN_RCVD 狀態(tài),超時(shí)達(dá)到 TCP_SYN_RCVD_TIMEOUT 秒(默認(rèn) 20 秒)
- PCB 控制塊處于 LAST_ACK 狀態(tài),超時(shí)達(dá)到 2 * TCP_MSL 秒(默認(rèn) 120 秒)
- 使能?;?、PCB 控制塊處于 ESTABLISHED 或 CLOSE_WAIT 狀態(tài),超時(shí)達(dá)到 pcb->keep_idle + TCP_KEEP_DUR(pcb) 秒(默認(rèn) 2 小時(shí) 10 分 48 秒)
tcp_abort 函數(shù)簡(jiǎn)化后的代碼為:
/*** Called every 500 ms and implements the retransmission timer and the timer that* removes PCBs that have been in TIME-WAIT for enough time. It also increments* various timers such as the inactivity timer in each PCB.** Automatically called from tcp_tmr().*/ void tcp_slowtmr(void) {while (pcb != NULL) {/* 這里表明處于 CLOSED、LISTEN 和 TIME_WAIT 狀態(tài)的連接不會(huì)有重傳 */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; // 處于SYN_SENT 狀態(tài),重傳達(dá)到 6 次LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));} else if (pcb->nrtx >= TCP_MAXRTX) {++pcb_remove; // 其它狀態(tài),重傳達(dá)到 12 次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; // 探查次數(shù)達(dá)到 12 次 */}}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; // 處于 FIN_WAIT_2 狀態(tài),超時(shí)達(dá)到 20 秒}}}/* 注意只有 ESTABLISHED 和 CLOSE_WAIT 狀態(tài)才會(huì)有 KEEPALIVE(?;?#xff09; */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; // 使能保活,超時(shí) 2 小時(shí) 10 分鐘 48 秒++pcb_reset;} }if (pcb->state == SYN_RCVD) {if ((u32_t)(tcp_ticks - pcb->tmr) >TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {++pcb_remove; // 處于 SYN_RCVD 狀態(tài),超時(shí)達(dá)到 20 秒}}if (pcb->state == LAST_ACK) {if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {++pcb_remove; // 處于 LAST_ACK 狀態(tài),超時(shí)達(dá)到 120 秒}}/* 需要移除 PCB 控制塊 */if (pcb_remove) {tcp_pcb_purge(pcb); // 釋放 PCB 中的數(shù)據(jù)緩沖區(qū)(refused_data、unsent、unacked、ooseq)if (prev != NULL) { // 從 tcp_active_pcbs 列表中移除 PCBprev->next = pcb->next;} else {tcp_active_pcbs = pcb->next;}if (pcb_reset) { // 根據(jù)需要發(fā)送 RST 標(biāo)志tcp_rst(pcb, pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,pcb->local_port, pcb->remote_port);}tcp_free(pcb2); // 釋放 PCB 控制塊內(nèi)存/* 調(diào)用錯(cuò)誤回調(diào)函數(shù) */TCP_EVENT_ERR(last_state, err_fn, err_arg, ERR_ABRT);} } }connected 回調(diào)函數(shù)
在 TCP 控制塊中,函數(shù)指針 connected 指向用戶實(shí)現(xiàn)的函數(shù),當(dāng)一個(gè) PCB 連接到遠(yuǎn)端主機(jī)時(shí),由協(xié)議棧調(diào)用此函數(shù)。
函數(shù)指針 connected 的類型為 tcp_connected_fn,該類型定義在 tcp.h 中:
協(xié)議棧通過(guò)宏 TCP_EVENT_CONNECTED(pcb,err,ret) 調(diào)用 pcb->connected 指向的函數(shù),宏 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)以關(guān)鍵字 TCP_EVENT_CONNECTED 搜索源碼,可以搜索到 1 處使用:
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);這句代碼在 tcp_process 函數(shù)中,PCB 控制塊處于 SYN_SENT 狀態(tài)的連接,收到 SYN + ACK 標(biāo)志且序號(hào)正確,則調(diào)用宏 TCP_EVENT_CONNECTED 回調(diào) connected 指向的函數(shù),報(bào)告應(yīng)用層連接
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; }accept 回調(diào)函數(shù)
在 TCP 控制塊中,函數(shù)指針 accept 指向用戶實(shí)現(xiàn)的函數(shù),當(dāng)監(jiān)聽(tīng)到有新的連接接入時(shí),由協(xié)議棧調(diào)用此函數(shù),通知用戶接受了新的連接或者通知用戶內(nèi)存不足。
函數(shù)指針 accept 的類型為 tcp_accept_fn ,該類型定義在 tcp.h 中:
協(xié)議棧通過(guò)宏 TCP_EVENT_ACCEPT(lpcb,pcb,arg,err,ret) 調(diào)用 lpcb->accept 指向的函數(shù)。宏 TCP_EVENT_ACCEPT 定義在 tcp_priv.h 中:
#define TCP_EVENT_ACCEPT(lpcb,pcb,arg,err,ret) \do { \if((lpcb)->accept != NULL) \(ret) = (lpcb)->accept((arg),(pcb),(err)); \else (ret) = ERR_ARG; \} while (0)以關(guān)鍵字 TCP_EVENT_ACCEPT 搜索源碼,可以搜索到 2 處使用:
TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err); TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, ERR_OK, err);1 由 tcp_listen_input 函數(shù)調(diào)用
處于 LISTEN 狀態(tài)的 TCP 控制塊 ,如果收到客戶端發(fā)送的 SYN 同步標(biāo)志,表示一個(gè)客戶端在請(qǐng)求建立連接了。
lwIP 會(huì)為這個(gè)新連接申請(qǐng)一個(gè) TCP_PCB ,這一過(guò)程在 tcp_listen_input 函數(shù)中完成的。然而 TCP_PCB 的個(gè)數(shù)是有限的,如果申請(qǐng)失敗,則會(huì)調(diào)用錯(cuò)誤碼為 ERR_MEM 的 accept 回調(diào)函數(shù),向用戶報(bào)告內(nèi)存分配失敗。簡(jiǎn)化后的代碼為:
這里需要注意,申請(qǐng) TCP_PCB 失敗的處理方法,lwIP 2.1.x 版本與 lwIP 1.4.1 不同。
再看看 lwIP 1.4.1 的 tcp_listen_input 函數(shù)代碼(經(jīng)簡(jiǎn)化):
可以看到, lwIP 1.4.1 版本 tcp_listen_input 函數(shù)具有返回值,如果申請(qǐng) TCP_PCB 失敗,則返回 ERR_MEM 錯(cuò)誤碼。而 lwIP 2.1.x 版本 tcp_listen_input 函數(shù)不具有返回值(返回類型為 void ),其次,lwIP 2.1.x 版本處理內(nèi)存錯(cuò)誤是通過(guò)調(diào)用 accept 回調(diào)函數(shù)來(lái)實(shí)現(xiàn)的。宏展開(kāi)代碼(簡(jiǎn)化后)如下所示,注意第二個(gè)參數(shù)為 NULL ,錯(cuò)誤碼為 ERR_MEM:
if(pcb->accept != NULL)pcb->accept(pcb->callback_arg, NULL, ERR_MEM);這個(gè)功能最早是由 Simon Goldschmidt 在 2016-03-23 提交的,提交記錄為:
tcp: call accept-callback with ERR_MEM when allocating a pcb fails onpassive open to inform the application about this errorATTENTION: applications have to handle NULL pcb in accept callback!tcp:在被動(dòng)打開(kāi)分配 pcb 失敗時(shí),使用 ERR_MEM 參數(shù)調(diào)用 accept 回調(diào)函數(shù),以通知應(yīng)用程序有關(guān)此錯(cuò)誤
注意:應(yīng)用程序必須在 accept 回調(diào)中處理 pcb 句柄為 NULL 的情況!
這就告訴我們一個(gè)重要的信息:lwIP 2.1.x 版本的 accept 回調(diào)函數(shù)編寫(xiě)方式與 lwIP 1.4.1 版本不同。lwIP 2.1.x 版本的 accept 回調(diào)函數(shù) 必須 在 accept 回調(diào)中處理 pcb 句柄為 NULL 的情況!!舉個(gè)例子。
lwIP 1.4.1 版本的 accept 回調(diào)函數(shù)可以這么寫(xiě):
而 lwIP 2.1.x 版本的accept 回調(diào)函數(shù)需要這么寫(xiě):
/* 客戶端連接時(shí), 回調(diào)此函數(shù) */ static err_t telnet_accept(void *arg, struct tcp_pcb *pcb, err_t err) {char * p_link_info = "已連接到Telnet!\r\n";if(pcb == NULL){if(err == ERR_MEM)// 處理 TCP 連接個(gè)數(shù)不足,可選return ERR_OK;}tcp_recv(pcb,telnet_recv);tcp_err(pcb,NULL);pcb->so_options |= SOF_KEEPALIVE; //增加?;顧C(jī)制tcp_write(pcb, p_link_info, strlen(p_link_info), TCP_WRITE_FLAG_COPY);return ERR_OK; }這里對(duì) pcb 句柄是否為 NULL 做了處理,如果檢測(cè)到 NULL,accpet 回調(diào)函數(shù)需要提前退出!。
2 由 tcp_process 函數(shù)調(diào)用
處于 SYN_RCVD 狀態(tài)的 TCP 控制塊,如果接收的正確的 ACK 標(biāo)志,則調(diào)用錯(cuò)誤碼為 ERR_OK 的 accept 回調(diào)函數(shù),向用戶報(bào)告接受了新的連接。簡(jiǎn)化后的代碼為:
static err_t tcp_process(struct tcp_pcb *pcb) {switch (pcb->state) {case SYN_RCVD:if (flags & TCP_ACK) {/* expected ACK number? */if (TCP_SEQ_BETWEEN(ackno, pcb->lastack + 1, pcb->snd_nxt)) {pcb->state = ESTABLISHED;/* Call the accept function. */TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, ERR_OK, err);if (err != ERR_OK) {/* If the accept function returns with an error, we abort the connection. */if (err != ERR_ABRT) {tcp_abort(pcb);}return ERR_ABRT;}tcp_receive(pcb);} }break;}return ERR_OK; }recv 回調(diào)函數(shù)
在 TCP 控制塊中,函數(shù)指針 recv 指向用戶實(shí)現(xiàn)的函數(shù),當(dāng)接收到有效數(shù)據(jù)時(shí),由協(xié)議棧調(diào)用此函數(shù),通知用戶處理接收到的數(shù)據(jù)。
函數(shù)指針 recv 的類型為 tcp_recv_fn ,該類型定義在 tcp.h 中:
協(xié)議棧通過(guò)宏 TCP_EVENT_RECV(pcb,p,err,ret) 調(diào)用 pcb->recv 指向的函數(shù)。宏 TCP_EVENT_RECV 定義在 tcp_priv.h 中:
#define TCP_EVENT_RECV(pcb,p,err,ret) \do { \if((pcb)->recv != NULL) { \(ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err));\} else { \(ret) = tcp_recv_null(NULL, (pcb), (p), (err)); \} \} while (0)以關(guān)鍵字 TCP_EVENT_RECV 搜索源碼,可以搜索到 2 處使用:
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err); TCP_EVENT_RECV(pcb, refused_data, ERR_OK, err);1 由 tcp_input 函數(shù)調(diào)用
指針 recv_data 是一個(gè) struct pbuf 類型的指針,定義在 tcp_in.c 文件中,是一個(gè)靜態(tài)變量:
static struct pbuf *recv_data;經(jīng)過(guò) tcp_process 函數(shù)處理后,如果接收到有效數(shù)據(jù),則指針 recv_data 指向數(shù)據(jù) pbuf ,此時(shí)協(xié)議棧通過(guò)宏 TCP_EVENT_RECV 調(diào)用用戶編寫(xiě)的數(shù)據(jù)處理函數(shù)。
簡(jiǎn)化后的代碼為:
void tcp_input(struct pbuf *p, struct netif *inp) {// 經(jīng)過(guò)一系列檢測(cè),沒(méi)有錯(cuò)誤/* 在本地找到有效的控制塊 pcb */if (pcb != NULL) {err = tcp_process(pcb);/* A return value of ERR_ABRT means that tcp_abort() was calledand that the pcb has been freed. If so, we don't do anything. */if (err != ERR_ABRT) {if (recv_data != NULL) {/* Notify application that data has been received. */TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if (err == ERR_ABRT) {goto aborted;}/* If the upper layer can't receive this data, store it */if (err != ERR_OK) {pcb->refused_data = recv_data;LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));}}/* Try to send something out. */tcp_output(pcb); // <--- 注意這里調(diào)用了發(fā)送函數(shù),所以 recv 回調(diào)函數(shù)就沒(méi)必要再調(diào)用這個(gè)函數(shù)}} }從以上代碼中可以看出:
所以上層如果來(lái)不及處理數(shù)據(jù),可以讓協(xié)議棧暫存。這里暫存數(shù)據(jù)使用了指針 pcb->refused_data ,需要注意一下,因?yàn)榻酉聛?lái)會(huì)再次看到它。
在 recv 回調(diào)函數(shù)中,處理完接收到的數(shù)據(jù)后,通常我們還會(huì)調(diào)用 tcp_write 函數(shù)回送數(shù)據(jù)。函數(shù)原型為:
/*** @ingroup tcp_raw* Write data for sending (but does not send it immediately).** It waits in the expectation of more data being sent soon (as* it can send them more efficiently by combining them together).* To prompt the system to send data now, call tcp_output() after* calling tcp_write().* * This function enqueues the data pointed to by the argument dataptr. The length of* the data is passed as the len parameter. The apiflags can be one or more of:* - TCP_WRITE_FLAG_COPY: indicates whether the new memory should be allocated* for the data to be copied into. If this flag is not given, no new memory* should be allocated and the data should only be referenced by pointer. This* also means that the memory behind dataptr must not change until the data is* ACKed by the remote host* - TCP_WRITE_FLAG_MORE: indicates that more data follows. If this is omitted,* the PSH flag is set in the last segment created by this call to tcp_write.* If this flag is given, the PSH flag is not set.** The tcp_write() function will fail and return ERR_MEM if the length* of the data exceeds the current send buffer size or if the length of* the queue of outgoing segment is larger than the upper limit defined* in lwipopts.h. The number of bytes available in the output queue can* be retrieved with the tcp_sndbuf() function.** The proper way to use this function is to call the function with at* most tcp_sndbuf() bytes of data. If the function returns ERR_MEM,* the application should wait until some of the currently enqueued* data has been successfully received by the other host and try again.** @param pcb Protocol control block for the TCP connection to enqueue data for.* @param arg Pointer to the data to be enqueued for sending.* @param len Data length in bytes* @param apiflags combination of following flags :* - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack* - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will not be set on last segment sent,* @return ERR_OK if enqueued, another err_t on error*/ err_t tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags)通過(guò)注釋可以得知,這個(gè)函數(shù)會(huì)盡可能把發(fā)送的數(shù)據(jù)組合在一起,然后一次性發(fā)送出去,因?yàn)檫@樣更有效率。換句話說(shuō),調(diào)用這個(gè)函數(shù)并不會(huì)立即發(fā)送數(shù)據(jù),如果希望立即發(fā)送數(shù)據(jù),需要在調(diào)用 tcp_write 函數(shù)之后調(diào)用 tcp_output 函數(shù)。
而現(xiàn)在我們又知道了,在 tcp_input 函數(shù)中,調(diào)用 recv 回調(diào)函數(shù)后,協(xié)議棧會(huì)執(zhí)行一次 tcp_output 函數(shù),這就是我們?cè)?recv 回調(diào)函數(shù)中調(diào)用 tcp_write 函數(shù)能夠立即將數(shù)據(jù)發(fā)送出去的原因!
2 由 tcp_process_refused_data 函數(shù)調(diào)用
在上一節(jié)提到 “上層如果來(lái)不及處理數(shù)據(jù),可以讓協(xié)議棧暫存。這里暫存數(shù)據(jù)使用了指針 pcb->refused_data ”,而 tcp_process_refused_data 函數(shù)就是把暫存的數(shù)據(jù)重新提交給應(yīng)用層處理。提交的方法是調(diào)用 recv 回調(diào)函數(shù),簡(jiǎn)化后的代碼為:
err_t tcp_process_refused_data(struct tcp_pcb *pcb) {/* set pcb->refused_data to NULL in case the callback frees it and thencloses the pcb */struct pbuf *refused_data = pcb->refused_data;pcb->refused_data = NULL;/* Notify again application with data previously received. */TCP_EVENT_RECV(pcb, refused_data, ERR_OK, err);if (err == ERR_ABRT) {return ERR_ABRT;} else if(err != ERR_OK){/* data is still refused, pbuf is still valid (go on for ACK-only packets) */pcb->refused_data = refused_data;return ERR_INPROGRESS;}return ERR_OK; }協(xié)議棧會(huì)在兩處調(diào)用 tcp_process_refused_data 函數(shù)。
2.1 在 tcp_input 函數(shù)中調(diào)用
通過(guò)以上代碼可以知道:
2.2 在 tcp_fasttmr 函數(shù)中調(diào)用
協(xié)議棧每隔 TCP_TMR_INTERVAL (默認(rèn) 250)毫秒調(diào)用一次 tcp_fasttmr 函數(shù),在這個(gè)函數(shù)中會(huì)檢查 TCP_PCB 是否有尚未給上層應(yīng)用處理的暫存數(shù)據(jù),如果有則調(diào)用 tcp_process_refused_data 函數(shù),將暫存數(shù)據(jù)上報(bào)給應(yīng)用層處理。簡(jiǎn)化后的代碼為:
recv 函數(shù)的復(fù)用行為
前面看到了錯(cuò)誤回調(diào)函數(shù)、連接成功回調(diào)函數(shù)、接收到數(shù)據(jù)回調(diào)函數(shù),后面還會(huì)看到發(fā)送成功回調(diào)函數(shù)等。那么我們合理推測(cè),應(yīng)該也有連接關(guān)閉回調(diào)函數(shù)。在連接關(guān)閉時(shí),協(xié)議棧確實(shí)回調(diào)了一個(gè)函數(shù),但這個(gè)函數(shù)也是 recv 回調(diào)函數(shù)!協(xié)議棧并沒(méi)有提供單獨(dú)的連接關(guān)閉回調(diào)函數(shù),而是復(fù)用了 recv 回調(diào)函數(shù)。協(xié)議棧使用宏 TCP_EVENT_CLOSED 封裝了這一過(guò)程,代碼為:
#define TCP_EVENT_CLOSED(pcb,ret) \do { \if(((pcb)->recv != NULL)) { \(ret) = (pcb)->recv((pcb)->callback_arg,(pcb),NULL,ERR_OK);\} else { \(ret) = ERR_OK; \} \} while (0)注意調(diào)用 recv 函數(shù)時(shí),第 3 個(gè)參數(shù)為 NULL ,這很重要。我們又知道,recv 的原型為:
typedef err_t (*tcp_recv_fn)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);所以第三個(gè)參數(shù)是 struct pbuf 型指針。
也就是說(shuō),我們必須在 recv 回調(diào)函數(shù)中處理 pbuf 指針為 NULL 的特殊情況,這表示遠(yuǎn)端主動(dòng)關(guān)閉了連接,這時(shí)我們應(yīng)主動(dòng)調(diào)用 tcp_close 函數(shù),關(guān)閉本地連接。一個(gè)典型的 recv 回調(diào)函數(shù)框架為:
協(xié)議棧在 tci_input 函數(shù)中調(diào)用宏 TCP_EVENT_CLOSED ,簡(jiǎn)化后的代碼為:
void tcp_input(struct pbuf *p, struct netif *inp) {// 經(jīng)過(guò)一系列檢測(cè),沒(méi)有錯(cuò)誤/* 在本地找到有效的控制塊 pcb */if (pcb != NULL) {err = tcp_process(pcb);if (err != ERR_ABRT) {if (recv_flags & TF_RESET) {// 收到 RST 標(biāo)志,回調(diào) errf 函數(shù)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) {// 收到數(shù)據(jù) ACK 應(yīng)答,回調(diào) sent 函數(shù)TCP_EVENT_SENT(pcb, (u16_t)acked16, err);if (err == ERR_ABRT) {goto aborted;}recv_acked = 0;}if (recv_data != NULL) {// 收到有效數(shù)據(jù), 回調(diào) recv 函數(shù)TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if (err == ERR_ABRT) {goto aborted;}}if (recv_flags & TF_GOT_FIN) {// 收到 FIN 標(biāo)志,回調(diào) recv 函數(shù),遠(yuǎn)端關(guān)閉連接TCP_EVENT_CLOSED(pcb, err); // <--- 這里if (err == ERR_ABRT) {goto aborted;}}/* Try to send something out. */tcp_output(pcb);}}} }sent 回調(diào)函數(shù)
在 TCP 控制塊中,函數(shù)指針 sent 指向用戶實(shí)現(xiàn)的函數(shù),當(dāng)成功發(fā)送數(shù)據(jù)后,由協(xié)議棧調(diào)用此函數(shù),通知用戶數(shù)據(jù)已成功發(fā)送。
函數(shù)指針 sent 的類型為 tcp_sent_fn ,該類型定義在 tcp.h 中:
通過(guò)注釋可以知道當(dāng)數(shù)據(jù)成功發(fā)送后(收到遠(yuǎn)端主機(jī) ACK 應(yīng)答),調(diào)用 sent 回調(diào)函數(shù),用于釋放某些資源(如果用到的話)。這也意味著 PCB 現(xiàn)在有可以發(fā)送新的數(shù)據(jù)的空間了。
協(xié)議棧通過(guò)宏 TCP_EVENT_SENT(pcb,space,ret) 調(diào)用 pcb->sent 指向的函數(shù)。宏 TCP_EVENT_SENT 定義在 tcp_priv.h 中:
以關(guān)鍵字 TCP_EVENT_SENT 搜索源碼,可以搜索到 1 處使用:
TCP_EVENT_SENT(pcb, (u16_t)acked16, err);這是在 tcp_input 函數(shù)中,如果收到數(shù)據(jù) ACK 應(yīng)答,則回調(diào) sent 函數(shù),應(yīng)答的數(shù)據(jù)字節(jié)數(shù)作為參數(shù)傳到到回調(diào)函數(shù)。
void tcp_input(struct pbuf *p, struct netif *inp) {// 經(jīng)過(guò)一系列檢測(cè),沒(méi)有錯(cuò)誤/* 在本地找到有效的控制塊 pcb */if (pcb != NULL) {err = tcp_process(pcb);if (err != ERR_ABRT) {if (recv_flags & TF_RESET) {// 收到 RST 標(biāo)志,回調(diào) errf 函數(shù)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) {// 收到數(shù)據(jù) ACK 應(yīng)答,回調(diào) sent 函數(shù)TCP_EVENT_SENT(pcb, (u16_t)acked16, err); // <--- 這里if (err == ERR_ABRT) {goto aborted;}recv_acked = 0;}if (recv_data != NULL) {// 收到有效數(shù)據(jù), 回調(diào) recv 函數(shù)TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if (err == ERR_ABRT) {goto aborted;}}if (recv_flags & TF_GOT_FIN) {// 收到 FIN 標(biāo)志,回調(diào) recv 函數(shù),遠(yuǎn)端關(guān)閉連接TCP_EVENT_CLOSED(pcb, err); if (err == ERR_ABRT) {goto aborted;}}/* Try to send something out. */tcp_output(pcb);}}} }poll 回調(diào)函數(shù)
在 TCP 控制塊中,函數(shù)指針 poll 指向用戶實(shí)現(xiàn)的函數(shù),協(xié)議棧周期性的調(diào)用此函數(shù),“周期“由用戶在注冊(cè)回調(diào)函數(shù)時(shí)指定,最小為 TCP_SLOW_INTERVAL 毫秒(默認(rèn) 500),用戶層可以使用這個(gè)回調(diào)函數(shù)做一些周期性處理。
函數(shù)指針 poll 的類型為 tcp_poll_fn ,該類型定義在 tcp.h 中:
協(xié)議棧通過(guò)宏 TCP_EVENT_POLL(pcb,ret) 調(diào)用 pcb->poll 指向的函數(shù)。宏 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)以關(guān)鍵字 TCP_EVENT_POLL 搜索源碼,可以搜索到 1 處使用:
TCP_EVENT_POLL(prev, err);這是在 tcp_slowtmr 函數(shù)中,當(dāng)達(dá)到設(shè)定的時(shí)間時(shí),調(diào)用 poll 回調(diào)函數(shù)。簡(jiǎn)化后的代碼為:
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);}}} }讀后有收獲,資助博主養(yǎng)娃 - 千金難買知識(shí),但可以買好多奶粉 (〃‘▽’〃)
總結(jié)
以上是生活随笔為你收集整理的lwIP 细节之三:TCP 回调函数是何时调用的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mybatis plus 查询排序_My
- 下一篇: oracle汉字排序