简单的 UDP 网络程序

文章目录:

  • 简单的UDP网络程序
    • 服务端创建套接字
    • 服务端绑定
    • 启动服务器
    • udp客户端
    • 本地测试
    • INADDR_ANY
  • 地址转换函数
  • 关于 inet_ntoa

简单的UDP网络程序

服务端创建套接字

我们将服务端封装为一个类,当定义一个服务器对象之后,需要立即进行初始化服务器,在 UDP 网络程序中,初始服务器的第一个步骤就是创建套接字。我们使用 socket 函数创建套接字。

socket 函数的定义如下所示:

int socket(int domain, int type, int protocol);

参数说明:

  • domain:整数,指定通信域(Communication Domain)。在同一主机上的进程之间进行通信时,我们使用 POSIX 标准定义的 AF_LOCAL。在不同的主机通过 IPv4 连接的进程之间通信时,我们使用 AF_INET,对于通过 IPv6 连接的进程之间进行通信时,我们使用 AF_INET6。
  • type:通信类型(Communication Type)。SOCK_STREAM:TCP(可靠的、面向连接的);SOCK_DGRAM:UDP(不可靠的、无连接的)。
  • protocol:互联网协议的协议值,通常为0。这与数据包的 IP 头中的协议字段中显示的数字相同。

返回值:套接字创建成功返回一个文件描述符,否则返回-1,同时错误码被设置。

在初始化服务器创建套接字时,需要调用 socket 函数创建套接字,我们需要填入的协议家族是 AF_INET(PF_INET),因为我们需要进行的是网络通信。服务器类型填入 SOCK_DGRAM ,因为编写的 UDP 服务器是面向数据报的。第三个参数设置为0,表示使用默认的传输协议。

下面是使用这些参数创建套接字的示例:

#include<iostream>
#include<sys/socket.h>

class UdpServer
{
public:
    void init()
    {
        // 创建套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0)
        {
            std::cerr << "Failed to create socket." << std::endl;
            exit(1);
        }
        std::cout << "socket create success, sockfd: " << sockfd_ << std::endl;

        // ......
    }
private:
    int sockfd_; // 文件描述符
};

我们对其进行一个测试,查看套接字的创建是否成功。

int main()
{
    UdpServer *svr=new UdpServer();
    svr->init();
    return 0;
}

运行测试结果:

在这里插入图片描述

服务端绑定

在套接字创建成功之后,需要将其与网络相关联,以便进行网络通信。对于一个 UDP 服务器,绑定操作是必要的。通过绑定,将套接字与指定的IP地址和端口号关联起来,以便监听和处理该地址上的网络数据。

使用 bind 函数将套接字与特定的IP地址和端口号进行绑定,bind 函数的定义如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

  • sockfd:需要绑定的套接字描述符。
  • addr:指向要绑定的地址结构的指针,需要将其转化为 struct sockaddr 类型。
  • addrlen:地址结构长度。

返回值:

  • 绑定成功返回0。失败则返回-1,且错误码被设置。

bind 函数的主要作用就是将套接字与指定的地址进行关联,以便在该地址上进行监听和处理网络数据。调用 bind 之前,需要确保套接字已经创建成功。

在绑定套接字之前,需要定义一个 struct sockaddr_in 结构,并将网络属性信息填充到该结构体中。由于 struct sockaddr_in 结构体中的一些字段是可选的,建议在填充网络信息之前先将结构体变量进行清空,然后填充协议家族、端口号、IP地址等信息。

下列示例展示了如何使用 bind 函数将套接字绑定到指定的IP地址和端口号:

class UdpServer
{
public:
    UdpServer(int port = 8080, std::string ip = "")
        : port_((uint16_t)port), ip_(ip), sockfd_(-1)
    {
    }

    ~UdpServer() {}

    void init()
    {
        // 1.创建套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0)
        {
            std::cerr << "Failed to create socket." << std::endl;
            exit(1);
        }
        std::cout << "socket create success, sockfd: " << sockfd_ << std::endl;

        // 2.绑定网络信息,指明IP+port
        // 2.1 先填充基本信息到 struct sockaddr_in
        struct sockaddr_in local;      // lock在用户栈上开辟的空间 -> 临时变量 -> 写入内核中
        bzero(&local, sizeof(local));  // bzero
        local.sin_family = AF_INET;    // 填充协议家族,域
        local.sin_port = htons(port_); // 填充服务器对应的端口信息,一定会发给对方,_port一定会到网络中
        // local.sin_addr // 服务器都必须具有IP地址("xx.yy.zz.aaa"字符串风格点分十进制) -> 4字节IP -> uint32_t ip
        local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());

