RT-Thread系列09——ETH网口设备

news2025/1/16 15:44:56

文章目录

  • 1. ETH测试
    • 第一步:cubemx配置。
    • 第二步:board.h配置。
    • 第三步:rtthread settings配置
    • 第四步:以太网复位引脚设置
    • 第五步:修改rtthread源码
    • 第六步:修改 cubemx 生成的 main 函数
    • 第七步:编译运行。
    • 第七步:通信测试
  • 2. UDP/TCP通信测试
    • 2.1 UDP测试
      • 2.1.1 UPD 服务端测试
      • 2.1.2 UPD 客户端测试
    • 2.2 TCP通信测试
      • 2.1.2 TCP 客户端测试
      • 2.2.2 TCP 服务端测试

====>>> 文章汇总(有代码汇总) <<<====

目标:使用网口和电脑通信。

  • RT-Thread studio,版本: 2.2.6,不一样其实区别也不大
  • RT-Thread:标准版,4.0.3版本
  • 芯片包版本:0.1.9
  • 开发板:自己做的,主控STM32F407VET6。
  • 网口芯片:太网口LAN8720A
  • cubemx版本:6.8.1

cubemx版本不一样的话,网口的配置过程也不大一样。我现在用的麻烦一点。老版本的cubemx相对简单。

Finsh组件是创建工程后就默认开启的,无需任何配置。

1. ETH测试

新建工程,选择自己的芯片和调试接口即可。

第一步:cubemx配置。

首先配置时钟,一般都拉满。
在这里插入图片描述
勾选上调试的串口、网口。参数默认。

在这里插入图片描述

我这里调试用的串口2,一般都是串口1,根据自己的选择即可。
网口也要根据自己的板子的引脚调整。

生成代码后,关闭cubemx,直接编译,一堆报错,基本都在cubemx下面的main函数里面。

第二步:board.h配置。

在这里插入图片描述

第三步:rtthread settings配置

勾选以太网驱动。
在这里插入图片描述
勾选网络层驱动。
在这里插入图片描述
设置板子 IP 信息
在这里插入图片描述

勾选之后,保存 关闭。

第四步:以太网复位引脚设置

在这里插入图片描述

这里根据自己的板卡引脚修改。

第五步:修改rtthread源码

修改 eth_demo\rt-thread\components\drivers\include\drivers\phy.h文件,注释掉32行。图中 drivers 文件夹下的 drv_eth.c 报错是因为这个变量重定义了。
在这里插入图片描述

第六步:修改 cubemx 生成的 main 函数

在这里插入图片描述

注意,把下面的函数体 也删掉。

此时编译,应该是没有报错能通过的。cubemx中的main函数会用到我们删掉的那两个函数,所有会有警告,不想看可以在main函数中也注释或者删掉。

第七步:编译运行。

调试串口输入 ifconfig 就可以看到板子的ip信息了。
在这里插入图片描述

第七步:通信测试

第一步:把板子的网口和电脑的网口使用网线连接起来。

第二步:把电脑端 ip 更改为和板卡在同一个网段下。

更改本地电脑ip方法,自己百度一下。

在这里插入图片描述

第三步:打开电脑端 cmd 窗口,使用ping命令测试,可以ping通。
在这里插入图片描述

2. UDP/TCP通信测试

在上面的步骤都能通过以后,进行UDP/TCP通信测试。

2.1 UDP测试

在工程中新建一个 c 文件,添加如下代码。位置根据自己需要放。

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-10-24     Haozi       the first version
 */
#include <rtthread.h>
#include <sys/socket.h> /* 使用BSD socket,需要包含socket.h头文件 */
#include <netdb.h>
#include <string.h>
#include <finsh.h>


/* UDP服务器参数 */
#define BUFSZ                   1024        //接收缓存大小
#define BOARD_SERVER_PORT       5000        //板卡作为服务器的端口号

/* UDP客户端参数 */
#define BOARD_UDP_CLIENT_PORT     20000               //板卡作为客户端端口号
#define UPPER_UDP_SERVER_IP       "192.168.1.125"     //要连接的服务器ip

