【Linux】多路转接 -- select函数

news2024/9/23 23:34:21

文章目录

  • 1. 认识select函数
  • 2. select函数原型
  • 3. socket就绪条件
  • 4. select工作流程
  • 5. select服务器
  • 6. select的优缺点

首先我们要了解一下,什么是多路转接?

多路转接也叫多路复用,是一种用于管理多个IO通道的技术。它能实现同时监听和处理多个IO事件,而不是为每个IO通道创建单独的线程或者进程,多路转接允许在单个进程或线程中同时处理多个IO操作,从而提高程序的性能和效率。

本篇文章介绍的select函数,就用于select系统调用的多路转接技术。

1. 认识select函数

select函数是系统提供的一个多路转接接口。

IO = 等待就绪 + 数据拷贝,而select是只负责等。

  • select系统调用可以让我们的程序同时监听多个文件描述符上的事件是否就绪。
  • select的核心工作就是等,当监听的多个文件描述符中有一个或多个事件就绪时,select函数才会成功返回并将对应文件描述符的就绪事件告知调用者。

2. select函数原型

在这里插入图片描述
参数说明:

  • nfds:需要监听的文件描述符中,最大的文件描述符值 + 1。
  • readfs:输入输出型参数,调用时用户告知内核需要监听哪些文件描述符的读事件是否就绪,返回时内核告诉用户哪些文件描述符的读事件已经就绪。
  • writefds:输入输出型参数,调用时用户告知内核需要监听哪些文件描述符的写事件是否就绪,返回时内核告知用户哪些文件描述符的写事件已经就绪。
  • exceptfds:输入输出型参数,调用时告知内核需要监听哪些文件描述符的异常事件是否就绪,返回时内核告知用户哪些文件描述符的异常事件已经就绪。
  • timeout:输入输出参数,调用时由用户设置select的等待时间,返回时表示timeout的剩余时间。

参数timeout的取值:

  • NULL/nullptr:select调用后进行阻塞等待,直到被监视的某个文件描述符上的事件就绪。
  • 0:select调用后进行非阻塞等待,无论被监视的文件描述符的事件是否就绪,select检测后都会立即返回。
  • 特定的时间值:select调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后select进行超时返回。

返回值说明:

  • 如果函数调用成功,则返回有事件就绪的文件描述符个数。
  • 如果timeout时间耗尽,则返回0。
  • 如果函数调用失败,则返回-1,同时错误码会被设置。

select调用失败,错误码可能被设置为:

  • EBADF:文件描述符有无效的或者该文件已关闭
  • EINTR:此调用被信号所中断
  • EINVAL:参数nfds为负值
  • ENOMEM:核心内存不足

fd_set 结构

fd_set 结构与 sigset_t 结构类似,fd_set 本质也是一个位图,用位图中对应的位来表示要监听的文件描述符。

调用select函数之前就需要用fd_set结构定义出对应的文件描述符集,然后将需要监视的文件描述符添加到文件描述符集当中,这个添加的过程本质就是在进行位操作,但是这个位操作不需要用户自己进行,系统专门提供了一组专门的接口,用户对fd_set位图进行各种操作。
在这里插入图片描述

timeval 结构

传入select函数的最后一个参数timeout,就是一个指向timeval结构的指针,timeval结构用于描述一段时间长度,该结构当中包含两个成员,其中tv_sec表示的是秒,tv_usec表示的是微妙。
在这里插入图片描述

3. socket就绪条件

读就绪

  • socket内核中,接收缓冲区中的字节数,大于等于低水位标记物SO_RCVLOWAT,此时可以无阻塞地读取该文件描述符,并且返回值大于0。
  • socket TCP通信中,对端连接关闭,此时对该socket读,则返回0。
  • 监听的socket上有新的连接请求。
  • socket上有未处理的错误。

