如果在ARP緩存表中沒有找到目標IP地址對應的表項,那么ARP協議就會創建一個表項,這也是ARP協議的核心處理,對于剛創建的表項,它在初始化網卡信息后會被設置為ETHARP\_STATE\_PENDING狀態,與此同時一個ARP請求包將被廣播出去,這個時候的表項是無法發送數據的,只有等待到目標主機回應了一個ARP應答包才能發送數據,那么這些數據在這段時間中將被掛到表項的等待隊列上,在ARP表項處于ETHARP\_STATE\_STABLE狀態完成數據的發送,函數源碼具體見代碼清單 10?13。
```
1 err_t
2 etharp_query(struct netif *netif,
3 const ip4_addr_t *ipaddr,
4 struct pbuf *q)
5 {
6 struct eth_addr *srcaddr = (struct eth_addr *)netif->hwaddr;
7 err_t result = ERR_MEM;
8 int is_new_entry = 0;
9 s16_t i_err;
10 netif_addr_idx_t i;
11
12 /* 檢是否為單播地址 */
13 if (ip4_addr_isbroadcast(ipaddr, netif) ||
14 ip4_addr_ismulticast(ipaddr) ||
15 ip4_addr_isany(ipaddr))
16 {
17 return ERR_ARG;
18 }
19
20 /* 在ARP緩存中查找表項,如果沒有則嘗試創建表項 */
21 i_err = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD, netif);(1)
22
23 /* 沒有發現表項或者沒有創建表項成功 */
24 if (i_err < 0)
25 {
26 LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE,
27 ("etharp_query: could not create ARP entry\n"));
28 if (q)
29 {
30 LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE,
31 ("etharp_query: packet dropped\n"));
32 ETHARP_STATS_INC(etharp.memerr);
33 }
34 return (err_t)i_err; //返回錯誤代碼
35 }
36 LWIP_ASSERT("type overflow", (size_t)i_err < NETIF_ADDR_IDX_MAX);
37
38 //找到對應的表項或者創建表項成功
39 i = (netif_addr_idx_t)i_err;
40
41 /* 將新表項標記為待處理 */
42 if (arp_table[i].state == ETHARP_STATE_EMPTY)
43 {
44 is_new_entry = 1;
45 arp_table[i].state = ETHARP_STATE_PENDING;
46 /* 記錄網絡接口 */
47 arp_table[i].netif = netif; (2)
48 }
49
50 /* 是否有新的表項 */
51 if (is_new_entry || (q == NULL)) (3)
52 {
53 /* 發送ARP請求包*/
54 result = etharp_request(netif, ipaddr);
55 if (result != ERR_OK)
56 {
57 /* 無法發送ARP請求 */
58 }
59 if (q == NULL)
60 {
61 return result; (4)
62 }
63 }
64
65 LWIP_ASSERT("q != NULL", q != NULL);
66 /* 表項狀態是否穩定 */
67 if (arp_table[i].state >= ETHARP_STATE_STABLE)
68 {
69 ETHARP_SET_ADDRHINT(netif, i);
70 /* 發送數據包 */
71 result = ethernet_output(netif, q,
72 srcaddr,
73 &(arp_table[i].ethaddr),
74 ETHTYPE_IP); (5)
75 }
76 /* 如果表項是ETHARP_STATE_PENDING狀態 */
77 else if (arp_table[i].state == ETHARP_STATE_PENDING)
78 {
79 /* 將給數據包'q'排隊 */
80 struct pbuf *p;
81 int copy_needed = 0;
82 /* 如果q包含必須拷貝的pbuf,請將整個鏈復制到一個新的PBUF_RAM */
83 p = q;
84 while (p)
85 {
86 LWIP_ASSERT("no packet queues allowed!",
87 (p->len != p->tot_len) || (p->next == 0));
88 if (PBUF_NEEDS_COPY(p)) (6)
89 {
90 //需要拷貝
91 copy_needed = 1;
92 break;
93 }
94 p = p->next;
95 }
96 if (copy_needed)
97 {
98 /* 將整個數據包復制到新的pbuf中 */
99 p = pbuf_clone(PBUF_LINK, PBUF_RAM, q); (7)
100 }
101 else
102 {
103 /* 引用舊的pbuf就足夠了 */
104 p = q;
105 pbuf_ref(p);
106 }
107
108 if (p != NULL)
109 {
110 /* 如果使用隊列 */
111 #if ARP_QUEUEING
112 struct etharp_q_entry *new_entry;
113 /* 分配一個新的arp隊列表項 */ (8)
114 new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
115 if (new_entry != NULL)
116 {
117 unsigned int qlen = 0;
118 new_entry->next = 0;
119 new_entry->p = p;
120 if (arp_table[i].q != NULL)
121 {
122 /* 隊列已經存在,將新數據包插入隊列后面 */
123 struct etharp_q_entry *r;
124 r = arp_table[i].q;
125 qlen++;
126 while (r->next != NULL)
127 {
128 r = r->next;
129 qlen++;
130 }
131 r->next = new_entry; (9)
132 }
133 else
134 {
135 /* 隊列不存在,數據包就是隊列的第一個節點 */
136 arp_table[i].q = new_entry; (10)
137 }
138 #if ARP_QUEUE_LEN
139 if (qlen >= ARP_QUEUE_LEN)
140 {
141 struct etharp_q_entry *old;
142 old = arp_table[i].q;
143 arp_table[i].q = arp_table[i].q->next;
144 pbuf_free(old->p);
145 memp_free(MEMP_ARP_QUEUE, old);
146 }
147 #endif
148 result = ERR_OK;
149 }
150 else
151 {
152 /* 申請內存失敗 */
153 pbuf_free(p);
154 result = ERR_MEM;
155 }
156 #else
157 /* 如果只是掛載單個數據包,那么始終只為每個ARP請求排隊一個數據包,
158 就需要釋放先前排隊的數據包 */
159 if (arp_table[i].q != NULL)
160 {
161 pbuf_free(arp_table[i].q); (11)
162 }
163 arp_table[i].q = p;
164 result = ERR_OK;
165
166 #endif
167 }
168 else
169 {
170 ETHARP_STATS_INC(etharp.memerr);
171 result = ERR_MEM;
172 }
173 }
174 return result;
175 }
```
(1)(2):函數的處理邏輯是很清晰的,首先調用etharp_find_entry()函數在ARP緩存表中查找表項,如果沒有找到就嘗試創建表項并且返回表項的索引,當然ARP緩存表中可能存在表項,可能為新創建的表項(ETHARP_STATE_EMPTY),也可能為ETHARP_STATE_PENDING或者ETHARP_STATE_STABLE狀態。如果是新創建的表項,那么表項肯定沒有其他信息,LwIP就會初始化一些信息,如網卡,然后就將表項設置為ETHARP_STATE_PENDING狀態。
(3):如果表項是剛創建的或者數據包是空的,那么就會調用etharp_request()函數發送一個ARP請求包。
(4):如果數據包是空的,直接返回結果
(5):如果表項的狀態大于等于 ETHARP_STATE_STABLE,表示表項已經是穩定狀態了,就調用ethernet_output()函數發送數據包。
(6):通過宏定義PBUF_NEEDS_COPY(p)對數據包的類型進行判斷,如果需要拷貝則將變量copy_needed設置為1,表示需要拷貝。
(7):將整個數據包復制到新的pbuf中。
(8):如果ARP_QUEUEING宏定義為1,則表示使用隊列,那么LwIP會分配一個新的ARP數據包隊列節點,然后插入隊列中。
(9):如果隊列已經存在,將新數據包插入隊列后面。
(10):如果隊列不存在,數據包就是隊列的第一個節點。
(11):如果只是掛載單個數據包,就需要釋放先前排隊的數據包,然后再掛載新的數據包。
掛載的這些數據在等待到目標主機產生ARP應答的時候會發送出去,此時的發送就是延時了,所以在沒有ARP表項的時候,發送數據會產生延時,在指定等待ARP應答時間內如果等不到目標主機的應答,那么這個表項將被系統回收,同時數據也無法發送出去。
上層數據包通過ARP協議進行發送數據的流程示意圖具體見圖 10 10。

