06_W5500_DHCP

news/2024/5/18 12:07:34 标签: 网络, udp, DHCP

DHCP%E5%8D%8F%E8%AE%AE%E4%BB%8B%E7%BB%8D%EF%BC%9A">1.DHCP协议介绍:

        DHCP(Dynamic Host Configuration Protocol)是一种用于自动分配IP地址和其他网络配置信息的协议。它允许网络中的设备(如计算机、手机、打印机等)在连接到网络时自动获取IP地址、子网掩码、默认网关、DNS服务器等信息,而无需手动配置。

DHCP工作原理是通过DHCP服务器向网络中的设备提供IP地址和其他配置信息。当设备连接到网络时,它会发送一个DHCP请求,请求一个可用的IP地址和其他配置信息。DHCP服务器收到请求后,会分配一个可用的IP地址,并将其他配置信息一并发送给设备,使设备能够顺利地加入到网络中。

DHCP的优点是简化了网络管理,减少了手动配置的工作量,同时也提高了网络的灵活性和可扩展性。通过DHCP,管理员可以更轻松地管理网络中的设备,并且可以更快速地进行网络扩展和变更。

DHCP%E6%8A%A5%E6%96%87">2.DHCP报文

DHCP%E6%8A%A5%E6%96%87%E7%A7%8D%E7%B1%BB%EF%BC%9A">DHCP报文种类:

DHCP一共有8中报文,各种类型报文的基本功能如下:

