【Linux】高级IO(一)

news2024/11/15 15:43:37

文章目录

  • 高级IO
    • 阻塞IO模型
    • 非阻塞IO模型
    • 多路转接IO
    • select简介
    • socket 就绪条件
    • select服务器
    • select的优缺点
    • 多路转接的使用场景

高级IO

非阻塞IO,记录锁,系统V流机制,I/O多路转接(I/O)多路复用,readvwritev函数以及存储映射IO(mmap),这些统称为高级IO

阻塞IO模型

#include <iostream>
#include <unistd.h>
#include <fcntl.h>

int main() {
  char buffer[1024];
  while (true) {
    ssize_t size = read(0, buffer, sizeof(buffer) - 1);
    if (size < 0) {
      std::cerr << "read error" << std::endl;
      break;
    }
    buffer[size - 1] = 0;
    std::cout << "Echo # " << buffer << std::endl; 
  }
  return 0;
}

非阻塞IO模型

打开文件时默认都是以阻塞的方式打开的,如果想要非阻塞的方式打开某个文件,需要在使用open()函数打开文件时携带O_NONBLOCKO_NDELAY选项,此时就能够以非阻塞方式打开文件,这是在打开文件时设置非阻塞的方式,如果要修改一个已经打开文件的属性,此时就要用到fcntl函数

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

fcntl 函数

int fcntl(int fd, int cmd, ... /* arg */ );
  • fd: 已经打开的文件描述符
  • cmd : 命令,需要进行的操作
  • … : 可变参数,传入的cmd不同,后面的追加参数也就不同
  • 调用成功,其返回值取决于具体操作。失败返回-1,同时设置错误码

fcntl 函数常用的五种功能和对应的cmd取值如下

  • F_DUPFD 复制一个现有描述符

  • F_GETFD 或 F_SETFD 获得/设置文件描述符标记

  • F_GETFL 或 F_SETFL 获得/设置文件状态标记

  • F_GETOWN 或 F_SETOWN 获得/设置异步I/O所有权

  • F_GETLK , F_SETLK, F_SETLKW 获得/设置记录锁

实现SetNonBlock函数

定义一个函数,将该函数指定的文件描述符设置成非阻塞状态

  • 首先使用fcntl函数获取该文件描述符的状态标记(int 类型的位图),cmd = F_GETFL
  • 获取到的文件状态标记上添加非阻塞标记O_NONBLOCK,再次调用fcntl函数对文件状态标记进行设置,cmd = F_SETFL
#pragma once
#include <iostream>
#include <unistd.h>
#include <fcntl.h>


bool SetNonBlock(int fd) {
  int fl = fcntl(fd, F_GETFL);
  if (fl < 0) {
    std::cerr << "fcntl error" << std::endl;
    return false;
  }
  fcntl(fd, F_SETFL, fl | O_NONBLOCK);
  return true;
}


以非阻塞轮询方式读取标准输入

#include "tool.hpp"
#include <memory.h>

int main() {
  SetNonBlock(0);  									 // 将标准输入设置成非阻塞读取
#define BUFFER_SIZE 128
  char buffer[BUFFER_SIZE];
  memset(buffer, 0, sizeof(buffer));
  while (true) {
    ssize_t size = read(0, buffer, sizeof(buffer) - 1);
    if (size < 0) {
      if (errno == EAGAIN || errno == EWOULDBLOCK) {  // 底层没有数据就绪
        std::cout << strerror(errno) << std::endl;
        sleep(3);
        continue;
      } else if (errno == EINTR) {                    // 在读取数据前被信号中断
        std::cout << strerror(errno) << std::endl;
        sleep(3);
        continue;
      } else {
        std::cerr << "read error" << std::endl;
        break;
      }
    }
    buffer[size - 1] = 0; 
    std::cout << "echo # " << buffer << std::endl;
  }
  return 0;
}

需要注意的是,当read函数以非阻塞方式读取标准输入时,如果底层数据不就序,那么read函数就会立即返回,当底层数据不就序时,read函数时就会立即返回,此时错误码被设置成EAGAINEWOULDBLOCK

此外,调用read()函数读取数据之前可能会收到其它信号中断,此时read函数也会以出错方式返回,错误码被设置成EINTR,此时应该重新执行read函数对数据进行读取

