lwip之数据收发流程_2
生活随笔
收集整理的這篇文章主要介紹了
lwip之数据收发流程_2
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
三.傳輸層(這里主要講TCP)TCP連接的建立過程(三次握手):1. 客戶端發送一個SYN標志置1的TCP數據報,握手包中指明源端口和目的端口,同時告知客戶端初始序號seqno_client2. 當服務器接收到該數據包并解析后,也發回一個SYN標志置1的數據報作為應答,應答中包含服務器端初始序號seqno_server,同時將ACK標志置1,將確認序號設置為seqno_client+13. 當客戶端接收到服務端的SYN應答包,會再次產生一個握手包,包中ACK標志置1,確認序號設置為seqno_server+1TCP連接的斷開過程(四次握手):1. 當客戶端應用程序主動執行關閉操作時,客戶端會向服務器發送一個FIN標志置1的報文段,用來關閉從客戶端到服務器的數據傳送,該報文段序號字段為seqno_client2. 當服務器接收到這個FIN報文段后,返回一個ACK報文,確認序號為seqno_client+1,當客戶端收到這個ACK后,從客戶端到服務器方向的連接就斷開了3. 服務器TCP向其上層應用程序通過客戶端的端口操作,這會導致服務器應用程序關閉它的連接,同樣,此時一個FIN置1的報文段將被發往客戶端,該報文段序號字段為seqno_server4. 當客戶端收到這個FIN報文段后,也會返回一個ACK作為響應,確認序號為seqno_server+1,從服務器到客戶端方向的連接也就被斷開了******************************************************************************************************************************************************************************************************?? ?lwip一共定義了11種TCP狀態:enum tcp_state{CLOSED?? ??? ?= 0,?? ?// 沒有連接LISTEN?? ??? ?= 1,?? ?// 服務器進入偵聽狀態,等待客戶端的連接請求SYN_SENT?? ?= 2,?? ?// 連接請求已發送,等待確認SYN_RCVD?? ?= 3,?? ?// 已收到對方的連接請求ESTABLISHED = 4,?? ?// 連接已建立FIN_WAIT_1?? ?= 5,?? ?// 程序已關閉該連接FIN_WAIT_2?? ?= 6,?? ?// 另一端已接受關閉該連接CLOSE_WAIT?? ?= 7,?? ?// 等待程序關閉連接CLOSING?? ??? ?= 8,?? ?// 兩端同時收到對方的關閉請求LAST_ACK?? ?= 9,?? ?// 服務器等待對方接受關閉操作TIME_WAIT?? ?= 10,?? ?// 關閉成功,等待網絡中可能出現的剩余數據}兩條最經典的TCP狀態轉換路徑:1.第一條路徑描述了客戶端申請建立連接與斷開連接的整個過程:CLOSED ————————> SYN_SENT ————————> ESTABLISHED ————> FIN_WAIT_1 ————> FIN_WAIT_2 ——————> TIME_WAIT ——> CLOSED主動打開/syn?? ??? ??? ??? ?syn+ack/ack?? ??? ??? ??? ??? ?? /fin?? ??? ??? ??? ??? ?ack/?? ??? ??? ??? ?fin/ack2.第二條路徑描述了服務器建立連接與斷開連接的整個過程:CLOSED ————————> LISTEN ————————> SYN_RCVD ————> ESTABLISHED ————————> CLOSE_WAIT ————> LAST_ACK ————> CLOSED被動打開/?? ??? ??? ??? ??? ?syn/syn+ack?? ??? ??? ??? ?ack/?? ??? ??? ??? ??? ?fin/ack?? ??? ??? ??? ??? ? ??? ?/fin?? ??? ??? ??? ?ack/******************************************************************************************************************************************************************************************************?? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ?lwip使用一個tcp_hdr的結構體來描述tcp協議包頭:struct tcp_hdr{u16_t src;?? ??? ??? ??? ??? ?// 源端口u16_t dest;?? ??? ??? ??? ??? ?// 目的端口u32_t seqno;?? ??? ??? ??? ?// 序號,用來標識從TCP發送端到接收端的數據字節流u32_t ackno;?? ??? ??? ??? ?// 確認序號,是發送確認的一段所期望收到的下一個序號u16_t _hdrlen_rsvd_flags;?? ?// 包含4位TCP包頭長(通常為5*4,即本結構體大小)、6個標志位(URG、ACK、PSH、RST、SYN、FIN)u16_t wnd;?? ??? ??? ??? ??? ?// 窗口大小字段,表示還能接收的字節數,實現流量控制u16_t chksum;?? ??? ??? ??? ?// 16位整個TCP報文校驗和,包含了TCP頭和TCP數據,由發送端計算并由接收端驗證u16_t urgp;?? ??? ??? ??? ??? ?// 緊急指針,暫略}******************************************************************************************************************************************************************************************************?? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ?lwip使用一個tcp_pcb控制塊來描述一個TCP連接(lwip實際定義了2種TCP控制塊,一種專門用于描述處于LISTEN狀態的連接,另一種用于描述處于其他狀態的連接):struct tcp_pcb{IP_PCB;?? ??? ??? ??? ??? ??? ?// 該宏描述了連接的IP相關信息,主要包含源IP、目的IP兩個重要字段?? ?// 這部分是2種類型TCP控制塊都具有的字段?? ??? ??? ??? ??? ??? ??? ??? ??? ??? ?struct tcp_pcb *next;?? ??? ?// 指向下一個tcp_pcb控制塊的鏈表指針enum tcp_state state;?? ??? ?// TCP連接的狀態,如上所述共11種u8_t prio;?? ??? ??? ??? ??? ?// 該控制塊的優先級,可用于回收低優先級控制塊void *callback_arg;?? ??? ??? ?// 指向用戶自定義數據,在函數回調時使用tcp_accept_fn accept;?? ??? ?// 連接accept時回調函數u16_t local_port;?? ??? ??? ?// 綁定的本地端口u16_t remote_port;?? ??? ??? ?// 遠程端口u8_t flags;?? ??? ??? ??? ??? ?// 控制塊狀態、標志字段,描述了當前控制塊的特性,各位的含義如下宏定義#define TF_ACK_DELAY?? ?0x01?? ?// 延遲發送ACK#define TF_ACK_NOW?? ??? ?0x02?? ?// 立即發送ACK#define TF_INFR?? ??? ??? ?0x04?? ?// 連接處于快重傳狀態#define TF_TIMESTAMP?? ?0x08?? ?// 連接的時間戳選項已使能#define TF_RXCLOSED ?? ?0x10?? ?// 因TCP連接斷開導致RX關閉#define TF_FIN?? ??? ??? ?0x20?? ?// 應用程序已關閉該連接#define TF_NODELAY?? ??? ?0x40?? ?// 禁止Nagle算法#define TF_NAGLEMEMERR?? ?0x80?? ?// 本地緩沖區溢出// 接收相關字段u32_t rcv_nxt;?? ??? ??? ??? ?// 期望接收的下一個序號,也即是本地將要反饋給對方的ACK的序號,也是本地接收窗口的左邊界u16_t rcv_wnd;?? ??? ??? ??? ?// 當前接收窗口大小,會隨著數據的接收與遞交動態變化u16_t rcv_ann_wnd;?? ??? ??? ?// 將向對方通告的窗口大小,也會隨著數據的接收與遞交動態變化u32_t rcv_ann_right_edge;?? ?// 上一次窗口通告時窗口的右邊界值// 時間相關字段u32_t tmr;?? ??? ??? ??? ??? ?// 其它各計數器都基于tmr的值來實現?? ??? ?u8_t polltmr, pollinterval;?? ?// 這兩個字段用于周期性調用一個函數,polltmr會周期性增加,當超過pollinterval時,poll函數會被調用s16_t rtime;?? ??? ??? ??? ?// 重傳定時器,當大于rto的值時則重傳報文u16_t mss;?? ??? ??? ??? ??? ?// 對方可接收的最大報文大小// RTT估計相關的參數u32_t rttest;u32_t rtseq;s16_t sa, sv;s16_t rto;?? ??? ??? ??? ??? ?// 重傳超時時間,使用上面3個RTT參數計算出來u8_t nrtx;?? ??? ??? ??? ??? ?// 重傳次數// 快速重傳與恢復相關字段u32_t lastack;?? ??? ??? ??? ?// 接收到的上一個確認序號,也就是最大確認序號u8_t dupacks;?? ??? ??? ??? ?// 上述最大確認序號被重復收到的次數?? ?// 阻塞控制相關參數u16_t cwnd; ??? ??? ??? ??? ?// 連接當前的阻塞窗口大小u16_t ssthresh;?? ??? ??? ??? ?// 擁塞避免算法啟動閾值// 發送相關字段u32_t snd_nxt;?? ??? ??? ??? ?// 下一個將要發送的序號u16_t snd_wnd;?? ??? ??? ??? ?// 當前發送窗口大小u32_t snd_wl1, snd_wl2;?? ??? ?// 上次窗口更新時收到的數據序號seqno和確認號acknou32_t snd_lbb; ?? ??? ??? ??? ?// 下一個被緩沖的應用程序數據的編號u16_t acked;?? ??? ??? ??? ?// 保存了被確認的已發送長度u16_t snd_buf; ?? ??? ??? ??? ?// 可用的發送空間(以字節為單位)u16_t snd_queuelen;?? ??? ??? ?// 被占用的發送空間(以數據段pbuf為單位)u16_t unsent_oversize;?? ??? ?// 尚未被發送的字節數struct tcp_seg *unsent;?? ??? ?// 未發送的數據段隊列,鏈表形式struct tcp_seg *unacked;?? ?// 發送了未收到確認的數據段隊列,鏈表形式struct tcp_seg *ooseq;?? ??? ?// 接收到有序序號以外的數據段隊列,鏈表形式struct pbuf *refused_data;?? ?// 指向上一次成功接收但未被應用層取用的數據pbuf// 回調函數err_t (*sent)(void *arg,struct tcp_pcb *pcb,u16_t space);?? ??? ??? ?// 數據成功發送后被調用?? ??? ?err_t (*recv)(void *arg,struct tcp_pcb,struct pbuf *p,err_t err);?? ?// 接收到數據后被調用err_t (*connected)(void *arg, struct tcp_pcb *tpcb, err_t err);?? ??? ?// 連接建立后被調用err_t (*poll)(void *arg, struct tcp_pcb *tpcb);?? ??? ??? ??? ??? ??? ?// 該函數被內核周期性調用void? (*errf)(void *arg, err_t err);?? ??? ??? ??? ??? ??? ??? ??? ?// 連接發生錯誤時被調用// 心跳相關參數u32_t keep_idle;?? ??? ??? ?// 最后一個正常報文結束到保活計時器(心跳)啟動的時間間隔u32_t keep_intvl;?? ??? ??? ?// 保活計時器(心跳)發送間隔u32_t keep_cnt;?? ??? ??? ??? ?// 保活計時器(心跳)最大重發次數?? ??u32_t persist_cnt;?? ??? ??? ?// 堅持定時器計數值u8_t persist_backoff;?? ??? ?// 堅持定時器開關,大于0開啟u8_t keep_cnt_sent;?? ??? ??? ?// 保活計時器(心跳)最大重發次數?? ??}?? ?struct tcp_pcb_listen{IP_PCB;struct tcp_pcb *next;?? ??? ?enum tcp_state state;?? ??? ?u8_t prio;?? ??? ??? ??? ??? ?void *callback_arg;?? ?tcp_accept_fn accept;?? ??? ?u16_t local_port;}注:#define IP_PCB ?? ?ip_addr_t local_ip;?? ??? ?// 本地IPip_addr_t remote_ip;?? ?// 目的IPu8_t ?? ?? so_options;?? ?// 套接字選項?? ?可取值:?? ?#define SOF_ACCEPTCONN??? (u8_t)0x02U#define SOF_REUSEADDR???? (u8_t)0x04U#define SOF_KEEPALIVE???? (u8_t)0x08U#define SOF_BROADCAST???? (u8_t)0x20U#define SOF_LINGER??????? (u8_t)0x80U#define SOF_INHERITED???? (SOF_REUSEADDR|SOF_KEEPALIVE|SOF_LINGER)u8_t?? ?? tos;?? ??? ??? ?// 服務類型u8_t?? ?? ttl;?? ??? ??? ?// TTL這個TCP控制塊是整個TCP協議的核心,TCP協議實現的本質就是對TCP控制塊中各字段的操作,所以非常重要!!!******************************************************************************************************************************************************************************************************?? ?tcp_input是TCP層的總輸入函數,它會為數據包尋找一個匹配的TCP控制塊,以及調用相應的函數tcp_timewait_input,tcp_listen_input,tcp_process進行處理void tcp_input(struct pbuf *p,struct netif *inp){struct tcp_pcb ?? ?*pcb,*prev;struct tcp_pcb_listen *lpcb;u8_t hdrlen;err_t err;// 略過IP包頭,提取TCP頭iphdr = (struct ip_hdr *)p->payload;tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr)*4)// 移動pbuf結構中的數據包指針,使指向TCP頭if (pbuf_header(p, -((s16_t)(IPH_HL(iphdr) * 4))) || (p->tot_len < sizeof(struct tcp_hdr))){pbuf_free(p);return;?? ?}// 不處理輸入的廣播包if (ip_addr_isbroadcast(¤t_iphdr_dest, inp) || ip_addr_ismulticast(¤t_iphdr_dest)){pbuf_free(p);return;?? ??? ?}// 驗證TCP校驗和if (inet_chksum_pseudo(p, ip_current_src_addr(), ip_current_dest_addr(),IP_PROTO_TCP, p->tot_len) != 0){pbuf_free(p);return;?? ?}// 繼續移動pbuf結構中的數據包指針,使指向TCP數據hdrlen = TCPH_HDRLEN(tcphdr);if(pbuf_header(p, -(hdrlen * 4)){pbuf_free(p);return;?? ?}// 網絡字節序轉主機字節序tcphdr->src = ntohs(tcphdr->src);?? ??? ??? ??? ?// 源端口tcphdr->dest = ntohs(tcphdr->dest);?? ??? ??? ??? ?// 目的端口seqno = tcphdr->seqno = ntohl(tcphdr->seqno);?? ?// 序號ackno = tcphdr->ackno = ntohl(tcphdr->ackno);?? ?// 確認序號tcphdr->wnd = ntohs(tcphdr->wnd);?? ??? ??? ??? ?// 窗口大小flags = TCPH_FLAGS(tcphdr);?? ??? ??? ??? ??? ??? ?// 6位標志位tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);?? ?// TCP數據包中數據的總長度,對于有FIN或SYN標志的數據包,該長度要加1// 以下就是對接收到的數據包進行分類處理,也就是尋找合適的接口,根據IP,port// 首先在tcp_active_pcbs 鏈表池中找,有沒有匹配的tcp_pcbprev = NULL;for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next){if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), ¤t_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), ¤t_iphdr_dest)){// 找到匹配的接口之后,將該tcp_pcb從tcp_active_pcbs鏈表池中取出,然后退出循環往下運行,這時pcb != NULLif (prev != NULL){prev->next = pcb->next;pcb->next = tcp_active_pcbs;tcp_active_pcbs = pcb;}?? ?break;}?? ?prev = pcb;}// 如果在tcp_active_pcbs中沒有找到,繼續在tcp_tw_pcbs 和tcp_listen_pcbs中找if (pcb == NULL){// 在tcp_tw_pcbs中找for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) ?? ?{if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), ¤t_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), ¤t_iphdr_dest)){// 進入TIME_WAIT狀態處理(解析見下文),處理完直接這里返回不再往下運行tcp_timewait_input(pcb);pbuf_free(p);return;}}// 在tcp_listen_pcbs中找prev = NULL;for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next){// 判斷端口是否匹配if (lpcb->local_port == tcphdr->dest) ?? ?{// 然后判斷IP是否匹配,或者是IPADDR_ANY接收任何IPif (ip_addr_cmp(&(lpcb->local_ip), ¤t_iphdr_dest) || ip_addr_isany(&(lpcb->local_ip))){// 找到匹配的接口之后退出循環往下運行,這時lpcb != NULLbreak;}?? ??? ?}prev = (struct tcp_pcb *)lpcb;}// 這里是判斷在tcp_listen_pcbs中是否找到if (lpcb != NULL){// 將該tcp_pcb從tcp_listen_pcbs.listen_pcbs鏈表池中取出if (prev != NULL){((struct tcp_pcb_listen *)prev)->next = lpcb->next;lpcb->next = tcp_listen_pcbs.listen_pcbs;tcp_listen_pcbs.listen_pcbs = lpcb;?? ?}?? ?// 進入LISTEN狀態處理(解析見下文),處理完直接這里返回不再往下運行tcp_listen_input(lpcb);pbuf_free(p);return;}}// 如果在tcp_active_pcbs中找到了,則經過處理后進入tcp_processif (pcb != NULL){inseg.next = NULL;?? ??? ?// 關閉報文段隊列功能inseg.len = p->tot_len;?? ?// 設置該報文段的數據長度inseg.p = p;?? ??? ??? ?// 設置報文段數據鏈表頭指針inseg.tcphdr = tcphdr;?? ?// 報文段的TCP頭recv_data = NULL;?? ??? ?// 數據接收結果被保存在該全局變量,然后往上層提交recv_flags = 0;?? ??? ??? ?// tcp_process執行完后的結果(控制塊的狀態變遷)將會被保存在該全局變量,首先在這里被清0// tcp_pcb的refused_data指針上是否還記錄有尚未往上層遞交的數據if (pcb->refused_data != NULL){// 有的話回調用戶recv函數接收未遞交的數據TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err);// 判斷處理recv函數的處理結果,成功refused_data指針清空,繼續往下執行tcp_processif (err == ERR_OK){pcb->refused_data = NULL;} ?? ?// 失敗意味著tcp_pcb都被占用滿,丟棄接收包不再處理,直接返回else if ((err == ERR_ABRT) || (tcplen > 0)){pbuf_free(p);return;}}tcp_input_pcb = pcb;?? ?// 記錄處理當前報文的控制塊// 這里就是進入tcp_process處理接收包環節了(解析見下文),該函數實現了TCP狀態轉換功能err = tcp_process(pcb);// 若返回值為ERR_ABRT,說明控制塊已經被完全刪除(tcp_abort()),什么也不需要做if (err != ERR_ABRT){// 返回值不為ERR_ABRT時,判斷報文處理的3種結果if (recv_flags & TF_RESET) ?? ??? ??? ?// 接收到對方的復位報文{// 回調用戶的errf函數TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST);// 刪除控制塊tcp_pcb_remove(&tcp_active_pcbs, pcb);// 釋放控制塊空間memp_free(MEMP_TCP_PCB, pcb);}?? ?else if (recv_flags & TF_CLOSED) ?? ?// 雙方連接成功斷開{// 刪除控制塊tcp_pcb_remove(&tcp_active_pcbs, pcb);// 釋放控制塊空間memp_free(MEMP_TCP_PCB, pcb);}else{err = ERR_OK;if (pcb->acked > 0) ?? ??? ??? ?// 如果有被確認的已發送數據長度?? ??? ?{// 回調用戶的send函數TCP_EVENT_SENT(pcb, pcb->acked, err);if (err == ERR_ABRT){goto aborted;}}if (recv_data != NULL)?? ??? ??? ?// 如果有數據被接收到{if (pcb->flags & TF_RXCLOSED) ?? ?// 如果本地TCP控制塊已經處于TF_RXCLOSED狀態,則后續接收到的數據都作廢{pbuf_free(recv_data);tcp_abort(pcb);goto aborted;}if (flags & TCP_PSH) ?? ??? ?// 如果TCP標志位中帶有PSH{// 設置pbuf首部的flag字段recv_data->flags |= PBUF_FLAG_PUSH;}// 回調用戶的recv函數,接收遞交上去的TCP數據recv_dataTCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);// 判斷返回值,如果是ERR_ABRT,則丟棄,返回if (err == ERR_ABRT){goto aborted;}// 除此之外,如果返回值是失敗,將這部分尚未往上遞交的數據暫存到refused_data指針中if (err != ERR_OK){pcb->refused_data = recv_data;}}if (recv_flags & TF_GOT_FIN)?? ?// 如果收到對方的FIN請求{// 糾正接收窗口if (pcb->rcv_wnd != TCP_WND){pcb->rcv_wnd++;}// 用一個NULL指針回調用戶的recv函數,通過這種方式用戶程序可以知道對方的關閉請求TCP_EVENT_CLOSED(pcb, err);if (err == ERR_ABRT){goto aborted;}}tcp_input_pcb = NULL;?? ??? ?// 當前報文到此處理完畢,清空當前報文的控制塊tcp_output(pcb);?? ??? ??? ?// 輸出報文}}aborted:tcp_input_pcb = NULL;recv_data = NULL;if (inseg.p != NULL){pbuf_free(inseg.p);inseg.p = NULL;}}else{// 如果在3張鏈表里都未找到匹配的pcb,則調用tcp_rst向源主機發送一個TCP復位數據包if (!(TCPH_FLAGS(tcphdr) & TCP_RST)){tcp_rst(ackno, seqno + tcplen,ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);}pbuf_free(p);}}******************************************************************************************************************************************************************************************************?? ?// 本函數是處于LISTEN狀態的控制塊對輸入報文的處理函數,處于LISTEN狀態的控制塊只能響應SYN握手包err_t tcp_listen_input(struct tcp_pcb_listen *pcb){struct tcp_pcb *npcb;err_t rc;// 處于listen狀態的pcb只能響應SYN握手包,對含有ACK標志的輸入報文返回一個RST報文if (flags & TCP_ACK){tcp_rst(ackno + 1, seqno + tcplen,ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);}// 處于listen狀態的服務器端等到了SYN握手包else if (flags & TCP_SYN){// 建立一個新的tcp_pcb,因為處于tcp_listen_pcbs鏈表上的pcb是tcp_pcb_listen結構的,而其他鏈表上的pcb是tcp_pcb結構npcb = tcp_alloc(pcb->prio);?? ?// 如果新建失敗,往往是因為內存不夠if (npcb == NULL){TCP_STATS_INC(tcp.memerr);ERR_MEM;}// 為這個新建的tcp_pcb填充成員ip_addr_copy(npcb->local_ip, current_iphdr_dest);npcb->local_port = pcb->local_port;ip_addr_copy(npcb->remote_ip, current_iphdr_src);npcb->remote_port = tcphdr->src;npcb->state = SYN_RCVD;?? ??? ??? ??? ??? ??? ??? ??? ?// 進入SYN_RCVD狀態npcb->rcv_nxt = seqno + 1;?? ??? ??? ??? ??? ??? ??? ?// 期望接收到的下一個序號,注意加1npcb->rcv_ann_right_edge = npcb->rcv_nxt;?? ??? ??? ?// 初始化右側通告窗口npcb->snd_wnd = tcphdr->wnd;?? ??? ??? ??? ??? ??? ?// 根據TCP頭中對方可接收數據長度,初始化本地發送窗口大小npcb->ssthresh = npcb->snd_wnd;?? ??? ??? ??? ??? ??? ?// 擁塞算法相關,暫略npcb->snd_wl1 = seqno - 1;?? ??? ??? ??? ??? ??? ??? ?// 初始化上次窗口更新時收到的序號npcb->callback_arg = pcb->callback_arg;?? ??? ??? ??? ?// 初始化用戶自定義數據npcb->accept = pcb->accept;?? ??? ??? ??? ??? ??? ??? ?// 初始化連接accept時的回調函數?? ?npcb->so_options = pcb->so_options & SOF_INHERITED;?? ?// 繼承socket選項TCP_REG(&tcp_active_pcbs, npcb);?? ??? ??? ??? ??? ?// 將這個設置好的tcp_pcb注冊到tcp_active_pcbs鏈表中去tcp_parseopt(npcb);?? ??? ??? ??? ??? ??? ??? ??? ??? ?// 從收到的SYN握手包中提取TCP頭中選項字段的值,并設置到自己的tcp_pcbnpcb->mss = tcp_eff_send_mss(npcb->mss, &(npcb->remote_ip));?? ?// 初始化mss// 回復帶有SYN和ACK標志的握手數據包rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);if (rc != ERR_OK){tcp_abandon(npcb, 0);return rc;}// TCP層的總輸出函數,詳見下文return tcp_output(npcb);}return ERR_OK;}******************************************************************************************************************************************************************************************************// 本函數是處于TIMEWAIT狀態的控制塊處理輸入報文的函數err_t tcp_timewait_input(struct tcp_pcb *pcb){// 如果報文中含RST標志,直接丟棄if (flags & TCP_RST) ?{return ERR_OK;?? ?}?? ?// 如果報文中含SYN標志if (flags & TCP_SYN){// 如果SYN的序號在接收窗口內,返回一個RST報文if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)){tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);return ERR_OK;?? ?}}// 如果報文中含FIN標志else if(flags & TCP_FIN){pcb->tmr = tcp_ticks;}// 如果TCP報文中有數據if(tcp_len > 0){pcb->flags |= TF_ACK_NOW;?? ?// 將當前控制塊設為TF_ACK_NOW狀態// TCP層的總輸出函數,詳見下文return tcp_output(pcb);?? ??? ?}return ERR_OK;}******************************************************************************************************************************************************************************************************?? ?// 除了處于LISTEN、TIME_WAIT狀態的其余所有狀態的pcb控制塊,其報文的輸入處理都在這里,該函數主要實現了TCP狀態轉換功能err_t tcp_process(struct tcp_pcb *pcb){struct ?? ?tcp_seg *rseg;u8_t?? ?acceptable = 0;err_t?? ?err;err?? ?= ERR_OK;// 首先判斷該報文是不是一個RST報文if(flags & TCP_RST){// 判斷該RST報文是否合法if (pcb->state == SYN_SENT) ?? ?// 第一種情況,連接處于SYN_SENT狀態{if (ackno == pcb->snd_nxt) ?? ?// 且輸入報文中的確認號就是控制塊中想要發送的下一個序號{acceptable = 1;?? ?}}else?? ??? ??? ??? ??? ??? ??? ?// 第二種情況,其他狀態下,輸入報文中的序號在接收窗口內{if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,pcb->rcv_nxt+pcb->rcv_wnd)){acceptable = 1;}?? ?}// 如果RST報文合法,則需要復位當前連接的控制塊,非法則直接返回不做處理if (acceptable){recv_flags |= TF_RESET;?? ??? ??? ?// 表明該輸入報文的處理結果中包含TF_RESETpcb->flags &= ~TF_ACK_DELAY;?? ?// 因為輸入是RST報文,意味當前控制塊必然不處于TF_ACK_DELAY狀態return ERR_RST;?? ?}else{return ERR_OK;}}// 然后處理握手報文SYN,在連接已經建立情況下,但還是接收到對方的握手包,說明這可能是一個超時重發的握手包,直接向對方返回一個ACK即可if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)){tcp_ack_now(pcb);?? ??? ??? ??? ??? ?// #define tcp_ack_now(pcb) ?? ?pcb->flags |= TF_ACK_NOW?? ?- 將當前控制塊設為TF_ACK_NOW狀態return ERR_OK;?? ?}// TCP連接不處于半關閉前提下,更新控制塊的活動計數器if ((pcb->flags & TF_RXCLOSED) == 0){pcb->tmr = tcp_ticks;}// 保活報文計數器清0pcb->keep_cnt_sent = 0;// 處理報文首部中的選項字段(暫略)tcp_parseopt(pcb);// 根據當前所處的不同的TCP狀態執行相應動作switch (pcb->state){case SYN_SENT:?? ?// 客戶端發出SYN后,就處于該狀態等待服務器返回SYN+ACK// 如果收到的是SYN+ACK,且輸入報文中的確認號,就是控制塊中已發送,但尚未收到應答報文段中的序號+1if ((flags & TCP_ACK) && (flags & TCP_SYN) && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1){pcb->snd_buf++;?? ??? ??? ??? ??? ??? ??? ?// 發出SYN被返回的ACK確認,釋放1字節空間,所以可用的發送空間加1字節?? ?pcb->rcv_nxt = seqno + 1;?? ??? ??? ??? ?// 期望接收的下一個序號,即接收端向發送端ACK報文中的確認號pcb->rcv_ann_right_edge = pcb->rcv_nxt;?? ?// 初始化通告窗口的右邊界值(略存疑問)pcb->lastack = ackno;?? ??? ??? ??? ??? ?// 更新接收到的最大確認號字段,也就是更新上一個確認號字段pcb->snd_wnd = tcphdr->wnd;?? ??? ??? ??? ?// 發送窗口設置為接收窗口大小,實現流量控制pcb->snd_wl1 = seqno - 1; ?? ??? ??? ??? ?// 上次窗口更新時收到的數據序號pcb->state = ESTABLISHED;?? ??? ??? ??? ?// 進入ESTABLISHED狀態pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));?? ?// 計算并設置最大報文段pcb->ssthresh = pcb->mss * 10;?? ??? ??? ??? ??? ??? ??? ??? ?// 重設mss后,ssthresh值也要相應修改pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);?? ?// 初始化阻塞窗口--pcb->snd_queuelen;?? ??? ??? ?// SYN被返回的ACK確認,所以占用的pbuf個數減1?? ?rseg = pcb->unacked;?? ??? ??? ?// 從發送了未收到確認的數據段隊列中取出SYN報文,相當于刪除pcb->unacked = rseg->next;?? ??? ?// 指向下一個發送了未收到確認的數據段if(pcb->unacked == NULL)?? ??? ?// 如果未確認的數據段為空,則停止重傳定時器pcb->rtime = -1;else ?? ??? ??? ??? ??? ??? ??? ?// 如果隊列中還有報文,則復位重傳定時器和重傳次數{pcb->rtime = 0;pcb->nrtx = 0;}tcp_seg_free(rseg);?? ??? ??? ??? ?// 釋放取下的SYN報文段內存空間TCP_EVENT_CONNECTED(pcb, ERR_OK, err);?? ?// 回調用戶的connect函數(詳解見下文)if (err == ERR_ABRT){return ERR_ABRT;}tcp_ack_now(pcb);?? ??? ??? ??? ?// 向服務器返回ACK,三次握手結束,具體含義見L753}// 如果只收到對方的ACK卻沒有SYN,則向對方返回RST報文else if(flag & TCP_ACK){tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);}break;case SYN_RCVD:?? ?// 服務器發送SYN+ACK后,就處于該狀態,等待客戶端返回ACK// 如果收到ACK,也就是三次握手的最后一個報文if(flags & TCP_ACK){// 如果ACK合法if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){u16_t old_cwnd;pcb->state = ESTABLISHED;?? ?// 進入ESTABLISHED狀態TCP_EVENT_ACCEPT(pcb, ERR_OK, err);?? ??? ?// 回調用戶的accept函數if (err != ERR_OK) ?? ??? ??? ??? ??? ??? ?// 如果accept函數返回錯誤,則關閉當前連接{if (err != ERR_ABRT){tcp_abort(pcb);}return ERR_ABRT;}old_cwnd = pcb->cwnd;?? ??? ?// 保存舊的阻塞窗口tcp_receive(pcb);?? ??? ??? ?// 如果該ACK報文中還攜帶了數據,則調用tcp_receive處理報文中的數據(解析見下文)// 調整本地未被確認的字節數,因為SYN報文占用1個字節,所以減1if (pcb->acked != 0) ?? ??? ?{pcb->acked--;?? ??? ??? ??? ?}pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);?? ?// 初始化阻塞窗口// 如果在上面的tcp_receive處理結果中包含FIN標志if (recv_flags & TF_GOT_FIN){tcp_ack_now(pcb);?? ??? ??? ?// 回復ACK,響應對方的FIN握手標志pcb->state = CLOSE_WAIT;?? ?// 進入CLOSE_WAIT狀態}}}else{// 對于不合法的ACK,則返回一個RSTtcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);}?? ?}// 如果收到客戶端重復SYN握手包,說明SYN+ACK包丟失,需要重傳else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)){tcp_rexmit(pcb);}break;case CLOSE_WAIT:?? ?// 服務器處于接收關閉的半連接狀態,會一直等待上層應用執行關閉指令,發出FIN,并將狀態變為LASK_ACKcase ESTABLISHED:?? ?// 連接雙方都處于穩定狀態tcp_receive(pcb);?? ??? ??? ??? ?// 調用函數處理報文中的數據// 如果在上面的tcp_receive處理結果中包含FIN標志if (recv_flags & TF_GOT_FIN){tcp_ack_now(pcb);?? ??? ??? ?// 回復ACK,響應對方的FIN握手標志pcb->state = CLOSE_WAIT;?? ?// 進入CLOSE_WAIT狀態}break;case FIN_WAIT_1:?? ?// 上層應用主動執行關閉指令,發送FIN后處于該狀態(通常對于客戶端來講)tcp_receive(pcb);?? ??? ??? ??? ??? ??? ?// 調用函數處理報文中的數據// 如果在上面的tcp_receive處理結果中包含FIN標志if (recv_flags & TF_GOT_FIN){// 如果該報文同時包含一個合法ACK,意味著本地端將直接跳過FIN_WAIT_2進入TIME_WAIT狀態if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)){tcp_ack_now(pcb);?? ??? ??? ??? ?// 回復ACKtcp_pcb_purge(pcb);?? ??? ??? ??? ?// 清除該連接中的所有現存數據TCP_RMV(&tcp_active_pcbs, pcb);?? ?// 從tcp_active_pcbs鏈表中刪除該控制塊pcb->state = TIME_WAIT;?? ??? ??? ?// 跳過FIN_WAIT_2狀態,直接進入TIME_WAIT狀態TCP_REG(&tcp_tw_pcbs, pcb);?? ??? ?// 將該控制塊加入tcp_tw_pcbs鏈表?? ?}// 如果該報文不含ACK,即表示雙方同時執行了關閉連接操作else{tcp_ack_now(pcb);?? ??? ??? ??? ?// 返回ACKpcb->state = CLOSING;?? ??? ??? ?// 進入CLOSING狀態}}// 如果只收到有效的ACKelse if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)){pcb->state = FIN_WAIT_2;?? ??? ??? ?// 進入FIN_WAIT_2狀態}break;?? ?case FIN_WAIT_2:?? ?// 主動關閉,發送FIN握手且收到ACK后處于該狀態?? ??? ?tcp_receive(pcb);?? ??? ??? ??? ??? ??? ?// 調用函數處理報文中的數據// 如果在上面的tcp_receive處理結果中包含FIN標志if (recv_flags & TF_GOT_FIN){tcp_ack_now(pcb);?? ??? ??? ??? ??? ?// 回復ACKtcp_pcb_purge(pcb);?? ??? ??? ??? ??? ?// 清除該連接中的所有現存數據TCP_RMV(&tcp_active_pcbs, pcb);?? ??? ?// 從tcp_active_pcbs鏈表中刪除該控制塊pcb->state = TIME_WAIT;?? ??? ??? ??? ?// 進入TIME_WAIT狀態TCP_REG(&tcp_tw_pcbs, pcb);?? ??? ??? ?// 將該控制塊加入tcp_tw_pcbs鏈表}break;case CLOSING:?? ??? ?// 雙方同時執行主動關閉,處于該狀態(特殊情況)tcp_receive(pcb);?? ??? ??? ??? ??? ??? ?// 調用函數處理報文中的數據// 如果收到合法ACKif (flags & TCP_ACK && ackno == pcb->snd_nxt){tcp_pcb_purge(pcb);?? ??? ??? ??? ??? ?// 清除該連接中的所有現存數據TCP_RMV(&tcp_active_pcbs, pcb);?? ??? ?// 從tcp_active_pcbs鏈表中刪除該控制塊pcb->state = TIME_WAIT;?? ??? ??? ??? ?// 進入TIME_WAIT狀態TCP_REG(&tcp_tw_pcbs, pcb);?? ??? ??? ?// 將該控制塊加入tcp_tw_pcbs鏈表}break;case LAST_ACK:?? ??? ?// 服務器在執行被動關閉時,發送完FIN,等待ACK時處于該狀態tcp_receive(pcb);?? ??? ??? ??? ??? ??? ?// 調用函數處理報文中的數據// 如果收到合法ACKif (flags & TCP_ACK && ackno == pcb->snd_nxt){recv_flags |= TF_CLOSED;?? ??? ??? ?// recv_flags設置為TF_CLOSED,由tcp_input函數對該控制塊進行釋放和清除}break;default:break;}return ERR_OK;}******************************************************************************************************************************************************************************************************
總結
以上是生活随笔為你收集整理的lwip之数据收发流程_2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lwip之数据收发流程_1
- 下一篇: #if 和#ifdef的区别