报文类型说明
Discover(0x01)    DHCP客户端在请求IP地址时并不知道DHCP服务器的位置,因此DHCP客户端会在本地网络内以广播方式发送Discover请求报文,以发现网络中的DHCP服务器。所有收到Discover报文的DHCP服务器都会发送应答报文,DHCP客户端据此可以知道网络中存在的DHCP服务器的位置。
Offer(0x02)DHCP服务器收到Discover报文后,就会在所配置的地址池中查找一个合适的IP地址,加上相应的租约期限和其他配置信息(如网关、DNS服务器等),构造一个Offer报文,发送给DHCP客户端,告知用户本服务器可以为其提供IP地址。但这个报文只是告诉DHCP客户端可以提供IP地址,最终还需要客户端通过ARP来检测该IP地址是否重复。
Request(0x03) DHCP客户端可能会收到很多Offer请求报文,所以必须在这些应答中选择一个。通常是选择第一个Offer应答报文的服务器作为自己的目标服务器,并向该服务器发送一个广播的Request请求报文,通告选择的服务器,希望获得所分配的IP地址。另外,DHCP客户端在成功获取IP地址后,在地址使用租期达到50%时,会向DHCP服务器发送单播Request请求报文请求续延租约,如果没有收到ACK报文,在租期达到87.5%时,会再次发送广播的Request请求报文以请求续延租约。
Decline(0x04)DHCP客户端收到DHCP服务器ACK应答报文后,通过地址冲突检测发现服务器分配的地址冲突或者由于其他原因导致不能使用,则会向DHCP服务器发送Decline请求报文,通知服务器所分配的IP地址不可用,以期获得新的IP地址。
ACK(0x05) DHCP服务器收到Request请求报文后,根据Request报文中携带的用户MAC来查找有没有相应的租约记录,如果有则发送ACK应答报文,通知用户可以使用分配的IP地址。
NAK(0x06如果DHCP服务器收到Request请求报文后,没有发现有相应的租约记录或者由于某些原因无法正常分配IP地址,则向DHCP客户端发送NAK应答报文,通知用户无法分配合适的IP地址。
Release(0x07) 当DHCP客户端不再需要使用分配IP地址时(一般出现在客户端关机、下线等状况)就会主动向DHCP服务器发送RELEASE请求报文,告知服务器用户不再需要分配IP地址,请求DHCP服务器释放对应的IP地址。
Inform(0x08) DHCP客户端如果需要从DHCP服务器端获取更为详细的配置信息,则向DHCP服务器发送Inform请求报文;DHCP服务器在收到该报文后,将根据租约进行查找到相应的配置信息后,向DHCP客户端发送ACK应答报文。目前基本上不用了。
DHCP%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F%EF%BC%9A">DHCP报文格式:

字段长度说明
op1byte是报文的操作类型,分为请求报文和响应报文,1为请求报文;2为响应报文。具体的报文类型在option字段中标识。
htype1byte硬件地址的长度,以太网的硬件地址长度为6bytes。
hlen1byte硬件地址的长度,以太网的硬件地址长度为6bytes。
hops1byte表示当前dhcp报文经过的DHCP中继的数目,每经过一个DHCP中继这个字段就加1.
xid4bytes由client端产生的随机数,用于匹配请求和应答报文,就是匹配应答报文是对哪个请求报文做出应答。
secs2bytes客户端进入IP地址申请进程的时间或者更新IP地址进程的时间;由客户端软件根据情况设定。目前没有使用,固定为0。
flags2bytes是标志字段,16比特中只使用了最高位比特(即最左边的比特),这个个比特是广播响应标识位,用来标识DHCP服务器发出的响应报文是广播还是单播,0是单播,1是广播。其余的比特位保留不用,都为0.
ciaddr4bytes是客户端的IP地址,可以是client自己的IP地址,也可以是server分配给client的IP地址。
yiaddr4bytes(Your IP Address),是server分配给client的IP地址。
siaddr4bytes是client端获取IP地址等信息的server端的地址。
giaddr4bytes是client发出请求报文后经过的第一个中继的IP地址。
chaddr16bytes是client端的硬件地址,在client发出报文时会把自己网卡的硬件地址写进这个字段。
sname64bytes服务器主机名,是client端获取IP地址等信息的服务器名称。
file128bytes是client的启动配置文件名,是服务器为client指定的启动配置文件名及路径信息,由服务器填写。
options可变是可选变长的选项字段,这个字段包含了终端的初始配置信息和网络配置信息,包括报文类型,有效租期,DNS服务器的IP地址等配置信息。

DHCP%E6%B5%81%E7%A8%8B%EF%BC%9A">2.DHCP流程:

DHCP的流程分为四个阶段:

分别是:

1.发现阶段DHCP客户端向DHCP服务端发送DHCP_DISCOVER报文。

2.提供阶段DHCP服务端收到客户端DHCP_DISCOVER报文后,向发送DHCP客户端发送DHCP_OFFER报文,该报文中包含尚未分配的地址信息。

3.选择阶段DHCP客户端选择IP地址,然后广播发送DHCP_REQUEST包,宣告使用它挑选的DHCP服务器地址并正式请求DHCP服务器分配地址

4.确认阶段:当DHCP服务器收到DHCP客户端的DHCP_REQUEST包后,便向客户端发送包含它所提供的IP地址及其他配置信息的DHCPACK确认包。

3.代码分析:

首先初始化单片机的外设:时钟、spi、uart、24c02、配置w5500参数等。

然后调用函数:init_dhcp_client函数对DHCP进行初始化,主要设置ip、mac等信息,把dhcp_state状态设置为STATE_DHCP_READY

void init_dhcp_client(void)
{
    uint8 txsize[MAX_SOCK_NUM] = {2, 2, 2, 2, 2, 2, 2, 2};
    uint8 rxsize[MAX_SOCK_NUM] = {2, 2, 2, 2, 2, 2, 2, 2};
    //uint8 ip_broadcast[4]={255,};
    uint8 ip_0[4] = {0,};
    DHCP_XID = 0x12345678;
    memset(OLD_SIP, 0, sizeof(OLD_SIP));
    memset(DHCP_SIP, 0, sizeof(DHCP_SIP));
    memset(DHCP_REAL_SIP, 0, sizeof(GET_SN_MASK));

    iinchip_init();

    setSHAR(ConfigMsg.mac);
    setSUBR(ip_0);
    setGAR(ip_0);
    setSIPR(ip_0);
    printf("MAC=%02x:%02x:%02x:%02x:%02x:%02x\r\n", DHCP_CHADDR[0], DHCP_CHADDR[1], DHCP_CHADDR[2], DHCP_CHADDR[3], DHCP_CHADDR[4], DHCP_CHADDR[5]);
    sysinit(txsize, rxsize);
    //clear ip setted flag

    dhcp_state = STATE_DHCP_READY;
    #ifdef _DHCP_DEBUG
    printf("init_dhcp_client:%u\r\n", SOCK_DHCP);
    #endif
}

进入while循环执行DHCP_run()函数

uint8_t DHCP_run(void)
{
    uint8_t  type;
    uint8_t  ret;

    if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;

    if(getSn_SR(SOCK_DHCP) != SOCK_UDP)
        socket(SOCK_DHCP, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);

    ret = DHCP_RUNNING;
    type = parseDHCPMSG();

    switch ( dhcp_state )
    {
        case STATE_DHCP_READY :
            DHCP_allocated_ip[0] = 0;
            DHCP_allocated_ip[1] = 0;
            DHCP_allocated_ip[2] = 0;
            DHCP_allocated_ip[3] = 0;

            send_DHCP_DISCOVER();
            dhcp_time = 0;
            dhcp_state = STATE_DHCP_DISCOVER;
            break;

        case STATE_DHCP_DISCOVER :
            if (type == DHCP_OFFER)
            {
                #ifdef _DHCP_DEBUG_
                printf("> Receive DHCP_OFFER\r\n");
                #endif
                DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
                DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
                DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
                DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3];

                send_DHCP_REQUEST();
                dhcp_time = 0;
                dhcp_state = STATE_DHCP_REQUEST;
            }
            else
                ret = check_DHCP_timeout();

            break;

        case STATE_DHCP_REQUEST :
            if (type == DHCP_ACK)
            {
                #ifdef _DHCP_DEBUG_
                printf("> Receive DHCP_ACK\r\n");
                #endif

                if (check_DHCP_leasedIP())
                {
                    // Network info assignment from DHCP
                    printf("ip:%d.%d.%d.%d\r\n", DHCP_allocated_ip[0], DHCP_allocated_ip[1], DHCP_allocated_ip[2], DHCP_allocated_ip[3]);
                    printf("sn:%d.%d.%d.%d\r\n", DHCP_allocated_sn[0], DHCP_allocated_sn[1], DHCP_allocated_sn[2], DHCP_allocated_sn[3]);
                    printf("gw:%d.%d.%d.%d\r\n", DHCP_allocated_gw[0], DHCP_allocated_gw[1], DHCP_allocated_gw[2], DHCP_allocated_gw[3]);
                    dhcp_ip_assign();
                    reset_DHCP_timeout();
                    dhcp_state = STATE_DHCP_LEASED;
                }
                else
                {
                    // IP address conflict occurred
                    reset_DHCP_timeout();
                    dhcp_ip_conflict();
                    dhcp_state = STATE_DHCP_READY;
                }
            }
            else if (type == DHCP_NAK)
            {
                #ifdef _DHCP_DEBUG_
                printf("> Receive DHCP_NACK\r\n");
                #endif
                reset_DHCP_timeout();
                dhcp_state = STATE_DHCP_DISCOVER;
            }
            else
                ret = check_DHCP_timeout();

            break;

        case STATE_DHCP_LEASED :
				
            ret = DHCP_IP_LEASED;

            if ((dhcp_lease_time != DEFAULT_LEASETIME) && ((dhcp_lease_time / 2) < dhcp_time))
            {

                #ifdef _DHCP_DEBUG_
                printf("> Maintains the IP address \r\n");
                #endif
                type = 0;
                OLD_allocated_ip[0] = DHCP_allocated_ip[0];
                OLD_allocated_ip[1] = DHCP_allocated_ip[1];
                OLD_allocated_ip[2] = DHCP_allocated_ip[2];
                OLD_allocated_ip[3] = DHCP_allocated_ip[3];

                DHCP_XID++;
                send_DHCP_REQUEST();
                reset_DHCP_timeout();
                dhcp_state = STATE_DHCP_REREQUEST;
            }

            break;

        case STATE_DHCP_REREQUEST :
            ret = DHCP_IP_LEASED;

            if (type == DHCP_ACK)
            {
                dhcp_retry_count = 0;

                if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] ||
                        OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
                        OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
                        OLD_allocated_ip[3] != DHCP_allocated_ip[3])
                {
                    ret = DHCP_IP_CHANGED;
                    dhcp_ip_update();
                    #ifdef _DHCP_DEBUG_
                    printf(">IP changed.\r\n");
                    #endif
                }

                #ifdef _DHCP_DEBUG_
                else printf(">IP is continued.\r\n");

                #endif
                reset_DHCP_timeout();
                dhcp_state = STATE_DHCP_LEASED;
            }
            else if (type == DHCP_NAK)
            {
                #ifdef _DHCP_DEBUG_
                printf("> Receive DHCP_NACK, Failed to maintain ip\r\n");
                #endif
                reset_DHCP_timeout();
                dhcp_state = STATE_DHCP_DISCOVER;
            }
            else ret = check_DHCP_timeout();

            break;

        default :
            break;
    }

    return ret;
}