因此在使用非阻塞方式读取数据时,需要对read函数进行进一步分类,如果返回值为-1,可能存在没有数据可读,信号中断,真的出错了三种情况,应该对错误码进行进一步判断

#include "tool.hpp"
#include <memory.h>


int main() {
  SetNonBlock(0);   // 将标准输入设置成非阻塞读取
#define BUFFER_SIZE 128
  char buffer[BUFFER_SIZE];
  memset(buffer, 0, sizeof(buffer));
  while (true) {
    ssize_t size = read(0, buffer, sizeof(buffer) - 1);
    if (size < 0) {
      if (errno == EAGAIN || errno == EWOULDBLOCK) {  // 底层没有数据就绪
        std::cout << strerror(errno) << std::endl;
        sleep(3);
        continue;
      } else if (errno == EINTR) {                    // 在读取数据前被信号中断
        std::cout << strerror(errno) << std::endl;
        sleep(3);
        continue;
      } else {										  // 真的出错了
        std::cerr << "read error" << std::endl;
        break;
      }
    }
    buffer[size - 1] = 0; 
    std::cout << "echo # " << buffer << std::endl;
  }
  return 0;
}

Resource temporarily unavailable   // 当没有数据时,会打印strerror(error) 错误信息
Resource temporarily unavailable
123								   // 当读取到数据时,输出数据
echo # 123
Resource temporarily unavailable
124
echo # 124
Resource temporarily unavailable

多路转接IO

select简介

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

  • select 系统调用可以让我们呢的程序同时监视多个文件描述符上的事件是否就绪
  • select 的核心工作就是等,当监视的多个文件描述符上有至少一个事件就绪时,select 就会返回并将对应的文件描述符上的就绪事件告诉调用者

select 函数

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds: 需要监视的文件描述符中,最大的文件描述符的值 + 1

  • readfds : 输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的读事件是否就绪,返回时内核告知用户哪些文件描述符的读事件已经就绪

  • writefds: 输入输出型参数, 调用时用户告知内核需要监视哪些文件描述符的写事件是否就绪,返回时内核告知用户哪些文件描述符上写事件已经就绪

  • exceptfds:输入输出型参数, 调用时用户告知内核需要监视哪些文件描述符上的异常事件是否就绪,返回时内核告知用户哪些文件描述符的异常事件已经就绪

  • timeout: 输入输出型参数,调用时由用户设置select的等待事件,返回时表示timeout的剩余事件。

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

timeout 有三种取值

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

select 调用失败后,可能出现的错误码

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

fd_set 结构

fd_set结构与sigset_t等结构类似,fd_set本质就是一个位图,用位图中的比特位来表示需要监视的文件描述符。对这个位图的操作系统提供了一组专门的结构,用于对fd_set类型位图进行各种操作

 void FD_CLR(int fd, fd_set *set);		// 清除 fd 
 int  FD_ISSET(int fd, fd_set *set);   	// 判断 fd
 void FD_SET(int fd, fd_set *set);		// 设置 fd
 void FD_ZERO(fd_set *set);				// 清空 位图

timeout结构

struct timeval {
	long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

tv_sec表示的是秒,tv_usec表示的是微秒

socket 就绪条件

读就绪

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

写就绪

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

异常就绪

  • socket 上收到外带数据

外带数据和TCP的进击模式相关,TCP报头中的URG标志位和16位紧急指针配合使用,能够发送/接收我带外数据

select服务器

如果我们想要实现一个简单的select服务器,首先我们必须能够拿到客户端发来的数据并进行打印,那么select服务其的工作流程应该

  • 初始化服务器,完成套接字创建,绑定,监听等
  • 定义fd_array数组保存需要关注的套接字,开始只需要将监听套接字放入即可,并设置fd_set,将fd_array中的文件描述符设置进位图中
  • 循环调用select函数,检测读事件是否就绪,就绪进行对应操作

Socket类

首先可以先编写一个Socket类,对网络编程套接字的接口进行一定程度封装,便于使用

#pragma once

#include <iostream>
#include <unistd.h>
#include <memory.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>

class Socket{
public:
  static int SocketCreate();						// 创建套接字
  static void SocketBind(int sock, int port);		// 绑定套接字
  static void SocketListen(int sock, int backlog);	// 监听套接字
};

int Socket::SocketCreate(){
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    std::cerr << "socket error" << std::endl;
    exit(2);
  }

