那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

LwIP中TCP協議是如何實現的

汽車電子技術 ? 來源:物聯網IoT開發 ? 作者: 杰杰mcu ? 2023-02-14 10:39 ? 次閱讀

前言說明

本文章代碼非常多,并且難懂,如非特別需要,否則不建議閱讀!建議學習TCP協議理論,等基礎扎實后再去閱讀lwip源碼,本文章的源碼只是輔助真正有需要的人閱讀!

TCP控制塊

與其他協議一樣,為了描述TCP協議,LwIP定義了一個名字叫tcp_pcb的結構體,可以稱之為TCP控制塊,其內定義了大量的成員變量,基本定義了整個TCP協議運作過程的所有需要的東西,如發送窗口、接收窗口、數據緩沖區。超時處理、擁塞控制、滑動窗口等等。

1/** TCP協議控制塊 */
  2struct tcp_pcb 
  3{
  4  IP_PCB;
  5/** 協議特定的PCB成員 */
  6  TCP_PCB_COMMON(struct tcp_pcb);
  7
  8  /* 遠端端口號 */
  9  u16_t remote_port;
 10
 11  tcpflags_t flags;
 12#define TF_ACK_DELAY   0x01U   /* 延遲發送ACK */
 13#define TF_ACK_NOW     0x02U   /* 立即發送ACK. */
 14#define TF_INFR        0x04U   /* 在快速恢復。 */
 15#define TF_CLOSEPEND   0x08U   /* 關閉掛起 */
 16#define TF_RXCLOSED    0x10U   /* rx由tcp_shutdown關閉 */
 17#define TF_FIN         0x20U   /* 連接在本地關閉 */
 18#define TF_NODELAY     0x40U   /* 禁用Nagle算法 */
 19#define TF_NAGLEMEMERR 0x80U   /* 本地緩沖區溢出 */
 20#define TF_TIMESTAMP   0x0400U   /* Timestamp option enabled */
 21#endif
 22#define TF_RTO         0x0800U /* RTO計時器 */
 23
 24  u8_t polltmr, pollinterval;
 25  /* 控制塊被最后一次處理的時間 */
 26  u8_t last_timer;           
 27  u32_t tmr;
 28
 29  /* 接收窗口相關的字段 */
 30  u32_t rcv_nxt;   /* 下一個期望收到的序號 */
 31  tcpwnd_size_t rcv_wnd;   /* 接收窗口大小 */
 32  tcpwnd_size_t rcv_ann_wnd; /* 告訴對方窗口的大小 */
 33  u32_t rcv_ann_right_edge; /* 窗口的右邊緣 */
 34
 35  /* 重傳計時器。*/
 36  s16_t rtime;
 37
 38  u16_t mss;   /* 最大報文段大小 */
 39
 40  /* RTT(往返時間)估計變量 */
 41  u32_t rttest; /* RTT估計,以為500毫秒遞增 */
 42  u32_t rtseq;  /* 用于測試RTT的報文段序號 */
 43  s16_t sa, sv; /* RTT估計得到的平均值與時間差 */
 44
 45  s16_t rto;    /* 重傳超時 */
 46  u8_t nrtx;    /* 重傳次數 */
 47
 48  /* 快速重傳/恢復 */
 49  u8_t dupacks;
 50  u32_t lastack; /* 接收到的最大確認序號 */
 51
 52  /* 擁塞避免/控制變量 */
 53  tcpwnd_size_t cwnd;     /* 連接當前的窗口大小 */
 54  tcpwnd_size_t ssthresh; /* 擁塞避免算法啟動的閾值 */
 55
 56
 57  u32_t rto_end;
 58
 59  u32_t snd_nxt;   /* 下一個要發送的序號 */
 60  u32_t snd_wl1, snd_wl2; /* 上一次收到的序號和確認號 */
 61  u32_t snd_lbb;       /* 要緩沖的下一個字節的序列號 */
 62  tcpwnd_size_t snd_wnd;   /* 發送窗口大小 */
 63  tcpwnd_size_t snd_wnd_max; /* 對方的最大發送方窗口 */
 64
 65  /* 可用的緩沖區空間(以字節為單位)。 */
 66  tcpwnd_size_t snd_buf;   
 67
 68  tcpwnd_size_t bytes_acked;
 69
 70  struct tcp_seg *unsent;   /* 未發送的報文段 */
 71  struct tcp_seg *unacked;  /* 已發送但未收到確認的報文段 */
 72  struct tcp_seg *ooseq; 
 73  /* 以前收到但未被上層處理的數據 */
 74  struct pbuf *refused_data; 
 75
 76#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
 77  struct tcp_pcb_listen* listener;
 78#endif 
 79
 80//TCP協議相關的回調函數
 81#if LWIP_CALLBACK_API
 82  /* 當數據發送成功后被調用 */
 83  tcp_sent_fn sent;
 84  /* 接收數據完成后被調用 */
 85  tcp_recv_fn recv;
 86  /* 建立連接后被調用 */
 87  tcp_connected_fn connected;
 88  /* 該函數被內核周期調用 */
 89  tcp_poll_fn poll;
 90  /* 發送錯誤時候被調用 */
 91  tcp_err_fn errf;
 92#endif 
 93
 94  /* 保持活性 */
 95  u32_t keep_idle;
 96  /* 堅持計時器計數器值 */
 97  u8_t persist_cnt;
 98  u8_t persist_backoff;
 99  u8_t persist_probe;
100
101  /* 保持活性報文發送次數 */
102  u8_t keep_cnt_sent;
103
104};

