前言
TCP源碼篇,當(dāng)前只分析TCP層的源碼實現(xiàn),按功能分塊分析,接口為RAW接口。
NETCONN接口和SOCKET接口會獨立一篇文章進行分析。
本文基于讀者已學(xué)習(xí)了TCP協(xié)議原理篇的基礎(chǔ)上進行源碼分析,不再在此篇文章中過多解析TCP相關(guān)概念。
?文章來源地址http://www.zghlxwxcb.cn/news/detail-462397.html
建議讀者對著LWIP庫源碼進行閱讀。對于初學(xué)者,可有點難度的,但是對于喜歡讀源碼的同學(xué)來說,會充實TCP原理。
?上一年就寫好了,一直沒時間整理出來,現(xiàn)在不整理了,直接放出來。
鏈接:https://www.cnblogs.com/lizhuming/p/17438682.html
TCP首部數(shù)據(jù)結(jié)構(gòu)
參考文件:./src/include/lwip/prot/tcp.h
TCP首部的數(shù)據(jù)結(jié)構(gòu)及字段操作都在這個文件中。
如:TCP首部數(shù)據(jù)結(jié)構(gòu)struct tcp_hdr
:
#define PACK_STRUCT_FIELD(x) x
struct tcp_hdr {
PACK_STRUCT_FIELD(u16_t src);
PACK_STRUCT_FIELD(u16_t dest);
PACK_STRUCT_FIELD(u32_t seqno);
PACK_STRUCT_FIELD(u32_t ackno);
PACK_STRUCT_FIELD(u16_t _hdrlen_rsvd_flags);
PACK_STRUCT_FIELD(u16_t wnd);
PACK_STRUCT_FIELD(u16_t chksum);
PACK_STRUCT_FIELD(u16_t urgp);
} PACK_STRUCT_STRUCT;
?文章來源:http://www.zghlxwxcb.cn/news/detail-462397.html
TCP控制塊
TCP控制塊(TCP PCB)這個是每個TCP連接的中央,非常重要,保存了TCP相關(guān)的重要數(shù)據(jù),所以先了解下TCP控制塊的各個字段。
對于初學(xué)者,可以先略過一眼TCP控制塊的各個字段,在分析具體操作源碼時,遇到不懂的變量可以回TCP控制塊查找。
/** the TCP protocol control block */
struct tcp_pcb {
/** common PCB members */
IP_PCB;
/** protocol specific PCB members */
TCP_PCB_COMMON(struct tcp_pcb);
/* ports are in host byte order */
u16_t remote_port;
tcpflags_t flags;
#define TF_ACK_DELAY 0x01U /* Delayed ACK. */
#define TF_ACK_NOW 0x02U /* Immediate ACK. */
#define TF_INFR 0x04U /* In fast recovery. */
#define TF_CLOSEPEND 0x08U /* If this is set, tcp_close failed to enqueue the FIN (retried in tcp_tmr) */
#define TF_RXCLOSED 0x10U /* rx closed by tcp_shutdown */
#define TF_FIN 0x20U /* Connection was closed locally (FIN segment enqueued). */
#define TF_NODELAY 0x40U /* Disable Nagle algorithm */
#define TF_NAGLEMEMERR 0x80U /* nagle enabled, memerr, try to output to prevent delayed ACK to happen */
#if LWIP_WND_SCALE
#define TF_WND_SCALE 0x0100U /* Window Scale option enabled */
#endif
#if TCP_LISTEN_BACKLOG
/* 接入的TCP客戶端握手成功,等待被accept() */
#define TF_BACKLOGPEND 0x0200U
#endif
#if LWIP_TCP_TIMESTAMPS
#define TF_TIMESTAMP 0x0400U /* Timestamp option enabled */
#endif
#define TF_RTO 0x0800U /* RTO計時器已觸發(fā),unacked隊列數(shù)據(jù)已遷回unsent隊列,并正在重傳 */
#if LWIP_TCP_SACK_OUT
#define TF_SACK 0x1000U /* Selective ACKs enabled */
#endif
/* Timers */
/* 空閑poll周期回調(diào)相關(guān):polltmr會周期性增加,當(dāng)其值超過pollinterval時,poll函數(shù)會被調(diào)用。 */
u8_t polltmr, pollinterval;
/* 控制塊被最后一次處理的時間 */
u8_t last_timer;
/* 保存這控制塊的TCP節(jié)拍起始值。用于當(dāng)前PCB的時基初始值參考 */
/* 活動計時器,收到合法報文時自動更新。 */
u32_t tmr;
/* receiver variables */
u32_t rcv_nxt; /* 期待收到的下一個seq號。一般發(fā)送報文段時,ACK值就是該值 */
tcpwnd_size_t rcv_wnd; /* 接收窗口實時大?。簭倪h端收到數(shù)據(jù),該值減?。粦?yīng)用層讀走數(shù)據(jù),該值增加。 */
tcpwnd_size_t rcv_ann_wnd; /* 窗口通告值大?。杭词歉嬖V發(fā)送方,我們這邊的接口窗口的大小 */
u32_t rcv_ann_right_edge; /* 窗口通告值右邊界 */
#if LWIP_TCP_SACK_OUT
/* SACK ranges to include in ACK packets (entry is invalid if left==right) */
struct tcp_sack_range rcv_sacks[LWIP_TCP_MAX_SACK_NUM]; /* SACK左右邊界,TCP協(xié)議最多支持4對 */
#define LWIP_TCP_SACK_VALID(pcb, idx) ((pcb)->rcv_sacks[idx].left != (pcb)->rcv_sacks[idx].right)
#endif /* LWIP_TCP_SACK_OUT */
s16_t rtime; /* 超時重傳計時器值,當(dāng)該值大于RTO值時,重傳報文 */
u16_t mss; /* 遠端的MSS */
/* RTT (round trip time) 估算 */
u32_t rttest; /* RTT測量,發(fā)送時的時間戳。精度500ms */
u32_t rtseq; /* 開始計算RTT時對應(yīng)的seq號 */
/* RTT估計出的平均值和時間差。
注意:sa為算法中8倍的均值;sv為4倍的方差。再去分析LWIP實現(xiàn)RTO的算法。 */
s16_t sa, sv; /* @see "Congestion Avoidance and Control" by Van Jacobson and Karels */
s16_t rto; /* 重傳超時時間。節(jié)拍宏:TCP_SLOW_INTERVAL。初始超時時間宏:LWIP_TCP_RTO_TIME *//* retransmission time-out (in ticks of TCP_SLOW_INTERVAL) */
u8_t nrtx; /* 重發(fā)次數(shù) */
/* 快重傳和快恢復(fù)相關(guān):參考卷一中的快速重傳和快速恢復(fù)章節(jié):21.7 */
u8_t dupacks; /* 收到最大重復(fù)ACK的次數(shù):一般收1-2次認(rèn)為是重排序引起的。收到3次后,可以確認(rèn)為失序,需要立即重傳。然后執(zhí)行擁塞避免算法中的快恢復(fù)。 */
u32_t lastack; /* 接收到的最大有序ACK號 */
/* congestion avoidance/control variables */
tcpwnd_size_t cwnd; /* 擁塞窗口大小 */
tcpwnd_size_t ssthresh; /* 擁塞避免算法啟動閾值。也叫慢啟動上門限值。 */
/* rto重傳的那些報文段的下一個seq號。用于解除rto狀態(tài)。 */
u32_t rto_end;
u32_t snd_nxt; /* 下一個需要發(fā)送的seq號。一般也是收到最新最大的ACK號。 */ /* next new seqno to be sent */
u32_t snd_wl1, snd_wl2; /* 上次發(fā)送窗口更新時,收到的seq號和ack號。在tcp_receive()用于更新發(fā)送窗口。 */ /* Sequence and acknowledgement numbers of last window update. */
u32_t snd_lbb; /* 下一個被緩沖的應(yīng)用程序數(shù)據(jù)的seq號 */ /* Sequence number of next byte to be buffered. */
tcpwnd_size_t snd_wnd; /* 發(fā)送窗口的大?。簩崟r的。發(fā)出數(shù)據(jù),該值減少;收到ACK,該值增加。 */ /* sender window */
tcpwnd_size_t snd_wnd_max; /* 發(fā)送窗口最大值:就是遠端的窗口通告值大小。 */ /* the maximum sender window announced by the remote host */
tcpwnd_size_t snd_buf; /* 發(fā)送緩沖區(qū)剩余空間 */ /* Available buffer space for sending (in bytes). */
#define TCP_SNDQUEUELEN_OVERFLOW (0xffffU-3)
u16_t snd_queuelen; /* 發(fā)送緩沖區(qū)中現(xiàn)有的pbuf個數(shù) */ /* Number of pbufs currently in the send buffer. */
#if TCP_OVERSIZE
/* 在未發(fā)送的TCP數(shù)據(jù)中,最后一個pbuf剩余的未使用的空間size */
u16_t unsent_oversize;
#endif /* TCP_OVERSIZE */
tcpwnd_size_t bytes_acked; /* 累計ACK新數(shù)據(jù)的量。擁塞避免時,用于判斷cwnd是否需要+1MSS。 */
/* 幾條TCP報文段緩存隊列指針 */
struct tcp_seg *unsent; /* 未發(fā)送的報文段隊列 */
struct tcp_seg *unacked; /* 已發(fā)送,但是未收到ACK的報文段隊列 */
#if TCP_QUEUE_OOSEQ
struct tcp_seg *ooseq; /* 接收到的亂序報文段隊列 */
#endif /* TCP_QUEUE_OOSEQ */
struct pbuf *refused_data; /* 接收到,但未被應(yīng)用層取走的報文段隊列 */
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
/* 當(dāng)前連接屬于哪個服務(wù)器 */
struct tcp_pcb_listen* listener;
#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
#if LWIP_CALLBACK_API
/* 幾個回調(diào)函數(shù)。由用戶注冊。 */
/* 數(shù)據(jù)發(fā)送成功后被回調(diào) */
tcp_sent_fn sent;
/* 收到有序數(shù)據(jù)后被回調(diào) */
tcp_recv_fn recv;
/* 建立連接后被回調(diào) */
tcp_connected_fn connected;
/* 該函數(shù)被內(nèi)核周期性回調(diào)。參考polltmr */
tcp_poll_fn poll;
/* 發(fā)生錯誤時被回調(diào) */
tcp_err_fn errf;
#endif /* LWIP_CALLBACK_API */
#if LWIP_TCP_TIMESTAMPS /* TSOPT選項:用于時間戳和防止序列號回繞。 */
u32_t ts_lastacksent; /* 期待收到下一個回顯時間戳對應(yīng)的seq號 */
u32_t ts_recent; /* 收到對端的時間戳 */
#endif /* LWIP_TCP_TIMESTAMPS */
/* keepalive計時器的上限值 */
u32_t keep_idle;
#if LWIP_TCP_KEEPALIVE
/* keepalive探測間隔 */
u32_t keep_intvl;
/* keepalive探測的上限次數(shù) */
u32_t keep_cnt;
#endif /* LWIP_TCP_KEEPALIVE */
/* 堅持定時器:用于解決遠端接收窗口為0時,定時詢問使用 */
u8_t persist_cnt; /* 堅持定時器節(jié)拍計數(shù)值 */
u8_t persist_backoff; /* 堅持定時器探查報文時間間隔列表索引 */
u8_t persist_probe; /* 堅持定時器窗口0時發(fā)出的探查報文次數(shù) */
/* KEEPALIVE counter */
/* ?;疃〞r器 */
/* 保活計數(shù)值 */
u8_t keep_cnt_sent;
#if LWIP_WND_SCALE /* WSOPT選項字段。用于TCP窗口擴展。 */
u8_t snd_scale; /* 發(fā)送窗口偏移bit */
u8_t rcv_scale; /* 接收窗口偏移bit */
#endif
};
?
報文段數(shù)據(jù)結(jié)構(gòu)
TCP是基于字節(jié)流的傳輸層通信協(xié)議。
每次收發(fā)都是報文段形式,所以需要相關(guān)數(shù)據(jù)結(jié)構(gòu)來管理收發(fā)的報文段。
在TCP控制塊中有三個緩沖隊列,都已報文段形式保存:
-
struct tcp_seg *unsent
:未發(fā)送隊列。即是等待發(fā)送的報文段隊列。 -
struct tcp_seg *unacked
:空中報文隊列。即是已經(jīng)發(fā)送,但是還沒收到ACK的報文段隊列。 -
struct tcp_seg *ooseq
:亂序報文隊列。即是收到的報文是窗口內(nèi),但是不是當(dāng)前期待收到的下一個SEQ的報文段。先用改隊列存起來,等收到前面空缺的報文后就可以直接接上這些報文段了。
?
tcp_seg
?數(shù)據(jù)結(jié)構(gòu)中維護TCP首部指針struct tcp_hdr *tcphdr;
?是很有必要的,因為tcp_seg
?在處理過程中,會頻繁移動pbuf->payload
?指針,所以需要一個專門的TCP首部指針來維護。
?
struct tcp_seg
:
/* This structure represents a TCP segment on the unsent, unacked and ooseq queues */
struct tcp_seg {
struct tcp_seg *next; /* 鏈表節(jié)點 */
struct pbuf *p; /* TCP報文:TCP首部 + TCP數(shù)據(jù) */
u16_t len; /* 報文段的純TCP數(shù)據(jù)長度(不統(tǒng)計SYN和FIN) */
#if TCP_OVERSIZE_DBGCHECK
u16_t oversize_left; /* 當(dāng)前報文段中最后一個pbuf的可用剩余空間 */
#endif /* TCP_OVERSIZE_DBGCHECK */
#if TCP_CHECKSUM_ON_COPY
u16_t chksum;
u8_t chksum_swapped;
#endif /* TCP_CHECKSUM_ON_COPY */
u8_t flags;
#define TF_SEG_OPTS_MSS (u8_t)0x01U /* Include MSS option (only used in SYN segments) */
#define TF_SEG_OPTS_TS (u8_t)0x02U /* Include timestamp option. */
#define TF_SEG_DATA_CHECKSUMMED (u8_t)0x04U /* ALL data (not the header) is
checksummed into 'chksum' */
#define TF_SEG_OPTS_WND_SCALE (u8_t)0x08U /* Include WND SCALE option (only used in SYN segments) */
#define TF_SEG_OPTS_SACK_PERM (u8_t)0x10U /* Include SACK Permitted option (only used in SYN segments) */
struct tcp_hdr *tcphdr; /* the TCP header */
};
?
?
重要全局?jǐn)?shù)據(jù)
?
TCP控制塊鏈表
參考文件:./src/core/tcp.c
TCP控制塊鏈表是記錄每個TCP連接,根據(jù)TCP狀態(tài)而寄存到不同的鏈表中:
/* 處于已綁定狀態(tài)的TCP PCB */
struct tcp_pcb *tcp_bound_pcbs;
/* 處于監(jiān)聽狀態(tài)的TCP PCB */
union tcp_listen_pcbs_t tcp_listen_pcbs;
/* 處于穩(wěn)定狀態(tài)的TCP PCB */
struct tcp_pcb *tcp_active_pcbs;
/* 處于TIME_WAIT狀態(tài)的TCP PCB */
struct tcp_pcb *tcp_tw_pcbs;
把這些鏈表都統(tǒng)一管理起來:
/** An array with all (non-temporary) PCB lists, mainly used for smaller code size */
struct tcp_pcb **const tcp_pcb_lists[] = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs,
&tcp_active_pcbs, &tcp_tw_pcbs
};
?
監(jiān)聽?wèi)B(tài)的TCP PCB數(shù)據(jù)結(jié)構(gòu):
因為處于監(jiān)聽?wèi)B(tài)的TCP PCB沒有實際的TCP連接,TCP PCB數(shù)據(jù)結(jié)構(gòu)中的大量數(shù)據(jù)都用不到,所以處于監(jiān)聽?wèi)B(tài)的TCP PCB,使用另一種數(shù)據(jù)結(jié)構(gòu)來管理,降低內(nèi)存使用。
監(jiān)聽鏈表相關(guān)的數(shù)據(jù)結(jié)構(gòu):
/* 注意:是聯(lián)合體 */
union tcp_listen_pcbs_t {
struct tcp_pcb_listen *listen_pcbs;
struct tcp_pcb *pcbs;
};
/* 因為監(jiān)聽?wèi)B(tài)的連接沒有大量復(fù)雜邏輯的數(shù)據(jù)交互需求,所以監(jiān)聽?wèi)B(tài)的TCP PCB比較簡單 */
struct tcp_pcb_listen {
/** Common members of all PCB types */
IP_PCB;
/** Protocol specific PCB members */
TCP_PCB_COMMON(struct tcp_pcb_listen);
#if LWIP_CALLBACK_API
/* 偵聽到有連接接入時被調(diào)用的函數(shù) */
tcp_accept_fn accept;
#endif /* LWIP_CALLBACK_API */
#if TCP_LISTEN_BACKLOG
u8_t backlog; /* 等待accept()連接的上限值 */
u8_t accepts_pending; /* 握手成功,準(zhǔn)備準(zhǔn)備好了,但是還沒有accept()的連接的數(shù)量 */
#endif /* TCP_LISTEN_BACKLOG */
};
?
TCP單幀入站相關(guān)數(shù)據(jù)
LWIP是一個內(nèi)核單線程的TCPIP協(xié)議棧,所以收到TCP包后,LWIP內(nèi)核就會單獨處理該TCP包,不會出現(xiàn)同一個LWIP內(nèi)核并發(fā)處理多個TCP包。
所以可以為TCP包創(chuàng)建一些全局值,減少函數(shù)間的參數(shù)傳遞。
/* 這些全局變量有tcp_input()收到TCP報文段后設(shè)置的,表示當(dāng)前接收到,正在處理的TCP報文段信息 */
static struct tcp_seg inseg; /* TCP報文段數(shù)據(jù)結(jié)構(gòu) */
static struct tcp_hdr *tcphdr; /* TCP首部 */
static u16_t tcphdr_optlen; /* 選項字段長度 */
static u16_t tcphdr_opt1len; /* 選項字段在第一個pbuf中的長度 */
static u8_t *tcphdr_opt2; /* 在下一個pbuf中的選項字段指針 */
static u16_t tcp_optidx; /* 選項字段索引 */
static u32_t seqno, ackno; /* TCP的seq號和ack號 */
static tcpwnd_size_t recv_acked; /* 本次接收到的報文段中能確認(rèn)pcb->unacked報文的長度(遇到SYN|FIN會--,所以最終是TCP數(shù)據(jù)長度) */
static u16_t tcplen; /* 報文段的數(shù)據(jù)區(qū)長度。注意:SYN或FIN也占用seq號,該值+1 */
static u8_t flags; /* TCP首部各個標(biāo)志字段 */
static u8_t recv_flags; /* 記錄tcp_process()對報文段的處理結(jié)果 */
static struct pbuf *recv_data; /* 單次提交到應(yīng)用層的數(shù)據(jù)緩沖區(qū)。本次input_receive()處理后,把需要遞交到應(yīng)用層的數(shù)據(jù),緩存到這里。 */
/* 當(dāng)前進行輸入處理的TCP PCB。時刻唯一 */
struct tcp_pcb *tcp_input_pcb;
?
TCP RAW接口分析
先分析北向接口,這些接口可供用戶使用。
相關(guān)文件:
- lwip/src/core/tcp.c
- lwip/src/include/lwip/tcp.h
?
LWIP接口層級:RAW --> NETCONN --> SOCKET。
?
RAW接口使用
建立連接
用于建立連接的函數(shù)類似于連續(xù)API和BSD套接字API的函數(shù)。
使用tcp_new()函數(shù)創(chuàng)建一個新的TCP連接標(biāo)識符(即協(xié)議控制塊PCB)。
然后可以將這個PCB設(shè)置為監(jiān)聽新的傳入連接,或者顯式地連接到另一個主機。
參考使用:
tcp_new(); /* 新建一個TCP */
tcp_bind(); /* 綁定本地服務(wù) */
tcp_listen(); /* or */ tcp_listen_with_backlog(); /* 監(jiān)聽(用于服務(wù)端) */
tcp_accept(); /* 接受連接(用于服務(wù)端) */
tcp_connect(); /* 建立一個連接(用于客戶端) */
?
發(fā)送數(shù)據(jù)
通過調(diào)用tcp_write()
?對數(shù)據(jù)進行排隊,并通過調(diào)用tcp_output()
?觸發(fā)發(fā)送TCP數(shù)據(jù)。
當(dāng)數(shù)據(jù)成功傳輸?shù)竭h程主機時,將通過tcp_sent()
?指定回調(diào)函數(shù)回調(diào)通知到應(yīng)用程序。
tcp_write(); /* 該函數(shù)用于把數(shù)據(jù)插入TCP發(fā)送緩沖區(qū) */
tcp_output(); /* 該函數(shù)用于觸發(fā)TCP緩沖區(qū)發(fā)送數(shù)據(jù) */
tcp_sent(); /* 注冊發(fā)送回調(diào)函數(shù) */
?
接收數(shù)據(jù)
TCP數(shù)據(jù)接收是基于回調(diào)函數(shù)實現(xiàn)的。
當(dāng)新數(shù)據(jù)到達時調(diào)用應(yīng)用程序之前tcp_recv()
?注冊的回調(diào)函數(shù)。
當(dāng)應(yīng)用程序獲得數(shù)據(jù)后,它必須調(diào)用tcp_recved()
?函數(shù)來指示TCP可以通告增加接收窗口。
tcp_recv(); /* 注冊接收回調(diào)函數(shù) */
tcp_recved(); /* 應(yīng)用層成功接收到數(shù)據(jù)通知回TCP的函數(shù) */
?
應(yīng)用輪詢(守護)
邏輯功能:就是注冊一個poll()函數(shù)到TCP內(nèi)核,這個函數(shù)會被TCP內(nèi)核周期調(diào)用。
當(dāng)連接空閑時(即,既沒有傳輸數(shù)據(jù)也沒有接收數(shù)據(jù)),lwip將通過調(diào)用指定的回調(diào)函數(shù)來反復(fù)輪詢應(yīng)用程序。
這既可以用作看門狗定時器來終止空閑時間過長的連接,也可以用作一種等待內(nèi)存可用的方法。
例如,如果由于內(nèi)存不可用而導(dǎo)致tcp_write()
?發(fā)送數(shù)據(jù)失敗,則應(yīng)用程序可能在連接空閑一段時間后使用輪詢功能再次調(diào)用tcp_write()
?。
tcp_poll(); /* 注冊周期回調(diào)函數(shù),被TCP內(nèi)核周期調(diào)用 */
?
關(guān)閉連接
關(guān)閉和中止連接。
tcp_close()
?是通過四次揮手(FIN
?)正常關(guān)閉連接。
tcp_abort()
?是通過RST
?強制終止連接。
tcp_err()
?是注冊異?;卣{(diào)函數(shù)。當(dāng)TCP異常時,會通過該函數(shù)注冊的回調(diào)函數(shù)通知應(yīng)用層。
- 注意:當(dāng)調(diào)用這個回調(diào)時,相應(yīng)的pcb已經(jīng)被釋放了!
tcp_close(); /* 正常關(guān)閉連接,釋放PCB資源 */
tcp_abort(); /* RST方式終止連接 */
tcp_err(); /* 注冊異?;卣{(diào)函數(shù) */
?
?
新建控制塊:tcp_new()
tcp_new()
?接口調(diào)用tcp_alloc()
?接口。
一個TCP連接需要TCP PCB(TCP 控制塊)來管理本連接的相關(guān)數(shù)據(jù)。
在本函數(shù)中,能了解到LWIP申請TCP PCB的內(nèi)存管理邏輯,也能找到TCP性能的默認(rèn)值(這個對TCP網(wǎng)絡(luò)分析的同學(xué)挺有用的)。
?
struct tcp_pcb *tcp_alloc(u8_t prio)
:申請&初始化TCP PCB。
-
u8_t prio
:新建的TCP PCB優(yōu)先級。 - 如果
MEMP_TCP_PCB
內(nèi)存池還有空間,則直接從該內(nèi)存池申請。 - 如果
MEMP_TCP_PCB
內(nèi)存池空間不足,則按照以下順序進行強制占用:最老的:TIME-WAIT > LAST_ACK > CLOSING > 優(yōu)先級更低的已激活的連接。
/**
* 申請tcp pcb內(nèi)存。
* 如果內(nèi)存不足,按以下順序釋放pcb:最老的:TIME-WAIT > LAST_ACK > CLOSING > 優(yōu)先級更低的已激活的連接。
* tcp pcb內(nèi)存資源申請成功后,初始化部分字段。
*
*/
struct tcp_pcb *
tcp_alloc(u8_t prio)
{
struct tcp_pcb *pcb;
LWIP_ASSERT_CORE_LOCKED();
pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
if (pcb == NULL) {
/* 先處理那些處于TF_CLOSEPEND狀態(tài)的pcb。主動觸發(fā)他們再次發(fā)起FIN。(之前發(fā)送FIN失敗的pcb,這些pcb都是我們想關(guān)閉的pcb了) */
tcp_handle_closepend();
LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest TIME-WAIT connection\n"));
/* 內(nèi)存不足,干掉最老的TIME_WAIT連接 */
tcp_kill_timewait();
pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
if (pcb == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest LAST-ACK connection\n"));
/* 還是內(nèi)存不足,就干掉最老的LAST_ACK連接 */
tcp_kill_state(LAST_ACK);
pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
if (pcb == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest CLOSING connection\n"));
/* 還是內(nèi)存不足,干掉最老的CLOSING連接 */
tcp_kill_state(CLOSING);
pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
if (pcb == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing oldest connection with prio lower than %d\n", prio));
/* 還是內(nèi)存不足,那就干掉優(yōu)先級更低的最老的連接 */
tcp_kill_prio(prio);
pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
if (pcb != NULL) {
/* 還是內(nèi)存不足,沒辦法了 */
MEMP_STATS_DEC(err, MEMP_TCP_PCB);
}
}
if (pcb != NULL) {
/* adjust err stats: memp_malloc failed multiple times before */
MEMP_STATS_DEC(err, MEMP_TCP_PCB);
}
}
if (pcb != NULL) {
/* adjust err stats: memp_malloc failed multiple times before */
MEMP_STATS_DEC(err, MEMP_TCP_PCB);
}
}
if (pcb != NULL) {
/* adjust err stats: memp_malloc failed above */
MEMP_STATS_DEC(err, MEMP_TCP_PCB);
}
}
if (pcb != NULL) {
/* 申請成功 */
memset(pcb, 0, sizeof(struct tcp_pcb)); /* 清空所有字段 */
pcb->prio = prio; /* 設(shè)置控制塊優(yōu)先級 */
pcb->snd_buf = TCP_SND_BUF; /* 設(shè)置發(fā)送緩沖區(qū)大小 */
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND); /* 初始化接收窗口和窗口通告值 */
pcb->ttl = TCP_TTL; /* TTL */
pcb->mss = INITIAL_MSS; /* 初始化MSS,在SYN時,會在選項字段發(fā)送到對端。 */
pcb->rto = LWIP_TCP_RTO_TIME / TCP_SLOW_INTERVAL; /* 初始RTO時間為LWIP_TCP_RTO_TIME,默認(rèn)3000ms */
pcb->sv = LWIP_TCP_RTO_TIME / TCP_SLOW_INTERVAL; /* 初始RTT時間差為RTO的初始值 */
pcb->rtime = -1; /* 初始為停止重傳計時值計時 */
pcb->cwnd = 1; /* 初始擁塞窗口值 */
pcb->tmr = tcp_ticks; /* 保存當(dāng)前TCP節(jié)拍值為當(dāng)前PCB的TCP節(jié)拍初始值 */
pcb->last_timer = tcp_timer_ctr; /* 初始化PCB最后一次活動的時間 */
/* RFC 5618建議設(shè)置ssthresh值盡可能高,比如設(shè)置為最大可能的窗口通告值大小(可以理解為最大可能的發(fā)送窗口大小 )。 */
/* 這里先設(shè)置為本地發(fā)送緩沖區(qū)大小,即是最大飛行數(shù)據(jù)量。后面進行窗口縮放和自動調(diào)優(yōu)時自動調(diào)整。 */
pcb->ssthresh = TCP_SND_BUF;
#if LWIP_CALLBACK_API
/* 默認(rèn)接收回調(diào) */
pcb->recv = tcp_recv_null;
#endif /* LWIP_CALLBACK_API */
/* 保活計時器超時值:默認(rèn)7200秒,即是兩小時。 */
pcb->keep_idle = TCP_KEEPIDLE_DEFAULT;
#if LWIP_TCP_KEEPALIVE
/* ?;顣r間間隔:默認(rèn)75秒 */
pcb->keep_intvl = TCP_KEEPINTVL_DEFAULT;
/* ?;钐綔y數(shù):默認(rèn)9次。 */
pcb->keep_cnt = TCP_KEEPCNT_DEFAULT;
#endif /* LWIP_TCP_KEEPALIVE */
}
return pcb;
}
?
綁定本地服務(wù):tcp_bind()
TCP PCB新建后,需要綁定本地的IP和端口號,這樣就能表示一個接入到應(yīng)用層的連接了。
?
err_t tcp_bind(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
:
-
struct tcp_pcb *pcb
:TCP PCB。 -
const ip_addr_t *ipaddr
:需要綁定的本地IP地址。如果本地IP填了NULL
或IP_ANY_TYPE
,則表示任意IP,綁定本地所有IP的意思。 -
u16_t port
:需要綁定的綁定端口號。如果本地端口號填了0,則會調(diào)用tcp_new_port()
申請一個隨機端口號。如果指定了端口號,需要檢查是否有復(fù)用。 -
SO_REUSE
:如果設(shè)置了SO_REUSEADDR
選項,且綁定的IP和PORT已經(jīng)被使用且處于TIME_WAIT
狀態(tài),也可以被重復(fù)使用。如果沒有設(shè)置,則不能釋放處于TIME_WAIT
狀態(tài)的PCB。 -
IP&PORT復(fù)用檢查:遍歷所有pcb鏈表
tcp_pcb_lists[]
,如果當(dāng)前IP和端口號已經(jīng)被使用了,且任意一個PCB沒有開啟端口復(fù)用選項SO_REUSEADDR
,本地綁定都視為綁定失敗。- 需要注意的是:任意IP(全0)是萬能的。
-
綁定成功后,把當(dāng)前PCB遷移到
tcp_bound_pcbs
鏈表。
/**
* @ingroup tcp_raw
* Binds the connection to a local port number and IP address.
* If the IP address is not given (i.e., ipaddr == IP_ANY_TYPE), the connection is bound to all local IP addresses.
* If another connection is bound to the same port, the function will return ERR_USE, otherwise ERR_OK is returned.
* @see MEMP_NUM_TCP_PCB_LISTEN and MEMP_NUM_TCP_PCB
*
* @param pcb the tcp_pcb to bind (no check is done whether this pcb is already bound!)
* @param ipaddr the local ip address to bind to (use IPx_ADDR_ANY to bind to any local address
* @param port the local port to bind to
* @return ERR_USE if the port is already in use
* ERR_VAL if bind failed because the PCB is not in a valid state
* ERR_OK if bound
*/
err_t
tcp_bind(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
{
int i;
int max_pcb_list = NUM_TCP_PCB_LISTS;
struct tcp_pcb *cpcb;
#if LWIP_IPV6 && LWIP_IPV6_SCOPES
ip_addr_t zoned_ipaddr;
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */
LWIP_ASSERT_CORE_LOCKED();
#if LWIP_IPV4
/* Don't propagate NULL pointer (IPv4 ANY) to subsequent functions */
if (ipaddr == NULL) {
ipaddr = IP4_ADDR_ANY;
}
#else /* LWIP_IPV4 */
LWIP_ERROR("tcp_bind: invalid ipaddr", ipaddr != NULL, return ERR_ARG);
#endif /* LWIP_IPV4 */
LWIP_ERROR("tcp_bind: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("tcp_bind: can only bind in state CLOSED", pcb->state == CLOSED, return ERR_VAL);
#if SO_REUSE /* 選項:SO_REUSEADDR */
/* 如果設(shè)置了SO_REUSEADDR選項,且綁定的IP和PORT已經(jīng)被使用且處于TIME_WAIT狀態(tài),也可以被重復(fù)使用。
如果沒有設(shè)置,則不能釋放處于TIME_WAIT狀態(tài)的PCB。 */
if (ip_get_option(pcb, SOF_REUSEADDR)) {
/* 不用遍歷處于TIME_WAIT狀態(tài)的TCP PCB是否被復(fù)用,因為SO_REUSEADDR選項運行其復(fù)用行為 */
max_pcb_list = NUM_TCP_PCB_LISTS_NO_TIME_WAIT;
}
#endif /* SO_REUSE */
#if LWIP_IPV6 && LWIP_IPV6_SCOPES
/* If the given IP address should have a zone but doesn't, assign one now.
* This is legacy support: scope-aware callers should always provide properly
* zoned source addresses. Do the zone selection before the address-in-use
* check below; as such we have to make a temporary copy of the address. */
if (IP_IS_V6(ipaddr) && ip6_addr_lacks_zone(ip_2_ip6(ipaddr), IP6_UNICAST)) {
ip_addr_copy(zoned_ipaddr, *ipaddr);
ip6_addr_select_zone(ip_2_ip6(&zoned_ipaddr), ip_2_ip6(&zoned_ipaddr));
ipaddr = &zoned_ipaddr;
}
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */
if (port == 0) {
/* 自動生成端口號 */
port = tcp_new_port();
if (port == 0) {
/* 端口號申請失敗,綁定失敗 */
return ERR_BUF;
}
} else {
/* 指定端口號。遍歷TCP PCB鏈表,IP和PORT是否被占用。 */
/* Check if the address already is in use (on all lists) */
for (i = 0; i < max_pcb_list; i++) {
for (cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
if (cpcb->local_port == port) {
#if SO_REUSE
/* 如果兩個TCP PCB都設(shè)置了SO_REUSEADDR選項,則可以復(fù)用同一個IP和端口號 */
if (!ip_get_option(pcb, SOF_REUSEADDR) ||
!ip_get_option(cpcb, SOF_REUSEADDR))
#endif /* SO_REUSE */
{
/* @todo: check accept_any_ip_version */
/* 注意:任意IP即是萬能IP */
if ((IP_IS_V6(ipaddr) == IP_IS_V6_VAL(cpcb->local_ip)) &&
(ip_addr_isany(&cpcb->local_ip) ||
ip_addr_isany(ipaddr) ||
ip_addr_eq(&cpcb->local_ip, ipaddr))) {
/* 如果IP和PORT已經(jīng)被占用了,則返回ERR_USE */
return ERR_USE;
}
}
}
}
}
}
if (!ip_addr_isany(ipaddr) /* 綁定的IP不是任意IP */
#if LWIP_IPV4 && LWIP_IPV6
/* 綁定的IP類型和原有IP類型不一致,也要更新 */
|| (IP_GET_TYPE(ipaddr) != IP_GET_TYPE(&pcb->local_ip))
#endif /* LWIP_IPV4 && LWIP_IPV6 */
) {
/* 綁定IP,更新TCP PCB本地IP字段 */
ip_addr_set(&pcb->local_ip, ipaddr);
}
/* 本地PORT */
pcb->local_port = port;
TCP_REG(&tcp_bound_pcbs, pcb);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_bind: bind to port %"U16_F"\n", port));
return ERR_OK;
}
?
監(jiān)聽:tcp_listen()
用于服務(wù)端。
tcp_listen()
?調(diào)用tcp_listen_with_backlog()
?調(diào)用tcp_listen_with_backlog_and_err()
?。
#define tcp_listen(pcb) tcp_listen_with_backlog(pcb, TCP_DEFAULT_LISTEN_BACKLOG)
struct tcp_pcb *
tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
{
LWIP_ASSERT_CORE_LOCKED();
return tcp_listen_with_backlog_and_err(pcb, backlog, NULL);
}
?
struct tcp_pcb *tcp_listen_with_backlog_and_err(struct tcp_pcb *pcb, u8_t backlog, err_t *err)
?:
-
struct tcp_pcb *pcb
??:PCB。 -
u8_t backlog
?:等待accept()
?連接的上限值。 -
err_t *err
?:當(dāng)返回NULL時,該回傳參數(shù)包含錯誤原因。 -
當(dāng)前函數(shù)就是設(shè)置PCB進入LISTEN狀態(tài)。如果已經(jīng)是LISTEN狀態(tài),則不需要處理。
-
SO_REUSE
?:如果設(shè)置了SOF_REUSEADDR
?則需要檢查是否有IP&PORT服務(wù)已經(jīng)處于LISTEN狀態(tài),如果有,則本次進入LISTEN失敗(因為不支持同時存在兩個及以上的正常服務(wù))。 -
重置PCB的數(shù)據(jù)結(jié)構(gòu)為
tcp_pcb_listen
?,降低內(nèi)存浪費。并初始化新的數(shù)據(jù)結(jié)構(gòu),當(dāng)然包括lpcb->state = LISTEN;
?。- 具體看本函數(shù)源碼。
-
把當(dāng)前PCB插入
tcp_listen_pcbs.pcbs
?鏈表中。
/**
* @ingroup tcp_raw
* 把當(dāng)前PCB設(shè)為LISTEN狀態(tài)(不可逆),表示可以處理連接進來的TCP客戶端。
* TCP PCB重新分配為監(jiān)聽專用的PCB,降低內(nèi)存占用。
*
* @param pcb the original tcp_pcb
* @param backlog the incoming connections queue limit
* @param err when NULL is returned, this contains the error reason
* @return tcp_pcb used for listening, consumes less memory.
*
* @note The original tcp_pcb is freed. This function therefore has to be
* called like this:
* tpcb = tcp_listen_with_backlog_and_err(tpcb, backlog, &err);
*/
struct tcp_pcb *
tcp_listen_with_backlog_and_err(struct tcp_pcb *pcb, u8_t backlog, err_t *err)
{
struct tcp_pcb_listen *lpcb = NULL;
err_t res;
LWIP_UNUSED_ARG(backlog);
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("tcp_listen_with_backlog_and_err: invalid pcb", pcb != NULL, res = ERR_ARG; goto done);
LWIP_ERROR("tcp_listen_with_backlog_and_err: pcb already connected", pcb->state == CLOSED, res = ERR_CLSD; goto done);
if (pcb->state == LISTEN) {
/* 已經(jīng)是監(jiān)聽狀態(tài)了,不需要重復(fù)處理 */
lpcb = (struct tcp_pcb_listen *)pcb;
res = ERR_ALREADY;
goto done;
}
#if SO_REUSE
if (ip_get_option(pcb, SOF_REUSEADDR)) {
/* Since SOF_REUSEADDR allows reusing a local address before the pcb's usage
is declared (listen-/connection-pcb), we have to make sure now that
this port is only used once for every local IP. */
/* 不能有相同IP和PORT的TCP服務(wù)器 */
for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
if ((lpcb->local_port == pcb->local_port) &&
ip_addr_eq(&lpcb->local_ip, &pcb->local_ip)) {
/* this address/port is already used */
lpcb = NULL;
res = ERR_USE;
goto done;
}
}
}
#endif /* SO_REUSE */
/* 由于當(dāng)前服務(wù)器原有的TCP PCB為tcp_pcb,對于TCP服務(wù)器的監(jiān)聽TCP來說,里面的很多字段都沒用到,
所以LWIP使用tcp_pcb_listen作為監(jiān)聽TCP的PCB,這樣占用內(nèi)存更小。 */
/* 申請TCP LISTEN PCB資源 */
lpcb = (struct tcp_pcb_listen *)memp_malloc(MEMP_TCP_PCB_LISTEN);
if (lpcb == NULL) {
res = ERR_MEM;
goto done;
}
/* 申請成功,填寫相關(guān)字段 */
lpcb->callback_arg = pcb->callback_arg;
lpcb->local_port = pcb->local_port;
lpcb->state = LISTEN; /* 標(biāo)記為監(jiān)聽狀態(tài) */
lpcb->prio = pcb->prio;
lpcb->so_options = pcb->so_options;
lpcb->netif_idx = pcb->netif_idx;
lpcb->ttl = pcb->ttl;
lpcb->tos = pcb->tos;
#if LWIP_VLAN_PCP
lpcb->netif_hints.tci = pcb->netif_hints.tci;
#endif /* LWIP_VLAN_PCP */
#if LWIP_IPV4 && LWIP_IPV6
IP_SET_TYPE_VAL(lpcb->remote_ip, pcb->local_ip.type);
#endif /* LWIP_IPV4 && LWIP_IPV6 */
ip_addr_copy(lpcb->local_ip, pcb->local_ip);
if (pcb->local_port != 0) {
/* 先把原生監(jiān)聽TCP PCB從tcp_bound_pcbs鏈表中移除 */
TCP_RMV(&tcp_bound_pcbs, pcb);
}
#if LWIP_TCP_PCB_NUM_EXT_ARGS
/* copy over ext_args to listening pcb */
memcpy(&lpcb->ext_args, &pcb->ext_args, sizeof(pcb->ext_args));
#endif
/* 釋放原生監(jiān)聽TCP PCB */
tcp_free(pcb);
#if LWIP_CALLBACK_API
/* 配置默認(rèn)accept() */
lpcb->accept = tcp_accept_null;
#endif /* LWIP_CALLBACK_API */
#if TCP_LISTEN_BACKLOG
/* 目前沒有阻塞需要接入當(dāng)前服務(wù)器的客戶端連接 */
lpcb->accepts_pending = 0;
tcp_backlog_set(lpcb, backlog);
#endif /* TCP_LISTEN_BACKLOG */
/* 修改點:https://github.com/yarrick/lwip/commit/6fb248c9e0a540112d0b4616b89f0130e4d57270 */
/* http://savannah.nongnu.org/task/?func=detailitem&item_id=10088#options */
/* 把新的簡版監(jiān)聽TCP PCB插回對應(yīng)狀態(tài)鏈表中 */
TCP_REG(&tcp_listen_pcbs.pcbs, (struct tcp_pcb *)lpcb);
res = ERR_OK;
done:
if (err != NULL) {
*err = res;
}
return (struct tcp_pcb *)lpcb;
}
?
?
接受連接:tcp_accept()
用于服務(wù)端。
注冊一個accept()
?回調(diào)函數(shù)到TCP內(nèi)核中,當(dāng)TCP內(nèi)核監(jiān)聽到TCP客戶端并握手成功后會調(diào)用該回調(diào)函數(shù)通知應(yīng)用層。
void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
?:
-
struct tcp_pcb *pcb
?:PCB。 -
tcp_accept_fn accept
?:需要注冊的回調(diào)函數(shù)。
/**
* @ingroup tcp_raw
* 用于指定當(dāng)偵聽連接已連接到另一個主機時應(yīng)調(diào)用的函數(shù)。
* @see MEMP_NUM_TCP_PCB_LISTEN and MEMP_NUM_TCP_PCB
*
* @param pcb tcp_pcb to set the accept callback
* @param accept callback function to call for this pcb when LISTENing
* connection has been connected to another host
*
* 注冊accept()函數(shù),TCP服務(wù)器接受一條客戶端連接時被調(diào)用。
*/
void
tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
{
LWIP_ASSERT_CORE_LOCKED();
if ((pcb != NULL) && (pcb->state == LISTEN)) {
struct tcp_pcb_listen *lpcb = (struct tcp_pcb_listen *)pcb;
lpcb->accept = accept;
}
}
?
?
連接遠端:tcp_connect()
用于客戶端。
會觸發(fā)三次握手的接口。
對于服務(wù)端來說,綁定成功后還需要對該IP&PORT進行監(jiān)聽,監(jiān)聽到了就進行ACCETP處理即可,表示已經(jīng)連接完成。
而對于客戶端來說,綁定成功后,就可以調(diào)用當(dāng)前函數(shù)連接服務(wù)端了。
?
err_t tcp_connect(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port, tcp_connected_fn connected)
:
-
struct tcp_pcb *pcb
:PCB。 -
const ip_addr_t *ipaddr
:需要連接的遠端IP地址。 -
u16_t port
:需要連接的遠端端口號。 -
tcp_connected_fn connected
:連接情況回調(diào)函數(shù)。 -
本函數(shù)用于連接到遠端TCP主機。
-
當(dāng)前函數(shù)是非阻塞的,如果不能連接(如內(nèi)存不足,參數(shù)錯誤等等),會立即返回。如果
SYN
報文能正常入隊,則會立即返回ERR_OK
:- 當(dāng)連接成功后,注冊進去的
connected()
回調(diào)函數(shù)會被調(diào)用。 - 當(dāng)連接失敗會調(diào)用之前注冊的
err()
回調(diào)函數(shù)返回結(jié)果。(如對端主機拒絕連接、沒收到對端響應(yīng)等握手失敗的可能)
- 當(dāng)連接成功后,注冊進去的
-
如果當(dāng)前PCB的端口號為0,在當(dāng)前連接函數(shù)中,也會隨機分配一個空閑端口號。
-
SO_REUSE
:如果設(shè)置了SOF_REUSEADDR選項值,則需要判斷五元組唯一才能連接:本地IP、本地PORT、遠端IP、遠端PORT和TCP PCB狀態(tài)。- 說明:復(fù)用IP和端口號,是不能復(fù)用連接的,所以復(fù)用的IP和端口號中,只能由一個能建立正常連接。
-
初始化報文相關(guān)字段,如ISS(起始SEQ)、接收窗口、發(fā)送窗口、擁塞窗口、注冊
connected()
回調(diào)。 -
把
SYN
報文插入發(fā)送隊列。 -
調(diào)用
tcp_output()
觸發(fā)處理發(fā)送隊列的報文段。
/**
* @ingroup tcp_raw
* Connects to another host.
* The function given as the "connected" argument will be called when the connection has been established.
* Sets up the pcb to connect to the remote host and sends the initial SYN segment which opens the connection.
*
* The tcp_connect() function returns immediately; it does not wait for the connection to be properly setup.
* Instead, it will call the function specified as the fourth argument (the "connected" argument) when the connection is established.
* If the connection could not be properly established, either because the other host refused the connection or because the other host didn't answer, the "err" callback function of this pcb (registered with tcp_err, see below) will be called.
*
* The tcp_connect() function can return ERR_MEM if no memory is available for enqueueing the SYN segment.
* If the SYN indeed was enqueued successfully, the tcp_connect() function returns ERR_OK.
*
* @param pcb the tcp_pcb used to establish the connection
* @param ipaddr the remote ip address to connect to
* @param port the remote tcp port to connect to
* @param connected callback function to call when connected (on error,
the err callback will be called)
* @return ERR_VAL if invalid arguments are given
* ERR_OK if connect request has been sent
* other err_t values if connect request couldn't be sent
*/
err_t
tcp_connect(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port,
tcp_connected_fn connected)
{
struct netif *netif = NULL;
err_t ret;
u32_t iss;
u16_t old_local_port;
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("tcp_connect: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("tcp_connect: invalid ipaddr", ipaddr != NULL, return ERR_ARG);
LWIP_ERROR("tcp_connect: can only connect from state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_connect to port %"U16_F"\n", port));
ip_addr_set(&pcb->remote_ip, ipaddr);
pcb->remote_port = port;
if (pcb->netif_idx != NETIF_NO_INDEX) {
netif = netif_get_by_index(pcb->netif_idx);
} else {
/* check if we have a route to the remote host */
netif = ip_route(&pcb->local_ip, &pcb->remote_ip);
}
if (netif == NULL) {
/* Don't even try to send a SYN packet if we have no route since that will fail. */
return ERR_RTE;
}
/* check if local IP has been assigned to pcb, if not, get one */
if (ip_addr_isany(&pcb->local_ip)) {
const ip_addr_t *local_ip = ip_netif_get_local_ip(netif, ipaddr);
if (local_ip == NULL) {
return ERR_RTE;
}
ip_addr_copy(pcb->local_ip, *local_ip);
}
#if LWIP_IPV6 && LWIP_IPV6_SCOPES
/* If the given IP address should have a zone but doesn't, assign one now.
* Given that we already have the target netif, this is easy and cheap. */
if (IP_IS_V6(&pcb->remote_ip) &&
ip6_addr_lacks_zone(ip_2_ip6(&pcb->remote_ip), IP6_UNICAST)) {
ip6_addr_assign_zone(ip_2_ip6(&pcb->remote_ip), IP6_UNICAST, netif);
}
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */
old_local_port = pcb->local_port;
if (pcb->local_port == 0) {
pcb->local_port = tcp_new_port();
if (pcb->local_port == 0) {
return ERR_BUF;
}
} else {
#if SO_REUSE
if (ip_get_option(pcb, SOF_REUSEADDR)) {
/* 如果設(shè)置了SOF_REUSEADDR選項值,
則需要判斷五元組唯一才能連接:本地IP、本地PORT、遠端IP、遠端PORT和TCP PCB狀態(tài) */
struct tcp_pcb *cpcb;
int i;
/* TCP PCB狀態(tài)鏈表只遍歷穩(wěn)定態(tài)和TIME_WAIT狀態(tài)的,不遍歷綁定態(tài)和監(jiān)聽?wèi)B(tài)的,因為設(shè)置了SOF_REUSEADDR,是允許客戶端復(fù)用服務(wù)器的。 */
for (i = 2; i < NUM_TCP_PCB_LISTS; i++) {
for (cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
if ((cpcb->local_port == pcb->local_port) &&
(cpcb->remote_port == port) &&
ip_addr_eq(&cpcb->local_ip, &pcb->local_ip) &&
ip_addr_eq(&cpcb->remote_ip, ipaddr)) {
/* linux returns EISCONN here, but ERR_USE should be OK for us */
return ERR_USE;
}
}
}
}
#endif /* SO_REUSE */
}
iss = tcp_next_iss(pcb); /* 獲取第一個要發(fā)送的seq號值 */
pcb->rcv_nxt = 0;
pcb->snd_nxt = iss;
pcb->lastack = iss - 1;
pcb->snd_wl2 = iss - 1;
pcb->snd_lbb = iss - 1;
/* 初始化接收窗口、窗口通告值、窗口通告值右邊沿值 */
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND);
pcb->rcv_ann_right_edge = pcb->rcv_nxt;
/* 初始化發(fā)送窗口 */
pcb->snd_wnd = TCP_WND;
/* 初始化MSS,LWIP限制在536 */
pcb->mss = INITIAL_MSS;
#if TCP_CALCULATE_EFF_SEND_MSS
/* 根據(jù)netif和遠端IP來設(shè)置MSS */
pcb->mss = tcp_eff_send_mss_netif(pcb->mss, netif, &pcb->remote_ip);
#endif /* TCP_CALCULATE_EFF_SEND_MSS */
/* 擁塞窗口初始值 */
pcb->cwnd = 1;
#if LWIP_CALLBACK_API
/* 回調(diào)函數(shù)connected() */
pcb->connected = connected;
#else /* LWIP_CALLBACK_API */
LWIP_UNUSED_ARG(connected);
#endif /* LWIP_CALLBACK_API */
/* 構(gòu)造一個連接請求報文到TCP PCB中:SYN + MSS option */
ret = tcp_enqueue_flags(pcb, TCP_SYN);
if (ret == ERR_OK) {
/* 更新為SYN_SENT狀態(tài) */
pcb->state = SYN_SENT;
if (old_local_port != 0) {
/* 舊TCP PCB端口不為0,則將TCP PCB先從tcp_bound_pcbs狀態(tài)鏈表移除 */
TCP_RMV(&tcp_bound_pcbs, pcb);
}
/* 再把當(dāng)前TCP PCB插入到穩(wěn)定態(tài)tcp_active_pcbs鏈表 */
TCP_REG_ACTIVE(pcb);
MIB2_STATS_INC(mib2.tcpactiveopens);
/* 將TCP PCB上的報文發(fā)送出去 */
tcp_output(pcb);
}
return ret;
}
?
應(yīng)用層通知TCP內(nèi)核成功接收數(shù)據(jù):tcp_recved()
tcp_recved()
?函數(shù)是被應(yīng)用層調(diào)用,用于通知TCP內(nèi)核:應(yīng)用層已經(jīng)從接收到的數(shù)據(jù)size,你可以釋放這部分?jǐn)?shù)據(jù)的內(nèi)存了。
void tcp_recved(struct tcp_pcb *pcb, u16_t len)
?:
-
struct tcp_pcb *pcb
?:pcb。 -
u16_t len
?:成功接收的長度。 -
窗口滑動:
- 當(dāng)前接收窗口恢復(fù)。
- 在糊涂窗口算法下,通告接收窗口。
/**
* @ingroup tcp_raw
* @param pcb the tcp_pcb for which data is read
* @param len the amount of bytes that have been read by the application
*
* 應(yīng)用程序從PCB緩沖區(qū)中提取走數(shù)據(jù)后,應(yīng)該調(diào)用當(dāng)前函數(shù)來更新當(dāng)前PCB的接收窗口。
*
*/
void
tcp_recved(struct tcp_pcb *pcb, u16_t len)
{
u32_t wnd_inflation;
tcpwnd_size_t rcv_wnd;
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("tcp_recved: invalid pcb", pcb != NULL, return);
/* pcb->state LISTEN not allowed here */
LWIP_ASSERT("don't call tcp_recved for listen-pcbs",
pcb->state != LISTEN);
/* 接收窗口擴大len */
rcv_wnd = (tcpwnd_size_t)(pcb->rcv_wnd + len);
/* 更新接收窗口值 */
if ((rcv_wnd > TCP_WND_MAX(pcb)) || (rcv_wnd < pcb->rcv_wnd)) {
/* window got too big or tcpwnd_size_t overflow */
LWIP_DEBUGF(TCP_DEBUG, ("tcp_recved: window got too big or tcpwnd_size_t overflow\n"));
pcb->rcv_wnd = TCP_WND_MAX(pcb);
} else {
pcb->rcv_wnd = rcv_wnd;
}
/* 更新滑動窗口。支持糊涂窗口避免算法。 */
wnd_inflation = tcp_update_rcv_ann_wnd(pcb);
/* 如果接收窗口右邊界滑動了 (1/4接收緩沖) || (4個MSS) 都可以立即發(fā)送窗口通告值到對端; */
/* 如果接收窗口右邊界滑動達不到閾值,就等正常發(fā)送數(shù)據(jù)時才附帶窗口通告值。 */
if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD) {
tcp_ack_now(pcb);
tcp_output(pcb);
}
LWIP_DEBUGF(TCP_DEBUG, ("tcp_recved: received %"U16_F" bytes, wnd %"TCPWNDSIZE_F" (%"TCPWNDSIZE_F").\n",
len, pcb->rcv_wnd, (u16_t)(TCP_WND_MAX(pcb) - pcb->rcv_wnd)));
}
?
關(guān)閉連接:tcp_close()
LISTEN狀態(tài)、未連接的PCB直接被釋放,不能再被引用。
如果PCB建立了連接(包括收到了SYN或處于closing狀態(tài)),就關(guān)閉連接,并按照狀態(tài)機轉(zhuǎn)換進入對應(yīng)的狀態(tài)。其PCB會在tcp_slowtmr()
?慢時鐘中被釋放。
注意,當(dāng)前函數(shù)也是一個協(xié)議不安全函數(shù),存在必要時會發(fā)送RST來關(guān)閉連接導(dǎo)致數(shù)據(jù)丟失:(ESTABLISHED || CLOSE_WAIT) && (應(yīng)用層還沒讀取完接收緩沖區(qū)的數(shù)據(jù))
?。
返回:
-
ERR_OK
?:關(guān)閉成功。 -
another err_t
?:關(guān)閉失敗或PCB沒有被釋放。- 如
ERR_MEM
?,在關(guān)閉連接時,可能需要發(fā)送FIN
?報文,這就需要申請報文段資源,如果申請失敗,就表示FIN
?發(fā)送不了,返回ERR_MEM
?通知回來。
- 如
所以,既然tcp_close()
?這個接口會因為內(nèi)存不足而導(dǎo)致關(guān)閉失敗,返回ERR_MEM
?,那么我們就需要檢查返回值操作,如遇到內(nèi)部內(nèi)存不足導(dǎo)致關(guān)閉失敗就需要繼續(xù)調(diào)用tcp_close()
?,而不是忽略返回值導(dǎo)致更多的內(nèi)存泄漏。
?
err_t
tcp_close(struct tcp_pcb *pcb)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("tcp_close: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_DEBUGF(TCP_DEBUG, ("tcp_close: closing in "));
tcp_debug_print_state(pcb->state);
if (pcb->state != LISTEN) {
/* Set a flag not to receive any more data... */
tcp_set_flags(pcb, TF_RXCLOSED);
}
/* ... and close */
return tcp_close_shutdown(pcb, 1);
}
?
報文段相關(guān)函數(shù)
學(xué)完RAW接口函數(shù)后,需要學(xué)習(xí)TCP內(nèi)部實現(xiàn)了。
TCP數(shù)據(jù)流是以報文段形式來承接的,所以本章前段也描述了報文段的相關(guān)數(shù)據(jù)結(jié)構(gòu),現(xiàn)在繼續(xù)描述報文段的相關(guān)函數(shù)。
?
新建一個報文段:tcp_create_segment()
其主要內(nèi)容是創(chuàng)建一個新的報文段segment并初始化。
/**
* Create a TCP segment with prefilled header.
*
* Called by @ref tcp_write, @ref tcp_enqueue_flags and @ref tcp_split_unsent_seg
*
* @param pcb Protocol control block for the TCP connection.
* @param p pbuf that is used to hold the TCP header.
* @param hdrflags TCP flags for header.
* @param seqno TCP sequence number of this packet
* @param optflags options to include in TCP header
* @return a new tcp_seg pointing to p, or NULL.
* The TCP header is filled in except ackno and wnd.
* p is freed on failure.
*/
static struct tcp_seg *
tcp_create_segment(const struct tcp_pcb *pcb, struct pbuf *p, u8_t hdrflags, u32_t seqno, u8_t optflags);
?
TCP發(fā)送數(shù)據(jù)
TCP數(shù)據(jù)收發(fā)都是一個復(fù)雜的協(xié)議實現(xiàn)。
在RAW接口層來看:
- 先調(diào)用
tcp_sent()
??注冊發(fā)送回調(diào)函數(shù)。 - 調(diào)用
tcp_write()
??把需要發(fā)送的數(shù)據(jù)組裝成報文段,插入TCP發(fā)送緩沖區(qū)中。 - 主動調(diào)用或內(nèi)部定時調(diào)用
tcp_output()
??函數(shù)來檢查發(fā)送緩沖區(qū)中的報文段并發(fā)送到IP層處理。
?
調(diào)用關(guān)系:tcp_output()
?-->tcp_output_segment()
?-->ip_output_if()
?給到IP層。
?
組裝報文段到PCB:tcp_write()
tcp_write()
?函數(shù)用于發(fā)送數(shù)據(jù),但不是立即發(fā)送,而是把數(shù)據(jù)組裝成報文段插入TCP發(fā)送緩沖區(qū):pcb->unsent
?隊列中。
相關(guān)參數(shù):
-
struct tcp_pcb *pcb
?:pcb。 -
void *arg
?:需要發(fā)送的數(shù)據(jù)的指針。 -
u16_t len
?:需要發(fā)送的數(shù)據(jù)的長度。 -
u8_t apiflags
?:-
TCP_WRITE_FLAG_COPY
?:數(shù)據(jù)會被復(fù)制到新的內(nèi)存中。 -
TCP_WRITE_FLAG_MORE
?:表示后續(xù)還有數(shù)據(jù),這個標(biāo)志位會導(dǎo)致TCP首部PSH
?標(biāo)志位不會被標(biāo)記。
-
?
如果需要發(fā)送的數(shù)據(jù)長度超過發(fā)送緩沖區(qū)空閑size 或 發(fā)送隊列的segment個數(shù)超過上限 都會返回ERR_MEM
?表示因內(nèi)存問題而發(fā)送失敗。
?
其函數(shù)目標(biāo)并很簡單:只是把數(shù)據(jù)合法封裝成報文段格式,插入到發(fā)送緩沖隊列中。
其函數(shù)的實現(xiàn)復(fù)雜度不在于協(xié)議的實現(xiàn),而是兼顧內(nèi)存的做法,所以對LWIP具體實現(xiàn)沒有深入研究的興趣的話,不需要分析當(dāng)前函數(shù)具體實現(xiàn)。
?
確定MSS
先為本次發(fā)送組包選擇一個MSS值:
/* 選擇本次發(fā)送的MSS,MIN(pcb->mss, 發(fā)送窗口/2) */
mss_local = LWIP_MIN(pcb->mss, TCPWND_MIN16(pcb->snd_wnd_max / 2));
mss_local = mss_local ? mss_local : pcb->mss;
?
檢查本次是否寫
- 會檢查TCP狀態(tài)、發(fā)送緩沖區(qū)size和發(fā)送緩沖區(qū)中報文段個數(shù)。
/* 檢查本次是否可寫 */
err = tcp_write_checks(pcb, len);
if (err != ERR_OK) {
return err;
}
?
選項字段
確認(rèn)選項字段長度:目前只支持TS:時間戳選項字段。
#if LWIP_TCP_TIMESTAMPS /* 時間戳選項字段 */
if ((pcb->flags & TF_TIMESTAMP)) {
/* Make sure the timestamp option is only included in data segments if we
agreed about it with the remote host. */
optflags = TF_SEG_OPTS_TS;
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(TF_SEG_OPTS_TS, pcb);
/* ensure that segments can hold at least one data byte... */
mss_local = LWIP_MAX(mss_local, LWIP_TCP_OPT_LEN_TS + 1);
} else
#endif /* LWIP_TCP_TIMESTAMPS */
{
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb); /* 統(tǒng)計選項字段長度 */
}
?
組件報文段
TCP報文段組建分為三個步驟,且難度逐漸增加,如果某個步驟就能完成數(shù)據(jù)寫入,后面步驟就不需要繼續(xù)了:
- 把數(shù)據(jù)先copy到最后一個
segment
?的oversize
?中。 - 新建一個pbuf,接入最后一個未發(fā)送
segment
?中。 - 創(chuàng)建一個新的
segments
?。
注意:在處理的途中隨時可能會遇到內(nèi)存耗盡問題。所以,我們應(yīng)該return ERR_MEM
?和不要改動PCB任何值。我們在處理時,先使用局部變量,在當(dāng)前函數(shù)處理完畢時,再把改變PCB的所有值一并推送到PCB。一些pcb字段在本地副本中維護:
-
queuelen = pcb->snd_queuelen
?; -
oversize = pcb->unsent_oversize
?。
/* 先找到pcb->unsent隊列尾 */
if (pcb->unsent != NULL) {
u16_t space; /* last_unsent這個segment剩余可以插入的空間 */
u16_t unsent_optlen;
/* @todo: 可以在PCB中追加last_unsent變量來快速找到unsent隊列尾部 */
for (last_unsent = pcb->unsent; last_unsent->next != NULL;
last_unsent = last_unsent->next);
/* 報文段MSS限制:最后一個unsent報文段的可用空間 */
unsent_optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(last_unsent->flags, pcb); /* unsent報文段選項字段長度 */
LWIP_ASSERT("mss_local is too small", mss_local >= last_unsent->len + unsent_optlen);
space = mss_local - (last_unsent->len + unsent_optlen); /* segment剩余空間 */
/*
* Phase 1: 先填滿最后一個segment的oversize。
*
* 復(fù)制的字節(jié)數(shù)記錄在oversize_used變量中。
* 實際的復(fù)制是在函數(shù)的底部完成的。
*/
#if TCP_OVERSIZE /* 支持oversize */
#if TCP_OVERSIZE_DBGCHECK
/* check that pcb->unsent_oversize matches last_unsent->oversize_left */
LWIP_ASSERT("unsent_oversize mismatch (pcb vs. last_unsent)",
pcb->unsent_oversize == last_unsent->oversize_left);
#endif /* TCP_OVERSIZE_DBGCHECK */
oversize = pcb->unsent_oversize; /* oversize */
if (oversize > 0) {
LWIP_ASSERT("inconsistent oversize vs. space", oversize <= space);
seg = last_unsent;
oversize_used = LWIP_MIN(space, LWIP_MIN(oversize, len)); /* 選出需要copy的size */
pos += oversize_used; /* 游標(biāo)更新 */
oversize -= oversize_used; /* 更新oversize */
space -= oversize_used; /* 更新segment可用空間 */
}
/* now we are either finished or oversize is zero */
LWIP_ASSERT("inconsistent oversize vs. len", (oversize == 0) || (pos == len));
#endif /* TCP_OVERSIZE */
#if !LWIP_NETIF_TX_SINGLE_PBUF
/*
* Phase 2: 新建一個pbuf,接入last_unsent這個segment中。
*
* 如果數(shù)據(jù)不支持copy,即是pbuf為PBUF_ROM類型,last_unsent最后一個pbuf的數(shù)據(jù)區(qū)也是不支持copy的,
* 而且本次需要寫入發(fā)送緩沖區(qū)的數(shù)據(jù)地址*arg是僅跟著last_unsent最后一個pbuf數(shù)據(jù)區(qū)的最后一個數(shù)據(jù)地址
* (即是地址連續(xù))(p->payload + p->len == (const u8_t *)arg),那就直接擴展last_unsent最后一個pbuf的有效size即可,
* 這樣就能省一個pbuf ROM。
*
*/
if ((pos < len) && (space > 0) && (last_unsent->len > 0)) {
u16_t seglen = LWIP_MIN(space, len - pos);
seg = last_unsent;
/* 創(chuàng)建或引用一個pbuf。 */
if (apiflags & TCP_WRITE_FLAG_COPY) {
/* 數(shù)據(jù)支持copy */
if ((concat_p = tcp_pbuf_prealloc(PBUF_RAW, seglen, space, &oversize, pcb, apiflags, 1)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n",
seglen));
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
oversize_add = oversize;
#endif /* TCP_OVERSIZE_DBGCHECK */
TCP_DATA_COPY2(concat_p->payload, (const u8_t *)arg + pos, seglen, &concat_chksum, &concat_chksum_swapped);
#if TCP_CHECKSUM_ON_COPY
concat_chksummed += seglen;
#endif /* TCP_CHECKSUM_ON_COPY */
queuelen += pbuf_clen(concat_p);
} else {
/* 數(shù)據(jù)不支持copy */
/* 如果last unsent pbuf是PBUF_ROM類型,就try to extend它 */
struct pbuf *p;
for (p = last_unsent->p; p->next != NULL; p = p->next); /* 找到last_unsent的最后一個pbuf */
if (((p->type_internal & (PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_DATA_VOLATILE)) == 0) && /* 數(shù)據(jù)不可改(PBUF_ROM) */
(const u8_t *)p->payload + p->len == (const u8_t *)arg) { /* 地址連續(xù),可擴展 */
LWIP_ASSERT("tcp_write: ROM pbufs cannot be oversized", pos == 0);
extendlen = seglen; /* 擴展這個ROM pbuf */
} else { /* 不是PBUF_ROM或地址不連續(xù),就需要新建一個pbuf */
if ((concat_p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) { /* 新建一個PBUF_ROM */
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
/* reference the non-volatile payload data */
((struct pbuf_rom *)concat_p)->payload = (const u8_t *)arg + pos; /* 綁定payload */
queuelen += pbuf_clen(concat_p); /* 累計pbuf個數(shù) */
}
#if TCP_CHECKSUM_ON_COPY
/* calculate the checksum of nocopy-data */
tcp_seg_add_chksum(~inet_chksum((const u8_t *)arg + pos, seglen), seglen,
&concat_chksum, &concat_chksum_swapped);
concat_chksummed += seglen;
#endif /* TCP_CHECKSUM_ON_COPY */
}
pos += seglen; /* 更新游標(biāo) */
}
#endif /* !LWIP_NETIF_TX_SINGLE_PBUF */
} else {
#if TCP_OVERSIZE
LWIP_ASSERT("unsent_oversize mismatch (pcb->unsent is NULL)",
pcb->unsent_oversize == 0);
#endif /* TCP_OVERSIZE */
}
/*
* Phase 3: 創(chuàng)建一個新的segment。
* 如果last_unsent這個segment未能全部裝完本次需要發(fā)送的數(shù)據(jù),就只能新建segment了。
*
* The new segments are chained together in the local 'queue' variable,
* ready to be appended to pcb->unsent.
*/
while (pos < len) { /* 還有數(shù)據(jù)未寫入TCP發(fā)送緩沖區(qū),新建segment來發(fā)送這些數(shù)據(jù) */
struct pbuf *p;
u16_t left = len - pos;
u16_t max_len = mss_local - optlen;
u16_t seglen = LWIP_MIN(left, max_len);
#if TCP_CHECKSUM_ON_COPY
u16_t chksum = 0;
u8_t chksum_swapped = 0;
#endif /* TCP_CHECKSUM_ON_COPY */
if (apiflags & TCP_WRITE_FLAG_COPY) { /* 數(shù)據(jù)支持copy */
/* 把剩余數(shù)據(jù)copy到新的pbuf中 */
if ((p = tcp_pbuf_prealloc(PBUF_TRANSPORT, seglen + optlen, mss_local, &oversize, pcb, apiflags, queue == NULL)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n", seglen));
goto memerr;
}
LWIP_ASSERT("tcp_write: check that first pbuf can hold the complete seglen",
(p->len >= seglen));
TCP_DATA_COPY2((char *)p->payload + optlen, (const u8_t *)arg + pos, seglen, &chksum, &chksum_swapped);
} else { /* 數(shù)據(jù)不支持copy */
/* 申請PBUF_ROM類型的pbuf來holding數(shù)據(jù)。(這些pbuf的payload是有應(yīng)用層維護的,內(nèi)部無權(quán)釋放,所以只能申請PBUF_ROM類型) */
struct pbuf *p2;
#if TCP_OVERSIZE
LWIP_ASSERT("oversize == 0", oversize == 0);
#endif /* TCP_OVERSIZE */
if ((p2 = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_ROM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
/* calculate the checksum of nocopy-data */
chksum = ~inet_chksum((const u8_t *)arg + pos, seglen);
if (seglen & 1) {
chksum_swapped = 1;
chksum = SWAP_BYTES_IN_WORD(chksum);
}
#endif /* TCP_CHECKSUM_ON_COPY */
/* reference the non-volatile payload data */
((struct pbuf_rom *)p2)->payload = (const u8_t *)arg + pos; /* pbuf綁定數(shù)據(jù) */
/* 然后再申請TCP首部PBUF */
if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
/* If allocation fails, we have to deallocate the data pbuf as well. */
pbuf_free(p2);
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: could not allocate memory for header pbuf\n"));
goto memerr;
}
/* 拼接TCP首部+TCP數(shù)據(jù)區(qū) */
pbuf_cat(p/*header*/, p2/*data*/);
}
queuelen += pbuf_clen(p); /* 累計pbuf數(shù)量 */
/* 如果pbuf數(shù)量溢出,就暫停本次發(fā)送,返回內(nèi)存不足 */
if (queuelen > LWIP_MIN(TCP_SND_QUEUELEN, TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: queue too long %"U16_F" (%d)\n",
queuelen, (int)TCP_SND_QUEUELEN));
pbuf_free(p);
goto memerr;
}
/* 新建segment */
if ((seg = tcp_create_segment(pcb, p, 0, pcb->snd_lbb + pos, optflags)) == NULL) {
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = oversize; /* 更新oversize */
#endif /* TCP_OVERSIZE_DBGCHECK */
#if TCP_CHECKSUM_ON_COPY
seg->chksum = chksum;
seg->chksum_swapped = chksum_swapped;
seg->flags |= TF_SEG_DATA_CHECKSUMMED;
#endif /* TCP_CHECKSUM_ON_COPY */
/* 維護局部segment鏈 */
if (queue == NULL) {
queue = seg;
} else {
/* Attach the segment to the end of the queued segments */
LWIP_ASSERT("prev_seg != NULL", prev_seg != NULL);
prev_seg->next = seg;
}
/* remember last segment of to-be-queued data for next iteration */
prev_seg = seg;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_write: queueing %"U32_F":%"U32_F"\n",
lwip_ntohl(seg->tcphdr->seqno),
lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));
pos += seglen; /* 游標(biāo)更新 */
}
?
報文段插入發(fā)送緩沖區(qū)隊列
到此,TCP確認(rèn)了能寫入,報文段也組裝好了,可以把相關(guān)數(shù)據(jù)推送到PCB:
- 如果數(shù)據(jù)已經(jīng)添加到last_unsent的剩余空間,我們更新pbuf鏈的長度字段,讓這些數(shù)據(jù)生效。
- 把需要插入發(fā)送緩沖區(qū)中最后一個報文段的數(shù)據(jù)進行插入操作。
- 接入新的segment,如果前面兩個步驟能裝滿本次發(fā)送的數(shù)據(jù),就不需要當(dāng)前segment,即是queue為空,接入也是無效的。
/*
* Phase 1: 如果數(shù)據(jù)已經(jīng)添加到last_unsent的剩余空間,我們更新pbuf鏈的長度字段,讓這些數(shù)據(jù)生效。
*/
#if TCP_OVERSIZE
if (oversize_used > 0) {
struct pbuf *p;
/* Bump tot_len of whole chain, len of tail */
/* 更新last_unsent這個segment的pbuf字段,讓追加的數(shù)據(jù)生效 */
for (p = last_unsent->p; p; p = p->next) {
p->tot_len += oversize_used;
if (p->next == NULL) {
TCP_DATA_COPY((char *)p->payload + p->len, arg, oversize_used, last_unsent);
p->len += oversize_used;
}
}
last_unsent->len += oversize_used; /* 更新segment有效數(shù)據(jù)長度 */
#if TCP_OVERSIZE_DBGCHECK
LWIP_ASSERT("last_unsent->oversize_left >= oversize_used",
last_unsent->oversize_left >= oversize_used);
last_unsent->oversize_left -= oversize_used;
#endif /* TCP_OVERSIZE_DBGCHECK */
}
pcb->unsent_oversize = oversize; /* 更新segment有效數(shù)據(jù)長度 */
#endif /* TCP_OVERSIZE */
/*
* Phase 2: concat_p連接到last_unsent->p,除非最后的pbuf是ROM pbuf,且地址連續(xù),可以擴展以包括新數(shù)據(jù)。
*/
if (concat_p != NULL) {
LWIP_ASSERT("tcp_write: cannot concatenate when pcb->unsent is empty",
(last_unsent != NULL));
pbuf_cat(last_unsent->p, concat_p); /* 拼接pbuf */
last_unsent->len += concat_p->tot_len; /* 更新segment數(shù)據(jù)長度 */
} else if (extendlen > 0) { /* 如果是擴展原有pbuf */
struct pbuf *p;
LWIP_ASSERT("tcp_write: extension of reference requires reference",
last_unsent != NULL && last_unsent->p != NULL);
for (p = last_unsent->p; p->next != NULL; p = p->next) {
p->tot_len += extendlen;
}
p->tot_len += extendlen;
p->len += extendlen;
last_unsent->len += extendlen;
}
#if TCP_CHECKSUM_ON_COPY
if (concat_chksummed) {
LWIP_ASSERT("tcp_write: concat checksum needs concatenated data",
concat_p != NULL || extendlen > 0);
/*if concat checksumm swapped - swap it back */
if (concat_chksum_swapped) {
concat_chksum = SWAP_BYTES_IN_WORD(concat_chksum);
}
tcp_seg_add_chksum(concat_chksum, concat_chksummed, &last_unsent->chksum,
&last_unsent->chksum_swapped);
last_unsent->flags |= TF_SEG_DATA_CHECKSUMMED;
}
#endif /* TCP_CHECKSUM_ON_COPY */
/*
* Phase 3: 接入新的segment,如果前面兩個步驟能裝滿本次發(fā)送的數(shù)據(jù),就不需要當(dāng)前segment,即是queue為空,接入也是無效的。
*/
if (last_unsent == NULL) {
pcb->unsent = queue;
} else {
last_unsent->next = queue;
}
更新PCB字段:
/* 更新PCB其它字段 */
/* 更新發(fā)送緩沖區(qū) */
pcb->snd_lbb += len;
pcb->snd_buf -= len;
pcb->snd_queuelen = queuelen;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: %"S16_F" (after enqueued)\n",
pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: valid queue length",
pcb->unacked != NULL || pcb->unsent != NULL);
}
/* 如果沒有設(shè)置TCP_WRITE_FLAG_MORE,即是本次報文是在應(yīng)用層看來是一個完整的報文段,可以設(shè)置PSH標(biāo)志位 */
if (seg != NULL && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE) == 0)) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
return ERR_OK; /* 寫入TCP發(fā)送緩沖區(qū)成功 */
?
經(jīng)過上述層層代碼,就組裝好需要發(fā)送的報文段并合法推送到TCP PCB中了。
?
發(fā)送報文報文段:tcp_output()
tcp_output()
?函數(shù)是把發(fā)送緩沖區(qū)中的報文段數(shù)據(jù)發(fā)送到IP層。
err_t tcp_output(struct tcp_pcb *pcb)
?:
-
struct tcp_pcb *pcb
?:PCB。 -
返回:
-
ERR_OK
?:執(zhí)行正常。 -
another err_t
?:執(zhí)行異常。
-
-
數(shù)據(jù)安全:如果PCB正在處理接收數(shù)據(jù),這里不輸出,直接返回。相當(dāng)于給數(shù)據(jù)處理上鎖。
- 因為在PCB處理完輸入數(shù)據(jù)后,內(nèi)核會觸發(fā)調(diào)用當(dāng)前
tcp_output()
?函數(shù),所以這里返回ERR_OK
?即可。
- 因為在PCB處理完輸入數(shù)據(jù)后,內(nèi)核會觸發(fā)調(diào)用當(dāng)前
-
發(fā)送窗口:從發(fā)送窗口和擁塞窗口中取小的作為有效的發(fā)送窗口。
-
立即ACK:如果發(fā)送緩沖區(qū)中沒有數(shù)據(jù)需要發(fā)送,且標(biāo)記了
TF_ACK_NOW
?,則先響應(yīng)一個純ACK回去先。然后return
?。 -
如果發(fā)送緩沖區(qū)中有數(shù)據(jù)需要發(fā)送,先打印相關(guān)信息。繼續(xù):
-
匹配網(wǎng)卡:通過
tcp_route()
?匹配出口網(wǎng)卡。匹配失敗,也就發(fā)送失敗。- 匹配邏輯:參考前面的IP章節(jié)。
-
確定TCP源IP:如果PCB還沒綁定本地IP,則先綁定本地IP地址,因為組TCP包需要。
-
窗口溢出檢查:如果當(dāng)前報文段size超出當(dāng)前發(fā)送窗口,則本地報文段暫不發(fā)送,且需要以下操作:
- 零窗口探查:如果對端通告過來的窗口不夠大,又沒有飛行數(shù)據(jù),則需要開啟堅持定時器,并按規(guī)則發(fā)送零窗口探查報文。
- 響應(yīng)ACK:標(biāo)記了
TF_ACK_NOW
?,則響應(yīng)一個純ACK回去。
-
滿足發(fā)送條件,關(guān)閉堅持定時器。
-
先從
pcb->unacked
?找出空中數(shù)據(jù)的最后一個報文段,方便當(dāng)前報文段發(fā)送出去后,直接接入空中數(shù)據(jù)隊列中。 -
使用
while()
?方式把窗口內(nèi)能發(fā)送的數(shù)據(jù)循環(huán)發(fā)送出去。- 發(fā)送時經(jīng)過nagle算法。如果nagle算法生效,則延遲發(fā)送,
break
?退出。 - 調(diào)試:打印窗口信息。
- 調(diào)用
tcp_output_segment()
?發(fā)送報文段。 - 發(fā)送成功后,且該報文段占用
SEQ
?號,則把該報文段從pcb->unsent
?遷入空中數(shù)據(jù)隊列pcb->unacked
?。并且清除該報文段的oversize
?。如果不占用SEQ
?號,直接釋放接口。
- 發(fā)送時經(jīng)過nagle算法。如果nagle算法生效,則延遲發(fā)送,
/**
* @ingroup tcp_raw
* Find out what we can send and send it
*
* @param pcb Protocol control block for the TCP connection to send data
* @return ERR_OK if data has been sent or nothing to send
* another err_t on error
*
* 能發(fā)啥就發(fā)啥,如發(fā)送ACK、發(fā)送緩沖區(qū)中的數(shù)據(jù)。
*
*/
err_t
tcp_output(struct tcp_pcb *pcb)
{
struct tcp_seg *seg, *useg;
u32_t wnd, snd_nxt;
err_t err;
struct netif *netif;
#if TCP_CWND_DEBUG
s16_t i = 0;
#endif /* TCP_CWND_DEBUG */
LWIP_ASSERT_CORE_LOCKED();
LWIP_ASSERT("tcp_output: invalid pcb", pcb != NULL);
/* pcb->state LISTEN not allowed here */
LWIP_ASSERT("don't call tcp_output for listen-pcbs",
pcb->state != LISTEN);
/* 如果PCB正在處理輸入數(shù)據(jù),這里不輸出,直接返回。相當(dāng)于給數(shù)據(jù)處理上鎖。
在PCB處理完輸入數(shù)據(jù)后,內(nèi)核會觸發(fā)調(diào)用當(dāng)前tcp_output()函數(shù),所以這里返回ERR_OK即可。 */
if (tcp_input_pcb == pcb) {
return ERR_OK;
}
/* 從發(fā)送窗口和擁塞窗口中取小的作為有效的發(fā)送窗口 */
wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);
/* 取出未發(fā)送報文隊列 */
seg = pcb->unsent;
if (seg == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: nothing to send (%p)\n",
(void *)pcb->unsent));
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F
", cwnd %"TCPWNDSIZE_F", wnd %"U32_F
", seg == NULL, ack %"U32_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd, pcb->lastack));
/* 如果標(biāo)記TF_ACK_NOW,即表示立即回ACK。如果沒有數(shù)據(jù),則構(gòu)造一個純粹的ACK發(fā)出去。 */
if (pcb->flags & TF_ACK_NOW) {
return tcp_send_empty_ack(pcb);
}
/* 如果不用發(fā)送,則退出 */
goto output_done;
} else {
/* 如果有數(shù)據(jù)需要發(fā)送,先打印相關(guān)信息 */
LWIP_DEBUGF(TCP_CWND_DEBUG,
("tcp_output: snd_wnd %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F
", effwnd %"U32_F", seq %"U32_F", ack %"U32_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd,
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len,
lwip_ntohl(seg->tcphdr->seqno), pcb->lastack));
}
/* 匹配出口網(wǎng)卡 */
netif = tcp_route(pcb, &pcb->local_ip, &pcb->remote_ip);
if (netif == NULL) {
/* 匹配不了網(wǎng)卡,就發(fā)不出去 */
return ERR_RTE;
}
/* 如果當(dāng)前連接沒有配置本地IP地址,就把匹配到的網(wǎng)卡的IP地址作為當(dāng)前連接的本地IP地址。 */
if (ip_addr_isany(&pcb->local_ip)) {
const ip_addr_t *local_ip = ip_netif_get_local_ip(netif, &pcb->remote_ip);
if (local_ip == NULL) {
return ERR_RTE;
}
ip_addr_copy(pcb->local_ip, *local_ip);
}
/* 先處理當(dāng)前報文段是否符合在窗口范圍內(nèi) */
if (lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd) {
/* 本報文段超窗口范圍了 */
/* 如果發(fā)送窗口比擁塞窗口小 && 沒有飛行中的數(shù)據(jù)(就是后面可能不會自動觸發(fā)發(fā)送數(shù)據(jù)) && 堅持定時器沒有開啟 */
/* 為了防止對端響應(yīng)附帶窗口信息的ACK丟失,這里需要開啟堅持定時器,定期詢問對方的接收窗口 */
if (wnd == pcb->snd_wnd && pcb->unacked == NULL && pcb->persist_backoff == 0) {
pcb->persist_cnt = 0;
pcb->persist_backoff = 1;
pcb->persist_probe = 0;
}
/* 如果標(biāo)記了TF_ACK_NOW,則需要立即響應(yīng)一個ACK */
if (pcb->flags & TF_ACK_NOW) {
return tcp_send_empty_ack(pcb);
}
goto output_done;
}
/* 窗口滿足發(fā)送數(shù)據(jù)條件,關(guān)閉堅持定時器 */
pcb->persist_backoff = 0;
/* 找出已發(fā)送,但是還沒收到ACK的最后一個報文段 */
useg = pcb->unacked;
if (useg != NULL) {
for (; useg->next != NULL; useg = useg->next);
}
/* 把在窗口內(nèi)未發(fā)送的數(shù)據(jù)發(fā)出去 */
while (seg != NULL &&
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {
LWIP_ASSERT("RST not expected here!",
(TCPH_FLAGS(seg->tcphdr) & TCP_RST) == 0);
/* 如果nagle算法生效,則延遲發(fā)送。
* 打破nagle算法生效的條件(即是nagle生效,也要馬上發(fā)送的條件)之一:
* - 如果之前調(diào)用tcp_write()時有內(nèi)存錯誤未能成功發(fā)送,為了防止延遲ACK超時,需要立即發(fā)送。
* - 如果FIN已經(jīng)在隊列中了,則沒必要再延遲發(fā)送了,立即把數(shù)據(jù)發(fā)出,加速閉環(huán)。
* 注意:SYN一直都是單獨報文段的。所以要么不存在SYN,如存在未發(fā)送數(shù)據(jù)seg->next != NULL; 要么只存在SYN,即是還沒有發(fā)送數(shù)據(jù),如pcb->unacked == NULL;。
* 注意:RST是不會通過tcp_wirte()和tcp_output()發(fā)送的。
*/
if ((tcp_do_output_nagle(pcb) == 0) &&
((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)) {
/* nagle算法生效 && 上次發(fā)送內(nèi)存正常 && 還沒有FIN */
break;
}
#if TCP_CWND_DEBUG /* 窗口信息打印 */
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F", effwnd %"U32_F", seq %"U32_F", ack %"U32_F", i %"S16_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd,
lwip_ntohl(seg->tcphdr->seqno) + seg->len -
pcb->lastack,
lwip_ntohl(seg->tcphdr->seqno), pcb->lastack, i));
++i;
#endif /* TCP_CWND_DEBUG */
if (pcb->state != SYN_SENT) { /* 除了發(fā)送握手第一步的SYN外,其他報文段都需要攜帶ACK。 */
TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
}
/* 把報文段發(fā)送出去 */
err = tcp_output_segment(seg, pcb, netif);
if (err != ERR_OK) {
/* 發(fā)送失敗,把PCB標(biāo)記為TF_NAGLEMEMERR,下次遇到nagle算法時,不要延遲,需要立即 */
tcp_set_flags(pcb, TF_NAGLEMEMERR);
return err;
}
/* 到這里,發(fā)送成功 */
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = 0; /* 清空報文段溢出空間字段。表示不允許后續(xù)報文并入當(dāng)前報文段了。 */
#endif /* TCP_OVERSIZE_DBGCHECK */
/* 把已發(fā)送的報文段從未發(fā)送隊列中移除 */
pcb->unsent = seg->next;
if (pcb->state != SYN_SENT) { /* 因為SYN_SENT的SYN包不含ACK,無需清空 */
/* 清空ACK標(biāo)志位 */
tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW);
}
/* 更新下一個需要發(fā)送的seq號 */
snd_nxt = lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);
if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) {
pcb->snd_nxt = snd_nxt;
}
/* 如果發(fā)送的報文段中包含TCP數(shù)據(jù)(占用seq號),則需要把該報文段插入pcb->unacked隊列:按seq號升序插入 */
if (TCP_TCPLEN(seg) > 0) {
seg->next = NULL;
/* unacked list is empty? */
if (pcb->unacked == NULL) {
pcb->unacked = seg;
useg = seg;
/* unacked list is not empty? */
} else {
/* In the case of fast retransmit, the packet should not go to the tail
* of the unacked queue, but rather somewhere before it. We need to check for
* this case. -STJ Jul 27, 2004 */
if (TCP_SEQ_LT(lwip_ntohl(seg->tcphdr->seqno), lwip_ntohl(useg->tcphdr->seqno))) {
/* 如果是重傳的報文段,入隊時也需要按序入隊 */
/* add segment to before tail of unacked list, keeping the list sorted */
struct tcp_seg **cur_seg = &(pcb->unacked);
while (*cur_seg &&
TCP_SEQ_LT(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_ntohl(seg->tcphdr->seqno))) {
cur_seg = &((*cur_seg)->next );
}
seg->next = (*cur_seg);
(*cur_seg) = seg;
} else {
/* add segment to tail of unacked list */
useg->next = seg;
useg = useg->next;
}
}
} else {
/* 如果當(dāng)前報文段不含TCP數(shù)據(jù)(不占用seq號),直接釋放。注意:SYN和FIN會占用一個seq號 */
tcp_seg_free(seg);
}
seg = pcb->unsent;
}
#if TCP_OVERSIZE
if (pcb->unsent == NULL) {
/* last unsent has been removed, reset unsent_oversize */
pcb->unsent_oversize = 0;
}
#endif /* TCP_OVERSIZE */
output_done:
tcp_clear_flags(pcb, TF_NAGLEMEMERR);
return ERR_OK;
}
?
TCP接收數(shù)據(jù)
接收數(shù)據(jù)的處理更加復(fù)雜,這部分需要對著本筆記自己認(rèn)真分析源碼。
TCP報文輸入數(shù)據(jù)流:ip_input()
??-->tcp_input()
??-->tcp_process()
??-->tcp_receive()
??。
?
tcp_input()
?:主要是層級處理,從IP層獲取TCP報文,進行簡單的分析,然后傳給tcp_process()
?做TCP協(xié)議算法處理。等待TCP協(xié)議算法處理完畢后,把收到的數(shù)據(jù)通過上層注冊到TCP PCB的接收回調(diào)函數(shù)遞交到上層,然后釋放相關(guān)資源。
?
tcp_process()
?:根據(jù)TCP狀態(tài)機處理接收到的TCP報文段。如果需要接納當(dāng)前TCP報文段,可通過tcp_receive()
?函數(shù)實現(xiàn)。
?
tcp_receive()
?:該函數(shù)就是處理接收TCP報文的協(xié)議算法核心。內(nèi)含窗口更新、RTT&RTO計算、報文段插入接收緩沖區(qū)、處理ACK
?的空中數(shù)據(jù)等等。
?
TCP入口處理:tcp_input()
tcp_input()
?該函數(shù)是被IP層調(diào)用的,使用該函數(shù)的主要作用是接收TCP數(shù)據(jù),組裝TCP報文段,匹配PCB,傳入TCP更深層處理。
?
void tcp_input(struct pbuf *p, struct netif *inp)
?:
-
struct pbuf *p
?:收到的pbuf。 -
struct netif *inp
?:接收到該pbuf的網(wǎng)卡。
?
TCP報文合法性檢查
TCP報文長度檢查:
/* pbuf長度檢查 */
if (p->len < TCP_HLEN) {
/* drop short packets */
TCP_STATS_INC(tcp.lenerr);
goto dropped;
}
/* 檢查TCP首部長度字段 */
hdrlen_bytes = TCPH_HDRLEN_BYTES(tcphdr);
if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) {
TCP_STATS_INC(tcp.lenerr);
goto dropped;
}
不處理廣播包、組播包的TCP報文:
/* 不處理廣播包、組播包的TCP報文 */
if (ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif()) ||
ip_addr_ismulticast(ip_current_dest_addr())) {
TCP_STATS_INC(tcp.proterr);
goto dropped;
}
校驗和:
#if CHECKSUM_CHECK_TCP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_TCP) {
/* Verify TCP checksum. */
/* TCP校驗和字段校驗。注意:包括TCP偽首部 */
u16_t chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
ip_current_src_addr(), ip_current_dest_addr());
if (chksum != 0) {
tcp_debug_print(tcphdr);
TCP_STATS_INC(tcp.chkerr);
goto dropped;
}
}
#endif /* CHECKSUM_CHECK_TCP */
?
?
提取TCP首部
從IP層獲取的數(shù)據(jù)pbuf的payload是指向TCP首部的,來的TCP層,我們應(yīng)該提取TCP首部,然后payload指向數(shù)據(jù)區(qū),隱藏TCP首部。
?
獲取TCP首部指針和長度,pbuf隱藏TCP首部:
- 這里需要注意的是,TCP首部可能分布在兩個buf中,所以需要特殊處理。
- lwip目前只支持TCP首部(不包括選項字段)那部分必須都在同一個pbuf中。
/* 提取TCP首部和選項字段后,pbuf payload偏移到TCP數(shù)據(jù)區(qū) */
/* 獲取選項字段長度 */
tcphdr_optlen = (u16_t)(hdrlen_bytes - TCP_HLEN);
tcphdr_opt2 = NULL;
if (p->len >= hdrlen_bytes) {
/* 所有選項字段都在第一個pbuf中 */
tcphdr_opt1len = tcphdr_optlen;
pbuf_remove_header(p, hdrlen_bytes); /* cannot fail */
} else {
u16_t opt2len;
/* TCP首部是全都在第一個pbuf中的,而選項字段不一定 */
/* 先偏移到第一個選項字段 */
pbuf_remove_header(p, TCP_HLEN);
tcphdr_opt1len = p->len; /* 選項字段在當(dāng)前pbuf的長度 */
opt2len = (u16_t)(tcphdr_optlen - tcphdr_opt1len); /* 選項字段在其他pbuf的長度 */
/* 當(dāng)前pbuf不含TCP數(shù)據(jù)區(qū),所以全部隱藏 */
pbuf_remove_header(p, tcphdr_opt1len);
/* 如果選項字段超出了第二個pbuf,lwip不支持 */
if (opt2len > p->next->len) {
/* drop short packets */
TCP_STATS_INC(tcp.lenerr);
goto dropped;
}
/* 獲取TCP選項字段在第二個pbuf中的位置 */
tcphdr_opt2 = (u8_t *)p->next->payload;
/* 隱藏TCP首部,使pbuf payload指向TCP數(shù)據(jù)區(qū) */
pbuf_remove_header(p->next, opt2len);
p->tot_len = (u16_t)(p->tot_len - opt2len);
}
?
pbuf隱藏TCP首部成功后,把TCP首部信息提取到全局參數(shù)中,這些參數(shù)作為TCP本次輸入處理的重要數(shù)據(jù):
/* 提取TCP首部剛剛字段,并轉(zhuǎn)換為主機字節(jié)序 */
tcphdr->src = lwip_ntohs(tcphdr->src);
tcphdr->dest = lwip_ntohs(tcphdr->dest);
seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno);
ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno);
tcphdr->wnd = lwip_ntohs(tcphdr->wnd);
flags = TCPH_FLAGS(tcphdr);
tcplen = p->tot_len;
if (flags & (TCP_FIN | TCP_SYN)) {
/* SYN和FIN占用一個seq號,需要ACK,計入TCP報文數(shù)據(jù)長度 */
tcplen++;
if (tcplen < p->tot_len) {
/* TCP報文數(shù)據(jù)區(qū)長度溢出的話,不處理當(dāng)前TCP報文 */
TCP_STATS_INC(tcp.lenerr);
goto dropped;
}
}
?
網(wǎng)卡匹配優(yōu)先級&快速遍歷算法導(dǎo)讀
匹配TCP PCB順序:穩(wěn)定態(tài)PCB、TIME_WAIT態(tài)PCB、監(jiān)聽?wèi)B(tài)PCB。
匹配成功后,表示PCB活躍,將活躍的移到對應(yīng)狀態(tài)鏈表最前面,方便下次遍歷。
?
PCB匹配之穩(wěn)定態(tài)tcp_active_pcbs
檢查完從IP層傳入的TCP報文是協(xié)議合法的數(shù)據(jù)包,且提取、準(zhǔn)備好TCP首部各字段數(shù)據(jù)后,我們就要根據(jù)這些數(shù)據(jù)進行匹配PCB,把當(dāng)前TCP報文給到對應(yīng)的IP&PORT應(yīng)用服務(wù)中。
?
先從tcp_active_pcbs
?,因為該鏈表記錄的是可收發(fā)數(shù)據(jù)的鏈接:
-
接收到當(dāng)前TCP報文的網(wǎng)卡 與 PCB綁定的網(wǎng)卡進行匹配。
-
IP&PORT服務(wù)匹配:
- 報文源IP 和 PCB遠端IP;
- 報文源端口 和 PCB遠端端口;
- 報文遠端IP 和 PCB源IP;
- 報文遠端端口 和 PCB源端口。
/* 遍歷穩(wěn)定態(tài)的TCP BCP鏈表,讓TCP報文匹配。支持多路復(fù)用。 */
for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
/* 網(wǎng)卡匹配:檢查當(dāng)前TCP PCB綁定的網(wǎng)卡和接收TCP報文的網(wǎng)卡是否一致 */
if ((pcb->netif_idx != NETIF_NO_INDEX) &&
(pcb->netif_idx != netif_get_index(ip_data.current_input_netif))) {
prev = pcb;
continue;
}
/* 服務(wù)匹配:TCP報文 和 TCP PCB
- 報文源IP 和 PCB遠端IP
- 報文源端口 和 PCB遠端端口
- 報文遠端IP 和 PCB源IP
- 報文遠端端口 和 PCB源端口 */
if (pcb->remote_port == tcphdr->src &&
pcb->local_port == tcphdr->dest &&
ip_addr_eq(&pcb->remote_ip, ip_current_src_addr()) &&
ip_addr_eq(&pcb->local_ip, ip_current_dest_addr())) {
/* 匹配成功后,把當(dāng)前PCB拉到最前,緊接著有數(shù)據(jù)進來后能快速遍歷 */
if (prev != NULL) {
prev->next = pcb->next;
pcb->next = tcp_active_pcbs;
tcp_active_pcbs = pcb;
} else {
TCP_STATS_INC(tcp.cachehit);
}
break;
}
/* 未能匹配成功,遍歷下一個TCP PCB */
prev = pcb;
}
?
PCB匹配之TIME_WAIT態(tài)tcp_tw_pcbs
如果穩(wěn)定態(tài)的TCP PCB匹配失敗,則去遍歷匹配TIME_WAIT狀態(tài)的TCP PCB。
TIME_WAIT態(tài)的PCB也可能接收到TCP報文的,如:
- 揮手時,最后一次揮手
ACK
?沒有到達對端,所以對端重發(fā)了FIN
?過來。 - 也可能是揮手前發(fā)送的TCP報文遲來了。
?
tcp_tw_pcbs
?鏈表記錄的是TIME_WAIT狀態(tài)的TCP:
-
網(wǎng)卡匹配。
-
IP&PORT服務(wù)匹配。
- 報文源IP 和 PCB遠端IP;
- 報文源端口 和 PCB遠端端口;
- 報文遠端IP 和 PCB源IP;
- 報文遠端端口 和 PCB源端口。
/* 如果穩(wěn)定態(tài)的TCP PCB匹配失敗,則去遍歷匹配TIME_WAIT狀態(tài)的TCP PCB */
for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
/* 網(wǎng)卡匹配 */
if ((pcb->netif_idx != NETIF_NO_INDEX) &&
(pcb->netif_idx != netif_get_index(ip_data.current_input_netif))) {
continue;
}
/* 服務(wù)匹配 */
if (pcb->remote_port == tcphdr->src &&
pcb->local_port == tcphdr->dest &&
ip_addr_eq(&pcb->remote_ip, ip_current_src_addr()) &&
ip_addr_eq(&pcb->local_ip, ip_current_dest_addr())) {
#ifdef LWIP_HOOK_TCP_INPACKET_PCB
/* 鉤子函數(shù):匹配成功后優(yōu)先傳入鉤子函數(shù)處理 */
if (LWIP_HOOK_TCP_INPACKET_PCB(pcb, tcphdr, tcphdr_optlen, tcphdr_opt1len,
tcphdr_opt2, p) == ERR_OK)
#endif
{
/* 由該函數(shù)處理 */
tcp_timewait_input(pcb);
}
/* 釋放當(dāng)前TCP報文 */
pbuf_free(p);
return;
}
?
PCB匹配之監(jiān)聽?wèi)B(tài)tcp_listen_pcbs
如果前面都沒有匹配成功,那就需要檢查是否是客戶端的握手請求報文。
?
tcp_listen_pcbs.listen_pcbs
?記錄的是能接收連接的TCP服務(wù)端:
-
網(wǎng)卡匹配。
-
IP&PORT服務(wù)匹配。因為監(jiān)聽?wèi)B(tài)的PCB是監(jiān)聽新接入的連接,這些連接在我們本地還不知道其IP&PORT,所以我們只需要匹配他們接入的目標(biāo)服務(wù)即可:
- 報文遠端IP 和 PCB源IP;
- 報文遠端端口 和 PCB源端口。
/* 依然沒有匹配成功,則匹配監(jiān)聽?wèi)B(tài)的TCP PCB。檢查是否是有TCP客戶端接入 */
prev = NULL;
for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
/* 網(wǎng)卡匹配 */
if ((lpcb->netif_idx != NETIF_NO_INDEX) &&
(lpcb->netif_idx != netif_get_index(ip_data.current_input_netif))) {
prev = (struct tcp_pcb *)lpcb;
continue;
}
/* 服務(wù)匹配:
- 報文遠端IP 和PCB源IP (注意任意IP)
- 報文遠端端口 和 PCB源端口 */
if (lpcb->local_port == tcphdr->dest) {
if (IP_IS_ANY_TYPE_VAL(lpcb->local_ip)) {
/* 匹配:PCB為任意版本IP,匹配成功 */
#if SO_REUSE /* 支持端口復(fù)用功能 */
lpcb_any = lpcb;
lpcb_prev = prev;
#else /* SO_REUSE */
break;
#endif /* SO_REUSE */
} else if (IP_ADDR_PCB_VERSION_MATCH_EXACT(lpcb, ip_current_dest_addr())) {
if (ip_addr_eq(&lpcb->local_ip, ip_current_dest_addr())) {
/* 精確匹配成功:IP一致 */
break;
} else if (ip_addr_isany(&lpcb->local_ip)) {
/* 匹配:IP版本匹配 + PCB還是任意IP */
#if SO_REUSE /* 支持端口復(fù)用功能 */
lpcb_any = lpcb;
lpcb_prev = prev;
#else /* SO_REUSE */
break;
#endif /* SO_REUSE */
}
}
}
/* 匹配失敗,遍歷下一個PCB */
prev = (struct tcp_pcb *)lpcb;
}
監(jiān)聽鏈表匹配成功后,就是要接入我們的TCP服務(wù)端,傳入tcp_listen_input()
?監(jiān)聽處理即可,處理完就退出:
-
tcp_listen_input()
?函數(shù)處理我們在后面章節(jié)分析。
if (lpcb != NULL) {
/* 在監(jiān)聽?wèi)B(tài)鏈表中匹配到PCB的話,將其移到最前面,以便下次遍歷 */
if (prev != NULL) {
((struct tcp_pcb_listen *)prev)->next = lpcb->next;
/* our successor is the remainder of the listening list */
lpcb->next = tcp_listen_pcbs.listen_pcbs;
/* put this listening pcb at the head of the listening list */
tcp_listen_pcbs.listen_pcbs = lpcb;
} else {
TCP_STATS_INC(tcp.cachehit);
}
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for LISTENing connection.\n"));
#ifdef LWIP_HOOK_TCP_INPACKET_PCB
/* 鉤子函數(shù):匹配成功后優(yōu)先傳入鉤子函數(shù)處理 */
if (LWIP_HOOK_TCP_INPACKET_PCB((struct tcp_pcb *)lpcb, tcphdr, tcphdr_optlen,
tcphdr_opt1len, tcphdr_opt2, p) == ERR_OK)
#endif
{
/* 傳入監(jiān)聽listen處理 */
tcp_listen_input(lpcb);
}
/* 當(dāng)前pbuf處理完畢,釋放 */
pbuf_free(p);
return;
}
?
傳入鉤子處理
穩(wěn)定態(tài)匹配成功后才會來到這。
在TCP處理當(dāng)前收到的TCP報文前,先傳入對應(yīng)鉤子函數(shù),在回來TCP處理:
- 用戶可以在這鉤子函數(shù)里實現(xiàn)自己的一些特性,比如入站記錄,TCP防火墻等等。
#ifdef LWIP_HOOK_TCP_INPACKET_PCB
/* 鉤子函數(shù):匹配成功后優(yōu)先傳入鉤子函數(shù)處理 */
if ((pcb != NULL) && LWIP_HOOK_TCP_INPACKET_PCB(pcb, tcphdr, tcphdr_optlen,
tcphdr_opt1len, tcphdr_opt2, p) != ERR_OK) {
pbuf_free(p);
return;
}
#endif
?
處理TCP報文段
穩(wěn)定態(tài)匹配成功后,鉤子函數(shù)沒有過濾掉這次收到的TCP報文段,我們就需要進入TCP協(xié)議算法處理該報文:
構(gòu)建segment數(shù)據(jù)結(jié)構(gòu):
/* 構(gòu)建tcp_seg數(shù)據(jù)結(jié)構(gòu) */
inseg.next = NULL;
inseg.len = p->tot_len;
inseg.p = p;
inseg.tcphdr = tcphdr;
/* 初始化相關(guān)全局變量 */
recv_data = NULL;
recv_flags = 0;
recv_acked = 0;
檢查PSH
?標(biāo)志位:
/* 檢查PSH標(biāo)志位 */
if (flags & TCP_PSH) {
p->flags |= PBUF_FLAG_PUSH;
}
?
如果當(dāng)前TCP接收緩沖區(qū)中還有數(shù)據(jù)沒有被應(yīng)用層讀取,先嘗試遞交到上層先:
- 調(diào)用
tcp_process_refused_data()
?實現(xiàn)。 - (如果檢查發(fā)現(xiàn)這個連接已經(jīng)被關(guān)閉了) 或 (上層沒有把緩沖區(qū)數(shù)據(jù)全部讀走 && 當(dāng)前收到的報文包含數(shù)據(jù)) 就忽略當(dāng)前接收到的TCP報文段。如果接收通告窗口為0,還需要響應(yīng)一個純
ACK
?到對端。
/* 如果PCB的接收緩沖區(qū)pcb->refused_data還有數(shù)據(jù),則先嘗試遞交到上層 */
if (pcb->refused_data != NULL) {
if ((tcp_process_refused_data(pcb) == ERR_ABRT) ||
((pcb->refused_data != NULL) && (tcplen > 0))) {
/* (PCB已經(jīng)被終止處理) || (上層沒有把緩沖區(qū)數(shù)據(jù)全部讀走 && 當(dāng)前收到的報文包含數(shù)據(jù)) 跳到aborted */
if (pcb->rcv_ann_wnd == 0) {
/* 發(fā)送一個純ACK,通告我方接收窗口為0 */
tcp_send_empty_ack(pcb);
}
TCP_STATS_INC(tcp.drop);
MIB2_STATS_INC(mib2.tcpinerrs);
goto aborted;
}
}
?
傳給tcp_process()
?處理:
- 該函數(shù)會根據(jù)狀態(tài)機處理,然后也會調(diào)用
tcp_receive()
?函數(shù)。
/* 鎖定當(dāng)前PCB,處理接收到的TCP報文 */
tcp_input_pcb = pcb;
err = tcp_process(pcb);
?
根據(jù)處理結(jié)果進行收尾操作:
-
TF_RESET
?:收到RST,就要釋放當(dāng)前TCP PCB。 -
如果本地收到的TCP報文段
ACK
?了更多的空中數(shù)據(jù),那么本地發(fā)送緩沖區(qū)就會釋放一些被ACK了的數(shù)據(jù)空間,所以我們需要調(diào)用上層注冊的可寫回調(diào)函數(shù)pcb->sent()
?,通知上層,當(dāng)前TCP發(fā)送緩沖區(qū)可寫。 -
調(diào)用上層注冊的接收回調(diào)函數(shù)
pcb->recv()
?,把接收到的數(shù)據(jù)遞交到上層。- 如果應(yīng)用層沒有接收這些數(shù)據(jù),但又不是終止連接的情況下,把這些數(shù)據(jù)緩存到
pcb->refused_data
?。
- 如果應(yīng)用層沒有接收這些數(shù)據(jù),但又不是終止連接的情況下,把這些數(shù)據(jù)緩存到
-
TF_CLOSED
?:如果收到FIN,并且揮手完成,則釋放當(dāng)前TCP PCB。 -
處理完接收到的TCP報文段后,還需要調(diào)用
tcp_output()
?觸發(fā)下發(fā)送數(shù)據(jù)的業(yè)務(wù)。發(fā)送緩沖區(qū)中的報文段,如果有ACK
?響應(yīng)需求,也能立即響應(yīng)。 -
注意:上述的數(shù)據(jù)size如果在開啟了窗口擴大系數(shù)后
LWIP_WND_SCALE
?或亂序報文TCP_QUEUE_OOSEQ
?后,需要在16bit溢出內(nèi)循環(huán)處理,即是每次處理不能超時16bit的size,如果總報文段超過了,就需要while()循環(huán)處理。
/* 如果err為ERR_ABRT,表示tcp_abort()被調(diào)用了,并且TCP PCB已經(jīng)被free了,這樣的話,應(yīng)該do nothing。 */
if (err != ERR_ABRT) {
if (recv_flags & TF_RESET) {
/* TF_RESET意味著當(dāng)前連接已經(jīng)被對端RST了。
我們應(yīng)該先回調(diào)一個ERR_RST到應(yīng)用層,然后再釋放PCB。 */
TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);
tcp_pcb_remove(&tcp_active_pcbs, pcb);
tcp_free(pcb);
} else {
err = ERR_OK;
/* 在這里,如果應(yīng)用層注冊了->sent()回調(diào)函數(shù),當(dāng)發(fā)送緩沖區(qū)有新的可用空間時,調(diào)用->sent()這個回調(diào)函數(shù)。 */
if (recv_acked > 0) {
/* 對端ACK了一些數(shù)據(jù),那么本地發(fā)送緩沖區(qū)就會釋放一些被ACK了的數(shù)據(jù)空間 */
u16_t acked16;
#if LWIP_WND_SCALE /* WSOPT窗口擴大 */
/* 如果開了窗口擴大功能,那發(fā)送窗口可能會超過u16_t,對端ACK的量也可能超過u16_t。
但是sent()回調(diào)函數(shù)中的acked參數(shù)是u16_t,所以需要分多次處理。 */
u32_t acked = recv_acked;
while (acked > 0) { /* 循環(huán)處理 */
acked16 = (u16_t)LWIP_MIN(acked, 0xffffu); /* 限溢出 */
acked -= acked16; /* 本次循環(huán)處理的量 */
#else
{
acked16 = recv_acked; /* 沒有開啟WSOPT窗口擴大功能,則直接賦值,因為兩者都是u16_t */
#endif
TCP_EVENT_SENT(pcb, (u16_t)acked16, err); /* 調(diào)用->sent()回調(diào) */
if (err == ERR_ABRT) { /* ERR_ABRT說明當(dāng)前PCB異常,需要釋放當(dāng)前連接及其資源 */
goto aborted;
}
}
recv_acked = 0; /* 處理完畢,清空ACK量 */
}
/* 如果TF_CLOSED被標(biāo)記,則釋放當(dāng)前PCB資源 */
if (tcp_input_delayed_close(pcb)) {
goto aborted;
}
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
/* [lzm][lab][ooseq][220927][1020] */
/* 開了OOSEQ && 開了窗口擴大系數(shù) recv_data 拼接上ooseq數(shù)據(jù)后可能會超過64K(u16_t),recv_data->tot_len也可能已經(jīng)溢出了,
但是不用擔(dān)心,pbuf_split_64k()能避開這個溢出。 */
while (recv_data != NULL) { /* 循環(huán)提取,每次提取不超過64K */
struct pbuf *rest = NULL;
pbuf_split_64k(recv_data, &rest); /* 截斷,限制pbuf->payload數(shù)據(jù)不超過64K */
#else /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
/* 如果沒有OOSEQ,pbuf->tot_len是不會溢出的,因為底層創(chuàng)建時就限制在u16_t */
/* 如果沒有窗口擴大系數(shù),pbuf->tot_len也是不會溢出的,因為窗口限制就在u16_t */
if (recv_data != NULL) {
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL);
/* 檢查RX */
if (pcb->flags & TF_RXCLOSED) {
/* 如果RX已經(jīng)被應(yīng)用層關(guān)閉了,還收到對端新發(fā)來的數(shù)據(jù),則直接響應(yīng)RST,關(guān)閉當(dāng)前連接 */
/* 釋放這些數(shù)據(jù) */
pbuf_free(recv_data);
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
if (rest != NULL) {
pbuf_free(rest);
}
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
tcp_abort(pcb); /* RST,終止當(dāng)前連接 */
goto aborted;
}
/* 調(diào)用->recv()回調(diào)通知應(yīng)用層接收數(shù)據(jù) */
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
if (err == ERR_ABRT) {
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
if (rest != NULL) { /* 終止連接,釋放未遞交到應(yīng)用層的數(shù)據(jù) */
pbuf_free(rest);
}
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
goto aborted;
}
/* 如果應(yīng)用層沒有接收這些數(shù)據(jù),但又不是終止連接的情況下,把這些數(shù)據(jù)緩存到pcb->refused_data */
if (err != ERR_OK) {
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
if (rest != NULL) {
pbuf_cat(recv_data, rest);
}
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
pcb->refused_data = recv_data;
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
break; /* 退出遍歷 */
} else {
/* 如果遞交到應(yīng)用層成功,則循環(huán)處理剩余數(shù)據(jù) */
recv_data = rest;
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
}
} /* 遞交到應(yīng)用層處理完畢 */
/* 如果收到了FIN,數(shù)據(jù)都已經(jīng)全部遞交到應(yīng)用層了,就調(diào)用->recv(),buffer參數(shù)為NULL,回調(diào)表示需要關(guān)閉當(dāng)前連接(EOF)。
數(shù)據(jù)還沒有全部遞交到應(yīng)用層,就在這些數(shù)據(jù)的pbuf上標(biāo)記收到FIN。 */
if (recv_flags & TF_GOT_FIN) {
if (pcb->refused_data != NULL) {
/* 還有數(shù)據(jù)未遞交到應(yīng)用層,在這些數(shù)據(jù)標(biāo)記FIN即可 */
pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN;
} else {
/* 如果全部數(shù)據(jù)都遞交到應(yīng)用層了,就回調(diào)到應(yīng)用層,需要close()當(dāng)前連接 */
if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) {
pcb->rcv_wnd++; /* FIN也占用一個SEQ,我們接收處理了這個FIN,接收窗口要+1。(應(yīng)用層只管TCP數(shù)據(jù)) */
}
TCP_EVENT_CLOSED(pcb, err);
if (err == ERR_ABRT) {
goto aborted;
}
}
}
tcp_input_pcb = NULL; /* 當(dāng)前收到的報文段處理完畢,釋放全局變量資源 */
/* 如果TF_CLOSED被標(biāo)記,則釋放當(dāng)前PCB資源 */
if (tcp_input_delayed_close(pcb)) {
goto aborted;
}
/* 觸發(fā)下發(fā)送數(shù)據(jù)的業(yè)務(wù)。發(fā)送緩沖區(qū)中的報文段 */
tcp_output(pcb);
#if TCP_INPUT_DEBUG
#if TCP_DEBUG
/* 調(diào)試LOG */
tcp_debug_print_state(pcb->state);
#endif /* TCP_DEBUG */
#endif /* TCP_INPUT_DEBUG */
}
}
?
TCP報文段協(xié)議處理:tcp_process()
TCP報文輸入數(shù)據(jù)流:ip_input()
?-->tcp_input()
?-->tcp_process()
?-->tcp_receive()
?。
來到tcp_process()
?,就是按狀態(tài)機處理。
?
大概的處理步驟就是:
- 先判斷是否是RST報文,如果是合法的RST報文,就復(fù)位連接。如果不是合法RST,就忽略不處理。
- 如果是SYN握手包,判斷是否是正常狀態(tài)下的SYN包。因為只有SYN_SENT狀態(tài)下才會收到,其它狀態(tài)要么是重傳要么是異常的。
- 重置保活超時計時器、堅持計時器及其它相關(guān)的定時字段。
- 解析&處理TCP首部選項字段。
- 根據(jù)各種TCP狀態(tài),合法處理接收到的報文。
?
各種TCP狀態(tài)下的處理都可能不同,需要看細節(jié)的可以分析源碼。
我這里貼出本人分析的源碼,需要的自己看,后面會重點分析,我們接納的TCP包的處理tcp_receive()
?。
static err_t
tcp_process(struct tcp_pcb *pcb)
{
struct tcp_seg *rseg;
u8_t acceptable = 0;
err_t err;
err = ERR_OK;
LWIP_ASSERT("tcp_process: invalid pcb", pcb != NULL);
/* 收到RST報文,根據(jù)當(dāng)前狀態(tài)判斷RST報文是否合法,若合法,則復(fù)位當(dāng)前連接 */
if (flags & TCP_RST) {
/* 1. 先判斷RST的合法性。如果RST是按TCP有序響應(yīng)給我們的,視為合法。 */
if (pcb->state == SYN_SENT) {
/* 情況1:處于SYN_SENT狀態(tài)下。報文的ACK號 == 下一個準(zhǔn)備發(fā)送的SEQ號即可 */
/* 發(fā)出SYN后,收到對端的ACK,含RST,我們需要復(fù)位當(dāng)前連接 */
if (ackno == pcb->snd_nxt) {
acceptable = 1;
}
} else {
/* 情況2:處于其它狀態(tài)下。報文的SEQ號 == 期待接收到的下一個SEQ號即可
注意:如果報文的SEQ號不是我們期待的下一個SEQ號,但卻在窗口內(nèi),只做ACK響應(yīng)。(防止RST欺騙) */
if (seqno == pcb->rcv_nxt) {
/* RST確實是給我們的,復(fù)位連接 */
acceptable = 1;
} else if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
pcb->rcv_nxt + pcb->rcv_wnd)) {
/* 報文的SEQ不是我們下一個期待的,但在窗口內(nèi),我們響應(yīng)一個ACK回去,讓其重發(fā)RST,直至SEQ對應(yīng)。 */
/* 參考RFC 5961 section 3.2節(jié),解決了RFC 793 RST處理中存在的CVE-2004-0230 (RST欺騙攻擊)問題。 */
tcp_ack_now(pcb);
}
}
/* 2. 根據(jù)RST的合法性再進行對應(yīng)處理 */
if (acceptable) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED);
/* 報文處理結(jié)果設(shè)置復(fù)位標(biāo)志,tcp_input()將會刪除該PCB */
recv_flags |= TF_RESET;
/* 清除延遲ACK標(biāo)志,加速閉環(huán) */
tcp_clear_flags(pcb, TF_ACK_DELAY);
return ERR_RST;
} else {
/* 復(fù)位報文不合法,直接返回,不對報文做處理 */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
seqno, pcb->rcv_nxt));
LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
seqno, pcb->rcv_nxt));
return ERR_OK;
}
}
/* 收到SYN報文。如果連接已經(jīng)建立了(即是不在握手階段)收到SYN報文,
則該報文可能是一個重傳的握手包,我們只需要響應(yīng)ACK即可。 */
if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) {
tcp_ack_now(pcb);
return ERR_OK;
}
if ((pcb->flags & TF_RXCLOSED) == 0) {
pcb->tmr = tcp_ticks; /* 如果TCP的RX還沒被關(guān)閉,則收到合法報文時,需要刷新這個活動計時器值 */
}
pcb->keep_cnt_sent = 0; /* ?;钣嫈?shù)器清零 */
pcb->persist_probe = 0; /* 堅持計時器清零 */
tcp_parseopt(pcb); /* 處理報文中的選項字段 */
/* SYN_SENT和SYN_RCVD這兩種情況下才對SYN報文做更多協(xié)議響應(yīng) */
if (flags & TCP_SYN) {
/* accept SYN only in 2 states: */
if ((pcb->state != SYN_SENT) && (pcb->state != SYN_RCVD)) {
return ERR_OK;
}
}
/* 根據(jù)PCB不同狀態(tài)進行不同處理 */
switch (pcb->state) {
case SYN_SENT:
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %s %"U32_F"\n",
ackno, pcb->snd_nxt, pcb->unacked ? "" : " empty:",
pcb->unacked ? lwip_ntohl(pcb->unacked->tcphdr->seqno) : 0));
/* SYN_SENT狀態(tài)下收到SYN|ACK,響應(yīng)ACK后,本地建立連接成功。
更新PCB相關(guān)字段,更新PCB狀態(tài):SYN_SENT --> ESTABLISHED */
if ((flags & TCP_ACK) && (flags & TCP_SYN)
&& (ackno == pcb->lastack + 1)) {
/* 注意:SYN和FIN占用一個SEQ號 */
pcb->rcv_nxt = seqno + 1; /* 更新下一個期待的SEQ號 */
pcb->rcv_ann_right_edge = pcb->rcv_nxt; /* 初始化窗口通告值右邊界 */
pcb->lastack = ackno; /* 收到被確認(rèn)的最大ACK號 */
pcb->snd_wnd = tcphdr->wnd; /* 發(fā)送窗口更新為收到對端的窗口通告值 */
pcb->snd_wnd_max = pcb->snd_wnd; /* 最大發(fā)送窗口,初始更新為發(fā)送窗口 */
pcb->snd_wl1 = seqno - 1; /* 上次更新發(fā)送窗口時收到的SEQ號,強制為seqno-1(比對端初始的seq號還?。┻@樣tcp_receive()會強制更新發(fā)送窗口 */ /* initialise to seqno - 1 to force window update */
pcb->state = ESTABLISHED; /* 更新PCB狀態(tài) */
#if TCP_CALCULATE_EFF_SEND_MSS
/* 結(jié)合網(wǎng)卡MTU和現(xiàn)有的MSS,選擇出更合適的MSS */
pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip);
#endif /* TCP_CALCULATE_EFF_SEND_MSS */
/* 計算初始擁塞窗口 */
pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss);
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SENT): cwnd %"TCPWNDSIZE_F
" ssthresh %"TCPWNDSIZE_F"\n",
pcb->cwnd, pcb->ssthresh));
LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
/* SYNi的報文被ACK了,發(fā)送緩沖現(xiàn)有占用的pbuf數(shù)也就-1;刪除SYNi報文段資源。 */
--pcb->snd_queuelen;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"TCPWNDSIZE_F"\n", (tcpwnd_size_t)pcb->snd_queuelen));
rseg = pcb->unacked;
if (rseg == NULL) {
/* 如果pcb->unacked沒有報文段,那可能是發(fā)生了重傳,導(dǎo)致SYNi的報文遷回了pcb->unsent隊列。
這樣直接提取pcb->unsent隊列頭的報文段出來刪除掉即可。 */
rseg = pcb->unsent;
LWIP_ASSERT("no segment to free", rseg != NULL);
pcb->unsent = rseg->next;
} else {
/* 把SYNi報文段遷出隊列 */
pcb->unacked = rseg->next;
}
/* 釋放SYNi報文段資源 */
tcp_seg_free(rseg);
/* 如果pcb->unacked沒有報文段,重傳計時器要停止;
如果有未被ACK的報文段,則重新開始計時,因為連接現(xiàn)在才建立。 */
if (pcb->unacked == NULL) {
pcb->rtime = -1;
} else {
pcb->rtime = 0;
pcb->nrtx = 0;
}
/* 調(diào)用用戶配置的connect()回到函數(shù),返回連接成功 */
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
if (err == ERR_ABRT) {
return ERR_ABRT;
}
/* 立即為這個SYNr報文段響應(yīng)一個ACK */
tcp_ack_now(pcb);
}
/* 只收到ACK,對這個ACK響應(yīng)RST,并立即重傳SYN請求。因為LWIP不支持半打開。 */
else if (flags & TCP_ACK) {
/* 先發(fā)送RST,使對端處于非SYN狀態(tài) */
tcp_rst(pcb, ackno, seqno + tcplen, ip_current_dest_addr(),
ip_current_src_addr(), tcphdr->dest, tcphdr->src);
/* 然后立即重傳SYN請求,不等待RTO */
if (pcb->nrtx < TCP_SYNMAXRTX) {
/* 重新計時 */
pcb->rtime = 0;
/* 立即重傳 */
tcp_rexmit_rto(pcb);
}
}
break;
case SYN_RCVD:
if (flags & TCP_SYN) {
if (seqno == pcb->rcv_nxt - 1) {
/* 對端重傳的SYN到達,可能是對端沒有收到我們的SYN|ACK,所以重發(fā)SYN|ACK就好了 */
tcp_rexmit(pcb);
}
} else if (flags & TCP_ACK) {
/* expected ACK number? */
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack + 1, pcb->snd_nxt)) {
/* 收到我們發(fā)出的SYN|ACK報文的確認(rèn)ACK,PCB狀態(tài)更新:SYN_RCVD --> ESTABLISHED */
pcb->state = ESTABLISHED;
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
if (pcb->listener == NULL) {
/* PCB是被連接的,但是沒有服務(wù)器歸屬,所以可能是listen pcb被關(guān)閉了。
所以返回“非法值”即可。 */
err = ERR_VAL;
} else
#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
{
#if LWIP_CALLBACK_API
LWIP_ASSERT("pcb->listener->accept != NULL", pcb->listener->accept != NULL);
#endif
/* 本地服務(wù)器與遠端客戶端成功建立連接,然后可以進行accept() */
/* 清空下當(dāng)前PCB的等待accept()標(biāo)記,有接口層netconn準(zhǔn)備好netconn資源后再標(biāo)記上 */
tcp_backlog_accepted(pcb);
/* 調(diào)用accept()函數(shù) */
TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, ERR_OK, err);
}
if (err != ERR_OK) {
/* 當(dāng)前連接沒有被上層正常accept(),則斷開連接 */
if (err != ERR_ABRT) {
tcp_abort(pcb); /* 給對端發(fā)送RST來終止該連接和回收資源 */
}
return ERR_ABRT;
}
/* 當(dāng)前連接成功被上層accept() */
/* 如果當(dāng)前ACK攜帶數(shù)據(jù),則也把這些數(shù)據(jù)給到上層 */
tcp_receive(pcb);
/* 因為SYN占用一個seq號,所以實際確認(rèn)TCP報文數(shù)據(jù)區(qū)的長度要-1 */
if (recv_acked != 0) {
recv_acked--;
}
/* 建立了新的連接,更新下?lián)砣翱?*/
pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss);
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SYN_RCVD): cwnd %"TCPWNDSIZE_F
" ssthresh %"TCPWNDSIZE_F"\n",
pcb->cwnd, pcb->ssthresh));
if (recv_flags & TF_GOT_FIN) {
/* 如果處理結(jié)果是被遠端FIN,則需要正常響應(yīng)ACK,并更新PCB狀態(tài):SYN_RCVD --> CLOSE_WAIT */
tcp_ack_now(pcb);
pcb->state = CLOSE_WAIT;
}
} else {
/* 收到錯誤的ACK號,響應(yīng)RST即可 */
tcp_rst(pcb, ackno, seqno + tcplen, ip_current_dest_addr(),
ip_current_src_addr(), tcphdr->dest, tcphdr->src);
}
}
break;
case CLOSE_WAIT:
/* 服務(wù)器處于半關(guān)閉狀態(tài),已經(jīng)不可能再接收到來自客戶端的報文了 */
/* 服務(wù)器在此狀態(tài)下會一直等待上層應(yīng)用執(zhí)行關(guān)閉命令 tcp_close(),并將PCB狀態(tài)設(shè)置為LAST_ACK */
/* FALLTHROUGH */
case ESTABLISHED: /* 連接雙方都處于穩(wěn)定狀態(tài) */
tcp_receive(pcb); /* 處理報文段中的TCP數(shù)據(jù) */
if (recv_flags & TF_GOT_FIN) { /* passive close */
/* 收到了對端的FINr,
就需要響應(yīng)對方的FIN揮手,并需要更新PCB狀態(tài):ESTABLISHED --> CLOSE_WAIT */
tcp_ack_now(pcb);
pcb->state = CLOSE_WAIT;
}
break;
case FIN_WAIT_1:
tcp_receive(pcb); /* 處理報文段中的TCP數(shù)據(jù) */
if (recv_flags & TF_GOT_FIN) {
/* 收到了對端的FINr */
/* 如果收到了FINi的ACK,并且本地沒有未發(fā)送的數(shù)據(jù),則直接進入TIME_WAIT狀態(tài) */
if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt) &&
pcb->unsent == NULL) {
LWIP_DEBUGF(TCP_DEBUG,
("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb); /* 為這個FINr響應(yīng)ACK */
tcp_pcb_purge(pcb); /* 釋放PCB緩沖資源 */
TCP_RMV_ACTIVE(pcb); /* 把該PCB踢出ACTIVE態(tài)鏈表 */
pcb->state = TIME_WAIT; /* PCB狀態(tài)更新為TIME_WAIT狀態(tài) */
TCP_REG(&tcp_tw_pcbs, pcb); /* PCB遷入TIME_WAI態(tài)鏈表 */
} else {
/* 還沒收到FINi對應(yīng)的ACK || 發(fā)送緩沖區(qū)中還有數(shù)據(jù)未發(fā)送 */
/* 這種情況下只響應(yīng)ACK,然后進入CLOSING狀態(tài)。 */
tcp_ack_now(pcb);
pcb->state = CLOSING;
}
} else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt) &&
pcb->unsent == NULL) {
/* 如果還沒收到FINr,但是收到FINi的ACK,pcb->unsent沒有報文,則進入FIN_WAIT_2狀態(tài) */
pcb->state = FIN_WAIT_2;
}
/* 說明:上面添加pcb->unsent == NULL,這個邏輯條件是想把unsent的數(shù)據(jù)都發(fā)送出去再關(guān)閉連接。
讀者不必?fù)?dān)心收到FINi的ACK,但是因為這個pcb->unsent == NULL導(dǎo)致丟棄這個FINi的ACK報文,
因為既然pcb->unsent不為NULL,說明PCB還會發(fā)數(shù)據(jù),對端還會響應(yīng)ACK或者RST,
這樣依然能對我們的FINi進行ACK或者直接RST連接。 */
break;
case FIN_WAIT_2:
tcp_receive(pcb); /* 處理報文段中的TCP數(shù)據(jù) */
/* 只需要等待收到遠端的FINr報文段,則響應(yīng)ACK,清除PCB緩沖資源,PCB遷入TIME_WAIT態(tài)鏈表,PCB狀態(tài)更新為TIME_WAIT */
if (recv_flags & TF_GOT_FIN) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb);
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
}
break;
case CLOSING:
tcp_receive(pcb); /* 處理報文段中的TCP數(shù)據(jù) */
/* 只需要等待 收到確認(rèn)了FINi的ACK && 沒有未發(fā)送的數(shù)據(jù) 就可以進入TIME_WAIT 狀態(tài)了 */
if ((flags & TCP_ACK) && ackno == pcb->snd_nxt && pcb->unsent == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
}
break;
case LAST_ACK:
tcp_receive(pcb); /* 處理報文段中的TCP數(shù)據(jù) */
/* 只需要等待 收到確認(rèn)了FINi的ACK && 沒有未發(fā)送的數(shù)據(jù) 就可以直接進入CLOSED狀態(tài)了,無需等待2MSL */
if ((flags & TCP_ACK) && ackno == pcb->snd_nxt && pcb->unsent == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: LAST_ACK %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
/* 注意:bug修正#21699:不要在這里設(shè)置pcb->state 為 CLOSED,否則會有片段泄漏的風(fēng)險 */
recv_flags |= TF_CLOSED;
}
break;
default:
break;
}
return ERR_OK;
}
?
?
TCP接納TCP包處理:tcp_receive()
被tcp_process()接收到合法報文段時調(diào)用。
需要把接收到的報文更新到本地接收緩存,并嘗試轉(zhuǎn)交給應(yīng)用層。
?
主要內(nèi)容:
-
窗口更新。
-
檢查ACK:
- 檢查是否是重復(fù)ACK。
- 檢查本次接收到的報文段的ACK,確認(rèn)了pcb->unacked中多少報文段。然后釋放這些被確認(rèn)了的報文段,并更新發(fā)送窗口。
-
RTT&RTO:
- 計算RTO。
-
檢查SEQ:檢查本次收到了多少TCP數(shù)據(jù),并把這些數(shù)據(jù)按序放入緩沖區(qū):
- 有序的放入
receive_data
?; - 亂序的放入
pcb->ooseq
?。 - 放入緩沖區(qū)的pbuf用
pbuf_ref
?管理,pbuf的引用+1,防止被其它地方free
?。 - 入隊是最麻煩的處理:需要考慮窗口、緩存、處理重疊。
- 有序的放入
-
SACK:
- 根據(jù)
->ooseq
?隊列生成SACK選項相關(guān)值。
- 根據(jù)
?
收到新的ACK:更新發(fā)送窗口+釋放被ACK的數(shù)據(jù)內(nèi)存
更新窗口:
/* 1. 先檢查報文是否包含ACK */
if (flags & TCP_ACK) {
/* 獲取當(dāng)前發(fā)送窗口右邊界:發(fā)送窗口大小 + 發(fā)送窗口左邊界(上次更新發(fā)送窗口時,收到的ACK號) */
right_wnd_edge = pcb->snd_wnd + pcb->snd_wl2;
/* 更新窗口 */
/* pcb->snd_wl1:上次更新發(fā)送窗口時的SEQ號 */
/* pcb->snd_wl2:上次更新發(fā)送窗口時的ACK號(也是對端期待接收到的下一個SEQ號) */
/* 1. 比上次更新窗口時,收到新的數(shù)據(jù)(這個觸發(fā)條件好像和發(fā)送窗口沒啥關(guān)系,不過也無所謂) */
/* 2. 或 沒有新數(shù)據(jù),但收到更多報文的確認(rèn) */
/* 3. 或 沒有新數(shù)據(jù),也沒有更多報文的確認(rèn),但收到接收窗口通告值變大了 */
if (TCP_SEQ_LT(pcb->snd_wl1, seqno) ||
(pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno)) ||
(pcb->snd_wl2 == ackno && (u32_t)SND_WND_SCALE(pcb, tcphdr->wnd) > pcb->snd_wnd)) {
/* 更新發(fā)送窗口為對端的接收窗口通告值 */
pcb->snd_wnd = SND_WND_SCALE(pcb, tcphdr->wnd);
/* keep track of the biggest window announced by the remote host to calculate the maximum segment size */
/* 跟蹤對端宣告的接收窗口通告值,記錄歷史最大那個 */
if (pcb->snd_wnd_max < pcb->snd_wnd) {
pcb->snd_wnd_max = pcb->snd_wnd;
}
pcb->snd_wl1 = seqno; /* 輔助:記錄本次更新發(fā)送窗口時的SEQ號 */
pcb->snd_wl2 = ackno; /* 輔助:記錄本次更新發(fā)送窗口時的ACK號 */
LWIP_DEBUGF(TCP_WND_DEBUG, ("tcp_receive: window update %"TCPWNDSIZE_F"\n", pcb->snd_wnd));
#if TCP_WND_DEBUG /* 窗口LOG */
} else {
/* 如果本次沒有更新發(fā)送窗口,且本次報文附帶的窗口和原窗口不一致,則打印相關(guān)LOG */
/* 如:收到一個舊的重復(fù)報文段時 */
if (pcb->snd_wnd != (tcpwnd_size_t)SND_WND_SCALE(pcb, tcphdr->wnd)) {
LWIP_DEBUGF(TCP_WND_DEBUG,
("tcp_receive: no window update lastack %"U32_F" ackno %"
U32_F" wl1 %"U32_F" seqno %"U32_F" wl2 %"U32_F"\n",
pcb->lastack, ackno, pcb->snd_wl1, seqno, pcb->snd_wl2));
}
#endif /* TCP_WND_DEBUG */
}
檢查ACK,這里就不貼代碼了,直接貼方法:
檢查重復(fù)ACK是很重要的,決定著快速重傳的判斷。
/* (From Stevens TCP/IP Illustrated Vol II, p970.)
* 通過以下條件可以判斷是否是重復(fù)的ACK:
* 1) 沒有ACK新數(shù)據(jù);
* 2) 沒有TCP數(shù)據(jù),也沒有SYN、FIN標(biāo)志;
* 3) 前面更新窗口算法中,本地發(fā)送窗口沒有更新;
* 4) 本地還有unacked數(shù)據(jù),并且重傳計時器在跑;
* 5) 當(dāng)前收到的ACK,是本次連接歷史最大的ACK。
*
* 如果上面5個條件都滿足,則是一個重復(fù)的ACK:
* a) 重復(fù) < 3次:do nothing
* b) 重復(fù) == 3次: 快重傳
* c) 重復(fù) > 3次: 擁塞窗口CWND+1MSS(擁塞避免算法)
*
* 如果只滿足條件1、2、3:重置重復(fù)ACK計數(shù)器。(并添加到統(tǒng)計中,但是LWIP沒有做這個統(tǒng)計)
*
* 如果只滿足條件1:重置重復(fù)ACK計數(shù)器。
*
*/
收到新數(shù)據(jù):推送到接收緩沖區(qū)
在正常狀態(tài)下收到含數(shù)據(jù)的報文段后,主要做三件事:
- 如果收到的TCP數(shù)據(jù)包含我們期待收到的下一個SEQ號的數(shù)據(jù),說明有有序的新數(shù)據(jù)到達。
我們需要把這些有序的新數(shù)據(jù)推送到應(yīng)用層。
然后調(diào)整下一個期待收到的SEQ號、通告窗口和滑動一下接收窗口。 - 如果收到的TCP數(shù)據(jù)跳過了我們期待收到的下一個SEQ號,我們就要把這些亂序的報文有序地放到
pcb->ooseq
?隊列中緩存起來。
讓后立即響應(yīng)一個ACK(和SACK),表示我們收到了亂序報文。
待我們收到中間空隙的數(shù)據(jù),再把這些數(shù)據(jù)拼接回來遞交到應(yīng)用層。 - 最后,我們檢查下亂序報文隊列
->ooseq
?中是否有報文有序了,即是->ooseq
?隊列第一個報文SEQ號 <= 下一個期待接收的SEQ號->rcv_nxt
?。
如果有,則提取、拼接、遞交到應(yīng)用層。然后調(diào)整相關(guān)字段:下一個期待的SEQ、通告窗口、接收窗口。
?
接收到合法的新數(shù)據(jù)是先保存到recv_data
?全局變量中,在當(dāng)前函數(shù)返回后,由調(diào)用者層級處理函數(shù)tcp_input()
?處理遞交到應(yīng)用層。
tcp_input()
?函數(shù)會調(diào)用TCP_EVENT_RECV()
?來嘗試遞交給應(yīng)用層,如果應(yīng)用層沒有成功接收,就會發(fā)到接收緩沖區(qū)pcb->refused_data
?中。
?
剩下的就是LWIP實現(xiàn)的具體代碼了,不貼出,有興趣的可以看源碼。
?
測量RTT和RTO計算
后面分析。
?
?
?
?
?
TCP層數(shù)據(jù)流圖
?
問題記錄
SO_REUSE復(fù)用IP和端口號
SOF_REUSEADD
選項表示可以復(fù)用處于TIME_WAIT
狀態(tài)的端口。
所以,在綁定IP&PORT時,可以復(fù)用處于TIME_WAIT
狀態(tài)的端口(相同IP&PORT的所有PCB都必須開啟SOF_REUSEADD
,本次綁定才能復(fù)用成功)。
在建立連接時,如果開啟了SOF_REUSEADD
,需要檢查五元組:本地IP、本地PORT、遠端IP、遠端PORT和TCP PCB狀態(tài)。才能正常連接,因為正常連接必須唯一對應(yīng)一個服務(wù)。
?
?
到了這里,關(guān)于【lwip】13-TCP協(xié)議分析之源碼篇鏈接:[https://www.cnblogs.com/lizhuming/p/17438682.html](https://www.cnblogs.com/lizhuming/p/17438682.html)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!