写就绪

  • socket内核中,发送缓冲区中的可用字节数,大于等于低水位标记SO_SNDLOWAT,此时可以无阻塞地写,并且返回值大于0。
  • socket的写操作被关闭(close或者shutdown),对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号。
  • socket使用非阻塞connect连接成功或失败之后。
  • socket上有未读取的错误。

异常就绪

  • socket上收到带外数据

4. select工作流程

这里我们只介绍select处理读取的操作。

如果我们要实现一个简单的select服务器,该服务器要做的就是读取客户端发来的数据并进行打印,那么这个select服务器的工作流程应该是这样的:

  • 先初始化服务器,完成套接字的创建、绑定和监听。
  • 定义一个fd_array数组用于保存监听套接字和已经与客户端建立连接的套接字,刚开始时就将监听套接字添加到fd_array数组当中。
  • 然后服务器开始循环调用select函数,检测读事件是否就绪,如果就绪则执行对应的操作。
  • 每次调用select函数之前,都需要定义一个读文件描述符集readfds,并将fd_array当中的文件描述符依此设置进readfds当中,表示让select帮我们监视这些文件描述符的读事件是否就绪。
  • 当select检测到数据就绪时会读事件就绪的文件描述符设置进readfds当中,此时我们能够得知哪些文件描述符的读事件就绪了,并对这些文件描述符进行对应的操作。
  • 如果读事件就绪的就是监听套接字,则调用accept函数从底层全连接队列获取已经建立好的连接,并将该连接对应的套接字添加到fd_array数组当中。
  • 如果读事件就绪的是与客户端建议连接的套接字,则调用read函数读取客户端发来的数据并进行打印输出。
  • 当然,服务器与客户端建立连接的套接字读事件就绪,也可能是因为客户端将连接关闭了,此时服务器应该调用close关闭套接字,并将该套接字从fd_array中清除,因为下一次不需要再监视该文件描述符的读事件了。

注意:

  • 因为传入的select函数的readfds、writefds和exceptfds都是输入输出型参数,当select函数返回时这些参数当中的值已经被修改了,因此每次调用seletct函数时都需要对其进行重新设置,timeout也是类似的道理。
  • 因为每次调用select函数之前都需要对readfds进行重新设置,所以需要定义一个fd_array数组保存与客户端已经建立的若干连接和监听套接字,实际fd_array数组当中的文件描述符就是需要让select监视读事件的文件描述符。
  • 我们的select服务器只是读取客户端发来的数据,因此只需让select帮我们监视特定文件描述符的读事件,如果同时让select帮我们监视特定文件描述符的读事件和写事件,则需要分别定义readfds和writefds,并定义两个数组分别保存需要被监视读事件和写事件的文件描述符,便于每次调用select函数前对readfds和writefds进行重新设置。
  • 服务器刚开始运行时,fd_array数组当中只有监听套接字,因此select第一次调用时只需要监视监听套接字的读事件是否就绪,但每次调用accept获取到新连接之后,都会将连接对应的套接字添加到fd_array当中,因此后续select调用时就需要监视监听套接字和若干连接套接字的读事件是否就绪。
  • 由于调用select时还需要传入被监视的文件描述符中最大文件描述符值+1,因此每次在遍历fd_array对readfds进行重新设置时,还需要记录最大文件描述符的值。

5. select服务器

Socket类

我们编写一个Socket类,对套接字相关的接口进行一定程序的封装,为了让外部能够直接调用Socket类当中的函数,我们将这些成员函数定义成静态成员函数。

#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>

class Socket
{
public:
    // 创建套接字
    static int SocketCreate()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0)
        {
            std::cerr << "socket error" << std::endl;
            exit(2);
        }

        // 设置端口复用
        int opt = 1;
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        return sock;
    }

    // 绑定
    static void SocketBind(int sock, int port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        socklen_t len = sizeof(local);

        if (bind(sock, (struct sockaddr*)&local, len) < 0)
        {
            std::cerr << "bind error" << std::endl;
            exit(3);
        }
    }

    // 监听
    static void SocketListen(int sock, int backlog)
    {
        if (listen(sock, backlog) < 0)
        {
            std::cerr << "listen error" << std::endl;
            exit(4);
        }
    }
};