IP_PCB又是一個宏定義,定義了IP層需要的一些成員變量:

1#define IP_PCB                              2  /* 本地ip地址與遠端IP地址 */              3  ip_addr_t local_ip;                       4  ip_addr_t remote_ip;                      5  /* 綁定netif索引 */                       6  u8_t netif_idx;                           7  /* 套接字選項 */                          8  u8_t so_options;                          9  /* 服務類型 */                           10  u8_t tos;                                11  /* 生存時間 */                           12  u8_t ttl                                 13  /* 鏈路層地址解析提示 */                 14  IP_PCB_NETIFHINT

TCP_PCB_COMMON則是定義了一些特定的TCP控制塊的成員變量:

1#define TCP_PCB_COMMON(type) 2  type *next; /* 指向鏈表中的下一個控制塊 */ 3  void *callback_arg; 4  TCP_PCB_EXTARGS 5  enum tcp_state state; /* TCP狀態 */ 6  u8_t prio; 7  /* 本地主機端口號 */ 8  u16_t local_port

LwIP中除了定義了一個完整的TCP控制塊之外,還定義了一個刪減版TCP控制塊——tcp_pcb_listen,它用于描述處于監聽狀態的TCP連接,因為分配完整的TCP控制塊是比較消耗內存資源的,而TCP協議在建立連接之前是無傳輸數據的,因此在監聽的時候只需要把建立連接的主機的相關信息得到,然后無縫切換到完整的TCP控制塊中,這樣子就能節省不少資源(畢竟在嵌入式設備中資源是有限的)。除了tcp_pcb_listen外,LwIP還定義了4個鏈表來維護TCP連接時的各種狀態,分別是:

  1. 新綁定的端口鏈表,用于記錄新綁定端口的TCP控制塊。
  2. 監聽鏈表:用于記錄處于監聽狀態的TCP控制塊 。
  3. 活動狀態鏈表:用于記錄處于其他(活動)狀態的TCP控制塊。
  4. TIME_WAIT鏈表:用于記錄處于TIME_WAIT狀態的控制塊。
1/** 用于監聽的TCP協議控制塊 */
 2struct tcp_pcb_listen {
 3/** 所有PCB類型的通用成員 */
 4  IP_PCB;
 5/** 協議特定的PCB成員 */
 6  TCP_PCB_COMMON(struct tcp_pcb_listen);
 7};
 8
 9/* TCP 控制塊鏈表. */
10/** 新綁定的端口 */
11struct tcp_pcb *tcp_bound_pcbs;
12/** 處于監聽狀態的TCP控制塊 */
13union tcp_listen_pcbs_t tcp_listen_pcbs;
14/** 穩定的TCP連接 */
15struct tcp_pcb *tcp_active_pcbs;
16/** 處于TIME_WAIT狀態的控制塊 */
17struct tcp_pcb *tcp_tw_pcbs;