分析DHCP_run函数:

1.  打开socket,调用parseDHCPMSG()函数,接受数据并解析,将收到的数据进行保存。

int8_t parseDHCPMSG(void)//20180625
{
    uint8_t svr_addr[6];
    uint16_t  svr_port;
    uint16_t len;

    uint8_t * p;
    uint8_t * e;
    uint8_t type;
    uint8_t opt_len;

    if((len = getSn_RX_RSR(SOCK_DHCP)) > 0)
    {
        len = recvfrom(SOCK_DHCP, (uint8_t *)pDHCPMSG, len, svr_addr, &svr_port);
        #ifdef _DHCP_DEBUG_
        printf("DHCP message : %d.%d.%d.%d(%d) %d received. \r\n", svr_addr[0], svr_addr[1], svr_addr[2], svr_addr[3], svr_port, len);
        #endif
    }
    else return 0;

    if (svr_port == DHCP_SERVER_PORT)
    {
        // compare mac address
        if ( (pDHCPMSG->chaddr[0] != DHCP_CHADDR[0]) || (pDHCPMSG->chaddr[1] != DHCP_CHADDR[1]) ||
                (pDHCPMSG->chaddr[2] != DHCP_CHADDR[2]) || (pDHCPMSG->chaddr[3] != DHCP_CHADDR[3]) ||
                (pDHCPMSG->chaddr[4] != DHCP_CHADDR[4]) || (pDHCPMSG->chaddr[5] != DHCP_CHADDR[5])   )
            return 0;

        type = 0;
        p = (uint8_t *)(&pDHCPMSG->op);//2
        p = p + 240;      // 240 = sizeof(RIP_MSG) + MAGIC_COOKIE size in RIP_MSG.opt - sizeof(RIP_MSG.opt)
        e = p + (len - 240);

        while ( p < e )
        {
            switch ( *p )
            {
                case endOption :
                    p = e;   // for break while(p < e)
                    break;

                case padOption :
                    p++;
                    break;

                case dhcpMessageType :
                    p++;
                    p++;
                    type = *p++;
                    break;

                case subnetMask :
                    p++;
                    p++;
                    DHCP_allocated_sn[0] = *p++;
                    DHCP_allocated_sn[1] = *p++;
                    DHCP_allocated_sn[2] = *p++;
                    DHCP_allocated_sn[3] = *p++;
                    break;

                case routersOnSubnet :
                    p++;
                    opt_len = *p++;
                    DHCP_allocated_gw[0] = *p++;
                    DHCP_allocated_gw[1] = *p++;
                    DHCP_allocated_gw[2] = *p++;
                    DHCP_allocated_gw[3] = *p++;
                    p = p + (opt_len - 4);
                    break;

                case dns :
                    p++;
                    opt_len = *p++;
                    DHCP_allocated_dns[0] = *p++;
                    DHCP_allocated_dns[1] = *p++;
                    DHCP_allocated_dns[2] = *p++;
                    DHCP_allocated_dns[3] = *p++;
                    p = p + (opt_len - 4);
                    break;

                case dhcpIPaddrLeaseTime :
                    p++;
                    opt_len = *p++;
                    dhcp_lease_time  = *p++;
                    dhcp_lease_time  = (dhcp_lease_time << 8) + *p++;
                    dhcp_lease_time  = (dhcp_lease_time << 8) + *p++;
                    dhcp_lease_time  = (dhcp_lease_time << 8) + *p++;
                    #ifdef _DHCP_DEBUG_
                    //               dhcp_lease_time = 10;
                    //				printf("dhcp_lease_time:%d\r\n",(int)dhcp_lease_time);
                    #endif
                    break;

                case dhcpServerIdentifier :
                    p++;
                    opt_len = *p++;
                    DHCP_SIP[0] = *p++;
                    DHCP_SIP[1] = *p++;
                    DHCP_SIP[2] = *p++;
                    DHCP_SIP[3] = *p++;
                    break;

                default :
                    p++;
                    opt_len = *p++;
                    p += opt_len;
                    break;
            } // switch
        } // while
    } // if

    return	type;
}