  // 设置端口复用
  int opt = 1;
  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  std::cout << "SocketCreate success" << std::endl;
  return sockfd;
}

void Socket::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);
  }
  std::cout << "SocketBind success" << std::endl;
}


void Socket::SocketListen(int sock, int backlog) {
  if (listen(sock, backlog) < 0) {
    std::cerr << "listen error" << std::endl;
    exit(4);
  }
  std::cout << "SocketListen success" << std::endl;
}

SelectServer类

服务器的IP地址直接使用INADDR_ANY即可,所以我们的服务器只需要将监听套接字和开放端口号设置进成员变量即可

#pragma once
#include "socket.hpp"
#include <sys/select.h>
#include <unistd.h>

#define BALCK_LOG 5
#define SERVER_PORT 8888

class SelectServer{
public:  
  static SelectServer* GetInstance(int port = SERVER_PORT);
  ~SelectServer(){ if (listen_sock > 0) close(listen_sock); }
  void InitSelectServer();
  void Run();
private:
  SelectServer(int _port) : port(_port){};
private:
  void ClearFdArray(int* fd_array, int num, int default_val);
private:
  void HandlerEvent(const fd_set& readfds, int fd_array[], int num);
  bool SetFdArray(int sock, int fd_array[], int num);
private:
  int listen_sock;
  int port;
  static SelectServer* instance;
};
SelectServer* SelectServer::instance = nullptr;

SelectServer* SelectServer::GetInstance(int port) {
  if (instance == nullptr) {
    instance = new SelectServer(port);
  }
  return instance;
}

void SelectServer::InitSelectServer() {
  listen_sock = Socket::SocketCreate();
  Socket::SocketBind(listen_sock, port);
  Socket::SocketListen(listen_sock, BALCK_LOG);
}

void SelectServer::Run() {
#define FD_ARRAY_NUM 1024
#define DFL_FD -1
  fd_set readfds; 
  int fd_array[FD_ARRAY_NUM];
  ClearFdArray(fd_array, FD_ARRAY_NUM, DFL_FD);
  fd_array[0] = listen_sock;
  for (;;){
    FD_ZERO(&readfds);
    // 扫描获取最大文件描述符
    int maxfd = DFL_FD;
    for (int i = 0; i < FD_ARRAY_NUM; i++) {
      if (fd_array[i] == DFL_FD) continue;
      
      // 将需要监视的套接字设置进readfds
      FD_SET(fd_array[i], &readfds);
      maxfd = maxfd > fd_array[i] ? maxfd : fd_array[i];
    }
    struct timeval timeout = {5, 0};
    // struct timeval timeout = {0, 0};
    switch (select(maxfd + 1, &readfds, nullptr, nullptr, &timeout)) {
      case 0:  std::cout << "timeout ..." << std::endl; break;
      case -1: std::cerr << "select error" << std::endl; break;
      default : std::cout << "有事件发生..." << std::endl; 
                HandlerEvent(readfds, fd_array, FD_ARRAY_NUM);
                break;
    }
  }
}

void SelectServer::ClearFdArray(int* fd_array, int num, int default_val) {
  for (int i = 0; i < num; i++) 
    *(fd_array + i) = default_val;
}


void SelectServer::HandlerEvent(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;

      if (!SetFdArray(sock, fd_array, FD_ARRAY_NUM)) { // 将获取到的套接字添加到fd_array中
        close(sock);
        std::cout << "select server is full, close fd : " << sock << std::endl;
      }
    }
    else if (FD_ISSET(fd_array[i], &readfds)) {                       // 数据读
#define BUF_NUM 1024
    char buffer[BUF_NUM];
    ssize_t size = read(fd_array[i], buffer, sizeof(buffer) - 1);
    if (size > 0) {
      buffer[size - 1] = 0;
      std::cout << "echo #" << buffer << std::endl;
    } else if (size == 0) {
      std::cout << "client quit ... close fd : " << fd_array[i] << std::endl;
      close(fd_array[i]);
      fd_array[i] = DFL_FD;
    } else {
      std::cerr << "read error" << std::endl;
      close(fd_array[i]);
      fd_array[i] = DFL_FD;                       // 清除网络文件
      }
    }
  }
}

select 服务器测试