SelectServer类

编写SelectServer类,因为我当前使用的是云服务器,所以编写的select服务器在绑定时只需将IP地址设置为INADDR_ANY即可,所以类中只包含监听套接字和端口号两个成员变量即可。

  • 在构造SelectServer对象时,需要指明select服务器的端口号,当然也可以在初始化select服务器的时候指明。
  • 在初始化select服务器的时候需要调用Socket类当中的函数,依此进行套接字的创建、绑定和监听即可。
  • 在析构函数中可以选择调用close函数将监听套接字进行关闭,但实际也可以不进行该动作,因为服务器运行后一般是不退出的。
#pragma once

#include "Socket.hpp"
#include <sys/select.h>

#define BACK_LOG 5

class SelectServer
{
public:
    SelectServer(int port)
        : _port(port)
    {}

    void InitSelectServer()
    {
        _listen_sock = Socket::SocketCreate();
        Socket::SocketBind(_listen_sock, _port);
        Socket::SocketListen(_listen_sock, BACK_LOG);
    }

    ~SelectServer()
    {
        if (_listen_sock >= 0) close(_listen_sock);
    }

private:
    int _listen_sock;
    int _port;
};

运行服务器

服务器初始化完毕之后就可以周期性地执行某种动作了,而select服务器要做的就是不断调用select函数,当事件就绪对应执行某种动作即可。

  • 首先,在select服务器开始死循环调用select函数之前,需要先定义一个fd_array数组,先把数组中所有的位置初始化为无效,并将监听套接字添加到该数组当中,fd_array数组当中保存的就是需要被select监视读事件是否就绪的文件描述符。
  • 此后,select服务器就不断调用select函数监视读事件是否就绪,每次调用select函数之前都需要重新设置readfds,具体设置过程就是遍历fd_array数组,将fd_array数组当中的文件描述符添加到readfds当中,并同时记录最大的文件描述符maxfd。
  • 当select函数返回后,如果返回值为0,则说明timeout时间耗尽,此时直接准备下一次select调用即可。如果select的返回值为-1,则说明select调用失败,此时也让服务器准备下一次select调用,但实际应该进一步判断错误码,根据错误码来判断是否应该继续调用select函数。
  • 如果select函数的返回值大于0,则说明select函数调用成功,此时已经有文件描述符的读事件就绪,接下来就应该对就绪事件进行处理。
#pragma once

#include "Socket.hpp"
#include <sys/select.h>

#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1

class SelectServer
{
public:
    SelectServer(int port)
        : _port(port)
    {}

    void InitSelectServer()
    {
        _listen_sock = Socket::SocketCreate();
        Socket::SocketBind(_listen_sock, _port);
        Socket::SocketListen(_listen_sock, BACK_LOG);
    }

    ~SelectServer()
    {
        if (_listen_sock >= 0) close(_listen_sock);
    }

    void Run()
    {
        fd_set readfds; // 创建读文件描述符集
        int fd_array[NUM]; // 保存需要被监视读事件是否就绪的文件描述符
        ClearFdArray(fd_array, NUM, DFL_FD); // 将数组中的所有位置设置为无效
        fd_array[0] = _listen_sock; // 将监听套接字添加到fd_array数组中的第0个位置
        while (1)
        {
            FD_ZERO(&readfds); // 清空readfds
            // 将fd_array数组当中的文件描述符添加到readfds中,并记录最大的文件描述符
            int maxfd = DFL_FD;
            for (int i = 0; i < NUM; ++i)
            {
                if (fd_array[i] == DFL_FD) continue; // 跳过无效的位置
                FD_SET(fd_array[i], &readfds);  // 将有效位置的文件描述符添加到readfds中
                if (fd_array[i] > maxfd) maxfd = fd_array[i]; // 更新最大文件描述符
            }

            switch (select(maxfd + 1, &readfds, nullptr, nullptr, nullptr))
            {
                case 0:
                    std::cout << "timeout..." << std::endl;
                    break;
                case -1:
                    std::cerr << "select error" << std::endl;
                    break;
                default:
                    std::cout << "有事件发生..." << std::endl;
                    break; 
            }
        }

    }

private:
    void ClearFdArray(int fd_array[], int num, int default_fd)
    {
        for (int i = 0; i < num; ++i) fd_array[i] = default_fd;
    }