/*
 * udp 服务端线程,一直等待接收其他udp客户端发来的消息。
 * 本机ip:192.168.1.30
 * 使用的端口号:5000
*/
void udp_server(void *para)
{
    int sock;
    int bytes_read;
    char *recv_data;
    socklen_t addr_len;
    struct sockaddr_in server_addr, client_addr;

    /* 分配接收用的数据缓冲 */
    recv_data = rt_malloc(BUFSZ);
    if (recv_data == RT_NULL)
    {
        /* 分配内存失败,返回 */
        rt_kprintf("No memory\n");
        return;
    }

    /* 创建一个socket,类型是SOCK_DGRAM,UDP类型 */
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        rt_kprintf("Socket error\n");

        /* 释放接收用的数据缓冲 */
        rt_free(recv_data);
        return;
    }

    /* 初始化服务端地址 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(BOARD_SERVER_PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    /* 绑定socket到服务端地址 */
    if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
    {
        /* 绑定地址失败 */
        rt_kprintf("Bind error\n");

        /* 释放接收用的数据缓冲 */
        rt_free(recv_data);
        return;
    }

    addr_len = sizeof(struct sockaddr);
    rt_kprintf("UDPServer Waiting for client on port 5000...\n");

    while (1)
    {
        /* 从sock中收取最大BUFSZ - 1字节数据 */
        bytes_read = recvfrom(sock, recv_data, BUFSZ - 1, 0, (struct sockaddr *)&client_addr, &addr_len);
        /* UDP不同于TCP,它基本不会出现收取的数据失败的情况,除非设置了超时等待 */

        recv_data[bytes_read] = '\0'; /* 把末端清零 */

        /* 输出接收的数据 */
        rt_kprintf("\n(%s , %d) said : ", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        rt_kprintf("%s", recv_data);

        /* 如果接收数据是exit,退出 */
        if (strcmp(recv_data, "exit") == 0)
        {
            closesocket(sock);

            /* 释放接收用的数据缓冲 */
            rt_free(recv_data);
            break;
        }
    }

    return;
}
MSH_CMD_EXPORT(udp_server, udp server);

/*
 * udp 客户端,每隔2s就往上位机发送当前系统时钟的计数值。
 * 本机ip:192.168.1.30
 * 使用的端口号:20000
*/
static const char send_data[] = "This is UDP Client from RT-Thread.\n"; /* 发送用到的数据 */

void udp_client(void *para)
{
    int sock;
    struct hostent *host;
    struct sockaddr_in server_addr;

    /* 获得host地址(如果是域名,会做域名解析) */
    host = (struct hostent *) gethostbyname(UPPER_UDP_SERVER_IP);

    /* 创建一个socket,类型是SOCK_DGRAM,UDP类型 */
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        rt_kprintf("Socket error\n");
        return;
    }

    /* 初始化预连接的服务端地址 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(BOARD_UDP_CLIENT_PORT);
    server_addr.sin_addr = *((struct in_addr *)host->h_addr);
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    /* 总计发送count次数据 */
    while (1)
    {
        /* 发送数据到服务远端 */
        sendto(sock, send_data, strlen(send_data), 0, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));

        /* 线程休眠一段时间 */
        rt_thread_delay(2000);
    }

    /* 关闭这个socket */
    closesocket(sock);
}

MSH_CMD_EXPORT(udp_client, udp client);

2.1.1 UPD 服务端测试

  1. 在串口调试助手中启动udp_server任务。
  2. 打开,网络调试助手。ip设置如下,然后点击连接。
  3. 在网络调试助手输入框输入消息并点击发送,串口会打印出板卡收到的消息。

在这里插入图片描述

电脑信息:端口是任意的,能连接即可。
板卡信息:端口是固定的,在上面板卡程序里面可以改。

2.1.2 UPD 客户端测试

  1. 打开,网络调试助手。ip设置如下。
  2. 重启板卡,在串口调试助手中启动 udp_client任务。
  3. 网络调试助手会受到板卡每2s发送过来的一次数据。

在这里插入图片描述

电脑信息:端口是固定的,在上面板卡程序里面可以改。
板卡信息:随便填吧,没用其实。