#include "select_server.hpp"
void Usage(char* proc) {
  std::cout << "Usage: " << proc << " port " << std::endl;
}
int main(int argc, char* argv[]) {
  if (argc != 2) {
    Usage(argv[0]);
    exit(1);
  }
  int port = atoi(argv[1]);
  SelectServer* ss = SelectServer::GetInstance(port);
  ss->InitSelectServer();
  ss->Run();
  return 0;
}

使用telnet工具连接服务器,此时就可以通过telnet向服务器发送的数据就可以被服务器读到并且打印了。可以看到虽然select服务器是一个单进程服务器,但是它却可以同时为多个客户端提供服务,根本原因是因为select函数调用后会告知select服务器是哪个客户端对应的连接事件就绪了,,此时select服务器就可以读取客户端数据,读取完后调用特定函数处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rz5bMqv5-1688823631374)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230708210517387.png)]

当前select 服务器存在的问题

  • 服务器并没有对客户端进行响应,如果使用select服务器的话,不能直接调用write函数发送数据,因为write函数本质上也需要分为等和拷贝两部分。我们也必须将等这个过程交给select函数,因此每次调用select函数之前需要重新设置readfds 和 writefds
  • 没有定制协议
  • 没有对应的输入和输出缓冲区,代码中的数据直接存储到了字符串数组buffer中,者是不眼睛的。因为一次数据读取可能并没有读取到一个完整的报文,半个报文是不能直接交给上层处理的。应当将数据存储到一个输入缓冲区中,当读取到一个完整的报文后再让服务器进行处理。当然如果服务器需要对客户端的请求进行响应,也不能直接调用write发送给客户端,必须存储到一个输出缓冲区,因为响应的数据可能很庞大,无法一次性发送完成需要分批发送

select的优缺点

优点

  • 可以同时等待多个文件描述符,并且实际只负责等待,将等的事件重叠,踢狗IO效率,实际IO操作由accept, read, write 等接口来完成,并且保证这些接口进行IO操作时不会被阻塞

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

缺点

  • 每次调用select 都必须要手动设置fd集合,每次都要遍历设置非常麻烦
  • 每次调用select, 都需要将fd集合从用户态拷贝到内核态,这个开销在fd_set大的时候开销会很大
  • 每次调用select都需要在内核遍历所有传进来的fd,开销在fd很多的时候也很大
  • 由于使用位图存储fd,位图的大小一定是优先的,所以select 可监控的文件描述符也是有限的

select 可监控文件描述符的个数

select 的可监控文件描述符个数可以通过sizeof(fd_set) * 8)来计算,实际上fd_set的大小是128字节也就是能够监视1024个文件描述符。但不同环境下fd_set的大小可能是不同的,并且其大小可以重新调整(重新编译内核)

一个进程可以打开的文件描述符个数

进程控制块PCB(task_struct) 当中会有一个files指针,该指针指向一个struct files_struct结构,进程的文件描述符fd_array就存储在该结构当中,其中文件描述符表中的fd_array定义大小为NR_OPEN_DEFALUT,其值就是32

但实际上一个进程可以打开的文件描述符个数是可以扩展的,使用ulimit -a就可以查看进程打开文件描述符的上限

[clx@VM-20-6-centos select_io]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 14686
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 100001        # 文件描述符上限

因此select只能监听1024个文件描述符太少了,说明其最多只能为1023个客户端进行服务

多路转接的使用场景

  • 多路转接接口一般适用于多链接的情况,并且多链接中只有少部分的连接比较活跃。因为大部分连接都不活跃,在一段时间只有少量连接在进行IO,大部分都在等待事件就绪,使用多路转接接口就可以将这些等待事件进行重叠,提高IO效率
  • 可以试想一下使用线程池,我们用五个线程提供服务,但是连接很多并且大部分时间都在等待用户输入信息,这就会导致阻塞队列中的连接越来越多,而服务器实际一直在等待客户输入,提供服务的时间其实很少
  • 所以对于多链接中大部分连接都很活跃的场景,多路转接其实不太适合。因为每个连接都很活跃,那么每个连接上的事件基本都是就绪的。因此根本不需要使用多路转接接口来帮我们进行等待,毕竟使用多路转接接口也是需要花费系统的事件和空间资源的

多链接但少量连接活跃的场景(QQ/微信等聊天工具),大量连接活跃的场景(数据库备份)

