如果在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。
![](https://box.kancloud.cn/7be7cb313de98579977fa8d30e419390_862x768.png)
整个ARP协议运作示意图具体见图 10‑11。
![](https://box.kancloud.cn/d0498f6171fe0c5e644cc791a2174e26_1162x752.png)
- 说明
- 第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地址