整個ARP協議運作示意圖具體見圖 10?11。

- 說明
- 第1章:網絡協議簡介
- 1.1:常用網絡協議
- 1.2:網絡協議的分層模型
- 1.3:協議層報文間的封裝與拆封
- 第2章:LwIP簡介
- 2.1:LwIP的優缺點
- 2.2:LwIP的文件說明
- 2.2.1:如何獲取LwIP源碼文件
- 2.2.2:LwIP文件說明
- 2.3:查看LwIP的說明文檔
- 2.4:使用vscode查看源碼
- 2.4.1:查看文件中的符號列表(函數列表)
- 2.4.2:函數定義跳轉
- 2.5:LwIP源碼里的example
- 2.6:LwIP的三種編程接口
- 2.6.1:RAW/Callback API
- 2.6.2:NETCONN API
- 2.6.3:SOCKET API
- 第3章:開發平臺介紹
- 3.1:以太網簡介
- 3.1.1:PHY層
- 3.1.2:MAC子層
- 3.2:STM32的ETH外設
- 3.3:MII 和 RMII 接口
- 3.4:PHY:LAN8720A
- 3.5:硬件設計
- 3.6:軟件設計
- 3.6.1:獲取STM32的裸機工程模板
- 3.6.2:添加bsp_eth.c與bsp_eth.h
- 3.6.3:修改stm32f4xx_hal_conf.h文件
- 第4章:LwIP的網絡接口管理
- 4.1:netif結構體
- 4.2:netif使用
- 4.3:與netif相關的底層函數
- 4.4:ethernetif.c文件內容
- 4.4.1:ethernetif數據結構
- 4.4.2:ethernetif_init()
- 4.4.3:low_level_init()
- 第5章:LwIP的內存管理
- 5.1:幾種內存分配策略
- 5.1.1:固定大小的內存塊
- 5.1.2:可變長度分配
- 5.2:動態內存池(POOL)
- 5.2.1:內存池的預處理
- 5.2.2:內存池的初始化
- 5.2.3:內存分配
- 5.2.4:內存釋放
- 5.3:動態內存堆
- 5.3.1:內存堆的組織結構
- 5.3.2:內存堆初始化
- 5.3.3:內存分配
- 5.3.4:內存釋放
- 5.4:使用C庫的malloc和free來管理內存
- 5.5:LwIP中的配置
- 第6章:網絡數據包
- 6.1:TCP/IP協議的分層思想
- 6.2:LwIP的線程模型
- 6.3:pbuf結構體說明
- 6.4:pbuf的類型
- 6.4.1:PBUF_RAM類型的pbuf
- 6.4.2:PBUF_POOL類型的pbuf
- 6.4.3:PBUF_ROM和PBUF_REF類型pbuf
- 6.5:pbuf_alloc()
- 6.6:pbuf_free()
- 6.7:其它pbuf操作函數
- 6.7.1:pbuf_realloc()
- 6.7.2:pbuf_header()
- 6.7.3:pbuf_take()
- 6.8:網卡中使用的pbuf
- 6.8.1:low_level_output()
- 6.8.2:low_level_input()
- 6.8.3:ethernetif_input()
- 第7章:無操作系統移植LwIP
- 7.1:將LwIP添加到裸機工程
- 7.2:移植頭文件
- 7.3:移植網卡驅動
- 7.4:LwIP時基
- 7.5:協議棧初始化
- 7.6:獲取數據包
- 7.6.1:查詢方式
- 7.6.2:ping命令詳解
- 7.6.3:中斷方式
- 第8章:有操作系統移植LwIP
- 8.1:LwIP中添加操作系統
- 8.1.1:拷貝FreeRTOS源碼到工程文件夾
- 8.1.2:添加FreeRTOS源碼到工程組文件夾
- 8.1.3:指定FreeRTOS頭文件的路徑
- 8.1.4:修改stm32f10x_it.c
- 8.2:lwipopts.h文件需要加入的配置
- 8.3:sys_arch.c/h文件的編寫
- 8.4:網卡底層的編寫
- 8.5:協議棧初始化
- 8.6:移植后使用ping測試基本響應
- 第9章:LwIP一探究竟
- 9.1:網卡接收數據的流程
- 9.2:內核超時處理
- 9.2.1:sys_timeo結構體與超時鏈表
- 9.2.2:注冊超時事件
- 9.2.3:超時檢查
- 9.3:tcpip_thread線程
- 9.4:LwIP中的消息
- 9.4.1:消息結構
- 9.4.2:數據包消息
- 9.4.3:API消息
- 9.5:揭開LwIP神秘的面紗
- 第10章:ARP協議
- 10.1:鏈路層概述
- 10.2:MAC地址的基本概念
- 10.3:初識ARP
- 10.4:以太網幀結構
- 10.5:IP地址映射為物理地址
- 10.6:ARP緩存表
- 10.7:ARP緩存表的超時處理
- 10.8:ARP報文
- 10.9:發送ARP請求包
- 10.10:數據包接收流程
- 10.10.1:以太網之數據包接收
- 10.10.2:ARP數據包處理
- 10.10.3:更新ARP緩存表
- 10.11:數據包發送流程
- 10.11.1:etharp_output()函數
- 10.11.2:etharp_output_to_arp_index()函數
- 10.11.3:etharp_query()函數
- 第11章:IP協議
- 11.1:IP地址.md
- 11.1.1:概述
- 11.1.2:IP地址編址
- 11.1.3:特殊IP地址