参考文章:2021dragon 的Linux高级IO
原文链接:https://blog.csdn.net/chenlong_cxy/article/details/126050039

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

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

相关文章

English Learning - L3 综合练习 10 口语语法串讲与思维回顾 2023.07.5 周三

English Learning - L3 综合练习 10 口语语法串讲与思维回顾 2023.07.5 周三 [知识点 1] 名词性从句问题&#xff1a;到底什么是名词笥从句&#xff1f;例 1&#xff1a;我的东西你都可以随便用例 2&#xff1a;不管是谁&#xff0c;放你鸽子就是混蛋例 3&#xff1a;说那种话的…

CMU15-445 2022 Fall 通关记录 —— Project 2:B+ Tree(下篇)

Project 2&#xff1a;B Tree Project #2 - BTree | CMU 15-445/645 :: Intro to Database Systems (Fall 2022) NOTE&#xff1a; 记录完成该Pro中&#xff0c;一些可能会遇到的问题&#xff1a; 本实验中&#xff0c;有很多API是需要自己去实现的&#xff0c;因此&#x…

Mycat【Mycat高可用(安装配置HAProxy、安装配置Keepalived)】(八)-全面详解(学习总结---从入门到深化)

目录 Mycat高可用_安装配置HAProxy Mycat高可用_安装配置Keepalived 复习&#xff1a; Mycat高可用_安装配置HAProxy 安装配置HAProxy 查看列表 yum list | grep haproxy yum安装 yum -y install haproxy 修改配置文件 $ vim /etc/haproxy/haproxy.cfg 启动HAProxy …

安全漏洞的检测利用

安全漏洞的检测&利用 一、安全漏洞的基本概念1.1、什么是漏洞1.2、漏洞的简单理解1.3、微软的RPC漏洞与蠕虫病毒1.4、微软经典的蓝屏漏洞1.5、Heartbleed&#xff08;心脏滴血&#xff09;漏洞1.6、破壳漏洞CVE-2014-62711.7、漏洞的危害1.8、漏洞的成因1.9、漏洞的信息的组…

mysql工具sequel pro

一、安装sequel pro 下载地址&#xff1a;https://www.sequelpro.com/ 需要翻墙 二、安装mysql 下载地址&#xff1a;https://www.mysql.com/ 傻瓜式安装即可 记得设置密码 三、配置环境变量 &#xff08;1&#xff09;打开终端 &#xff08;2&#xff09;open ~/.bash_profile…

【送书福利-第十五期】计算机全栈高手到底该怎么发展?

大家好&#xff0c;我是洲洲&#xff0c;欢迎关注&#xff0c;一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大家在CSDN后台私信我&#xff01; 本文目录 一、前言二、书籍介绍1、《前端…

0代码训练GPT-5?MIT微软证实GPT-4涌现自我纠错能力迭代

我们都知道&#xff0c;大模型具有自省能力&#xff0c;可以对写出的代码进行自我纠错。 这种自我修复背后的机制&#xff0c;究竟是怎样运作的&#xff1f; 对代码为什么是错误的&#xff0c;模型在多大程度上能提供准确反馈&#xff1f; 近日&#xff0c;MIT和微软的学者发…

【数据分析 - 基础入门之NumPy①】Anaconda安装及使用

知识目录 前言一、 Anaconda是什么二、为什么使用Anaconda三、安装步骤3.1 下载安装3.2 配置conda源 结语 前言 大家好&#xff01;我是向阳花花花花&#xff0c;本期给大家带来的是 Anaconda 安装及使用。 每日金句分享&#xff1a;故事不长&#xff0c;也不难讲。』—— 「…

期望DP入门

期望DP一般步骤&#xff1a; 1.模拟过程&#xff0c;找出线性性质&#xff0c;作为阶段&#xff08;这本质上也是线性DP&#xff09; 2.涉及DP状态 原则&#xff1a; 体现线性性质 体现边权 根据对期望有无贡献来设计状态 一般在状态设计时需要倒着设计 3.转移 根据边…

如何将自定义字体添加到 iOS 应用程序(SwiftUI + 得意黑)

1. 工具 Xcode Version 14.3 (14E222b)SwiftUI得意黑 Smiley Sans 2. Download https://github.com/atelier-anchor/smiley-sans/releases 3. Add Files to xxx 4. Add Test Code Text("Less is more. 朱洪苇 123").font(.custom("SmileySans-Oblique",…