2.2 TCP通信测试

在工程中新建一个 c 文件,添加如下代码。位置根据自己需要放。

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-10-24     Haozi       the first version
 */
#include <rtthread.h>
#include <sys/socket.h> /* 使用BSD socket,需要包含socket.h头文件 */
#include <netdb.h>
#include <string.h>
#include <finsh.h>
#include <sys/errno.h>

#define BUFSZ       (1024)
static const char send_data[] = "This is TCP Server from RT-Thread."; /* 发送用到的数据 */

/*
* 程序清单:tcp 服务端
 *
 * 这是一个 tcp 服务端的例程
 * 导出 tcpserv 命令到控制终端
 * 命令调用格式:tcpserv
 * 无参数
 * 程序功能:作为一个服务端,接收并显示客户端发来的数据 ,接收到 exit 退出程序
*/
void tcp_server(int argc, char **argv)
{
    char *recv_data; /* 用于接收的指针,后面会做一次动态分配以请求可用内存 */
    socklen_t sin_size;
    int sock, connected, bytes_received;
    struct sockaddr_in server_addr, client_addr;
    rt_bool_t stop = RT_FALSE; /* 停止标志 */
    int ret;

    recv_data = rt_malloc(BUFSZ + 1); /* 分配接收用的数据缓冲 */
    if (recv_data == RT_NULL)
    {
        rt_kprintf("No memory\n");
        return;
    }

    /* 一个socket在使用前,需要预先创建出来,指定SOCK_STREAM为TCP的socket */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        /* 创建失败的错误处理 */
        rt_kprintf("Socket error\n");

        /* 释放已分配的接收缓冲 */
        rt_free(recv_data);
        return;
    }

    /* 初始化服务端地址 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(20000); /* 服务端工作的端口 */
    server_addr.sin_addr.s_addr = INADDR_ANY;
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    /* 绑定socket到服务端地址 */
    if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
    {
        /* 绑定失败 */
        rt_kprintf("Unable to bind\n");

        /* 释放已分配的接收缓冲 */
        rt_free(recv_data);
        return;
    }

    /* 在socket上进行监听 */
    if (listen(sock, 5) == -1)
    {
        rt_kprintf("Listen error\n");

        /* release recv buffer */
        rt_free(recv_data);
        return;
    }

    rt_kprintf("\nTCPServer Waiting for client on port 5000...\n");
    while (stop != RT_TRUE)
    {
        sin_size = sizeof(struct sockaddr_in);

        /* 接受一个客户端连接socket的请求,这个函数调用是阻塞式的 */
        connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);
        /* 返回的是连接成功的socket */
        if (connected < 0)
        {
            rt_kprintf("accept connection failed! errno = %d\n", errno);
            continue;
        }

        /* 接受返回的client_addr指向了客户端的地址信息 */
        rt_kprintf("I got a connection from (%s , %d)\n",
                   inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        /* 客户端连接的处理 */
        while (1)
        {
            /* 发送数据到connected socket */
            ret = send(connected, send_data, strlen(send_data), 0);
            if (ret < 0)
            {
                /* 发送失败,关闭这个连接 */
                closesocket(connected);
                rt_kprintf("\nsend error,close the socket.\r\n");
                break;
            }
            else if (ret == 0)
            {
                /* 打印send函数返回值为0的警告信息 */
                rt_kprintf("\n Send warning,send function return 0.\r\n");
            }

            /* 从connected socket中接收数据,接收buffer是1024大小,但并不一定能够收到1024大小的数据 */
            bytes_received = recv(connected, recv_data, BUFSZ, 0);
            if (bytes_received < 0)
            {
                /* 接收失败,关闭这个connected socket */
                closesocket(connected);
                break;
            }
            else if (bytes_received == 0)
            {
                /* 打印recv函数返回值为0的警告信息 */
                rt_kprintf("\nReceived warning,recv function return 0.\r\n");
                closesocket(connected);
                break;
            }

            /* 有接收到数据,把末端清零 */
            recv_data[bytes_received] = '\0';
            if (strcmp(recv_data, "q") == 0 || strcmp(recv_data, "Q") == 0)
            {
                /* 如果是首字母是q或Q,关闭这个连接 */
                closesocket(connected);
                break;
            }
            else if (strcmp(recv_data, "exit") == 0)
            {
                /* 如果接收的是exit,则关闭整个服务端 */
                closesocket(connected);
                stop = RT_TRUE;
                break;
            }
            else
            {
                /* 在控制终端显示收到的数据 */
                rt_kprintf("RECEIVED DATA = %s \n", recv_data);
            }
        }
    }

    /* 退出服务 */
    closesocket(sock);

    /* 释放接收缓冲 */
    rt_free(recv_data);

    return ;
}
MSH_CMD_EXPORT(tcp_server, tcp server);



