06_W5500_DHCP

1.DHCP协议介绍:

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

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

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

2.DHCP报文

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报文格式:

字段长度说明
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地址等配置信息。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/238593.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

geolife笔记:比较不同轨迹相似度方法

1 问题描述 在geolife 笔记&#xff1a;将所有轨迹放入一个DataFrame-CSDN博客中&#xff0c;已经将所有的轨迹放入一个DataFrame中了&#xff0c;我们现在需要比较&#xff0c;在不同的轨迹距离度量方法下&#xff0c;轨迹相似度的效果。 这里采用论文笔记&#xff1a;Deep R…

前沿技术|张磊:RR22 Blazing Fast PSI 实现介绍

“隐语”是开源的可信隐私计算框架&#xff0c;内置 MPC、TEE、同态等多种密态计算虚拟设备供灵活选择&#xff0c;提供丰富的联邦学习算法和差分隐私机制 开源项目 github.com/secretflow gitee.com/secretflow ​ 11月25日&#xff0c;「隐语开源社区 Meetup西安站」顺利举…

Pika:AIGC新秀,视频生成产业或迎来GPT时刻

今天分享的AIGC系列深度研究报告&#xff1a;《Pika&#xff1a;AIGC新秀&#xff0c;视频生成产业或迎来GPT时刻》。 &#xff08;报告出品方&#xff1a;中泰证券&#xff09; 报告共计&#xff1a;11页 Pika&#xff1a;专注Text to Video生成场景&#xff0c;支持3D和动漫…

从视频中提取图片,轻松制作专属视频封面

你是否曾经为如何制作一个吸引人的视频封面而烦恼&#xff1f;现在&#xff0c;我们将向你展示如何从视频中提取图片&#xff0c;并轻松制作专属的视频封面。无论你是视频编辑新手&#xff0c;还是经验丰富的专业人士&#xff0c;这个技巧都能够帮助你快速提升你的视频品质。 …

时间戳与QDateTime转换,以及QString转时间戳

1、主要有时间戳->QDateTime,QDateTime->QString 2、同时QString->QDateTime,QDateTime->时间戳 详情见代码&#xff1a; //QDateTime转时间戳qint64 time QDateTime::currentSecsSinceEpoch();double nowTime (double)time;qDebug()<<"nowTime1111…

【VTK】VTK中的光标样式

很高兴在雪易的CSDN遇见你 前言 本文分享VTK中的光标设置相关内容技术&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xff0c;一起进步&#xff01; 你的点赞就是我的动力(&#xff3e;&#xff35;&#…

你知道如何画时间轴吗?

时间轴的英文是time axis。贯穿四维空间的一条线&#xff0c;是虚数轴&#xff0c;时间轴上一段距离表示时间 。&#xff08;源自“百度百科”&#xff09; 时间轴&#xff1a;通过互联网技术&#xff0c;依据时间顺序&#xff0c;把一方面或多方面的事件串联起来&#xff0c;…

12.11作业

1. 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&…

java面试题-mysql索引相关问题

远离八股文&#xff0c;面试大白话&#xff0c;通俗且易懂 看完后试着用自己的话复述出来。有问题请指出&#xff0c;有需要帮助理解的或者遇到的真实面试题不知道怎么总结的也请评论中写出来&#xff0c;大家一起解决。 java面试题汇总-目录-持续更新中 这一块本想着晚一点再整…

大型软件编程实际应用实例:个体诊所电子处方系统,使用配方模板功能输入症状就可开出处方软件操作教程

一、前言&#xff1a; 在开电子处方的时候&#xff0c;如果能够输入症状就可以一键导入配方&#xff0c;则在很大程度上可以节省很多时间。而且这个配方可以根据自己的经验自己设置&#xff0c;下面以 佳易王诊所电子处方软件为例说明。 二、具体一键导入配方详细操作教程 点击…

Linux本地部署Mosquitto MQTT协议消息服务端并实现远程访问【内网穿透】