    int _listen_sock;
    int _port;
};

启动服务器

#include "SelectServer.hpp"
#include <string>

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << "SelectServer" << " port" << std::endl;
        exit(1);
    }
    int port = atoi(argv[1]);

    SelectServer* svr = new SelectServer(port);
    svr->InitSelectServer();
    svr->Run();

    return 0;
}

由于当前服务器调用select函数时直接将timeout设置为了nullptr,因此select函数调用后会进行阻塞等待。而服务器在第一次调用select函数时只让select函数监视监听套接字的读事件,所以运行服务器之后如果没有客户端发来连接请求,那么读事件就不会就绪,而服务器会一直在第一次调用的select函数中进行阻塞等待。
在这里插入图片描述
当我们借助telnet工具向select服务器发起连接请求之后,select函数就会立马检测到监听套接字的读事件就绪,此时select函数便会返回成功,并将我们设置的提示语句进行打印输出,因为当前程序没有对就绪事件进行处理,此后每次select函数一调用就会检测到读事件就绪成功返回,因此屏幕不但打印输出提示语句。
在这里插入图片描述
如果服务器在调用select函数时将timeout的值设置为0,那么select函数调用后就会进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,select检测后都会立即返回。

此时如果select监视的文件描述符上有事件就绪,那么select函数的返回值就是大于0的,如果select函数监视的文件描述符上没有事件就绪,那么select的返回值就是小于0的,这里也就不进行演示了。

事件处理

当select检测到右文件描述符的读事件就绪并成功返回后,接下来就应该对就绪事件进行处理了,这里编写一个HandleEvent函数,当读事件就绪之后就调用该函数进行事件处理。

  • 在进行事件处理时需要遍历fd_array数组当中的文件描述符,以此判断各个文件描述符对应的读事件是否就绪,如果就绪则需要进行事件处理。
  • 当一个文件描述符的读事件就绪之后,还需要进一步判断该文件描述符是否是监听套接字,如果是监听套接字的读事件就绪,那么就应该调用accept函数将底层的连接获取上来。但是只调用accept函数将连接获取上来还不够,为了下一次调用select函数时能够让select帮我们监视新连接的事件是否就绪,在连接获取上来后还应该将连接对应的文件描述符添加到fd_array数组当中,这样在下一次调用select函数前对readfds重新设置时就能将该文件描述符添加进去了。
  • 如果是客户端建立的连接对应的读事件就绪,那么就应该调用read函数读取客户端发来的连接,如果读取成功则将读到的数据在服务端进行打印。如果调用read函数读取失败或者客户端关闭了连接,那么select服务器也应该调用close函数关闭对应的连接,但此时只关闭连接也是不够的,还应该将该连接对应的文件描述符从fd_array数组当中清除,否则后续调用的select函数还会帮我们监视该连接的读事件是否就绪,但实际已经不需要了。
    void HandleEvent(const fd_set& readfds, int fd_array[], int num)
    {
        for (int i = 0; i < num; ++i)
        {
            // 跳过无效位置
            if (fd_array[i] == DFL_FD) continue;
                
            // 连接事件就绪
            if (fd_array[i] == _listen_sock && FD_ISSET(fd_array[i], &readfds))
            {
                // 获取连接
                struct sockaddr_in peer;
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
                if (sock < 0)
                {
                    std::cerr << "accept error" << std::endl;
                    continue;
                }
                std::string peer_ip = inet_ntoa(peer.sin_addr);
                int peer_port = ntohs(peer.sin_port);
                std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;
                // 将获取到的文件描述符添加到fd_array中
                if (!SetFdArray(fd_array, num, sock))
                {
                    // 如果添加失败,关闭文件描述符
                    close(sock);
                    std::cout << "select server is full, close fd: " << sock << std::endl;
                }
            }
        }
    }