/*
 * 程序清单:tcp 客户端
 *
 * 这是一个 tcp 客户端的例程
 * 导出 tcp_client 命令到控制终端
 * 命令调用格式:tcp_client URL PORT
 * URL:服务器地址 PORT::端口号
 * 程序功能:接收并显示从服务端发送过来的信息,接收到开头是 'q' 或 'Q' 的信息退出程序
*/
void tcp_client(int argc, char **argv)
{
    int ret;
    char *recv_data;
    struct hostent *host;
    int sock, bytes_received;
    struct sockaddr_in server_addr;
    const char *url;
    int port;

    if (argc < 3)
    {
        rt_kprintf("Usage: tcp_client URL PORT\n");
        rt_kprintf("Like: tcp_client 192.168.1.125 20000\n");
        return ;
    }

    url = argv[1];
    port = strtoul(argv[2], 0, 10);

    /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */
    host = gethostbyname(url);

    /* 分配用于存放接收数据的缓冲 */
    recv_data = rt_malloc(BUFSZ);
    if (recv_data == RT_NULL)
    {
        rt_kprintf("No memory\n");
        return;
    }

    /* 创建一个socket,类型是SOCKET_STREAM,TCP类型 */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        /* 创建socket失败 */
        rt_kprintf("Socket error\n");

        /* 释放接收缓冲 */
        rt_free(recv_data);
        return;
    }

    /* 初始化预连接的服务端地址 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr = *((struct in_addr *)host->h_addr);
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    /* 连接到服务端 */
    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
    {
        /* 连接失败 */
        rt_kprintf("Connect fail!\n");
        closesocket(sock);

        /*释放接收缓冲 */
        rt_free(recv_data);
        return;
    }
    else
    {
        /* 连接成功 */
        rt_kprintf("Connect successful\n");
    }

    while (1)
    {
        /* 从sock连接中接收最大BUFSZ - 1字节数据 */
        bytes_received = recv(sock, recv_data, BUFSZ - 1, 0);
        if (bytes_received < 0)
        {
            /* 接收失败,关闭这个连接 */
            closesocket(sock);
            rt_kprintf("\nreceived error,close the socket.\r\n");

            /* 释放接收缓冲 */
            rt_free(recv_data);
            break;
        }
        else if (bytes_received == 0)
        {
            /* 默认 recv 为阻塞模式,此时收到0认为连接出错,关闭这个连接 */
            closesocket(sock);
            rt_kprintf("\nreceived error,close the socket.\r\n");

            /* 释放接收缓冲 */
            rt_free(recv_data);
            break;
        }

        /* 有接收到数据,把末端清零 */
        recv_data[bytes_received] = '\0';

        if (strncmp(recv_data, "q", 1) == 0 || strncmp(recv_data, "Q", 1) == 0)
        {
            /* 如果是首字母是q或Q,关闭这个连接 */
            closesocket(sock);
            rt_kprintf("\n got a 'q' or 'Q',close the socket.\r\n");

            /* 释放接收缓冲 */
            rt_free(recv_data);
            break;
        }
        else
        {
            /* 在控制终端显示收到的数据 */
            rt_kprintf("\nReceived data = %s ", recv_data);
        }

        /* 发送数据到sock连接 */
        ret = send(sock, send_data, strlen(send_data), 0);
        if (ret < 0)
        {
            /* 接收失败,关闭这个连接 */
            closesocket(sock);
            rt_kprintf("\nsend error,close the socket.\r\n");

            rt_free(recv_data);
            break;
        }
        else if (ret == 0)
        {
            /* 打印send函数返回值为0的警告信息 */
            rt_kprintf("\n Send warning,send function return 0.\r\n");
        }
    }
    return;
}
MSH_CMD_EXPORT(tcp_client, tcp client);