tcp_bound_pcbs鏈表上的TCP控制塊可以看做是處于CLOSED狀態,那些新綁定的端口初始的時候都是處于這個狀態。tcp_listen_pcbs鏈表用于記錄處于監聽狀態的TCP控制塊,一般就是記錄tcp_pcb_listen控制塊。tcp_tw_pcbs鏈表用于記錄連接中處于TIME_WAIT狀態下的TCP控制塊。而tcp_active_pcbs鏈表用于記錄所有其他狀態(活動狀態)的TCP控制塊,這些端口是活躍的,可以不斷進行狀態轉移。

窗口

關于窗口的理論我不想講太多,大家有興趣可以看一下網絡上的其他博文,都是描述得很詳細的。

TCP控制塊中關于接收窗口的成員變量有rcv_nxt、rcv_wnd、rcv_ann_wnd、rcv_ann_right_edgercv_nxt表示下次期望接收到的數據編號,rcv_wnd表示接收窗口的大小,rcv_ann_wnd用于告訴發送方窗口的大小,rcv_ann_right_edge記錄了窗口的右邊界,這4個成員變量都會在數據傳輸的過程中動態改變的。

TCP控制塊中關于發送窗口的成員變量有lastack、snd_nxt、snd_lbb、snd_wndlastack記錄了已經確認的最大序號,snd_nxt表示下次要發送的序號,snd_lbb是表示下一個將被應用線程緩沖的序號,而snd_wnd表示發送窗口的大小,是由接收已方提供的。這些值也是動態變化的,當發送的數據收到確認,就會更新lastack,并且隨著數據的發送出去,窗口會向右移動,即snd_nxt的值在增加。

每條TCP 連接的每一端都必須設有兩個窗口:一個發送窗口和一個接收窗口,TCP 的可靠傳輸機制用字節的序號(編號)進行控制,TCP 所有的確認都是基于數據的序號而不是基于報文段,發送過的數據未收到確認之前必須保留,以便超時重傳時使用,發送窗口在沒收到確認序號之前是保持不動的,當收到確認序號就會向右移動,并且更新lastack 的值。

發送緩沖區用來暫時存放應用程序發送給對方的數據,這是主機已發送出但未收到確認的數據。接收緩存用來暫時存放按序到達的、但尚未被接收應用程序讀取的數據以及 不按序到達的數據。

關于窗口的概念必須強調2點:

  1. 發送方的發送窗口并不總是和 接收方接收窗口一樣大,因為有一定的時間滯后。
  2. TCP 標準沒有規定對不按序到達的數據應如何處理,通常是先臨時存放在接收窗口中,等到字節流中所缺少的字節收到后,再按序交付上層的應用進程。

TCP報文段發送

每個已經連接的TCP控制塊中維護了3個是指針,分別是unsent、unacked、oosequnsent指向未發送的報文段緩沖隊列,unacked指向已發送但未收到確認的報文段緩沖隊列,ooseq指向已經收到的無序報文段緩沖隊列。

簡單來說TCP協議發送報文就是將應用層的數據寫入發送緩沖區(緩沖隊列)中,根據窗口大小進行發送。在LwIP中,為了更高效發送數據,Nagle算法是默認打開的。因此LwIP的處理是調用tcp_write()函數將應用層的數據寫入TCP報文段緩沖隊列,即TCP控制塊中的unsent指針所指向的隊列中,但是不會立即發送,而是存儲在緩沖區里面,等待更多的數據進行高效的發送。當然只要你寫入的數據滿足Nagle算法的大小MSS,這是可以立即發送出去的,否則就等待超時或者數據達到MSS就會將數據發送出去。當然,我們也能將Nagle算法禁用。ps:關于寫入緩沖隊列的操作大家可以直接看源碼即可。

當然,我們也能手動在寫入數據后直接調用TCP協議的發送數據函數來發送這些數據(RAW API 比較常用這種方法),LwIP是調用tcp_output()函數來發送這些數據的,這樣子一個應用層的數據就通過TCP協議傳遞給IP層了。

關于Nagle算法的介紹,我引用維基百科的一段描述:

