1.stm32f207,无操作系统,LWIP-1.3.2,开发TCP服务器下的双路ip;
实现前提:先调通,实现单路IP;
参考帖子(然而最后的感觉都是没啥卵用):
STM32 LwIP单网卡绑定多个IP地址-https://blog.csdn.net/sinat_20006769/article/details/80847989
LWIP 双IP实现-https://blog.csdn.net/weixin_38307270/article/details/80873032
参考资料2:https://www.cnblogs.com/x_wukong/p/5924798.html
参考资料1:零死角玩转STM32—F407霸天虎.pdf ; STM32F4开发指南-库函数版本_V1.1.pdf
2.简述执行过程
2.1初始化:
void LwIP_Init(void)---LwIP_Init 函数用于初始化 LwIP 协议栈,一般在 main 函数中调用。
- 首先是内存相关初始化,mem_init 函数是动态内存堆初始化,memp_init 函数是存储池初始化,LwIP 是实现内存的高效利用,内部需要不同形式的内存管理模式。
- 接下来为 ipaddr、netmask 和 gw 结构体变量赋值,设置本地 IP 地址、子网掩码和网关,如果使用 DHCP 功能直接赋值为 0 即可。 netif_add 是以太网设备添加函数,即向 LwIP 协议栈申请添加一个网卡设备,函数有 7 个形参,第一个为 netif 结构体类型变量指针,这里赋值为 gnetif 地址,该网卡设备属性就存放在 gnetif 变量中;第二个为 ip_addr 结构体类型变量指针,用于设置网卡 IP 地址;第三个 ip_addr 结构体类型变量指针,用于设置子网掩码;第四个为 ip_addr 结构体类型变量指针,用于设置网关;第五个为 void 变量,用户自定义字段,一般不用直接赋值 NULL; 第六个为 netif_init_fn 类型函数指针,用于指向网卡设备初始化函数,这里赋值为指向 ethernetif_init 函数,该函数在 ethernetif.c 文件定义,初始化 LwIP 与 ETH 外设连接函数; 最后一个参数为 netif_input_fn 类型函数指针,用于指向以太网帧接收函数,这里赋值为指向 ethernet_input 函数,该函数定义在 etharp.c 文件中。 netif_set_default 函数用于设置指定网卡为默认的网络通信设备。--两个以上设置要注意!!
- 在无硬件连接错误时,调用 ETH_BSP_Config(优先 LwIP_Init 函数被调用)时会将EthStatus 变量对应的 ETH_LINK_FLAG 位使能,所以在 LwIP_INIT 函数中会执行 if 判断语句代码,置位网卡设备标志位以及运行 netif_set_up 函数启动网卡设备。否则执行netif_set_down 函数停止网卡设备。
- 最后,根据需要调用 netif_set_link_callback 函数实在当链路状态发生改变时需要调用的回调函数配置
- 第六个参数-ethernetif_init()引入硬件初始化函数--low_level_init()
low_level_init 函数是网络接口初始化函数,在 ethernetif_init 函数被调用,在系统启动时被运行一次,用于初始化与网络接口相关硬件。
函数先是给 netif 结构体成员赋值配置网卡参数,调用 ETH_MACAddressConfig 函数绑定网卡 MAC 地址,ETH_DMATxDescChainInit 和 ETH_DMARxDescChainInit 初始化网络数据帧发送和接收描述符,设置为链模式。
调用ETH_DMARxDescReceiveITConfig 函数使能 DMA 数据接收相关中断。通过定义宏 CHECKSUM_BY_HARDWARE,可以使能发送数据硬件校验和,这个需要硬件支持,STM32F4xx 控制器是支持的。
调用 sys_thread_new 函数创建一个任务,设置任务函数是 ethernetif_input,该函数用于讲接收到数据包转入到 LwIP 内部缓存区,这里还传递了 netif 结构体变量。
最后,调用 ETH_Start 函数使能 ETH。low_level_output 和 low_level_input 两个函数内容(备注:无操作系统时网络接口函数--主要有三个部分函数,一个是 low_level_init,用于初始化 MAC 相关工作环境、初始化 DMA 描述符链表,并使能 MAC 和 DMA;一个是low_level_output,它是最底层发送一帧数据函数;最后一个是 low_level_input,它是最底层接收一帧数据函数。)
- 第七个函数ethernet_input
参考帖子理解:http://blog.sina.com.cn/s/blog_62a85b950101anvx.html
看到是上面的第一个截图,网络接口层的第一个函数为netif->input,接收到的数据,有两种,一个是以太网+ip包,一个是arp包;如下左图:
LWIP利用netif.input指向的函数接收以太网数据包,通常这个函数是ethernet_input。注意,这里并不是说ethernet_input直接与底层硬件交互接收数据包,而是更底层的函数接收到数据包后将数据包递交给ethernet_input,ethernet_input再对其进行处理。
以太网的帧类型可以是:IP,ARP,甚至可以是pppoe, wlan等。这里主要分析IP和ARP两种类型的数据包。ethernet_input根据以太网首部的类型字段判断收到的数据包的类型(如下图所示!),如果是IP包,则将该包递交给etharp_ip_input,如果是ARP包,则将该包递交给etharp_arp_input。如上左图; ARP从功能上来说可以简单的分成两个部分:
当有数据包输入时,更新arp表,如果是ip包则递交给ip层,如果是arp包,则针对不同的arp包类型做相应的响应;当向目的ip发送一个数据包的时候,需要通过arp实现ip到MAC地址的映射,必要时,需要发送广播数据包获得目标机器的MAC地址。
对于arp类型的数据包,etharp_arp_input函数首先利用数据包头信息更新arp表的内容,然后再判断该ARP数据包的类型,如果是ARP请求包,则首先判断这个包是不是给自己的,如果是给自己的,则在原有包的基础上重组一个ARP应答包发送出去(注意此处并没有重新分配一个pbuf,而是借用了原来的缓冲结构)。如果不是给自己的,则直接忽略。如果是ARP应答包,主要的工作就是更新arp表,但是这一步已经在arp包刚进来的时候就处理了,所以这里不需要再重复做,这样ARP包的处理也完毕。
对于ip类型的数据包,etharp_ip_input首先检查是否开启了ETHARP_TRUST_IP_MAC这个选项,如果开启了就是要用这个帧中的信息和update_arp_entry函数来更新arp表(利用帧首部的源mac地址和帧数据中ip报文中的源ip地址),然后丢弃以太网帧首部,将IP报文通过ip_input函数递交给ip层。
代码说明如下
err_t ethernet_input(struct pbuf *p,struct netif *netif)
{
struct eth_hdr *ethhdr; // 以太网数据包头结构体
u16_t type;
s16_t ip_hdr_offset = SIZEOF_ETH_HDR; // 包头固定值14字节
ethhdr = (eth_hdr *)p->payload;
type = htons(ethhdr->type);
switch(type)
{
case ETHTYPE_IP: // IP数据包 0x0800
etharp_ip_input(netif,p); // 使用收到的IP包更新ARP缓存表,详见《lwip之ARP协议》
pbuf_header(p, -ip_hdr_offset); // 调整以太网数据包指针,使掠过包头,指向IP协议包头
ip_input(p,netif);// 提交IP层,ip_input为IP层主要函数,解析见下文
case ETHTYPE_ARP: // ARP数据包 0806
etharp_arp_input(netif,(struct eth_addr *)netif->hwaddr,p); // ARP数据包处理,第二个形参是本机MAC,详见《lwip之ARP协议》
break;
default:
break;
}
}
2.2 数据帧结构
- 前导字段,也称报头,这是一段方波,用于使收发节点的时钟同步。内容为连续 7 个字节的 0x55。字段和帧起始定界符在 MAC 收到数据包后会自动过滤掉。
- 帧起始定界符(SFD):用于区分前导段与数据段的,内容为 0xD5。
- MAC 地址: MAC 地址由 48 位数字组成,它是网卡的物理地址,在以太网传输的最底层,就是根据 MAC 地址来收发数据的。部分 MAC 地址用于广播和多播,在同一个网络里不能有两个相同的 MAC 地址。PC 的网卡在出厂时已经设置好了 MAC 地址,但也可以通过一些软件来进行修改,在嵌入式的以太网控制器中可由程序进行配置。
- 数据包中的 DA 是目标地址,SA 是源地址。
- 数据包类型:本区域可以用来描述本 MAC 数据包是属于 TCP/IP 协议层的 IP 包、ARP包还是 SNMP 包,也可以用来描述本 MAC 数据包数据段的长度。如果该值被设置大于 0x0600,不用于长度描述,而是用于类型描述功能,表示与以太网帧相关的 MAC客户端协议的种类。
- 数据段:数据段是 MAC 包的核心内容,它包含的数据来自 MAC 的上层。其长度可以从 0~1500 字节间变化。
- 填充域:由于协议要求整个 MAC 数据包的长度至少为 64 字节(接收到的数据包如果少于 64 字节会被认为发生冲突,数据包被自动丢弃),当数据段的字节少于 46 字节时,在填充域会自动填上无效数据,以使数据包符合长度要求。
- 校验和域:MAC 数据包的尾部是校验和域,它保存了 CRC 校验序列,用于检错。
以上是标准的 MAC 数据包,IEEE 802.3 同时还规定了扩展的 MAC 数据包,它是在标准的 MAC 数据包的 SA 和数据包类型之间添加 4 个字节的 QTag 前缀字段,用于获取标志的 MAC 帧。前 2 个字节固定为 0x8100,用于识别 QTag 前缀的存在;后两个字节内容分别为 3 个位的用户优先级、1 个位的标准格式指示符(CFI)和一个 12 位的 VLAN 标识符。
2.3 ping IP 的基本流程:
当主机A要与主机B通信时,ARP协议可以将主机B的IP地址解析成主机B的MAC地址,工作流程如下:
第一步:主机A先检查自己的ARP缓冲,看是否存在主机B匹配的MAC地址,如果没有,就会向外广播一个ARP请求包
第二步:其他主机收到后,发现请求的IP地址与自己的IP地址不匹配,则丢弃ARP请求
第三步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射到本地ARP缓存中
第四步:主机B将包含其MAC地址的ARP回复发回给主机A
第五步:主机A收到从主机B发来的ARP回复时,会将主机B的IP地址和MAC地址映射更新到本地ARP缓存中。主机B的MAC地址一旦确定,主机A就可以向主机B发送IP通信了
参考:lwip之数据收发流程 https://blog.csdn.net/banruoju/article/details/54340684
2.4 主函数修改
- LwIP_Pkt_Handle 函数用于从以太网存储器读取一个以太网帧并将其发送给 LwIP,它在接收到以太网帧时被调用,它是直接调用 ethernetif_input 函数实现的,该函数定义在ethernetif.c 文件中。
- 获取活跃的网卡号
以上是所有的改动点,Ping通是通讯的第一步!
跟另外两个帖子处理不一样;
3 TCP服务器网络通讯的收发
...待续!