2.1.2 TCP 客户端测试

  1. 网络调试助手设置为 TCP Server模式。并设置地址和端口号。
  2. 在串口调试助手中打开 TCP Client任务。接收电脑服务器发过去的数据。
  3. 在网络调试助手中输入要发送的消息点击发送,串口即可显示接收到的数据。

在这里插入图片描述

2.2.2 TCP 服务端测试

有问题,网络助手连接不上,后续再补。
百度说,把虚拟机的网络禁用就好了,这里测试没行,理由未知。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线程安全(JAVA)

线程安全对于我们编写多线程代码是非常重要的。 什么是线程安全&#xff1f; 在我们平时的代码中有些代码在单线程程序中可以正常执行&#xff0c;但如果同样的代码放在在多个线程中执行就会引发BUG&#xff0c;而这种现象我们一般称为 “线程安全问题” 或 “线程不安全”。…

漏洞复现--奇安信360天擎未授权访问

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

界面控件DevExtreme图表和仪表(v23.1) - 新功能(Angular,React,Vue,jQuery)

本文将为大家总结下DevExtreme在v23.1版本中发布的一些与图表和仪表盘相关的功能。 DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#…

TCP和UDP C#代码实战

网络传输的七层结构&#xff1a; 其中TCP和UDP协议在传输层。 TCP/IP协议 TCP/IP中包含了四层架构中的多个协议&#xff0c;取其中两个进行了命名&#xff1a; TCP TCP的特点 粘包问题处理 TCP一次性接收过多数据必然出现粘包&#xff0c;即不同时发送的数据黏连在一…

OV5640的参数与配置方法

分辨率和速率&#xff08;FPS&#xff09; 寄存器配置 I/O 板的驱动能力和方向控制 system clock control OV5640 PLL 允许输入时钟频率范围为 6~27 MHz&#xff0c;最大 VCO 频率为 800 MHz。 MipiClk 用于 MIPI&#xff0c;SysClk 用于图像信号处理 (ISP) 模块的内部时钟。 …

网络营销利器:海外IP代理如何助力你的网络营销?如何选择?

在当今数字化的时代&#xff0c;网络营销已经成为企业营销策略的重要组成部分。而对于进去海外市场的跨境玩家来说&#xff0c;海外的推广营销是重中之重。然而&#xff0c;在开展网络营销的过程中&#xff0c;我们常常会遇到各种挑战&#xff0c;如地域限制、访问速度慢等。 …

力扣每日一道系列 --- LeetCode 88. 合并两个有序数组

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构探索 ✅LeetCode每日一道 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 思路1&#xff1a;暴力求解思路2&#xff1a;原地合并 LeetCode 88. 合并两个有序数组…

Docker - 镜像

Docker - 镜像 镜像是什么 镜像是一种轻量级&#xff0c;可执行的独立软件包&#xff0c;用来打包软件运行环境和基于运行环境开发的软件&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;包括代码&#xff0c;运行时&#xff0c;库&#xff0c;环境变量和配置文件。…

数据结构与算法(一)数据结构基础

目录 一、绪论1.1 什么是程序 二、算法2.1 定义2.2 特性2.3 算法时间效率2.4 时间复杂度1&#xff09;大 O 阶推导法&#xff1a;2&#xff09;举个例子&#xff1a;3&#xff09;常见的时间复杂度 2.5 空间复杂度1&#xff09;计算方法2&#xff09;存储空间 2.6 常见算法的时…

Yolov5 + 界面PyQt5 +.exe文件部署运行

介绍 Yolov5是一种基于深度学习的目标检测算法&#xff0c;PyQt5是一个Python编写的GUI框架&#xff0c;用于创建交互式界面。在部署和运行Yolov5模型时&#xff0c;结合PyQt5可以方便地创建一个用户友好的界面&#xff0c;并将代码打包为.exe文件以供其他人使用。 下面是一个…

