lwip-数据收发流程1-链路层,网络层

一、概述

lwip从逻辑上看也是分为4层:链路层、网络层(IP、ARP、(ICMP、IGMP这两个协议是网络层的补充协议,并不严格属于网络层))、传输层(TCP、UDP)、应用层,基本等同TCP/IP,只是各层之间可以进行交叉存取,没有严格划分。

协议汇总:

1. ARP协议:根据IP地址获取物理地址MAC的一个TCP/IP协议

一个典型的lwip系统包含3个进程:首先是上层应用程序进程,然后是lwip协议栈进程,最后是底层硬件数据包接收进程

动态内存管理:

采用ucos-ii内存管理系统,即申请一块内存,分割成整数个大小相同的内存块

二. 链路层(ethernet_input)

当主机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通信了

       连接链路层和网络层的纽带:以太网数据包接收进程tcpip_thread
        static void tcpip_thread(void *arg)
        {
            struct tcpip_msg *msg;        // 消息来自于网卡中断
            
            while(1)
            {
                // 该任务阻塞在这里接收要处理的消息,当有数据包到来时,网卡芯片中断函数接收数据,并post消息,中断退出后,该任务获取消息
                sys_timeouts_mbox_fetch(&mbox, (void **)&msg);    
                
                // 判断本条消息的类型,只关注数据包消息TCPIP_MSG_INPKT
                switch (msg->type)
                {
                    case TCPIP_MSG_INPKT:                                            // 数据包消息
                        if(msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET))
                            ethernet_input(msg->msg.inp.p,msg->msg.inp.netif);    // 如果支持ARP,先进行ARP处理,再判断是否递交IP层,对于IP数据包,这里2个选择最终都要调用ip_input进入IP层
                        else
                            ip_input(msg->msg.inp.p, msg->msg.inp.netif);            // 否则直接递交IP层,ip_input为IP层主要函数,解析见下文,这里直接调用ip_input存在问题,有误,需要先以太网数据包指针,使掠过包头,指向IP协议包头
                        memp_free(MEMP_TCPIP_MSG_INPKT, msg);                        // 释放消息内存
                        break;
                    case TCPIP_MSG_TIMEOUT:                                            // 超时消息
                        sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
                        memp_free(MEMP_TCPIP_MSG_API, msg);
                        break;
                    default:
                        break;
                }
            }
        }
        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数据包
                    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数据包
                    etharp_arp_input(netif,(struct eth_addr *)netif->hwaddr,p);    // ARP数据包处理,第二个形参是本机MAC,详见《lwip之ARP协议》
                    break;
                default:
                    break;
            }
        }
        
        注:消息结构体struct tcpip_msg {
                            enum tcpip_msg_type type;                // 本条消息的类型:TCPIP_MSG_INPKT - 数据包消息,TCPIP_MSG_TIMEOUT - 超时消息
                            sys_sem_t *sem;                            // 事件控制块ECB
                            union{
                                struct api_msg *apimsg;
                                struct netifapi_msg *netifapimsg;
                                struct {
                                    struct pbuf *p;
                                    struct netif *netif;
                                } inp;                                // inp结构体最重要,内含数据包内容结构、网络接口结构
                                struct {
                                      tcpip_callback_fn function;
                                      void *ctx;
                                } cb;
                                struct {
                                      u32_t msecs;
                                      sys_timeout_handler h;
                                      void *arg;
                                } tmo;
                            }msg;
                        }
    
---------------------------------------------------------------------------------------------