【电子量产工具】4. UI系统

文章目录 前言一、UI界面分析二、结构体描述按钮三、按钮初始化四、默认绘制按键事件函数五、默认按下按键事件函数六、测试程序实验效果总结 前言 最近看了 电子量产工具 这个项目&#xff0c;本专栏是对该项目的一个总结。 一、UI界面分析 UI 是用户界面&#xff08;User In…

GEE:提取地区 NDVI/LST/RVI 并进行时间序列线性插值和SG滤波

作者&#xff1a;CSDN _养乐多_ 本文将介绍使用Landsat数据集&#xff0c;构建时间序列&#xff0c;并使用线性插值算法填补缺失数据&#xff0c;或者去云空洞&#xff0c;并进一步对完整的时间序列数据进行SG滤波处理。 文章目录 一、代码二、代码链接三、需要请私聊 一、代…

OPCUA 的历史数据库/聚合服务器的实现细节

进入了AI 大数据时代&#xff0c;无论是工业自动化系统&#xff0c;还是物联网系统&#xff0c;对大数据的采集&#xff0c;存储和分析都十分重要。大数据被称为工业的石油&#xff0c;未来制造业的持续改善离不开大数据。 传统的应用中&#xff0c;历史数据的存储是特定的数据…

官方外设库SDA安装和验证

第一种方法 1.打开mobaxterm&#xff0c;通过windows浏览器打开https://github.com/orangepi-xunlong/wiringOP下载压缩包&#xff0c;点击上传外设库的压缩包 2.输入命令 unzip 解压 3.输入命令 sudo ./build 安装工具包 4.验证安装完毕用 输入gpio readall 显示下面图片 第二…

数据分析实战(基础篇):从数据探索到模型解释

前言 本文着重介绍数据分析实战的基础知识和技巧&#xff0c;探索从数据探索到建模再到模型解释的完整过程内容包含数据探索、模型建立、调参技巧、SHAP模型解释数据来源于kaggle平台&#xff0c;crab age prediction数据集&#xff0c;数据详情 数据说明 数据背景 螃蟹味道…

【性能设计篇】聊聊异步处理

在性能设计的时候&#xff0c;其实主要的三板斧就是数据库(读写分离、分库分表)&#xff0c;缓存&#xff08;提升读性能&#xff09;&#xff0c;异步处理&#xff08;提升写性能&#xff09;以及相关的秒杀设计以及边缘设计等。 本篇主要介绍异步处理的哪些事&#xff0c;我们…

6.2.1 网络基本服务---域名解析系统DNS

6.2.1 网络基本服务—域名解析系统DNS 因特网是需要提供一些最基本的服务的&#xff0c;今天我们就来讨论一下这些基本的服务。 域名系统&#xff08;DNS&#xff09;远程登录&#xff08;Telnet&#xff09;文件传输协议&#xff08;FTP&#xff09;动态主机配置协议&#x…

Day47

思维导图 练习 实现登录框中&#xff0c;当登录成功时&#xff0c;关闭登录界面&#xff0c;并跳转到其他界面 second.h #ifndef SECOND_H #define SECOND_H#include <QWidget>namespace Ui { class Second; }class Second : public QWidget {Q_OBJECTpublic:explicit …

Matlab绘图时的几个小技巧(修改刻度线长度、添加/去掉右边和上面的轴与刻度线、出图时去掉旁边的空白部分)

set(gca,TickLength,[0.005,0.035]); %修改坐标轴刻度线的长度 box on; %开启右面和上面的坐标轴 box off;%关闭右面和上面的坐标轴 set(gca, LooseInset, [0,0,0,0]);%删除掉图旁边多余的空白部分首先随便出一张图 我想让刻度线更长或更短一些&#xff1a; 我想让右侧和上面…

OpenCV的安装与配置指南(Windows环境,Python语言)

OpenCV 的安装与配置指南&#xff08;Windows环境&#xff0c;Python语言&#xff09; 导语一、安装 Python 二、安装 OpenCV 库三、配置 OpenCV 环境变量四、验证 OpenCV 安装总结 导语 OpenCV 是一个功能强大的计算机视觉库&#xff0c;广泛应用于图像处理和计算机视觉领域。…