        // 2.2 绑定网络信息
        if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) == -1)
        {
            std::cerr << "Failed to bind socket." << std::endl;
            exit(2);
        }
        std::cout << "Socket bound successfully." << std::endl;
    }

private:
    uint16_t port_;  // 服务器必须得有端口号信息
    std::string ip_; // 服务器必须得有IP地址
    int sockfd_;     // 文件描述符
};

启动服务器

UDP 服务器的初始化在上面已经完成。服务器初始化完成之后,就可以启动服务器并提供服务了。服务器通常是以循环的方式运行的,以便持续接收和处理客户端请求。UDP 服务器在接收到客户端发送的数据后,可以直接读取这些数据,而无需建立连接。

recvfrom函数:
recvfrom 函数是在网络编程中使用的一个系统调用函数,用于从一个指定的套接字接收数据,并将数据存储到指定的缓冲区中。

recvfrom 函数的定义如下:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd:套接字描述符,表示从该套接字描述符索引的文件中读取数据。
  • buf:指向接收数据的缓冲区。
  • len:期望读取数据的字节数。
  • flags:可选的标志参数,用于控制接收操作的行为。常用的标志有:0、MSG_DONTWAIT。
  • src_addr:指向用于存储发送方地址信息的 struct sockaddr 结构体的指针,包括协议家族、IP地址、端口号等。
  • addrlen:src_addr 结构体的长度,及其可用空间大小,这是一个输出型参数。

当使用 recvfrom 函数接收 UDP 数据时,除了获取数据内容外,还可以获取发送方的网络属性信息,包括IP地址和端口号。调用 recvfrom 函数之前,需要将 addrlen 参数设置为接收方地址结构体的大小,以确保函数可以正确填充发送方的地址信息。

接下来使用下列函数启动服务器:

当使用 recvfrom 函数读取客户端数据后,可以将读取到的数据视为字符串,并将最后一个位置设置为 ‘\0’ ,以便于进行输出。此时,我们也获取到了客户端的IP地址和端口号。recvfrom 函数调用成功之后返回的端口号是网络序列(以大端字节序表示),我们需要调用 ntohs 函数将其转换为主机序列(与本地字节序相匹配)。同样的,获取的IP地址也需要用 inet_ntoa 函数进行转化。

class UdpServer
{
public:
    void start()
    {
        // 服务器设计的时候都是死循环
        char inbuffer[1024];  // 储存读取到的数据
        char outbuffer[1024]; // 储存发送的数据
        while (true)
        {
            struct sockaddr_in peer;      // 输出型参数
            socklen_t len = sizeof(peer); // 输入型参数

            ssize_t s = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);

            if (s > 0)
                inbuffer[s] = 0; // 将其当作字符串
            else if (s == -1)
            {
                std::cerr << "recvfrom:" << strerror(errno) << ":" << sockfd_ << endl;
                continue; // 调用失败,继续进行读取,服务器不能因为一个客户端连接失败就退出
            }
            // 读取成功,除了读取到对方的数据,还要读取到对方的网络地址[ip,port]
            std::string peerIp = inet_ntoa(peer.sin_addr);
            uint32_t peerPort = ntohs(peer.sin_port);
        }
    }
    
private:
    uint16_t port_;  // 服务器必须得有端口号信息
    std::string ip_; // 服务器必须得有IP地址
    int sockfd_;     // 文件描述符
};

在构建服务器时,我们可以引入命令行参数用于指定服务器的IP地址和端口号。使用云服务器,实际上不需要传入IP地址,直接运行程序时指定端口即可,这里可以将IP地址设置为(127.0.0.1),它代表本地主机或本地环回地址,相当于 localhost 。

以下为一个示例代码,演示如何通过命令行参数传递端口号,并将IP地址设置为本地环回地址:

static void Usage(const std::string proc)
{
    cout << "Usage:\n\t" << proc << " port [ip]" << endl;
}