private:
    bool SetFdArray(int fd_array[], int num, int fd)
    {
        for (int i = 0; i < num; ++i)
        {
            if (fd_array[i] == DFL_FD) 
            {
                fd_array[i] = fd;
                return true;
            }
        }
        return false;
    }

添加文件描述符到fd_array数组中,本质就是遍历fd_array数组,找到一个没有被使用的位置将该文件描述符添加进去即可。但有可能fd_array数组中全部的位置都已经被占用了,那么文件描述符就会添加失败,此时就只能将刚刚获取上来的连接对应的套接字进行关闭,因为此时服务器是没有能力处理这个连接的。

该select服务器存在的一些问题。

  • 服务器没有对客户端进行响应,select服务器如果要向客户端发送数据,不能直接调用write函数,因为调用write函数时实际也为了“等”和“拷贝”两步,我们也应该将“等”的这个过程交给select函数,因此在每次调用select函数之前,除了需要重新设置readfds之外还需要重新设置writefds,并且还需要一个数组来保存需要被监视事件是否就绪的文件描述符,当某一文件描述符的写事件就绪时我们才能够调用write函数向客户端发送数据。
  • 没有定制协议,代码中读取数据时并没有按照某种规则进行读取,此时就可能造成粘包问题。比如HTTP协议规定在读取底层数据时读取到空行就表明读完了一个HTTP报头,此时再根据HTTP报头当中的Content-Length属性得知正文的长度,最终就能读取到一个完整的HTTP报文,HTTP协议通过这种方式就避免了粘包问题。
  • 没有对应的输入输出缓冲区,代码中直接将读取的数据存储到了字符数组buffer中,这是不严谨的,因为本地数据读取可能并没有读取到一个完整的报文,此时服务器就不能进行数据的分析处理,一个将读取到的数据存储到一个输入缓冲区中,当读取到一个完整的报文之后再让服务器进行处理。此外,如果服务器能够对客户端进行响应,那么服务器的响应数据也不应该直接调用write函数发送给客户端,应该先存储到一个输出缓冲区当中,因为响应数据可能很庞大,无法一次发送完毕,可能需要进行分批发送。

6. select的优缺点

select的优点

  • 可以同时等待多个文件描述符,并且只负责等待,实际的IO操作由accept、read、write等接口完成,这些接口在进行IO操作时不会被阻塞。
  • select同时等待多个文件描述符,因此可以将“读”的时间重叠,提高了IO的效率。

当然,这也是所有多路转接接口的优点。

select的缺点

  • 每次调用select,都需要手动设置fd集合,从接口使用角度来看并不方便。
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
  • 同时每次调用select时都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
  • select可监控的文件描述符数量太少。

select可监控的文件描述符有1024个,除去其中的一个监听套接字,那么它最多只能连接1023个客户端。

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

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

相关文章

C++ 访问控制——公有继承、私有继承、保护继承

派生类继承了基类的全部数据成员和除了构造函数和析构函数之外的全部函数成员&#xff0c;但是这些成员的访问属性在派生的过程中是可以调整的。从基类继承的成员&#xff0c;其访问属性由继承方式控制。 基类的成员有public&#xff08;公有&#xff09;、protected&#xff…

ArduPilot开源代码之Companion Computers简单分析