2.然后进入switch状态机,首先dhcp_state的值为STATE_DHCP_READY,执行一下代码:

进入发现阶段DHCP客户端向DHCP服务端发送DHCP_DISCOVER报文,然后把dhcp_state的值设为STATE_DHCP_DISCOVER。

 case STATE_DHCP_READY :
            DHCP_allocated_ip[0] = 0;
            DHCP_allocated_ip[1] = 0;
            DHCP_allocated_ip[2] = 0;
            DHCP_allocated_ip[3] = 0;

            send_DHCP_DISCOVER();
            dhcp_time = 0;
            dhcp_state = STATE_DHCP_DISCOVER;
            break;

3.然后退出switch,再次调用parseDHCPMSG()函数去接受DHCP服务端发来的数据并解析,再次进入switch状态机中,执行STATE_DHCP_DISCOVER代码,如果数据包的类型是DHCP_OFFER

就执行以下代码:DHCP客户端选择IP地址,然后广播发送DHCP_REQUEST包,宣告使用它挑选的DHCP服务器地址并正式请求DHCP服务器分配地址。

 case STATE_DHCP_DISCOVER :
            if (type == DHCP_OFFER)
            {
                #ifdef _DHCP_DEBUG_
                printf("> Receive DHCP_OFFER\r\n");
                #endif
                DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
                DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
                DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
                DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3];

                send_DHCP_REQUEST();
                dhcp_time = 0;
                dhcp_state = STATE_DHCP_REQUEST;
            }

 4.如果正确接收到DHCP服务端的DHAP_ACK数据包,则DHCP请求就成功了。执行一下代码:

若是DHAP_ACK数据包,则调用check_DHCP_leasedIP()函数检查IP是否有效,然后我们去设置w5500的IP等信息,把dhcp_state状态设为STATE_DHCP_LEASED,若是DHCP_NAK数据包,调用reset_DHCP_timeout()把超时时间复位, 然后将dhcp_state值改为STATE_DHCP_DISCOVER重新进入发现阶段。

 case STATE_DHCP_REQUEST :
            if (type == DHCP_ACK)
            {
                #ifdef _DHCP_DEBUG_
                printf("> Receive DHCP_ACK\r\n");
                #endif

                if (check_DHCP_leasedIP())
                {
                    // Network info assignment from DHCP
                    printf("ip:%d.%d.%d.%d\r\n", DHCP_allocated_ip[0], DHCP_allocated_ip[1], DHCP_allocated_ip[2], DHCP_allocated_ip[3]);
                    printf("sn:%d.%d.%d.%d\r\n", DHCP_allocated_sn[0], DHCP_allocated_sn[1], DHCP_allocated_sn[2], DHCP_allocated_sn[3]);
                    printf("gw:%d.%d.%d.%d\r\n", DHCP_allocated_gw[0], DHCP_allocated_gw[1], DHCP_allocated_gw[2], DHCP_allocated_gw[3]);
                    dhcp_ip_assign();
                    reset_DHCP_timeout();
                    dhcp_state = STATE_DHCP_LEASED;
                }
                else
                {
                    // IP address conflict occurred
                    reset_DHCP_timeout();
                    dhcp_ip_conflict();
                    dhcp_state = STATE_DHCP_READY;
                }
            }
            else if (type == DHCP_NAK)
            {
                #ifdef _DHCP_DEBUG_
                printf("> Receive DHCP_NACK\r\n");
                #endif
                reset_DHCP_timeout();
                dhcp_state = STATE_DHCP_DISCOVER;
            }
            else
                ret = check_DHCP_timeout();

            break;