// ./udpServer port [ip]
int main(int argc,char *argv[])
{
    if(argc!=2&&argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    std::string ip="127.0.0.1";
    uint16_t port=atoi(argv[1]);
    UdpServer svr(port,ip);
    svr.init();
    svr.start();
}

如下所示,运行程序并加上端口号就可以创建 udp 服务器成功了:

在这里插入图片描述

使用 netstat 命令来查看当前网络的状态,下图标出的就是当前运行的 udpServer 程序:

在这里插入图片描述

netstat 是一个用于显示网络连接、路由表和网络接口等相关信息的命令。以下是 netstat 常用的选项:

  • -a (all):显示所有的sockets(包括监听和非监听的)。
  • -t (tcp):显示 TCP 协议相关的连接信息。
  • -u (udp):显示 UDP 协议相关的连接信息。
  • -n (numeric):以数字形式显示IP地址和端口号,不进行主机名和服务名的解析。
  • -p (program):显示与每个连接关联的进程/程序的 PID 和名称。
  • -l (listening):显示每个处于监听状态的 sockets。

注意:不同操作系统上的 netstat 命令可能会不同,包括选项的名称和支持的功能。因此,在使用 netstat 命令时可以通过 netstat --help 来查看系统文档使用说明。

udp客户端

udp客户端的实现步骤如下所示,用于与服务器进行 UDP 通信:

  • 参数检查:通过检查命令行参数,来确保命令行输入的参数个数是否正确,这是为了确保程序能够正确获取服务器的IP地址和端口号,避免后续出现错误。
  • 获取服务端的IP地址和端口号:根据命令行参数获取服务器的IP地址和端口号,并将它们存储在变量 server_ip 和 server_port 中。这样客户端就获取到了需要连接服务器的地址信息。
  • 创建 UDP 套接字:使用 socket 函数创建一个 UDP 套接字,即创建一个用于网络通信的套接字。AF_INET 参数表示使用 IPv4 地址,SOCK_DGRAM 参数表示使用数据报(UDP) 套接字类型。创建套接字成功之后,会返回一个文件描述符,用于后续该套接字的操作。
  • 设置服务器地址信息:UDP 通信中,需要指定连接的服务器地址信息。这里通过填充 server 结构体来实现。bzero 函数将 server 结构体清零。sin_family 表示地址族为 IPv4 ,sin_port 表示服务器的端口号,sin_addr.s_addr 表示服务器的IP地址。
// ./udpServer server_ip server_port
// 客户端要连接服务端,需要知道server对应的IP和port
class UdpClient
{
public:
    UdpClient(std::string ip = "", uint16_t port = 8080)
        : ip_(ip), port_(port), sockfd_(-1)
    {
    }

    void init()
    {
        // 创建套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0)
        {
            std::cerr << "socket create error" << std::endl;
            exit(1);
        }
    }

    void start()
    {
        // 填写服务器对应信息
        struct sockaddr_in peer;
        bzero(&peer, sizeof peer);
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port_);
        peer.sin_addr.s_addr = inet_addr(ip_.c_str());

        std::string buffer;
        // 启动客户端
        while (true)
        {
            std::cout << "Please Enter# ";
            getline(cin, buffer);

            // 发送消息给server
            sendto(sockfd_, buffer.c_str(), buffer.size(), 0, (const struct sockaddr *)&peer, sizeof(peer));
        }
    }

    ~UdpClient()
    {
        if (sockfd_ >= 0)
            close(sockfd_);
    }

private:
    std::string ip_;
    uint16_t port_;
    int sockfd_;
};

static void Usage(const std::string name)
{
    cout << "Usage:\n\t" << name << " server_ip server_port" << endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(2);
    }

    // 根据命令行,设置需要访问的服务器IP
    std::string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);
    UdpClient client(server_ip, server_port);
    client.init();
    client.start();
    return 0;
}

sendto函数

sendto 函数用于在 UDP 通信中发送数据,它的函数定义如下:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); 

参数说明:

  • sockfd:使用 socket 函数创建的套接字描述符。

  • buf:指向要发送的数据的缓冲区的指针。数据可以是任意类型的指针,通常是一个 char 数组或字符串。

  • len:要发送的数据的长度,单位是字节。

  • flags:指定发送操作的可选标志。一般使用0或与 MSG_DONTROUTE 等特殊标志进行按位或运算。有以下常用标志:
    MSG_DONTROUTE:不使用路由表发送数据,直接发送到目的地址。
    MSG_OOB:发送外带数据。
    MSG_NOSIGNAL:在发送数据时忽略 SIGPIPE 信号,避免导致进程终止。

  • dest_addr:对端网络相关的属性信息。在 UDP 通信中,该参数指向 sockaddr 结构体的指针,其中包含了目标服务器的IP地址和端口号。

  • addrlen:传入 dest_addr 结构体的长度,通常是 sizeof(struct sockaddr_in)。