TCP/IP協議中,無論發送多少數據,總是要在數據前面加上協議頭,同時,對方接收到數據,也需要發送ACK表示確認。為了盡可能的利用網絡帶寬,TCP總是希望盡可能的發送足夠大的數據。(一個連接會設置MSS參數,因此,TCP/IP希望每次都能夠以MSS尺寸的數據塊來發送數據)。Nagle算法就是為了盡可能發送大塊數據,避免網絡中充斥著許多小數據塊。

Nagle算法的基本定義是任意時刻,最多只能有一個未被確認的小段。所謂“小段”,指的是小于MSS尺寸的數據塊,所謂“未被確認”,是指一個數據塊發送出去后,沒有收到對方發送的ACK確認該數據已收到。

代碼的實現如下:

1err_t
  2tcp_output(struct tcp_pcb *pcb)
  3{
  4  struct tcp_seg *seg, *useg;
  5  u32_t wnd, snd_nxt;
  6  err_t err;
  7  struct netif *netif;
  8
  9  //如果控制塊有數據在處理,直接返回
 10  if (tcp_input_pcb == pcb) {
 11    return ERR_OK;
 12  }
 13
 14  //得到合適的發送窗口
 15  wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);
 16
 17  //找到控制塊中的未發送數據緩沖區鏈表
 18  seg = pcb->unsent;
 19
 20  //根據控制塊IP地址信息找到合適的網卡發送
 21  netif = tcp_route(pcb, &pcb->local_ip, &pcb->remote_ip);
 22  if (netif == NULL) {
 23    return ERR_RTE;
 24  }
 25
 26  /* 如果沒有本地IP地址,我們會從netif獲得一個 */
 27  if (ip_addr_isany(&pcb->local_ip)) {
 28    const ip_addr_t *local_ip = 
 29    ip_netif_get_local_ip(netif, &pcb->remote_ip);
 30    if (local_ip == NULL) {
 31      return ERR_RTE;
 32    }
 33    ip_addr_copy(pcb->local_ip, *local_ip);
 34  }
 35
 36  /* 處理當前不適合窗口的報文段 */
 37  if (lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd) 
 38  {
 39    //開始持續定時器
 40    if (wnd == pcb->snd_wnd && pcb->unacked == NULL &&
 41    pcb->persist_backoff == 0) 
 42    {
 43      pcb->persist_cnt = 0;
 44      pcb->persist_backoff = 1;
 45      pcb->persist_probe = 0;
 46    }
 47    /* 我們需要ACK,但現在無法發送數據(無法捎帶),所以發送一個ACK報文段 */
 48    if (pcb->flags & TF_ACK_NOW) {
 49      return tcp_send_empty_ack(pcb);
 50    }
 51    goto output_done;
 52  }
 53  /* 停止持續計時器 */
 54  pcb->persist_backoff = 0;
 55
 56  /* useg指向未應答隊列中的最后一個tcp_seg結構 */
 57  useg = pcb->unacked;
 58  if (useg != NULL) {
 59    for (; useg->next != NULL; useg = useg->next);
 60  }
 61  /* 可用數據和窗口允許它發送報文段,直到把未發送鏈表的數據完全發送出去或者直到填滿發送窗口 */
 62  while (seg != NULL &&lwip_ntohl(seg->tcphdr->seqno)
 63         - pcb->lastack + seg->len <= wnd) 
 64    {
 65    if ((tcp_do_output_nagle(pcb) == 0) &&
 66        ((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)) {
 67      break;
 68    }
 69
 70    if (pcb->state != SYN_SENT) {
 71      TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
 72    }
 73
 74    //真正發送TCP報文的函數,此處發送TCP報文段
 75    err = tcp_output_segment(seg, pcb, netif);
 76
 77    if (err != ERR_OK) 
 78    {
 79      tcp_set_flags(pcb, TF_NAGLEMEMERR);
 80      return err;
 81    }
 82
 83    //得到下一個未發送的tcp_seg
 84    pcb->unsent = seg->next;
 85    if (pcb->state != SYN_SENT) 
 86    {
 87      tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW);
 88    }
 89    //計算snd_nxt的值
 90    snd_nxt = lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);
 91
 92    //更新下一個要發送的數據編號
 93    if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) 
 94    {
 95      pcb->snd_nxt = snd_nxt;
 96    }
 97    /* 如果發送出去的數據長度>0,則將這些報文段放在未確認鏈表中 */
 98    if (TCP_TCPLEN(seg) > 0) 
 99    {
100      seg->next = NULL;
101      /* 未確認鏈表為空,插入即可 */
102      if (pcb->unacked == NULL) {
103        pcb->unacked = seg;
104        useg = seg;
105
106      } 
107      //如果不為空,按照順序插入未確認鏈表中
108      else 
109      {
110        if (TCP_SEQ_LT(lwip_ntohl(seg->tcphdr->seqno),
111        lwip_ntohl(useg->tcphdr->seqno))) 
112        {
113          struct tcp_seg **cur_seg = &(pcb->unacked);
114          while (*cur_seg &&
115                 TCP_SEQ_LT(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_ntohl(seg->tcphdr->seqno))) {
116            cur_seg = &((*cur_seg)->next );
117          }
118          seg->next = (*cur_seg);
119          (*cur_seg) = seg;
120        }
121        else 
122        {
123          useg->next = seg;
124          useg = useg->next;
125        }
126      }
127    }
128    else 
129    {
130      tcp_seg_free(seg);
131    }
132    seg = pcb->unsent;
133  }
134
135output_done:
136  tcp_clear_flags(pcb, TF_NAGLEMEMERR);
137  return ERR_OK;
138}