05_W5500_UDP通信 <--------- 上一篇                                                                                                下一篇----------> 07——W5500


http://www.niftyadmin.cn/n/5262540.html

相关文章

Python 注释:解释和优化代码可读性

注释可以用来解释Python代码。注释可以用来使代码更易读。注释可以用来在测试代码时防止执行。 创建注释 注释以#开始&#xff0c;Python会忽略它们&#xff1a; 示例&#xff1a;获取您自己的Python注释 # 这是一个注释 print("Hello, World!")注释可以放在一行的…

单端反激(离散系统仿真)

单端反激&#xff08;离散系统仿真&#xff09; 指令电压为0,电机微速旋转,补足指令电压。 把仿真变成离散的。 最大步长设置方法&#xff1a;如果是对于相控形式的电路&#xff0c;我觉得设置1e-4秒大概就够了&#xff0c;如果是对于斩波形式的电路&#xff0c;设置1e-6或者…

每日OJ题_算法_双指针④_力扣11. 盛最多水的容器

目录 力扣11. 盛最多水的容器 解析代码 力扣11. 盛最多水的容器 11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 难度 中等 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两…

【Android开发-30】Android中获取全局Context和使用Intent传递对象的讲解

1&#xff0c;获取全局Context的技巧 在Android编程中&#xff0c;Context对象常常扮演着重要的角色&#xff0c;例如在弹出Toast、启动活动、发送广播、操作数据库和使用通知等场景下都需要它的支持。然而&#xff0c;在某些情况下&#xff0c;直接获取Context对象并不那么容…

【算法Hot100系列】无重复字符的最长子串

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

c/c++ 文件操作(2)

文件操作读和写 顺序读写 1、fgetc、fputc 函数功能fgetc字符输入函数----->对应打开方式是 “r”fputc字符输出函数-----> 对应打开方式是 “w” 2、fgets、fputs 函数功能fgets文本行输入函数------> 对应打开方式是"r"fputs文本行输出函数------>…

ES6中的迭代器和set、map集合

什么是迭代器&#xff1f; 一种机制&#xff0c;也是一种接口&#xff0c;为数据结构提供统一访问接口&#xff0c;依次处理数据据结构成员 只要实现了迭代器接口&#xff0c;就可以使用for...of循环遍历。 /*** 迭代器是一种机制 是一种接口 只要数据解构实现了接口 就可…

基于sfunction builder的c-sfunction编写及案例测试分析

目录 前言 1.前期准备工作及文件说明 1.1前期准备工作 1.2 文件说明 1.3 编译方式