虚拟化服务器+华为防火墙+kiwi_syslog访问留痕

一、适用场景 1、大中型企业需要对接入用户的访问进行记录时&#xff0c;以前用3CDaemon时&#xff0c;只能用于小型网络当中&#xff0c;记录的数据量太大时&#xff0c;本例采用破解版的kiwi_syslog。 2、当网监、公安查到有非法访问时&#xff0c;可提供基于五元组的外网访…

在全志XR806上移植st7789屏幕驱动

前言 很高兴有机会参加本次极术社区举办的“「免费试用」搭载安谋科技STAR-MC1的全志XR806 Wi-FiBLE 开发板试用活动”。 去年就对全志的mcu芯片感兴趣了&#xff0c;一直没有机会接触&#xff0c;看到本次极术社区提供的全志wifi BLE开发板试用&#xff0c;就马上参加了。板…

WebGL智慧城市软件项目

WebGL开发智慧城市项目时&#xff0c;需要考虑多个方面&#xff0c;包括技术、隐私、安全和可持续性。以下是一些需要注意的关键问题&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.隐私和数据安全…

6.3二叉树的层序遍历(LC102,LC107-M)

二叉树的层序遍历&#xff08;LC102&#xff09;&#xff1a; 算法&#xff08;长度法&#xff09;&#xff1a; 需要借用一个辅助数据结构即队列来实现&#xff0c;队列先进先出&#xff0c;符合一层一层遍历的逻辑&#xff0c;而用栈先进后出适合模拟深度优先遍历也就是递归…

【外部服务对接】对接Firebase支持谷歌、Facebook、苹果等第三方平台用户注册登录

【外部服务对接】对接Firebase支持谷歌、Facebook、苹果等第三方平台登录 背景 因主要做国外尼日的市场&#xff0c;相关的应用的全是国外用户使用&#xff0c;为了方便用户的注册和登录&#xff0c;接入国外的统一平台Firebase,集成使用很方便。 主要步骤 1.注册登录Fireb…

如何提高小红书笔记的互动率

相信有很多新手在运营小红书的时候&#xff0c;可能都会遇到过以下这样的情况&#xff1a; 笔记点赞、收藏数据明明还可以&#xff0c;但评论区却没有人留言&#xff1f;为何大家只给点赞、收藏&#xff0c;却不关注账号&#xff1f; 其实&#xff0c;这背后有很多运营技巧&a…

​做好研发管理的三个条件​

1.制造鼓励创新的环境 要做好研发管理&#xff0c;首先要制造一个鼓励创新、适合研发的环境&#xff0c;必须采取弹性而目标化的管理&#xff0c;不以死板的制度限制员工的创意&#xff0c;必须要求实质的成果。 2.融入行销观念 将行销的观念融入研发中&#xff1a;为使有限的…

xlua游戏热更新(lua访问C#)

CS.UnityEngine静态方法访问unity虚拟机 创建游戏物体 CS.UnityEngine.GameObject(new by lua);静态属性 CS.UnityEngine.GameObject(new by lua); -- 创建 local camera CS.UnityEngine.GameObject.Find(Main Camera); --查找 camera.name Renamed by Lua;访问组件 loca…

通配符匹配

题目链接 通配符匹配 题目描述 注意点 s 仅由小写英文字母组成p 仅由小写英文字母、‘?’ 或 ‘*’ 组成‘?’ 可以匹配任何单个字符‘*’ 可以匹配任意字符序列&#xff08;包括空字符序列&#xff09; 解答思路 最初想到的是dfs 剪枝&#xff0c;但是用例超时了参照题…

小型企业如何数字化转型?ZohoCRM助力小企业转型

小型企业数字化之路倍加艰难&#xff0c;其组织规模有限、资源有限&#xff0c;数字化布局或转型&#xff0c;也存在与数字平台匹配度的问题。其实小型企业可以通过CRM客户管理系统实现高效的客户关系管理&#xff0c;进一步提高市场竞争力。 建立高效易用的客户关系管理系统 …