總的來說,流程還是很簡單明了的,如果控制塊的flags字段被設置為TF_ACK_NOW,表示需要立即響應對方,但是此時還沒有數據發送,就只發送一個純粹的ACK應答報文段,如果能發送數據,那就將ACK應答捎帶過去(捎帶機制),這樣子就能減少網絡中的流量。TCP協議在發送的時候先找到未發送隊列unsent,然后調用tcp_output_segment()->ip_output_if()函數進行發送,將TCP報文段傳遞到IP層,直到把未發送隊列的數據完全發送出去或者直到填滿發送窗口才算是完成一次發送操作,同時要更新發送窗口相關字段,還要將這些已發送但是未確認的數據存儲在已發送但未確認鏈表unacked中,以防丟失數據進行重發操作,放入未確認鏈表的時候是按序號升序進行排序的。

TCP報文段接收

IP數據報中如果是遞交給TCP協議的數據,就會調用tcp_input()函數往上層傳遞,因此TCP協議的報文段接收函數就是tcp_input()函數。只不過這個函數太過于復雜,我自己都不想看它,就簡單用文字描述一下處理過程吧,然后刪減一下代碼讓大伙看看。

tcp_input()函數會對傳遞進來的IP數據報進行處理,做一些校驗,檢查數據報是否正確等操作,查看一下數據報中是否有數據,如果沒有就丟掉,再看一下是不是多播、廣播報文,如果是就不做處理,釋放pbuf。將TCP首部中的各字段內容提取出來,首先在 tcp_active_pcbs鏈表中尋找對應的TCP控制塊,找到了就調用tcp_process()函數進行處理;如果找不到就去tcp_tw_pcbs鏈表中查找,找到了就調用tcp_timewait_input()函數處理它;如果還是找不到就去tcp_listen_pcbs鏈表中找,如果找到就調用tcp_listen_input()函數處理,如果還是找不到的話,那沒辦法了,這收到的是垃圾數據,釋放pbu。

還要注意的是,TCP協議很可能收到不是正常數據,而是一些特殊TCP報文段

  1. 如果收到的是復位報文終止連接應答報文,那么就釋放pbuf,終止連接
  2. 如果是收到了應答報文段(發送數據后必須等待應答),那么就調用宏TCP_EVENT_SENT(其實是一個sent的回調函數)去處理,并且更新窗口
  3. 如果報文段中包含有效的數據,就調用TCP_EVENT_RECV去處理它,此時將產生應答報文與更新接收窗口的操作
  4. 如果是收到FIN報文,則調用TCP_EVENT_CLOSED去處理它,此時將產生應答并且開始終止連接

代碼如下:

1void
  2tcp_input(struct pbuf *p, struct netif *inp)
  3{
  4  struct tcp_pcb *pcb, *prev;
  5  struct tcp_pcb_listen *lpcb;
  6
  7  u8_t hdrlen_bytes;
  8  err_t err;
  9
 10  LWIP_UNUSED_ARG(inp);
 11
 12  PERF_START;
 13
 14  TCP_STATS_INC(tcp.recv);
 15  MIB2_STATS_INC(mib2.tcpinsegs);
 16
 17  tcphdr = (struct tcp_hdr *)p->payload;
 18
 19  /* 檢查報文段是否有有效數據 */
 20  if (p->len < TCP_HLEN) 
 21  {
 22    /* 如果沒有就丟掉報文段 */
 23    TCP_STATS_INC(tcp.lenerr);
 24    goto dropped;
 25  }
 26
 27  /* 不處理傳入的廣播/多播報文段。 */
 28  if (ip_addr_isbroadcast(ip_current_dest_addr(), 
 29      ip_current_netif()) ||
 30      ip_addr_ismulticast(ip_current_dest_addr())) 
 31  {
 32    TCP_STATS_INC(tcp.proterr);
 33    goto dropped;
 34  }
 35
 36  /* 檢查TCP報文段首部長度 */
 37  hdrlen_bytes = TCPH_HDRLEN_BYTES(tcphdr);
 38  if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) 
 39  {
 40    TCP_STATS_INC(tcp.lenerr);
 41    goto dropped;
 42  }
 43
 44  /* 移動pbuf指針,指向TCP報文段數據區域 */
 45  tcphdr_optlen = (u16_t)(hdrlen_bytes - TCP_HLEN);
 46  tcphdr_opt2 = NULL;
 47  if (p->len >= hdrlen_bytes) 
 48  {
 49    tcphdr_opt1len = tcphdr_optlen;
 50    pbuf_remove_header(p, hdrlen_bytes); 
 51  } 
 52
 53  /* 將TCP首部中的各字段內容提取出來。 */
 54  tcphdr->src = lwip_ntohs(tcphdr->src);
 55  tcphdr->dest = lwip_ntohs(tcphdr->dest);
 56  seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno);
 57  ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno);
 58  tcphdr->wnd = lwip_ntohs(tcphdr->wnd);
 59
 60  flags = TCPH_FLAGS(tcphdr);
 61  tcplen = p->tot_len;
 62
 63  if (flags & (TCP_FIN | TCP_SYN)) 
 64  {
 65    tcplen++;
 66    if (tcplen < p->tot_len) 
 67    {
 68      /* u16_t溢出,無法處理這個 */
 69      TCP_STATS_INC(tcp.lenerr);
 70      goto dropped;
 71    }
 72  }
 73
 74  prev = NULL;
 75
 76  //遍歷tcp_active_pcbs鏈表尋找對應的TCP控制塊
 77  for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) 
 78  {
 79    /* 檢查控制塊是否與對應的網卡綁定 */
 80    if ((pcb->netif_idx != NETIF_NO_INDEX) &&
 81        (pcb->netif_idx != 
 82        netif_get_index(ip_data.current_input_netif))) 
 83    {
 84      prev = pcb;
 85      continue;
 86    }
 87     /* ··· */
 88     /* 省略處理 */
 89     /* ··· */
 90
 91  if (pcb == NULL) 
 92  {
 93    /* 如果TCP控制塊沒有處于連接狀態,就去tcp_tw_pcbs鏈表中找 */
 94    for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) 
 95    {
 96      /* 檢查控制塊是否與對應的網卡綁定 */
 97      if ((pcb->netif_idx != NETIF_NO_INDEX) &&
 98          (pcb->netif_idx != netif_get_index
 99          (ip_data.current_input_netif))) 
100          {
101            continue;
102          }
103
104      if (pcb->remote_port == tcphdr->src &&
105          pcb->local_port == tcphdr->dest &&
106          ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
107          ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) 
108      {
109            //找到了就處理它
110            tcp_timewait_input(pcb);
111
112        pbuf_free(p);
113        return;
114      }
115    }
116
117    /* 還是找不到就去tcp_listen_pcbs鏈表中找 */
118    prev = NULL;
119    for (lpcb = tcp_listen_pcbs.listen_pcbs; 
120    lpcb != NULL; lpcb = lpcb->next) 
121    {
122      /* 檢查控制塊是否與對應的網卡綁定 */
123      if ((lpcb->netif_idx != NETIF_NO_INDEX) &&
124          (lpcb->netif_idx != netif_get_index(ip_data.current_input_netif))) {
125        prev = (struct tcp_pcb *)lpcb;
126        continue;
127      }
128     /* ··· */
129     /* 省略處理 */
130     /* ··· */
131
132    //找到了處于監聽狀態的TCP控制塊
133    if (lpcb != NULL) 
134    {
135      if (prev != NULL) {
136        ((struct tcp_pcb_listen *)prev)->next = lpcb->next;
137        lpcb->next = tcp_listen_pcbs.listen_pcbs;
138        tcp_listen_pcbs.listen_pcbs = lpcb;
139      } else {
140        TCP_STATS_INC(tcp.cachehit);
141      }
142      //處理報文段
143      tcp_listen_input(lpcb);
144      pbuf_free(p);
145      return;
146    }
147  }
148
149     /* ··· */
150     /* 省略處理 */
151     /* ··· */
152
153    tcp_input_pcb = pcb;
154    err = tcp_process(pcb);
155
156     /* ··· */
157     /* 省略處理 */
158     /* ··· */
159
160    }
161}
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 代碼
    +關注

    關注

    30

    文章

    4825

    瀏覽量

    69046
  • 變量
    +關注

    關注

    0

    文章

    613

    瀏覽量

    28465
  • LwIP
    +關注

    關注

    2

    文章

    88

    瀏覽量

    27361
  • 結構體
    +關注

    關注

    1

    文章

    130

    瀏覽量

    10872
  • TCP協議
    +關注

    關注

    1

    文章

    101

    瀏覽量

    12121
