日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

lwIP 细节之三:TCP 回调函数是何时调用的

發(fā)布時(shí)間:2024/1/8 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 lwIP 细节之三:TCP 回调函数是何时调用的 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

使用 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ù) poll

errf 回調(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 中:

/** Function prototype for tcp error callback functions. Called when the pcb* receives a RST or is unexpectedly closed for any other reason.** @note The corresponding pcb is already freed when this callback is called!** @param arg Additional argument to pass to the callback function (@see tcp_arg())* @param err Error code to indicate why the pcb has been closed* ERR_ABRT: aborted through tcp_abort or by a TCP timer* ERR_RST: the connection was reset by the remote host*/ typedef void (*tcp_err_fn)(void *arg, err_t err);

從注釋得知,錯(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 處使用:

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 個(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 控制塊!
具體就是:

  • 先調(diào)用 tcp_kill_timewait 函數(shù),試圖找到 TIME_WAIT 狀態(tài)下生存時(shí)間最長(zhǎng)的連接,如果找到符合條件的控制塊 pcb ,則調(diào)用 tcp_abort(pcb) 函數(shù) “殺” 掉這個(gè)連接,這會(huì)發(fā)送 RST 標(biāo)志,以便通知遠(yuǎn)端釋放連接;
  • 如果第 1 步失敗了,則調(diào)用 tcp_kill_state 函數(shù),試圖找到 LAST_ACK 和 CLOSING 狀態(tài)下生存時(shí)間最長(zhǎng)的連接,如果找到符合條件的控制塊 pcb ,則調(diào)用 tcp_abandon(pcb, 0) 函數(shù) “殺” 掉這個(gè)連接,注意這個(gè)函數(shù)并不會(huì)發(fā)送 RST 標(biāo)志,處于這兩種狀態(tài)的連接都是等到對(duì)方發(fā)送的 ACK 就會(huì)結(jié)束連接,不會(huì)有數(shù)據(jù)丟失;
  • 如果第 2 步也失敗了,則調(diào)用 tcp_kill_prio(prio) 函數(shù),試圖找到小于指定優(yōu)先級(jí)(prio)的最低優(yōu)先級(jí)且生存時(shí)間最長(zhǎng)的有效(active)連接!如果找到符合條件的控制塊 pcb ,則調(diào)用 tcp_abort(pcb) 函數(shù) “殺” 掉這個(gè)連接,這會(huì)發(fā)送 RST 標(biāo)志。
  • 這里的第 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)化代碼為:

    void tcp_abort(struct tcp_pcb *pcb) {tcp_abandon(pcb, 1); }
    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 中:

    /** 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);

    協(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 中:

    /** Function prototype for tcp accept callback functions. Called when a new* connection can be accepted on a listening pcb.** @param arg Additional argument to pass to the callback function (@see tcp_arg())* @param newpcb The new connection pcb* @param err An error code if there has been an error accepting.* Only return ERR_ABRT if you have called tcp_abort from within the* callback function!*/ typedef err_t (*tcp_accept_fn)(void *arg, struct tcp_pcb *newpcb, err_t err);

    協(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)化后的代碼為:

    static void tcp_listen_input(struct tcp_pcb_listen *pcb) {// 通過(guò)一系列檢查 沒(méi)有錯(cuò)誤 npcb = tcp_alloc(pcb->prio); // 申請(qǐng)新的 TCP_PCB if (npcb == NULL) { // 內(nèi)存錯(cuò)誤處理LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err);return;}// 申請(qǐng)成功,初始化新申請(qǐng)的pcbnpcb->state = SYN_RCVD;// 發(fā)送 ACK|SYN 標(biāo)志return; }

    這里需要注意,申請(qǐng) TCP_PCB 失敗的處理方法,lwIP 2.1.x 版本與 lwIP 1.4.1 不同
    再看看 lwIP 1.4.1 的 tcp_listen_input 函數(shù)代碼(經(jīng)簡(jiǎn)化):

    static err_t tcp_listen_input(struct tcp_pcb_listen *pcb) {// 通過(guò)一系列檢查 沒(méi)有錯(cuò)誤 npcb = tcp_alloc(pcb->prio); // 申請(qǐng)新的 TCP_PCB if (npcb == NULL) { // 內(nèi)存錯(cuò)誤處理LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));return ERR_MEM;}// 申請(qǐng)成功,初始化新申請(qǐng)的pcb// 發(fā)送 ACK|SYN 標(biāo)志return ERR_OK; }

    可以看到, 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ě):

    /* 客戶端連接時(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";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; }

    而 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 中:

    /** Function prototype for tcp receive callback functions. Called when data has* been received.** @param arg Additional argument to pass to the callback function (@see tcp_arg())* @param tpcb The connection pcb which received data* @param p The received data (or NULL when the connection has been closed!)* @param err An error code if there has been an error receiving* Only return ERR_ABRT if you have called tcp_abort from within the* callback function!*/ typedef err_t (*tcp_recv_fn)(void *arg, struct tcp_pcb *tpcb,struct pbuf *p, err_t err);

    協(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ù)}} }

    從以上代碼中可以看出:

  • 回調(diào)函數(shù)有返回值,若發(fā)現(xiàn)異常,用戶層可以主動(dòng)調(diào)用 tcp_abort 函數(shù)終止連接,然后返回 ERR_ABRT 錯(cuò)誤碼,協(xié)議棧會(huì)完成后續(xù)的操作:
  • /* Notify application that data has been received. */ TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err); if (err == ERR_ABRT) {goto aborted; }
  • 如果正確的處理了數(shù)據(jù),回調(diào)函數(shù)必須返回 ERR_OK 錯(cuò)誤碼,否則協(xié)議棧會(huì)認(rèn)為用戶沒(méi)有接收這包數(shù)據(jù),就會(huì)對(duì)它進(jìn)行緩存:
  • /* 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")); }

    所以上層如果來(lái)不及處理數(shù)據(jù),可以讓協(xié)議棧暫存。這里暫存數(shù)據(jù)使用了指針 pcb->refused_data ,需要注意一下,因?yàn)榻酉聛?lái)會(huì)再次看到它。

  • 注意這里會(huì)調(diào)用 TCP 發(fā)送函數(shù):
  • /* Try to send something out. */ tcp_output(pcb);

    在 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)用

    void tcp_input(struct pbuf *p, struct netif *inp) {// 經(jīng)過(guò)一系列檢測(cè),沒(méi)有錯(cuò)誤/* 在本地找到有效的控制塊 pcb */if (pcb != NULL) {/* If there is data which was previously "refused" by upper layer */if (pcb->refused_data != NULL) {if ((tcp_process_refused_data(pcb) == ERR_ABRT) || // <--- 這里((pcb->refused_data != NULL) && (tcplen > 0))) {/* pcb has been aborted or refused data is still refused and the new segment contains data */if (pcb->rcv_ann_wnd == 0) {/* this is a zero-window probe, we respond to it with current RCV.NXTand drop the data segment */tcp_send_empty_ack(pcb);}goto aborted;}}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;}}/* Try to send something out. */tcp_output(pcb);}} }

    通過(guò)以上代碼可以知道:

  • 在處理接收數(shù)據(jù)之前,先檢查一下是否有上次暫存的數(shù)據(jù),如果有則調(diào)用 tcp_process_refused_data 函數(shù),將暫存數(shù)據(jù)上報(bào)給應(yīng)用層處理。
  • 無(wú)論上層有多少數(shù)據(jù)沒(méi)有處理,協(xié)議棧只暫存最后一次接收且上層沒(méi)有處理的數(shù)據(jù):
  • /* If the upper layer can't receive this data, store it */ if (err != ERR_OK) {pcb->refused_data = recv_data; }

    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)化后的代碼為:

    void tcp_fasttmr(void) {++tcp_timer_ctr;tcp_fasttmr_start:pcb = tcp_active_pcbs;while (pcb != NULL) {if (pcb->last_timer != tcp_timer_ctr) {next = pcb->next;/* If there is data which was previously "refused" by upper layer */if (pcb->refused_data != NULL) {tcp_active_pcbs_changed = 0;tcp_process_refused_data(pcb); // <--- 這里if (tcp_active_pcbs_changed) {/* application callback has changed the pcb list: restart the loop */goto tcp_fasttmr_start;}}pcb = next;} else {pcb = pcb->next;}} }

    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ù)框架為:

    static err_t app_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {if (p == NULL) {// 連接關(guān)閉前的處理,可選tcp_close(pcb);} else {if (err != ERR_OK) {// 目前還沒(méi)有使用 ERR_OK 之外的回調(diào)參數(shù),這里兼容以后的協(xié)議棧pbuf_free(p);return err;}// 更新窗口值,必須調(diào)用tcp_recved(pcb,p->tot_len);// 在這里處理接收到的數(shù)據(jù)// 釋放 pbuf,必須 pbuf_free(p);}return ERR_OK; }

    協(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 中:

    /** 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);

    通過(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 中:

    #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)

    以關(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 中:

    /** 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);

    協(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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    国产成人精品一区二区在线观看 | 一区二区视频免费在线观看 | av色综合网 | 色成人亚洲网 | 久久成年人视频 | 在线观看视频中文字幕 | 色综合久久久久综合 | 久草.com| 亚洲国产免费网站 | 日本天天色 | 亚洲影视九九影院在线观看 | 婷婷丁香社区 | 99视频国产在线 | 午夜美女福利直播 | 韩国精品在线 | 中文字幕在线观看免费高清电影 | 99国产在线 | 91成人免费看 | 久久a级片 | 亚洲第一av在线播放 | 日韩成人不卡 | av线上看 | 国产精品久久久久av福利动漫 | 亚洲精品在线视频网站 | 日韩av在线免费看 | 国产精品久久久久久久久蜜臀 | 久草视频精品 | 国产手机视频精品 | 久久精品综合 | 久久精品一区二 | wwwwww色 | 日韩电影中文字幕在线 | 国产精品成人一区二区三区吃奶 | 成人黄在线观看 | 干天天| 久久综合精品国产一区二区三区 | 久久久久久久久影院 | 国产精品自在线 | 免费在线观看成人小视频 | 狠狠的操| 日韩高清免费在线 | 免费看久久久 | 涩涩网站免费 | 免费视频黄 | 国产在线视频一区二区 | 久久久久久久久毛片精品 | 黄色成人影视 | 国产日韩精品一区二区在线观看播放 | a久久免费视频 | 黄色高清视频在线观看 | 在线视频电影 | 奇米影音四色 | 91精品办公室少妇高潮对白 | 亚洲最大激情中文字幕 | 亚洲视频精品在线 | 成人sm另类专区 | 亚洲精品91天天久久人人 | 欧美极品一区二区三区 | 久草色在线观看 | 午夜电影一区 | 久久久五月天 | 国产又粗又长又硬免费视频 | 国产喷水在线 | 国产精品2020 | 免费高清无人区完整版 | 国产精品黄 | 在线视频婷婷 | 久久久精华网 | 国产视频18 | 天干啦夜天干天干在线线 | 欧美另类调教 | 欧美一区二区三区在线播放 | 青青草在久久免费久久免费 | 天天插日日插 | 在线激情网 | 亚洲国产日韩精品 | 青青草视频精品 | 美女亚洲精品 | 在线观看国产高清视频 | 视频一区二区三区视频 | 色吧久久| 精品伊人久久久 | 成人小视频在线免费观看 | 久久精品欧美 | 97精品国产手机 | 成人免费在线视频观看 | 97香蕉久久国产在线观看 | 免费av小说 | 91探花国产综合在线精品 | 日韩av专区 | 9在线观看免费高清完整版在线观看明 | 成人中文字幕av | 狠狠狠狠狠狠操 | 五月天天色| 黄色一及电影 | 麻豆av电影 | 国产黄色美女 | 综合久久婷婷 | av丝袜天堂 | 精品日韩在线一区 | 午夜精品一区二区三区在线播放 | 精品久久久久久久久久久久 | 黄色软件在线观看免费 | 99久久精品视频免费 | 美女久久久久久 | 国产精品久久麻豆 | 日韩视频一二三区 | 欧美日韩不卡一区二区 | 欧美a在线免费观看 | 亚洲一二视频 | 日韩精品视频在线观看免费 | 亚洲精品2区 | 亚洲美女精品区人人人人 | 日韩欧美精品一区二区三区经典 | 亚洲国产福利视频 | 最近2019年日本中文免费字幕 | 欧美美女一级片 | 伊人中文字幕在线 | 天天操天天射天天添 | 最新av网址在线观看 | 国产在线观看a | 免费在线观看一区 | 久久99久久99精品 | 狠狠的日 | 99热这里只有精品国产首页 | www.久久久 | 国产亚洲精品中文字幕 | 日韩精品在线视频免费观看 | 欧美性色网站 | 国产精品久久99精品毛片三a | 日韩免费观看视频 | 97免费视频在线 | 超碰在线最新 | 日韩一片| 中文字幕二区在线观看 | 欧美成人按摩 | av免费观看网址 | 久久黄色片子 | 91成年人视频 | 91福利试看 | 香蕉影视在线观看 | 久久毛片高清国产 | 在线日本看片免费人成视久网 | 麻豆 91 在线 | 欧美在线观看视频一区二区 | 91精品国产综合久久福利 | 蜜臀久久99精品久久久无需会员 | 国产成人久久精品 | 久久精品一二三区 | 久久99久久精品国产 | 伊人亚洲综合网 | 日韩高清av| 日韩三区在线观看 | 亚洲人在线 | 国产亚洲成av片在线观看 | 在线观看视频91 | 日本久久精 | www.com久久久 | 国产一二三区在线观看 | 免费视频二区 | 免费黄在线观看 | 欧美一级日韩三级 | 91av在线免费视频 | 四川bbb搡bbb爽爽视频 | 在线影院中文字幕 | 亚洲精品在线观看中文字幕 | 国产精品永久在线观看 | 国产又粗又猛又爽又黄的视频先 | av福利在线播放 | 欧美日韩一区二区三区视频 | 欧美成年人在线视频 | 久久99国产一区二区三区 | 五月综合激情网 | 欧洲亚洲激情 | 高清av在线免费观看 | 精品福利在线观看 | 国产视频一区二区在线 | 五月激情av | 国产福利久久 | 成人国产精品免费观看 | 久久国产精品视频 | 999视频网 | av资源免费看 | 91精品在线免费观看 | 日韩理论影院 | 色资源网免费观看视频 | 国产亚洲精品久久久久久无几年桃 | 国产精品国产三级国产 | 久久免费视频在线 | 国产亚洲永久域名 | 日韩va欧美va亚洲va久久 | www日韩视频 | 欧美精品久久99 | 999精品视频 | 91爱看片 | 人人藻人人澡人人爽 | www.夜夜| 97成人免费 | 午夜精品久久久久久久99热影院 | 国产精品国产亚洲精品看不卡15 | 91精彩视频在线观看 | 91免费看片黄 | 国产成人61精品免费看片 | 国产精品久久久久婷婷二区次 | 在线91av| 日韩欧美精品一区 | 欧美精品999 | 91综合视频在线观看 | 日本在线成人 | 精品国产成人在线影院 | 国产一区二区三区久久久 | 成人免费视频播放 | 在线不卡的av | 天天干天天插伊人网 | 五月激情丁香婷婷 | 亚洲一级片免费观看 | 久久人人添人人爽添人人88v | 在线看片91 | 黄色大片免费播放 | 久久免费看a级毛毛片 | 国产日韩欧美在线观看视频 | 蜜桃av久久久亚洲精品 | 黄色小说免费在线观看 | 深夜福利视频一区二区 | 丝袜一区在线 | 五月婷影院 | 99精品国产高清在线观看 | 97av在线| 欧美一级网站 | 成人久久久久久久久久 | 2021国产在线 | 国产区网址| 日韩在线高清视频 | 精品久久久久久综合 | 国产一区二区在线影院 | 色婷婷久久 | 亚洲欧洲在线视频 | 成人av av在线| 96久久精品| 在线精品视频免费观看 | 中文字幕久久精品一区 | 国产成人三级一区二区在线观看一 | 激情综合网五月婷婷 | 九九视频网| 91黄站| 青草视频在线 | av免费试看 | 在线视频 国产 日韩 | 亚洲区二区 | 狠狠夜夜 | 在线观看岛国片 | 色免费在线 | 一区二区不卡 | 天天天天色综合 | 欧美日韩在线观看一区二区三区 | 深夜免费福利视频 | 亚洲精品美女久久久 | 久久久精品网站 | 一区 在线观看 | 91视频久久久久 | 国产福利在线不卡 | 免费视频网 | 在线观看www视频 | 精品国内 | 色播五月激情综合网 | 久久艹影院 | 色婷婷影视 | 久草网站在线 | 五月天综合婷婷 | 日韩精品免费一区二区在线观看 | 97精品一区二区三区 | 93久久精品日日躁夜夜躁欧美 | 69亚洲视频 | 国产精品美女久久久久久免费 | 91传媒91久久久 | 日韩精品免费在线视频 | 国产免费嫩草影院 | 欧美日韩二三区 | 日韩精品一区二区三区视频播放 | 91久久久久久久 | 国产九色在线播放九色 | 亚洲国产黄色片 | 丁香婷婷综合激情五月色 | 日韩视频免费在线观看 | 成年人在线免费看片 | 中文高清av | 国产在线欧美在线 | 四虎影视成人精品 | 91刺激视频 | 色婷av| 中文字幕久久网 | 男女啪啪免费网站 | 久久精品视频在线免费观看 | 深夜成人av| 欧美在线你懂的 | 美女网站久久 | 成人免费xyz网站 | 欧美日韩亚洲在线观看 | 久久久18 | 日本精品视频免费观看 | 国产精品久久久久aaaa九色 | 91桃色国产在线播放 | 国产一及片 | 亚洲一区二区精品视频 | 成人久久久精品国产乱码一区二区 | 三三级黄色片之日韩 | 欧美日韩免费在线视频 | 色视频网站在线观看一=区 a视频免费在线观看 | 婷婷深爱五月 | 国产精品美女免费视频 | www色com| 久久国语露脸国产精品电影 | 黄色视屏av | 婷婷久月| 国产精品免费久久久久影院仙踪林 | 久久成人国产精品入口 | 欧美国产日韩激情 | 在线免费观看麻豆 | 天天插天天干 | 97夜夜澡人人爽人人免费 | 国产精品美女免费视频 | 欧美激情在线看 | 久久久久久久久久久免费 | 伊人婷婷综合 | 精品久久99| 久久精品一二三 | 在线观看视频你懂的 | 在线亚洲天堂网 | 亚洲欧洲精品一区 | av大片网址| 精品一区久久 | 成av在线| 久久这里只有精品久久 | 人人看人人爱 | 国内精品中文字幕 | 成年人黄色免费网站 | 人人看人人草 | 免费97视频| 正在播放一区 | 91中文在线视频 | 96亚洲精品久久久蜜桃 | 国产精品久久一区二区三区, | 久草在线免费看视频 | 国产黄色成人av | 免费福利在线视频 | 色婷婷影视 | 中日韩三级视频 | 成年人视频在线 | 一区二区理论片 | 国产精品美女久久久久久 | 国内精品久久久久久久影视简单 | 69xxxx欧美 | 俺要去色综合狠狠 | 一本一道久久a久久综合蜜桃 | 岛国一区在线 | 国内精品久久久久影院日本资源 | 99视频在线免费播放 | 国产精品区二区三区日本 | 欧美日韩激情视频8区 | 免费看在线看www777 | 亚洲无线视频 | 欧美十八 | 欧美大jb| 在线观看中文字幕一区 | 日韩精品不卡在线观看 | 久久久久久高潮国产精品视 | 视频一区视频二区在线观看 | 亚洲欧美日韩精品久久奇米一区 | 久久综合久久88 | 91视频在线网址 | 久久人人干| 久久精品视频免费播放 | 欧美最猛性xxxxx免费 | 日本久草电影 | 日日操夜夜操狠狠操 | 国产精品免费久久久久 | 国产日韩精品欧美 | 中文电影网 | 99精品欧美一区二区三区黑人哦 | 久久一本综合 | 亚洲高清在线观看视频 | 成人av一二三区 | 欧美精品一区二区三区一线天视频 | 色资源网免费观看视频 | 久久精品中文 | 啪啪资源| 成人h动漫在线看 | 在线观看视频你懂的 | 在线观看www. | 日韩av影视在线观看 | 91社区国产高清 | 亚洲永久精品视频 | 青草视频在线 | 国产精品2020 | 久久久久久欧美二区电影网 | 国产精品女主播一区二区三区 | 亚洲精品乱码久久 | 久久精品视频免费播放 | 成人动漫一区二区 | 激情五月在线观看 | 日韩欧美在线观看一区二区三区 | 91精品爽啪蜜夜国产在线播放 | 日韩免费久久 | 午夜久久精品 | 91av在线免费| 伊人中文网 | 91在线视频免费 | 国产91免费在线 | 97超碰在线人人 | 日本中文字幕视频 | 碰天天操天天 | 亚洲成人黄 | 日韩高清免费电影 | 中文字幕中文字幕在线一区 | 国产成人av在线 | 黄a网站 | 精品国产人成亚洲区 | 亚洲少妇久久 | 日韩在线欧美在线 | av电影中文字幕 | 丁香婷婷激情网 | 亚洲精品美女视频 | 国产夫妻自拍av | 日韩视频中文字幕在线观看 | 激情五月看片 | 视频在线观看入口黄最新永久免费国产 | 亚洲成av人片一区二区梦乃 | 999国内精品永久免费视频 | 日韩免费视频在线观看 | 91精品人成在线观看 | 久久ww| 国产在线观看免费 | 日韩免费一级a毛片在线播放一级 | 成人在线免费看视频 | 久久免费视频观看 | 免费一级片在线 | 人人澡视频 | 天天综合操 | 亚洲一区美女视频在线观看免费 | 97色se| 国产一区二区播放 | 国产99免费视频 | 黄色成人av | 国产精品2020| 亚洲国内在线 | 中文字幕在线观看一区二区 | 国产精品综合久久久久 | 亚洲自拍偷拍色图 | 精品在线二区 | 国语对白少妇爽91 | 亚洲国产精品va在线看 | 天天干天天碰 | 97国产大学生情侣白嫩酒店 | 97超碰在线免费 | 在线观看黄色国产 | 婷婷四房综合激情五月 | 日本中文一级片 | 中文字幕第一页在线播放 | 天天干天天草天天爽 | 成人久久免费视频 | 精品国产乱码 | 国产xx视频 | 97中文字幕 | 在线а√天堂中文官网 | 成人少妇影院yyyy | 亚州欧美精品 | 久草在线观看 | 亚洲国产精品va在线看黑人动漫 | 色 免费观看 | 丝袜足交在线 | 精品自拍网 | 亚洲精品啊啊啊 | 久久毛片网 | 色婷婷视频 | 日韩欧美区 | 国产99久久久久久免费看 | 国色天香在线观看 | 在线看黄网站 | 99色视频 | 国产a高清| 草 免费视频 | 中文字幕在线播放av | 久久区二区 | 视频91在线 | 欧美黑人xxxx猛性大交 | 在线观看国产区 | 国产精品手机看片 | 亚洲欧美激情插 | 日本中文字幕免费观看 | 国产成人福利 | 97爱| 日本最新高清不卡中文字幕 | 国产精品不卡一区 | av超碰在线 | 国产一区在线免费 | 日韩精品aaa | 日韩激情网 | 美女精品 | 国产中文字幕视频在线 | 国产精品麻豆一区二区三区 | 婷婷久久亚洲 | 在线中文字幕网站 | 99国产成+人+综合+亚洲 欧美 | 97**国产露脸精品国产 | 美女久久久久久久久久久 | 美腿丝袜av| 最近免费中文字幕 | 亚洲精品视 | 亚洲欧美国产日韩在线观看 | 国产精品福利小视频 | 成年人视频在线免费 | 中文区中文字幕免费看 | 最近中文字幕大全中文字幕免费 | 亚洲综合在线一区二区三区 | 久久99精品一区二区三区三区 | 日本久久久久久久久久 | 六月丁香综合网 | 免费看的黄色的网站 | 人人澡av| 久久成人麻豆午夜电影 | 麻豆国产视频下载 | 2023亚洲精品国偷拍自产在线 | www99精品 | 99久久日韩精品免费热麻豆美女 | 久久精国产 | 九九爱免费视频在线观看 | 成人 亚洲 欧美 | 黄色aaaaa| 97av在线视频免费播放 | 色94色欧美| 久久午夜影视 | 草久久久久久 | h视频日本 | 亚洲精品视频在线免费 | 天堂av最新网址 | 97看片网| 日韩高清dvd | 婷婷av色综合 | 亚洲精品视频二区 | 2022中文字幕在线观看 | 亚洲国产wwwccc36天堂 | 免费高清在线一区 | 国产精品久久久久永久免费观看 | av在线免费在线 | 天天插天天爱 | 日韩欧美在线中文字幕 | 999久久国精品免费观看网站 | 国产精品va最新国产精品视频 | 成人不用播放器 | 99久久精| 亚洲黄色小说网址 | 国产免费一区二区三区最新 | 日韩伦理一区二区三区av在线 | 亚洲一区 av| 五月天电影免费在线观看一区 | 国产精品国产三级国产aⅴ入口 | 国产精品自产拍在线观看中文 | bbbbb女女女女女bbbbb国产 | 天天插视频 | 国产91粉嫩白浆在线观看 | 久久婷婷国产色一区二区三区 | 国内视频1区 | 在线免费观看视频一区 | 综合色在线观看 | 亚洲色综合 | 美女黄视频免费看 | 久久理论视频 | 国产精品久久久久久久久久免费 | 婷婷丁香九月 | 黄色影院在线免费观看 | 日韩欧美国产精品 | a在线免费观看视频 | 亚洲人久久久 | 99精品国产视频 | 日韩激情三级 | 精品一区二区久久久久久久网站 | 亚洲综合在线观看视频 | 午夜黄色影院 | 久草视频资源 | 亚洲天堂香蕉 | 久久久999 | 婷婷精品视频 | 免费视频在线观看网站 | 亚洲精品综合欧美二区变态 | 久久婷婷一区二区三区 | 日韩在线免费播放 | 一区二区精品在线 | 麻豆国产电影 | 国产精品初高中精品久久 | 欧美精品亚洲精品 | 久久五月情影视 | 久久久久久久99 | 91av视频在线免费观看 | 黄色av免费看 | av在线色| 国产精品伦一区二区三区视频 | 在线观看亚洲专区 | 国产视频中文字幕 | 四虎影视4hu4虎成人 | 欧美二区三区91 | 日韩日韩日韩日韩 | 国产一级视屏 | 久久九九影院 | 99视频在线免费观看 | 国产中文自拍 | 国产成人久久久77777 | 9999精品免费视频 | 日韩1页 | 久草在线视频免费资源观看 | 伊人久操 | 日韩经典一区二区三区 | 97视频精品 | 国产夫妻性生活自拍 | 国产成人精品久久亚洲高清不卡 | 午夜精品麻豆 | 免费观看mv大片高清 | 四虎影院在线观看av | 国产精品剧情在线亚洲 | 五月天六月丁香 | 夜夜骑首页 | 综合激情网 | 永久免费av在线播放 | 中文字幕亚洲欧美日韩2019 | 国产视频黄 | 亚洲免费高清视频 | 一区二区国产精品 | 在线视频久 | 国产小视频国产精品 | 人人超在线公开视频 | 国产无套精品久久久久久 | 欧美精品一区二区在线播放 | 最近最新最好看中文视频 | 国产日韩中文在线 | 日韩欧美视频一区二区 | 欧洲精品久久久久毛片完整版 | 久久免费在线视频 | 一区二精品 | 九九色在线观看 | 精品国内自产拍在线观看视频 | 免费在线国产 | 在线观看中文 | 97在线免费 | 草久视频在线 | 久久99精品久久久久久久久久久久 | 中文字幕亚洲高清 | 日韩黄在线观看 | 国产日韩欧美在线播放 | 17婷婷久久www | 欧美日韩一区二区免费在线观看 | 99视| 亚洲黄色片在线 | 区一区二区三区中文字幕 | www.亚洲视频 | 日韩欧美一区二区三区免费观看 | 黄色资源在线 | 国产精品一区二区在线播放 | 91视频91自拍 | a√天堂资源 | 国产 一区二区三区 在线 | www.色综合.com | 国产日韩精品欧美 | 欧美在线一级片 | 中文在线中文a | 国产精品视频在线看 | 久久亚洲福利视频 | 国产成人精品久久久 | 国产精品黑丝在线观看 | 伊人天天操| 九九视频免费观看视频精品 | 国产精品久久久久永久免费看 | 亚洲国产三级在线观看 | 久久一级片| 日韩欧美视频免费在线观看 | 久久免费黄色网址 | 99爱国产精品 | 久久99精品久久只有精品 | 少妇视频一区 | 婷婷激情久久 | 成人午夜精品福利免费 | 久久99中文字幕 | 亚洲日韩欧美视频 | 日韩免费专区 | 黄色大片中国 | 91传媒激情理伦片 | 午夜一级免费电影 | 色综合久久88色综合天天免费 | 成人小视频在线 | 国产一级在线 | 久久精品久久久久久久 | 99久久精品免费看 | 日韩xxxx视频 | 日韩精品视频一二三 | 亚洲精选视频免费看 | 黄网站污 | 欧美国产精品久久久久久免费 | 国产美女无遮挡永久免费 | 一级电影免费在线观看 | 久久亚洲综合国产精品99麻豆的功能介绍 | 99视频99 | 日韩网站免费观看 | 亚洲视频分类 | 婷婷丁香九月 | 一本—道久久a久久精品蜜桃 | 国产精品久久毛片 | 最近最新中文字幕 | 91九色porny在线 | 在线观看黄网站 | 激情网第四色 | 欧美日韩在线观看一区 | 九九九九色 | 在线视频一区观看 | 成人永久在线 | 我爱av激情网 | 免费色视频在线 | 最近免费观看的电影完整版 | 久久亚洲欧美日韩精品专区 | 中文字幕中文字幕 | 香蕉视频在线观看免费 | 欧美日韩国产一区二区三区 | 人人爽久久久噜噜噜电影 | 久久综合九色综合久久久精品综合 | 国产精品视频区 | 久久久久亚洲天堂 | 亚洲欧美日韩国产精品一区午夜 | 少妇av网| 久草精品在线播放 | 国产亚洲精品久久久久久移动网络 | 午夜少妇av| 国产精品毛片一区视频播不卡 | 亚洲人天堂 | 欧美精品一区在线发布 | 欧美国产视频在线 | 黄色福利视频网站 | 久久99久久精品国产 | 国产成人av综合色 | 中文字幕在线一区二区三区 | 精品一区二区免费视频 | 国产香蕉视频 | 国产精品成人自拍 | 国产精品欧美久久久久天天影视 | 一区二区三区免费在线 | 欧美精品在线免费 | 午夜视频免费播放 | 欧美精品一区在线 | 狠狠色丁香婷婷 | 99在线播放 | 国产精品久久久久久久久久东京 | 国产精品毛片 | 日韩资源在线观看 | 亚洲精品久久久久久久不卡四虎 | 人人干网| 丁香五月缴情综合网 | 中文字幕在线国产精品 | 91麻豆精品国产91久久久久久 | 天天色综合久久 | 99视频一区| 夜夜操天天 | 射久久久 | 99国产免费网址 | 亚洲精品免费观看 | 中文字幕美女免费在线 | 日韩久久久久 | 91成人观看 | 成年人视频在线免费播放 | 久久艹久久| 婷婷色网站 | 特级西西444www大精品视频免费看 | mm1313亚洲精品国产 | 国产精品 日韩精品 | 又黄又刺激又爽的视频 | 91精品导航| 久久久久久久久久福利 | 欧美一级片 | 国产又粗又猛又色又黄网站 | 日韩大片在线免费观看 | 91精品久久久久久久久 | 日韩在线国产 | 亚洲国产精品激情在线观看 | 中文字幕亚洲在线观看 | 国产精品资源 | 亚洲国产午夜视频 | 亚洲婷婷综合色高清在线 | 色综合天天综合 | 国内精品久久久久影院男同志 | 夜夜看av | 日日夜夜操av | 久久一区二区三区国产精品 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 夜夜夜夜操 | 中文字幕视频在线播放 | 久草在线手机视频 | 91av免费看 | 欧美十八 | 日韩免费视频 | 日本不卡一区二区三区在线观看 | 成人一区在线观看 | 国产高清亚洲 | 中文字幕在线一区观看 | 国产永久免费高清在线观看视频 | 国产精品一区二区三区四区在线观看 | 国产色道| 免费久久99精品国产婷婷六月 | av怡红院 | www.99av | 国产在线2020| 国产91精品久久久久久 | 国产亚洲一区二区在线观看 | 人人爽人人爽av | 国产v欧美 | 我要色综合天天 | 久久久久夜色 | 狠狠色噜噜狠狠狠狠 | 亚洲,播放 | 999色视频 | 亚州精品成人 | 国产精品久久久久久久久久不蜜月 | 亚洲综合黄色 | 亚洲精品在线国产 | 日本三级国产 | 97在线公开视频 | www婷婷| 91在线日本| 国产麻豆精品久久 | 久色 网 | 天天操天天爽天天干 | 波多野结衣亚洲一区二区 | 久久超| 国产群p| 色先锋资源网 | 亚洲欧美综合 | 狠狠色噜噜狠狠 | 国产一区免费观看 | a级片网站 | 8x成人免费视频 | 国产精品孕妇 | 国产xxxxx在线观看 | 伊人五月天.com | 一级特黄av| 欧美在线观看小视频 | 一区久久久 | 亚洲一区二区天堂 | 可以免费观看的av片 | 日韩一区二区三区不卡 | 热久久精品在线 | 久久久久久国产精品亚洲78 | 久久tv| 美女黄频免费 | 国产 日韩 中文字幕 | 国内久久看 | 国产视频在线观看一区二区 | 天天性天天草 | 色综合天天狠狠 | 涩涩网站在线 | 天天干天天操天天 | 中文字幕人成乱码在线观看 | 国产一级片在线播放 | 91久久久久久国产精品 | 国产视频 亚洲精品 | 天天爽综合网 | 午夜国产在线观看 | 麻豆91网站 | 国产精品人人做人人爽人人添 | 成人啊 v| 二区中文字幕 | 日韩精品专区在线影院重磅 | 日韩欧美一区二区不卡 | 国产一级片在线播放 | 色88久久| 国产九九热视频 | 丰满少妇在线观看资源站 | 色wwwww| 在线观看日韩视频 | 久草电影免费在线观看 | 激情久久久久久久久久久久久久久久 | 最新av网址在线 | 天天做天天干 | 新版资源中文在线观看 | 国产中的精品av小宝探花 | 黄色大片av | 免费影视大全推荐 | 精品国产一区二区三区久久久蜜月 | 人人澡人人模 | 在线www色 | 欧美最猛性xxxxx亚洲精品 | 97色噜噜| 国产成人精品一区在线 | 久久在线免费视频 | 中文字幕2021 | 欧美 日韩 国产 中文字幕 | 亚洲资源在线网 | 国产精品黄网站在线观看 | 天天干天天操人体 | 亚洲最大色 | 人人爽人人爽人人片 | 亚洲国产视频网站 | 欧美日韩国产二区三区 | www日日| 91网站在线视频 | 日日夜夜网 | 91黄视频在线 | 色欧美综合 | 视频精品一区二区三区 | 99爱在线观看 | h网站免费在线观看 | 久久伦理电影网 | av中文在线影视 | 在线亚洲欧美日韩 | 久久久久电影网站 | 色亚洲网| 亚洲综合精品视频 | 色噜噜日韩精品一区二区三区视频 | 久久黄色片子 | 99精品在线视频观看 | 久久全国免费视频 | 亚洲日本va在线观看 | 免费高清影视 | 色99之美女主播在线视频 | 中文字幕 国产视频 | 国产精品入口传媒 | 久久久久久久久久久网 | 国产精品乱码一区二区视频 | 中文网丁香综合网 | 久久亚洲婷婷 | 午夜视频在线观看网站 | 成人午夜电影在线 | 亚洲精品一区二区18漫画 | 操操日日| 成人午夜网 | av成人亚洲 | 最近日本字幕mv免费观看在线 | av导航福利| 毛片基地黄久久久久久天堂 | 91视频在线免费看 | 日日综合 | 一区二区三区中文字幕在线 | 日韩理论电影在线 | 91精品一区二区三区蜜桃 | 亚洲免费av在线 | 在线视频婷婷 | 日韩欧美在线观看一区二区三区 | 亚洲一级特黄 | 午夜美女福利 | 亚洲视频精品在线 | 天天综合网久久综合网 | 91麻豆精品国产91久久久久久 | 天天操天天摸天天爽 | 国产精品视频在线观看 | 久久久久欧美精品 | 天天干视频在线 | 国产精品18久久久久久首页狼 | 精品国产区| 欧美大香线蕉线伊人久久 | 亚洲经典精品 | 亚洲精品视频在线看 | 又紧又大又爽精品一区二区 | 久久视频在线视频 | 国产在线永久 | 伊人婷婷色 | 色噜噜日韩精品一区二区三区视频 | 在线看91| 精品免费国产一区二区三区四区 | 91毛片视频| 91精品秘密在线观看 | 亚洲综合欧美日韩狠狠色 | 国产一级片网站 | 五月婷婷一区 | 免费91麻豆精品国产自产在线观看 | 日韩大片在线观看 | 国产精品 9999| 精品二区久久 | 天天精品视频 | www.99热精品 | 日本韩国精品一区二区在线观看 | 国产精品九色 | 国际精品久久 | 亚洲精品一区中文字幕乱码 | 国产成人精品亚洲a | 狠狠狠色丁香婷婷综合激情 | 国内精品在线看 | 1024在线看片 | 日韩av影片在线观看 | 欧美久久久久久久久久久久 | 欧美一级日韩免费不卡 | 五月开心网 | 国产精品久久久久久久久免费 | 久久高清av| 久草热视频| 又色又爽又黄高潮的免费视频 | 最新午夜| 午夜精品一区二区三区在线 | 免费观看日韩 | 久久久一本精品99久久精品 | 四虎影视www | 蜜臀av性久久久久蜜臀aⅴ四虎 | 精品国产理论 | 国产91大片 | 黄色小网站在线 | 人人干免费 |