ArduPilot开源代码之Companion Computers简单分析 1. 源由2. 伴机系统2.1 APSync2.2 DroneKit2.3 FlytOS2.4 Maverick2.5 ROS2.6 Rpanion-server 3. 总结4. 参考资料 1. 源由 从稳定性&#xff0c;社区群体&#xff0c;以及开源方式的角度看&#xff0c;Ardupilot是不错的选择…

骑砍二 ATC MOD 使用教程与应用案例解析

骑砍二 ATC MOD 使用教程与应用案例解析 作者&#xff1a;blibli-财不外漏 / NEXUSMODS-PuepleKarmen 案例MOD依赖&#xff1a;ATC - Adonnay’s Troop Changer & AEW - Adonnay’s Exotic Weaponry & New Armor 文本编辑工具&#xff1a;VS Code&#xff08;推荐使用&…

【小沐学NLP】在线AI绘画网站(百度:文心一格)

文章目录 1、简介2、文心一格2.1 功能简介2.2 操作步骤2.3 使用费用2.4 若干示例2.4.1 女孩2.4.2 昙花2.4.3 山水画2.4.4 夜晚2.4.5 古诗2.4.6 二次元2.4.7 帅哥 结语 1、简介 当下&#xff0c;越来越多AI领域前沿技术争相落地&#xff0c;逐步释放出极大的产业价值&#xff0…

Amazon CloudFront 部署小指南(四)- CloudFront Function 基础与诊断

内容简介 本文适用于希望使用 Amazon CloudFront Functions 提升 Amazon CloudFront 边缘计算能力的用户&#xff0c;旨在帮助您更好的进行 CloudFront Functions 的开发、调试、测试、部署等工作。 首先我们会对 CloudFront Function 做个简单的介绍&#xff0c;然后分为七个步…

全志F1C200S嵌入式驱动开发(应用程序开发)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 我们在开发soc驱动的时候,很多情况下也要验证下当前的驱动功能是否正确。当然除了验证驱动功能之外,我们还要编写业务代码和流程代码。这中间就和各行各业有关了,有的是算法,有…

Redis BigKey案例

面试题&#xff1a; 阿里广告平台&#xff0c;海量数据里查询某一固定前缀的key小红书&#xff0c;你如何生产上限制keys*/flushdb/flushall等危险命令以防止误删误用&#xff1f;美团&#xff0c;MEMORY USAGE命令你用过吗&#xff1f;BigKey问题&#xff0c;多大算big&#…

GODOT游戏引擎简介,包含与unity性能对比测试,以及选型建议

GODOT&#xff0c;是一个免费开源的3D引擎。本文以unity作对比&#xff0c;简述两者区别和选型建议。由于是很久以前写的ppt&#xff0c;技术原因视频和部分章节丢失了。建议当做业务参考。 GODOT目前为止遇到3个比较重大的基于&#xff0c;第一个是oprea的合作奖&#xff0c;…

【redis】redis的认识和安装

目录 1.redis是什么2.Redis的特点3.安装redis4.设置远程连接4.1 开启隧道4.2 可视化客户端连接4.3 开启防火墙 5.redis常见数据类型5.1 redis的一些全局命令5.2 数据结构 6. redis的典型应用---缓存&#xff08;cache&#xff09;6.1 使用redis做缓存6.2 缓存穿透&#xff0c;缓…

【绪论0】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 知识框架No.0 引言No.1 操作系统的概念功能和定义一、操作系统的概念和定义1、电脑的演变 二、操作系统的功能和目标 No.2 操作系统的特征一、并发二、共享三、虚拟四、异步 No.3 操作系统的发展与分类一、手工操作阶段二、批处理阶段…

Windows11 家庭中文版关于本地组策略编辑器gpedit.msc找不到即打不开的解决办法(征诚小张售后实测有效)