本地测试

udp 的服务端和客户端的代码都已经编写完成。因此,我们可以先在本地进行测试,使用本地环回地址(127.0.0.1)作为服务器的IP地址,客户端可以连接到该地址来与服务器进行通信。同时,我们需要确保服务器绑定的端口号与客户端连接时指定的端口号一致。

在这里插入图片描述
当客户端和服务端成功建立连接并进行通信时,可以通过使用 netstat 命令来看网络信息来确认连接的建立。

在这里插入图片描述

INADDR_ANY

INADDR_ANY 是一个IP地址,当我们不想将套接字绑定到任何特定的IP时使用。在实现通信时,我们需要将套接字绑定到IP地址。当我们不知道自己及其的IP地址或者其它情况时,可以使用特殊的IP地址 INADDR_ANY。它允许我们的服务器接收任何接口作为目标的数据包。

在进行网络测试时,你需要确保服务器能够通过公网IP地址进行访问。然而,直接将服务器绑定到公网IP可能会导致绑定失败的问题。

在这里插入图片描述

在云服务器中,你所获得的公网IP地址并不一定是真正的公网IP,而是有云服务器厂商提供的内部IP地址,这些内部IP地址无法直接在服务器代码中进行绑定。

为了让服务器能够通过公网IP进行访问,可以使用一个特殊的绑定值,即 INADDR_ANY 。这个值是系统提供的宏,对应的数值为0。通过将服务器绑定到 INADDR_ANY ,就可以让服务器接收来自任意IP地址的连接请求。

若想要我们写的服务端能够被外部网络进行访问,我们可以这样做:

  1. 在服务器代码中,将与IP地址相关的代码去掉。这样可以使服务器动态绑定到可用的IP地址。
  2. 填充 struct sockaddr_in 结构体时,将IP地址设置为 INADDR_ANY 。这样服务器就可以接收到来自任意IP地址的连接请求了。
struct sockaddr_in peer;
bzero(&peer, sizeof peer);
peer.sin_family = AF_INET;
peer.sin_port = htons(port_);
peer.sin_addr.s_addr = INADDR_ANY;

注意:INADDR_ANY 的值是0,它不需要进行网络字节序的转化。因此,在设置时无需进行大小端的处理。

在这里插入图片描述

编译并运行修改后的代码时,再次使用 netstat 命令查看网络连接情况时,该服务器的IP地址变成了 0.0.0.0 ,意味着该服务器可以接收任意主机发起的连接请求:

在这里插入图片描述

地址转换函数

这里主要介绍的是 IPv4 的 socket 网络编程。sockaddr_in 中的成员 struct in_addr sin_addr 表示32位的IP地址。但是我们通常使用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和 in_addr 表示之间进行转换:

字符串转 in_addr 的函数:

#include <arpa/inet.h>

// 将字符串cp表示的IP地址转换为in_addr结构体,并将结果存储在inp中
int inet_aton(const char *cp, struct in_addr *inp);

// 将字符串cp表示的IP地址转换为in_addr_t类型的值
in_addr_t inet_addr(const char *cp);

// 将字符串从src表示的IP地址转换为指定地址族af的二进制表示,并将转换后的结果存储在dst中
int inet_pton(int af, const char *src, void *dst);

in_addr转字符串的函数:

// 将in_addr结构体中的IP地址转换为字符串形式
char *inet_ntoa(struct in_addr in);

// 将指定地址族af的二进制表示src转化为字符串形式,并将结果存储在dst中
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr ,还可以转换 IPv6 的 in6_addr 因此函数接口是 void *addrptr 。

关于 inet_ntoa

inet_ntoa 这个函数的返回值类型是 char*,很显然这个函数在自己内部申请了一块内存来报错IP结果,那么是否需要调用者手动释放呢?

在这里插入图片描述
man 手册上说,inet_ntoa 函数,是把这个结果放在了静态存储区。这时不需要我们进行手动释放。那么,若多次调用该函数,会有什么样的效果呢?

请看代码:

#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

int main()
{
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr2.sin_addr.s_addr = 0xffffffff;
    char *ptr1 = inet_ntoa(addr1.sin_addr);
    char *ptr2 = inet_ntoa(addr2.sin_addr);
    cout << "ptr1:" << ptr1 << "  ptr2:" << ptr2 << endl;
    return 0;
}

运行结果如下所示:

在这里插入图片描述

