💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
从前面的章节我们知道,网络接口(如以太网接口)是硬件接口,(提示:网络接口又可以称之为网卡,为了统一,下文均采用网卡表示网络接口),LwIP是软件,那么怎么让硬件与软件无缝连接起来呢?而且,网卡又有多种多样,怎么能让LwIP使用同样的软件能兼容不同的硬件呢?原来LwIP使用一个数据结构——netif来描述一个网卡,但是由于网卡是直接与硬件打交道的,硬件不同则处理基本是不同的,所以必须由用户提供最底层接口函数,LwIP提供统一的接口,但是底层的实现需要用户自己去完成,比如网卡的初始化,网卡的收发数据,当LwIP底层得到了网络的数据之后,才会传入内核中去处理;同理,LwIP内核需要发送一个数据包的时候,也需要调用网卡的发送函数,这样子才能把数据从硬件接口到软件内核无缝连接起来。LwIP中的 ethernetif.c文件即为底层接口的驱动的模版,用户为自己的网络设备实现驱动时应参照此模块做修改。ethernetif.c文件中的函数通常为与硬件打交道的底层函数,当有数据需要通过网卡接收或者发送数据的时候就会被调用,经过LwIP协议栈内部进行处理后,从应用层就能得到数据或者可以发送数据。 简单来说,netif是LwIP抽象出来的网卡,LwIP协议栈可以使用多个不同的接口,而ethernetif.c文件则提供了netif访问各种不同的网卡,每个网卡有不同的实现方式,用户只需要修改ethernetif.c文件即可。 在单网卡中,这个netif结构体只有一个,可能还有人会问,那么一个设备中有多个网卡怎么办,很简单,LwIP会将每个用netif描述的网卡连接成一个链表(单向链表),该链表就记录每个网卡的netif。屏蔽硬件接口的差异,完成了对不同网卡的抽象,因此了解netif结构体是移植LwIP的关键。 我们可以理解将整个网络的数据传输理解为物流,那么网卡就是不同的运输工具,我们可以选择汽车、飞机、轮船等运输工具,不同的运输工具速度是不一样的,但是对于一个物流公司而言,可能同时存在很多种运输的工具,这就需要物流公司去记录这些运输工具,当有一个包裹需要通过飞机运输出去,那么物流公司就会将这个包裹通过飞机发送出去,这就好比我们的网卡,需要哪个网卡发送或者接收网络数据的时候,就会让对应的网卡去工作。 下面一起来看看netif数据结构是怎么样的,具体见代码清单 4‑1。 ``` 1 struct netif 2 { 3 #if !LWIP_SINGLE_NETIF 4 /* 指向netif链表中的下一个 */ 5 struct netif *next; (1) 6 #endif 7 8 #if LWIP_IPV4 9 /* 网络字节中的IP地址、子网掩码、默认网关配置 */ 10 ip_addr_t ip_addr; 11 ip_addr_t netmask; 12 ip_addr_t gw; (2) 13 #endif /* LWIP_IPV4 */ 14 15 /* 此函数由网络设备驱动程序调用,将数据包传递到TCP/IP协议栈。 16 * 对于以太网物理层,这通常是ethernet_input()*/ 17 netif_input_fn input; (3) 18 19 #if LWIP_IPV4 20 21 /* 此函数由IP层调用,在接口上发送数据包。通常这个功能, 22 * 首先解析硬件地址,然后发送数据包。 23 * 对于以太网物理层,这通常是etharp_output() */ 24 netif_output_fn output; (4) 25 26 #endif /* LWIP_IPV4 */ 27 /* 此函数由ethernet_output()调用,当需要在网卡上发送一个数据包时。 28 * 底层硬件输出数据函数,一般是调用自定义函数low_level_output*/ 29 netif_linkoutput_fn linkoutput; (5) 30 31 #if LWIP_NETIF_STATUS_CALLBACK 32 /* 当netif状态设置为up或down时调用此函数 */ 33 netif_status_callback_fn status_callback; (6) 34 #endif /* LWIP_NETIF_STATUS_CALLBACK */ 35 36 #if LWIP_NETIF_LINK_CALLBACK 37 /* 当netif链接设置为up或down时,将调用此函数 */ 38 netif_status_callback_fn link_callback; (7) 39 #endif /* LWIP_NETIF_LINK_CALLBACK */ 40 41 #if LWIP_NETIF_REMOVE_CALLBACK 42 /* 当netif被删除时调用此函数 */ 43 netif_status_callback_fn remove_callback; (8) 44 #endif /* LWIP_NETIF_REMOVE_CALLBACK */ 45 46 /* 此字段可由设备驱动程序设置并指向设备的状态信息。 47 * 主要是将网卡的某些私有数据传递给上层,用户可以自由发挥,也可以不用。*/ 48 void *state; (9) 49 50 #ifdef netif_get_client_data 51 void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA]; 52 #endif 53 #if LWIP_NETIF_HOSTNAME 54 /* 这个netif的主机名,NULL也是一个有效值 */ 55 const char* hostname; 56 #endif /* LWIP_NETIF_HOSTNAME */ 57 58 #if LWIP_CHECKSUM_CTRL_PER_NETIF 59 u16_t chksum_flags; 60 #endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/ 61 62 /** 最大传输单位(以字节为单位),对于以太网一般设为 1500 */ 63 u16_t mtu; (10) 64 65 /** 此网卡的链路层硬件地址 */ 66 u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; (11) 67 68 /** 硬件地址长度,对于以太网就是 MAC 地址长度,为6字节 */ 69 u8_t hwaddr_len; (12) 70 71 /* 网卡状态信息标志位,是很重要的控制字段, 72 * 它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。 */ 73 u8_t flags; (13) 74 75 /* 字段用于保存每一个网卡的名字。用两个字符的名字来标识网络接 76 * 口使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网卡 77 * 表示的硬件的种类。比如蓝牙设备( bluetooth)的网卡名字可以是 bt, 78 * 而 IEEE 802.11b WLAN 设备的名字就可以是wl,当然设置什么名字用户是可 79 * 以自由发挥的,这并不影响用户对网卡的使用。当然,如果两个网卡 80 * 具有相同的网络名字,我们就用 num 字段来区分相同类别的不同网卡*/ 81 char name[2]; (14) 82 83 /* 用来标示使用同种驱动类型的不同网卡 */ 84 u8_t num; (15) 85 86 #if MIB2_STATS 87 /* 连接类型 */ 88 u8_t link_type; 89 /* 连接速度 */ 90 u32_t link_speed; 91 /* 最后一次更改的时间戳 */ 92 u32_t ts; 93 /** counters */ 94 struct stats_mib2_netif_ctrs mib2_counters; 95 #endif /* MIB2_STATS */ 96 97 #if LWIP_IPV4 && LWIP_IGMP 98 /** 可以调用此函数来添加或删除多播中的条目 99 以太网MAC的过滤表。*/ 100 netif_igmp_mac_filter_fn igmp_mac_filter; 101 #endif /* LWIP_IPV4 && LWIP_IGMP */ 102 103 #if LWIP_NETIF_USE_HINTS 104 struct netif_hint *hints; 105 #endif /* LWIP_NETIF_USE_HINTS */ 106 107 #if ENABLE_LOOPBACK 108 /* List of packets to be queued for ourselves. */ 109 struct pbuf *loop_first; 110 struct pbuf *loop_last; 111 112 #if LWIP_LOOPBACK_MAX_PBUFS 113 u16_t loop_cnt_current; 114 #endif /* LWIP_LOOPBACK_MAX_PBUFS */ 115 116 #endif /* ENABLE_LOOPBACK */ 117 }; ``` 我们挑一些比较重要的netif字段进行讲解: * (1):LwIP使用链表来管理同一设备的多个网卡。在netif.c文件中定义两个全局指针:struct netif *netif_list和struct netif *netif_default,其中netif_list就是网卡链表指针,指向网卡链表的首节点(第一个网卡),后者表示默认情况下(有多网口时)使用哪个网卡。next字段指向下一个netif结构体指针,在一个设备中有多个网卡时,才使用该字段。 * (2):ip_addr字段记录的是网络中的IP地址,netmask字段记录的是子网掩码,gw记录的是网关地址,这些字段是用于描述网卡的网络地址属性。 IP地址必须与网卡对应,即设备拥有多少个网卡那就必须有多少个IP地址;子网掩码可以用来判断某个IP地址与当前网卡是否处于同一个子网中,IP在发送数据包的时候会选择与目标IP地址处于同一子网的网卡来发送;网关地址在数据包的发送、转发过程非常重要,如果要向不属于同一子网的主机(主机目标IP地址与网卡不属于同一子网)发送一个数据包,那么LwIP就会将数据包发送到网关中,网关设备会对该数据包进行正确的转发,除此之外,网关还提供很多高级功能,如DNS,DHCP等。 * (3):input是一个函数指针,指向一个函数,该函数由网络设备驱动程序调用,将数据包传递到TCP/IP协议栈(IP层)。对于以太网物理层,这通常是ethernet_input(),参数为pbuf和netif类型,其中pbuf为接收到的数据包。 * (4):output也是一个函数指针,指向一个函数,此函数由IP层调用,在接口上发送数据包。用户需要编写该函数并使output指向它,通这个函数的处理步骤是首先解析硬件地址,然后发送数据包。对于以太网物理层,该函数通常是etharp_output(),参数为pbuf、netif和ip_addr类型,其中,ipaddr代表要将该数据包发送到的地址,但不一定是数据包最终到到达的IP地址,比如,要发送IP数据报到一个并不在本网络的主机上,该数据包要被发送到一个路由器上,这里的ipaddr就是路由器IP地址。 * (5):linkoutput字段和output类似,也需要用户自己实现一个函数,但只有两个参数,它是由ARP模块调用的,一般是自定义函数low_level_output()。当需要在网卡上发送一个数据包时,该函数会被ethernet_output()函数调用。 * (6):当netif状态设置为up或down时,将调用此函数。 * (7):当netif连接设置为up或down时,将调用此函数。 * (8):当netif被删除时调用此函数。 * (9):此字段可由设备驱动程序设置并指向设备的状态信息。主要是将网卡的某些私有数据传递给上层,用户可以自由发挥,也可以不用。 * (10):最大传输单位(以字节为单位),对于以太网一般设为 1500,在IP层发送数据的时候,LwIP会使用该字段决定是否需要对数据包进行分片处理,为什么是在IP层进行分片处理?因为链路层不提供任何的差错处理机制,如果在网卡中接收的数据包不满足网卡自身的属性,那么网卡可能就会直接丢弃该数据包,也可能在底层进行分包发送,但是这种分包在IP层看来是不可接受的,因为它打乱了数据的结构,所以只能由IP层进行分片处理。 * (11):此网卡的链路层硬件地址。 * (12):硬件地址长度,对于以太网就是 MAC 地址长度,为6字节 * (13):网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。 * (14):name字段用于保存每一个网卡的名字。用两个字符的名字来标识网卡使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网卡表示的硬件的种类。比如蓝牙设备(bluetooth)的网卡名字可以是 bt,而 IEEE 802.11b WLAN设备的名字就可以是wl,当然设置什么名字用户是可以自由发挥的,这并不影响用户对网卡的使用。当然,如果两个网卡具有相同的网络名字,我们就用 num字段来区分相同类别的不同网卡。 * (15):用来标识使用同种驱动类型的不同网卡。