Windows11 家庭中文版关于本地组策略编辑器gpedit.msc找不到即打不开的解决办法 根本原因&#xff1a;是因为Windows11家庭中文版的 版本系统没内置安装本地策略组编辑器 好了废话不多说 直接说解决办法 第一步 首先电脑上新建一个空文本文件 输入以下内容&#xff1a; echo o…

Android Studio安装AI编程助手Github Copilot

csdn原创谢绝转载 简介 文档链接 https://docs.github.com/en/copilot/getting-started-with-github-copilot 它是个很牛B的编程辅助工具&#xff0c;装它&#xff0c;快装它&#xff0e; 支持以下IDE: IntelliJ IDEA (Ultimate, Community, Educational)Android StudioAppC…

Qt应用开发(基础篇)——时间类 QDateTime、QDate、QTime

一、前言 时间类QDateTime、QDate、QTime、QTimeZone保存了Qt的时间、日期、时区信息&#xff0c;常用的时间类部件都会用到这些数据结构&#xff0c;常用概念有年、月、日、时、分、秒、毫秒和时区&#xff0c;时间和时区就关系到时间戳和UTC的概念。 UTC时间&#xff0c;又称…

FPGA初步学习之串口发送模块【单字节和字符串的发送】

串口相关简介 UART 在发送或接收过程中的一帧数据由4部分组成&#xff0c;起始位、数据位、奇偶校验位和停止位&#xff0c;如图所示。其中&#xff0c;起始位标志着一帧数据的开始&#xff0c;停止位标志着一帧数据的结束&#xff0c;数据位是一帧数据中的有效数据。 通常用…

【贪心算法】leetcode刷题

贪心算法无固定套路。 核心思想&#xff1a;先找局部最优&#xff0c;再扩展到全局最优。 455.分发饼干 两种思路&#xff1a; 1、从大到小。局部最优就是大饼干喂给胃口大的&#xff0c;充分利用饼干尺寸喂饱一个&#xff0c;全局最优就是喂饱尽可能多的小孩。先遍历的胃口&a…

Win11大小写切换图标关闭方法

大家使用Win11操作系统的时候经常会切换大小写键盘&#xff0c;有些游戏本在游戏过程中需要切换大小写&#xff0c;这个时候电脑的屏幕就会出现大小写切换的图标而影响游戏体验&#xff1b; 那么想要关闭Win11电脑上大小写切换图标&#xff0c;又不知道具体怎么操作&#xff0c…

VS Code search tab

Vs Code search 栏的应用 我发现&#xff0c;在vs code种&#xff0c;上面的搜索框的功能非常多。在最初使用vscode时候&#xff0c;以为这只是一个普通的搜索框。后来&#xff0c;发现它可以用于全局搜索文件&#xff0c;比如使用ctrlshiftp。 后来&#xff0c;我发现&#xf…

lifecycleScope Unresolved reference

描述 导入了lifecycle.lifecycleScope&#xff0c;但是在activity中使用lifecycleScope报错出现Unresolved reference找不到引用。 导包 import androidx.lifecycle.lifecycleScope使用 lifecycleScope.launch(Dispatchers.IO) {...}错误 方案 代码中的activity继承Activ…

SSM(Vue3+ElementPlus+Axios+SSM前后端分离)--功能实现【四】

文章目录 SSM--功能实现实现功能06-修改家居信息需求分析/图解思路分析代码实现注意事项和细节 实现功能07-删除家居信息需求分析/图解思路分析代码实现 实现功能08-分页显示列表需求分析/图解思路分析代码实现完成测试分页显示效果 SSM–功能实现 实现功能06-修改家居信息 需…

39.利用matlab寻找素数(matlab程序)

1.简述 MATLAB嵌套循环允许使用一个循环在另一循环内&#xff0c;下面用一个嵌套循环来把所有从1到100的素数显示出来。 2.代码 %% 学习目标&#xff1a;寻找素数 clear sum5; %求0&#xff5e;100素数之和 ss0; %用来标定是否是素数&#xff0c;0表示不是 p…