由于 inet_ntoa 函数使用了静态缓冲区,每次调用该函数时,返回的指针都会指向同一个缓冲区。这意味着如果在多个地方同时使用了 inet_ntoa 返回值,并且后续的调用覆盖了之前的结果,那么之前获取的字符串指针将变为无效。

思考:如果有多个线程调用 inet_ntoa,是否会出现异常情况呢?

  • 在 APUE 中,明确提出了 inet_ntoa 不是线程安全的函数。因为它使用了一个全局共享的缓冲区来保存转化后的字符串。所以多线程环境下,该函数可能会导致竞争条件,从而出错。
  • 但是在 centos7 上测试,并没有出现问题,这可能是在内部加了互斥锁来保证线程安全性。
  • 在多线程环境下,推荐使用 inet_ntop 函数,它是线程安全的。inet_ntop 函数要求调用者提供一个缓冲区来存储转换后的字符串,避免了静态缓冲区的共享和竞争条件,可以规避线程安全的问题。

多线程调用 inet_ntoa 代码示例如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

void *Func1(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr1: %s\n", ptr);
    }
    return NULL;
}
void *Func2(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr2: %s\n", ptr);
    }
    return NULL;
}

int main()
{
    pthread_t tid1 = 0;
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr2.sin_addr.s_addr = 0xffffffff;
    pthread_create(&tid1, NULL, Func1, &addr1);
    pthread_t tid2 = 0;
    pthread_create(&tid2, NULL, Func2, &addr2);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

在该程序测试中并没有出现问题,但这并不意味着 inet_ntoa 函数在多线程环境中是安全的。为了确保线程安全,仍然建议使用线程安全的函数 inet_ntop 来替代 inet_ntoa 。

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

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

相关文章

【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏1(附项目源码)

文章目录 先看本次实现的最终效果前言素材一、绘制路径点和连线1. 新建Waypoint &#xff0c;绘制路径点和连线2. 绘制路径点按钮效果3. 显示路径顺序文本4. 实时修改路径点位置 二、生成敌人1. 固定生成敌人配置2. 随机生成敌人配置 三、对象池创造敌人四、控制敌人沿前面绘制…

SpringBoot3自动配置流程及原理、SpringBootApplication注解详解

参考尚硅谷课程: https://www.yuque.com/leifengyang/springboot3/vznmdeb4kgn90vrx https://www.yuque.com/leifengyang/springboot3/lliphvul8b19pqxp 1.自动配置流程及原理 核心流程总结: 1.导入starter&#xff0c;就会导入autoconfigure包 2.autoconfigure 包里面 有一个…

Leetcode hot100之“结合递归+二分“题目详解

1 总结 题目 215 (“数组中的第 K 个最大元素”) 和题目 4 (“寻找两个正序数组的中位数”) 之间的联系主要体现在它们都涉及到寻找一个有序集合中的第 k 个元素的问题。尽管这两个问题的具体应用场景和所处理的数据结构不同&#xff0c;它们共享相似的算法思想和技术。 题目…

在3+1的方向上展开结构加法4a3+4a14

4a3 4a14 - - 1 - - - - - - - - - - - - - 1 1 1 - 1 1 - 1 - - 1 - - - 要求得到的图片只能有4个点&#xff0c;并且需要最大限度的保留4a3和4a14两张图片的内在结构特征。 4个点的结构总可以认为是3个点的结构1合成的 - - 1 - - …

[PyTorch][chapter 63][强化学习-时序差分学习]

目录&#xff1a; 蒙特卡罗强化学习的问题 基于转移的策略评估 时序差分评估 Sarsa-算法 Q-学习算法 一 蒙特卡罗强化学习的的问题 有模型学习&#xff1a; Bellman 等式 免模型学习: 蒙特卡罗强化学习 迭代&#xff1a; 使用策略 生成一个轨迹&#xff0c; for t…

【网络奇遇记】我和因特网的初相遇2 —— 三种交换方式

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 前言一. 电路交换1.1 电路交换讲解1.2 电路交换实例 二. 分组交换1.1 分组交换讲解1.2 分组交换实例…

损失函数——KL散度(Kullback-Leibler Divergence,KL Divergence)

KL散度&#xff08;Kullback-Leibler Divergence&#xff0c;简称KL散度&#xff09;是一种度量两个概率分布之间差异的指标&#xff0c;也被称为相对熵&#xff08;Relative Entropy&#xff09;。KL散度被广泛应用于信息论、统计学、机器学习和数据科学等领域。 KL散度衡量的…

基于Java+SpringBoot制作一个智能用电小程序

在当今快节奏的生活中,高效利用能源变得越来越重要。制作一个智能用电小程序,旨在帮助您更智能地管理家庭电器的用电,从而提升能源利用效率,助您掌握用电情况,降低能耗成本,实现绿色低碳生活。 目录 一、小程序1.1 项目创建1.2 首页轮播图快捷导航iconfont图标引入

整理笔记——MOS管、三极管、IGBT

一、MOS管 在实际生活要控制点亮一个灯&#xff0c;例如家里的照明能&#xff0c;灯和电源之间就需要一个开关需要人为的打开和关闭。 再设计电路板时&#xff0c;如果要使用MCU来控制一个灯的开关&#xff0c;通常会用mos管或是三极管来做这个开关元件。这样就可以通过MCU的信…

kafka个人笔记

大部分内容源于https://segmentfault.com/a/1190000038173886, 本人手敲一边加强印象方便复习 消息系统的作用 解耦 冗余 扩展性 灵活性&#xff08;峰值处理 可恢复 顺序保证 缓冲 异步 解耦&#xff1a;扩展两边处理过程&#xff0c;只需要让他们遵守约束即可冗余&#xf…

网络层协议 ——— IP协议

文章目录 IP协议基本概念IP协议格式分片与组装网段划分特殊的IP地址IP地址的数量限制私网IP地址和公网IP地址路由路由表生成算法 IP协议 IP协议全称为“网际互连协议&#xff08;Internet Protocol&#xff09;”&#xff0c;IP协议是TCP/IP体系中的网络层协议。 基本概念 网…

只使用JS怎么给静态页面网站添加站内全局搜索功能?

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 背景 静态页面通常由HTML、CSS 和 JavaScript…

荣誉榜再度添彩!热烈祝贺旭帆科技荣获安徽省大数据企业!

2023年11月3日&#xff0c;安徽省数据资源管理局网站发布《关于2023年度安徽省大数据企业名单的公示》&#xff0c;经企业申报、各市初审推荐、专家评审、审查认定等程序&#xff0c;安徽旭帆信息科技有限公司&#xff08;以下简称“旭帆科技”&#xff09;凭借在视频大数据应用…

【Unity地编】地形系统搭建入门详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;UI_…

系列十、堆参数调优

一、堆内存调优参数 -Xms堆空间的最小值&#xff0c;默认为物理内存的1/64-Xmx堆空间的最大值&#xff0c;默认为物理内存的1/4-XX:PrintGCDetails输出详细的GC处理日志 二、获取堆内存的默认物理内存 /*** Author : 一叶浮萍归大海* Date: 2023/11/16 14:50* Description: 获…

在Linux上安装Oracle 数据库 11g

好久没碰11g了&#xff0c;今天&#xff08;2023年11月16日&#xff09;因为有个需求又装了一遍。 在OCI上安装了一个Oracle Linux 6实例&#xff1a; $ uname -a Linux instance-20231116-1239-db11g 4.1.12-124.80.1.el6uek.x86_64 #2 SMP Mon Oct 9 02:32:10 PDT 2023 x86…

ATE测试设备功能、原理、特点详解

ATE(Automatic Test Equipment)自动测试设备是用于检测电子产品、电气设备的自动化测试系统&#xff0c;是电测行业首选的一种测试方式&#xff0c;被广泛应用于通信、消费电子、汽车电子、智能家居、半导体、电源模块、医疗电子、航天航空等领域。ATE测试设备在电子设计、研发…

【自动化测试】基于Selenium + Python的web自动化框架!

一、什么是Selenium&#xff1f; Selenium是一个基于浏览器的自动化工具&#xff0c;她提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分&#xff1a;Selenium IDE、Selenium WebDriver 和Selenium Grid&#xff1a;  1、Selenium IDE&…

网站使用什么协议比较好

网站协议大多数使用HTTP和HTTPS HTTP协议&#xff0c;超文本传输协议&#xff08;Hypertext Transfer Protocol&#xff0c;HTTP&#xff09;是一个简单的请求-响应协议。 HTTP是应用层协议&#xff0c;同其他应用层协议一样&#xff0c;是为了实现某一类具体应用的协议&…

前台页面从数据库中获取下拉框值

后端&#xff1a;查询所有信息 前台&#xff1a;elementUI <el-select v-model"searchData.stationName" clearable> <el-option :label"item.stationName" :value"item.stationName" v-for"item in stationNameList&quo…