收藏 人收藏

    評論

    相關推薦

    STM32之LWIP網絡協議TCP服務器創建

    lwip是瑞典計算機科學院網絡嵌入式系統小組(SICS)的Adam Dunkels(亞當·鄧克爾) 開發的一個小型開源的TCP/IP協議棧。實現的重點是在保持
    的頭像 發表于 06-02 08:44 ?1w次閱讀
    STM32之<b class='flag-5'>LWIP</b>網絡<b class='flag-5'>協議</b>棧<b class='flag-5'>TCP</b>服務器創建

    基于MM32F5270的Ethernet實現LwIP協議棧移植

    LwIP是輕量化的TCP/IP協議,由瑞典計算機科學院(SICS)的Adam Dunkels 開發的一個小型開源的TCP/IP協議棧。
    的頭像 發表于 06-21 10:28 ?1321次閱讀
    基于MM32F5270的Ethernet<b class='flag-5'>實現</b><b class='flag-5'>LwIP</b><b class='flag-5'>協議</b>棧移植

    lwip tcp ip 協議

    本帖最后由 mr.pengyongche 于 2013-4-30 03:11 編輯 lwip tcp ip 協議
    發表于 02-22 16:49

    LWIP TCP報文基礎知識及其LWIPTCP協議實現

    ,只有URG標志位被設值時該字段才有意義,表示緊急數據相對序列號(Sequence Number字段的值)的偏移。用URG值+序號得到最后一個緊急字節。LWIPTCP協議
    發表于 10-18 14:54

    Lwip協議棧的設計方案

    LWIPTCP/IP協議棧的一種實現LWIP的主要目的是減少存儲器利用量和代碼尺寸,使LWIP
    發表于 09-16 15:18 ?33次下載
    <b class='flag-5'>Lwip</b><b class='flag-5'>協議</b>棧的設計方案

    lwip協議中文版

    LWIPTCP/IP協議棧的一種實現LWIP的主要目的是減少存儲器利用量和代碼尺寸,使LWIP
    發表于 02-03 16:47 ?0次下載
    <b class='flag-5'>lwip</b><b class='flag-5'>協議</b>中文版

    LwIP協議詳解

    LwIP協議詳解,LwIP是Light Weight (輕型)IP協議,有無操作系統的支持都可以運行。LwIP
    發表于 11-09 18:25 ?49次下載

    TCPIP協議棧的實現lwip

    TCPIP協議棧的實現lwip方便初學者剛開始接觸lwip,有個大概的了解與認識。
    發表于 03-14 15:40 ?13次下載

    lwip協議棧源碼詳解說明

    lwip是瑞典計算機科學院(SICS)的Adam Dunkels 開發的一個小型開源的TCP/IP協議棧。實現的重點是在保持TCP
    發表于 12-11 15:27 ?3.7w次閱讀
    <b class='flag-5'>lwip</b><b class='flag-5'>協議</b>棧源碼詳解說明

    介紹tcp_ip協議lwip的特點

    簡介了嵌入式tcp_ip協議lwip的基本信息
    的頭像 發表于 07-03 13:05 ?3693次閱讀

    LWIP協議Raw TCP中使用

    本文檔的主要內容詳細介紹的是LWIP協議Raw TCP中使用的資料免費下載
    發表于 11-05 17:36 ?17次下載
    <b class='flag-5'>LWIP</b><b class='flag-5'>協議</b>棧<b class='flag-5'>中</b>Raw <b class='flag-5'>TCP</b>中使用

    基于μcosⅡ和S3C2410實現TCP/IP協議LwIP的應用方案

    隨著嵌入式系統與網絡的日益結合,越來越多的嵌入式設備需要實現Internet網絡化,支持嵌入式設備接入網絡,已成為嵌入式領域重要的研究方向。而目前嵌入式系統中大量應用低速處理器,受內存和速度限制,實現完整的TCP/IP
    的頭像 發表于 06-22 15:36 ?1907次閱讀
    基于μcosⅡ和S3C2410<b class='flag-5'>實現</b><b class='flag-5'>TCP</b>/IP<b class='flag-5'>協議</b>棧<b class='flag-5'>LwIP</b>的應用方案

    如何更好的理解LWIP協議

    LwIP(Light weight IP),是一種輕量化且開源的TCP/IP協議棧,它可以在有限的RAM和ROM條件下,實現一個完整的TCP
    的頭像 發表于 10-27 09:13 ?4304次閱讀

    基于LwIPTCP客戶端設計

    上一篇我們基于LwIP協議棧的RAW API實現了一個TCP服務器的簡單應用,接下來一節我們來實現一個T
    的頭像 發表于 12-14 15:12 ?2350次閱讀
    基于<b class='flag-5'>LwIP</b>的<b class='flag-5'>TCP</b>客戶端設計

    LwIP協議棧源碼詳解—TCP/IP協議實現

    電子發燒友網站提供《LwIP協議棧源碼詳解—TCP/IP協議實現.pdf》資料免費下載
    發表于 07-03 11:22 ?3次下載
    大发888娱乐下载网址| sp全讯网新2| 赌博百家乐赢不了| 百家乐官网庄闲符号记| 泰顺县| 大发888苹果手机下载| 百家乐高手怎么下注| 百家乐庄闲下载| 悦榕庄百家乐官网的玩法技巧和规则 | 百家乐官网翻天下载| 百家乐官网公式书| 百家乐官网也能赢钱么| 利博娱乐| 大发888官网网址| 百樂坊百家乐的玩法技巧和规则 | 百家乐官网又称什么| bet365注册找谁| 太阳城娱乐管理网| 亚洲百家乐的玩法技巧和规则| 新2百家乐娱乐城| 网络百家乐可靠吗| 百家乐官网技巧开户网址| 百家乐官网网上真钱娱乐网 | 任你博娱乐城| 威尼斯人娱乐城位置| 加州百家乐的玩法技巧和规则| 百家乐桌布| 百家乐平台注册送现金| 百家乐几点开奖| 在线百家乐官网下| 百家乐官网必赢| 中骏百家乐官网的玩法技巧和规则 | 百家乐盛大娱乐城城| 百家乐2号干扰| 百家乐官网过滤工具| 四方百家乐官网的玩法技巧和规则| 玩百家乐官网的高手| 百家乐官网正品地址| 百家乐官网百姓话题| 太阳百家乐官网3d博彩通| 百家乐官网输了100万|