文章目录 前言1. Linux 搭建 Mosquitto2. Linux 安装Cpolar3. 创建MQTT服务公网连接地址4. 客户端远程连接MQTT服务5. 代码调用MQTT服务6. 固定连接TCP公网地址7. 固定地址连接测试 前言 Mosquitto是一个开源的消息代理&#xff0c;它实现了MQTT协议版本3.1和3.1.1。它可以在不…

牛客周赛 Round 22(C、D题解)

C、小红的数组构造&#xff08;思维&#xff09; 一、题目要求 链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 小红想让你构造一个长度为 n 的数组&#xff0c;满足以下三个条件&#xff1a; 1. 该数组最大值不超过 k。 2. 该数…

adb命令学习记录

1、 adb ( android debug bridge)安卓调试桥&#xff0c;用于完成电脑和手机之间的通信控制。 xcode来完成对于ios设备的操控&#xff0c;前提是有个mac电脑。 安卓系统是基于linux内核来进行开发的。 2、adb的安装: 本身 adb是 android SDK 其中自带的工具&#xff0c;用于完…

项目必备手册-项目计划书

项目开发计划包括项目描述、项目组织、成本预算、人力资源估算、设备资源计划、沟通计划、采购计划、风险计划、项目过程定义及项目的进度安排和里程碑、质量计划、数据管理计划、度量和分析计划、监控计划和培训计划等。 软件全资料获取&#xff1a;点我获取

充分发挥SQL能力之数列

SQL数列 1、数列概述2、SQL数列2.1、简单递增序列2.2、等差数列2.3、等比数列3、SQL数列的应用3.1、连续问题3.2、多维分析1、数列概述 数列是最常见的数据形式之一,实际数据开发场景中遇到的基本都是有限数列。常见的数列例如:简单递增序列、等差数列、等比数列等 如何充分…

(新手必看)自定义数据传输通信协议+STM32代码详解

前言 本篇博客主要学习和了解一些单片机协议的格式&#xff0c;在对传输大数据或者要求准确性的时候&#xff0c;都需要通过协议来发送接收&#xff0c;下面通过了解协议的基本构成和代码来分析和实现协议的发送和接收。本篇博客大部分是自己收集和整理&#xff0c;如有侵权请联…

yolov8 onnx推理

前言&#xff1a;yolov8的后处理在某些情况下会导致转模型失败&#xff0c;因此需要把后处理剥离出来。 代码需要做如下修改&#xff1a; 改完后&#xff0c;网络会有三个输出&#xff0c;如图&#xff1a; 最后&#xff0c;用python写网络的后处理&#xff1a; import onn…

用心研发好产品:健康品牌podeey是如何做到的?

在分析消费者健康需求的同时&#xff0c;美国podeey能量生命有限公司&#xff08;PODEEY Biotechnology LLC.&#xff09;不断提升自主研发实力&#xff0c;并且一直注重汇集全球前沿的研发力量&#xff0c;与贵州宏臻菌业达成战略合作&#xff0c;始终致力于以科学技术为核心&…

软件测试HR总结的软件测试常见面试题

一、测试流程是什么样的&#xff1f; 1.产品确定需求后&#xff0c;邀请项目经理&#xff0c;开发&#xff0c;测试等人员参加需求评审会&#xff1b; 2.评审结束后开发根据需求文档和接口文档开发&#xff0c;测试制定测试计划和编写手工测试用例&#xff0c;测试脑图&#xf…

【Redis】深入理解 Redis 常用数据类型源码及底层实现(1.结构与源码概述)

在文章【Redis】不卡壳的 Redis 学习之路&#xff1a;从十大数据类型开始入手中我们介绍了Redis常用的10大数据类型&#xff0c;这10大数据类型可并不是直接在底层通过代码实现的&#xff0c;而是通过不同的底层数据结构组合起来的&#xff0c;这篇我们介绍下Redis常用数据类型…