三. 网络层(ip_input)


        lwip使用一个ip_hdr的结构体来描述IP协议包头:
                                                struct ip_hdr{
                                                    u16_t _v_hl_tos;    // 包含4位版本号(IPv4 - 4,IPv6 - 6)、4位IP包头长(通常为5*4,即本结构体大小)、8位服务类型
                                                    u16_t _len;            // 整个IP数据包长度
                                                    u16_t _id;            // 16位标识用于标识IP层发出的每一份IP报文,自增
                                                    u16_t _offset;        // 包含3位标志和13位片偏移,IP数据包分片时使用
                                                    u8_t _ttl;            // TTL描述该IP数据包最多能被转发的次数,自减
                                                    u8_t _proto;        // 协议字段用于描述该IP数据包的上层协议,0x01 - ICMP,0x02 - IGMP,0x06 - TCP,0x17 - UDP
                                                    u16_t _chksum;        // 16位的IP首部校验和
                                                    ip_addr_p_t src;    // 源IP
                                                    ip_addr_p_t dest;    // 目的IP
                                                }
    
        ip_input为IP层主干函数,完成了IP层数据包处理(核心工作就是IP地址匹配;得到完整数据包),然后将合适的数据包提交给上层,这里的p->payload已经越过了14字节包头,指向了IP头
        err_t ip_input(struct pbuf *p,struct netif *inp)
        {
            struct     ip_hdr *iphdr;    // 指向IP包头的指针
            struct     netif *netif;    // 指向netif硬件网络接口设备描述符的指针
            u16_t    iphdr_hlen;        // IP包头的长度,通常是固定20字节
            u16_t    iphdr_len;        // 整个IP包长,包含IP包头、上层协议头、数据
            
            // 取出 IP数据包头
            iphdr = (struct ip_hdr *)p->payload;
            
            // 检查IP包头中的版本号字段,IPv4 - 4,IPv6 - 6
            if(IPH_V(iphdr) != 4)
            {
                pbuf_free(p);
                return ERR_OK;    
            }
            
            // 提取IP包头中的头长度字段,通常固定值20字节
            iphdr_hlen = IPH_HL(iphdr);
            iphdr_hlen *= 4;
            
            // 提取IP包头中的IP包总长度字段,确保小于递交上来的pbuf包中的总长度
            iphdr_len = ntohs(IPH_LEN(iphdr));
            if(iphdr_len > p->len || iphdr_len > p->tot_len)
            {
                pbuf_free(p);
                return ERR_OK;    
            }
            
            // 校验IP数据包头
            if (inet_chksum(iphdr, iphdr_hlen) != 0)
            {
                pbuf_free(p);
                return ERR_OK;    
            }
            
            // 对IP数据报进行截断,得到完整无冗余IP数据包
            pbuf_realloc(p, iphdr_len);
            
            // 遍历netif_list链表(系统存在2个网卡设备,意味着有2个netif分别用于描述它们,也意味着本机有2个IP地址,所以此时就需要遍历),检测IP数据包中的目的IP是否与本机相符,不符则丢弃或转发
            ip_addr_copy(current_iphdr_dest, iphdr->dest);
            ip_addr_copy(current_iphdr_src, iphdr->src);
            int first = 1;
            netif = inp;
            do{
                // 通过netif->flag标志位判断该网卡设备是否配置且使能,同时判断本机IP是否有效
                if ((netif_is_up(netif)) && (!ip_addr_isany(&(netif->ip_addr))))
                {
                    // 如果目的IP地址与本机IP地址匹配或者目的IP地址是广播类型,意味着成功匹配,退出遍历
                    if(ip_addr_cmp(&current_iphdr_dest, &(netif->ip_addr)) || ip_addr_isbroadcast(&current_iphdr_dest, netif))    
                    {
                        break;    
                    }
                }
                if (first)
                {
                    first = 0;
                    netif = netif_list;
                }
                else
                {
                    netif = netif->next;
                }
                  if (netif == inp)
                  {
                    netif = netif->next;
                  }
            }while(netif != NULL);
            
            //  如果该数据包中的源IP地址是广播IP,则直接丢弃
            if ((ip_addr_isbroadcast(&current_iphdr_src, inp)) || (ip_addr_ismulticast(&current_iphdr_src)))
            {
                pbuf_free(p);
                return ERR_OK;
            }
            
            // 遍历完成以后,如果依旧没有找到匹配的netif结构体,说明该数据包不是给本机的,转发或丢弃(这里直接丢弃)
              if (netif == NULL)
              {
                  pbuf_free(p);
                return ERR_OK;
              }
              
              // 判断该IP包是否是分片数据包
              // 如果是分片数据包,则需要将该分片包暂存,等接收完所有分片包后,统一将整个数据包提交给上层
              if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0)
              {
                  // 在这里重组接收到的分片包,如果还没接收完整,p=NULL
                  p = ip_reass(p);
                // 如果分片包还没接收完整,本函数结束
                if (p == NULL)
                {
                  return ERR_OK;
                }
                // 如果分片包接收完整,这时的p已经是一个完整的数据包结构体了
                // 再从p中获取完整的IP包
                iphdr = (struct ip_hdr *)p->payload;            
              }
              
            // 能到达这一步的数据包必然是未分片的或经过分片重组完整后的数据包
            current_netif = inp;
              current_header = iphdr;
              if (raw_input(p, inp) == 0)
              {
                  // 根据IP数据包头中的协议字段判断该数据包应该被递交给上层哪个协议
                  switch (IPH_PROTO(iphdr))
                  {
                      case IP_PROTO_UDP:    // UDP协议
                          udp_input(p, inp);    // 从这里进入传输层,解析见下文
                          break;    
                      case IP_PROTO_TCP:    // TCP协议
                          tcp_input(p, inp);    // 从这里进入传输层,解析见下文
                        break;
                    case IP_PROTO_ICMP:    // ICMP协议
                        icmp_input(p, inp);
                        break;
                    case IP_PROTO_IGMP:    // IGMP协议
                        igmp_input(p, inp, &current_iphdr_dest);
                        break;
                    default:            // 如果都不是
                        // 如果不是广播数据包,返回一个协议不可达ICMP数据包给源主机
                        if (!ip_addr_isbroadcast(&current_iphdr_dest, inp) && !ip_addr_ismulticast(&current_iphdr_dest))
                        {
                            p->payload = iphdr;
                            icmp_dest_unreach(p, ICMP_DUR_PROTO);
                        }    
                        pbuf_free(p);        
                  }    
              }
            
            current_netif = NULL;
            current_header = NULL;
            ip_addr_set_any(&current_iphdr_src);
            ip_addr_set_any(&current_iphdr_dest);
        }

            
