lwIP——4 网络接口
1.lwIP网络接口
网络接口(网卡):个人理解是处理网络层和数据传输关系的接口(tcp/ip协议栈中的网络接口层部分),直接与硬件平台打交道
lwIP协议栈支持多种不同的网络接口(网卡),由于网卡是直接和硬件平台打交道,硬件不同则处理也是不同的,所以由用户提供最底层的接口函数,lwIP提供统一的接口,但是底层的实现需要用户自己去完成(ethernetif.c)
在lwIP中每一个网卡都由一个netif 结构体来表示,这些结构体描述了各个网卡的底层实现函数及状态,并以链表形式链接起来
netif 屏蔽了硬件接口的差异,完成了对不同网络接口的抽象
流程示例(发送数据):
- 网络层准备数据:网络层根据上层应用需求,准备好要发送的数据,如组装 IP 数据包等。
- 统一接口传递:网络层调用 ethernet.c 中
ethernet_output
函数,将数据传递到下层。(封装:目的 MAC 地址、源 MAC 地址、帧类型)- netif 结构体处理:
ethernet_output
函数将数据发送给 netif 结构体,netif 结构体对数据进行一些适配处理,为发送到具体物理接口做准备。- 底层文件与物理接口发送:ethernetif.c 的
linkoutput
函数从 netif 结构体获取数据,按照物理接口(如 WIFI、以太网接口)的要求进行最后的封装等操作,然后通过物理接口将数据发送到网络中。(也就是调用 low_level_output 把 pbuf 数据包拷贝到以太网发送描述符管理的 Buffer 当中,并调用函数 HAL_ETH_TransmitFrame 发送此帧)
2.lwIP的netif结构体
这些成员变量是根据相关网卡的特性,针对性填写
struct netif {
struct netif *next; /* 指向下一个节点的指针 */
ip_addr_t ip_addr; /* 设置网卡的IP地址、子网页码及网关地址 */
ip_addr_t netmask;
ip_addr_t gw;
netif_input_fn input; /* 指向数据包输入函数 */
netif_output_fn output; /* 指向数据包待发送函数 ----检测目标IP地址的MAC地址等操作 */
netif_linkoutput_fn linkoutput; /* 指向数据包输出函数 */
netif_status_callback_fn link_callback; /* 链接状态回调函数 */
void *state; /* 虚拟网卡状态 */
u16_t mtu; /* 最大传输单元 */
u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; /* 网卡的MAC地址 */
u8_t hwaddr_len; /* MAC地址长度 */
u8_t flags; /* 虚拟网卡的标志符 */
char name[2]; /* 网卡的名称 */
/*................*/ };
3. netif 相关函数
lwIP网络接口函数 | 描述 |
netif_init() | 虚拟网卡初始化(lwip_init()) |
netif_add() | 添加一个虚拟网卡 |
netif_remove() | 移除一个虚拟网卡 |
netif_set_default() | 设置默认虚拟网卡(用于未知的数据包) |
………………….. | ………………….. |
全局变量 struct netif *netif_list:指向该链表表头
全局变量 struct netif *netif_default:指向的netif结构所表示的网卡为缺省网卡。(在发送消息的时候,会首先会通过这个网卡,若是没有回应,再使用其他网卡)
3.1 netif_add()函数
struct netif *netif_add( struct netif *netif,
const ip4_addr_t *ipaddr,
const ip4_addr_t *netmask,
const ip4_addr_t *gw,
void *state,
netif_init_fn init,
netif_input_fn input)
{
// 清空网络接口的 IP 地址、子网掩码和网关地址信息,将其初始化为零
ip_addr_set_zero_ip4(&netif->ip_addr);
ip_addr_set_zero_ip4(&netif->netmask);
ip_addr_set_zero_ip4(&netif->gw);
// 将网络接口的输出函数初始化为空 IP4 输出函数,作为默认的输出处理方式
netif->output = netif_null_output_ip4;
// 初始化网络接口的最大传输单元(MTU)为 0,MTU 表示该网络接口一次能传输的最大数据长度
netif->mtu = 0;
// 初始化网络接口的标志位为 0,标志位可用于表示网络接口的各种状态
netif->flags = 0;
// 将网络接口的客户端数据数组清零,客户端数据可用于存储用户自定义的与该网络接口相关的数据
memset(netif->client_data, 0, sizeof(netif->client_data));
// 将用户传入的自定义状态数据指针赋值给网络接口的 state 字段,方便后续使用
netif->state = state;
// 为当前网络接口分配一个唯一的编号,netif_num 可能是一个全局变量用于记录网络接口编号
netif->num = netif_num;
// 将用户传入的网络接口输入处理函数指针赋值给网络接口的 input 字段,用于处理接收到的数据包
netif->input = input;
// 调用 netif_set_addr 函数设置网络接口的 IP 地址、子网掩码和网关地址
// 如果传入的参数为 NULL,则保持之前清零时的零值
netif_set_addr(netif, ipaddr, netmask, gw);
// 调用用户提供的网络接口初始化函数对网络接口进行进一步的初始化操作
// 如果初始化函数返回值不为 ERR_OK,表示初始化失败
if (init(netif) != ERR_OK) {
// 初始化失败,返回 NULL 表示网络接口添加失败
return NULL;
}
// 将当前网络接口添加到全局网络接口列表的头部
// 先将当前网络接口的 next 指针指向原来的网络接口列表头
netif->next = netif_list;
// 再将全局网络接口列表头指针指向当前网络接口,完成添加操作
netif_list = netif;
// 调用 mib2_netif_added 函数,该函数可能用于更新 MIB(管理信息库)相关信息,以反映新网络接口的添加
mib2_netif_added(netif);
// 调用 netif_invoke_ext_callback 函数触发网络接口添加的外部回调函数
// 通知其他模块网络接口已成功添加
netif_invoke_ext_callback(netif, LWIP_NSC_NETIF_ADDED, NULL);
// 网络接口添加成功,返回指向该网络接口结构体的指针
return netif;
}
3.2 netif_set_default() 函数
该函数就是设置某一个 netif 结构体为默认的网卡
// 定义 netif_set_default 函数,用于设置默认网络接口
void netif_set_default(struct netif *netif) {
if (netif == NULL) {
/* 删除默认路由 */
mib2_remove_route_ip4(1, netif);
} else {
/* 添加默认路由 */
mib2_add_route_ip4(1, netif);
}
// 选择指定的网络接口作为默认网络接口
netif_default = netif;
}
// 以下是使用 netif_set_default 函数的示例代码
int main() {
struct netif xnetif;
ip4_addr_t ipaddr, netmask, gw;
// 假设这里初始化 IP 地址、子网掩码和网关地址
// 例如:
IP4_ADDR(&ipaddr, 192, 168, 1, 100);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 192, 168, 1, 1);
// 通过 netif_add 函数,将网络接口添加到链表中
netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
// 注册默认的网络接口
netif_set_default(&xnetif);
return 0;
}