---------------------------------------------------------------------------------------------

IP层的补充协议:ICMP、IGMP

这时候主机A学到了主机B的MAC地址,就把这个MAC封装到ICMP协议中向主机B发送,报文格式如下:

包头14字节 :因为ICMP协议包属于网络层协议,所以帧类型是0x0800

+ ICMP协议头(主要是二级协议类型、源IP、目的IP) :二级协议类型ICMP对应值0x01

+ ICMP协议主体(主要是一个类别) :类别取值0x00 - 这是一条回应信息 0x03 - 目的不可达 0x08 - 请求回应信息

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
STM32F1是意法半导体公司推出的一款32位ARM Cortex-M3单片机系列产品,具有良好的性能和扩展能力。它采用了低功耗技术,集成了丰富的外设和内存,适用于广泛的应用领域。 寄存器是计算机体系结构中的重要组成部分,用于存储和操作数据。STM32F1芯片内部集成了大量的寄存器,包括通用寄存器、特殊功能寄存器和外设寄存器等。通过对寄存器的读写操作,可以实现对芯片内部各种功能的配置和控制。 LwIP-2.1.2是一个开源的轻量级网络协议栈,适用于嵌入式系统。它提供了TCP/IP协议栈的实现,支持各种网络协议和服务,例如IP、TCP、UDP、ARP、DHCP、DNS等。LwIP-2.1.2具有较小的内存占用和高性能的特点,适用于资源有限的嵌入式环境。 ENC28J60是一款低成本的SPI以太网控制器芯片,由微芯科技(Microchip Technology)公司推出。它支持10Mbps以太网通信,采用硬件SPI接口和内部缓存,能够有效减少主控制器的负担。ENC28J60与STM32F1可以通过SPI总线进行连接,用于实现嵌入式设备与以太网的通信。 综上所述,STM32F1是一款强大的单片机系列产品,具备丰富的外设和可编程寄存器,可以灵活配置和控制芯片内部功能。LwIP-2.1.2是一个轻量级的网络协议栈,用于实现嵌入式系统的网络通信。ENC28J60是一款低成本的以太网控制器芯片,可以与STM32F1通过SPI总线进行连接。这些技术的结合可以实现嵌入式设备的网络功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江鸟的坚持

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值