Linux之·网络编程·I/O复用·select

系列文章目录


文章目录

  • 前言
  • 一、概述
    • 1.1 介绍IO复用的概念和作用
      • 1.1.1 I/O复用具体使用的场景
      • 1.1.2 I/O复用常用函数
  • 二、select函数的重要性和用途
    • 2.1 基本的select函数
    • 2.2 如何使用FD_SET、FD_CLR等宏来设置和清除文件描述符集合
    • 2.3 select()函数函数整体使用框架:
    • 2.4 如何使用select函数同时监听多个文件描述符
    • 2.5 如何使用select函数处理超时情况
      • 2.5.1 网络编程为什么需要超时检测?
  • 三、实例演示
    • 3.1 select函数的应用对比
  • 总结


前言

Linux下进行网络编程时会有同步/异步,阻塞和非阻塞四种调用方式

同步和异步的概念描述的是用户线程与内核的交互方式:

同步: 同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;
异步: 异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后通知用户线程,或是调用用户线程注册的回调函数。

阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:

阻塞: 阻塞是指IO操作在没有接受完数据或者是没有得到结果前不会返回,需要彻底完成后才能返回到用户空间;
非阻塞: 非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成后,此时会持续一个轮询状态直到得出结果。

操作系统的五种IO模型

  • 同步阻塞IO

  传统的IO模型,也是最常见,最简单的IO模型,在Linux中默认情况下所有的socket都是阻塞模式。
  当用户进程调用read()系统调用时,会从用户进程空间转到内核空间处理,内核会等待数据(因为对于网络IO来说,很多时候数据一开始还未到达)此时进程会处于阻塞状态,直到数据到达后内核会将数据拷贝到用户内存中,然后内核返回结果,用户进程解除阻塞状态,重新开始运行。
在这里插入图片描述

  • 同步非阻塞IO

  用户线程在发起IO请求后可以立即返回,如果此次读操作未取到数据,用户线程需要不断地发起IO请求,直到数据到达后读取数据,线程继续执行。
在这里插入图片描述

  • IO多路复用

  多路复用IO是在高并发场景中使用最为广泛的一种IO模型。在并发环境下,多个线程需要向内核获取数据,而数据获取时间未知因此会发生阻塞,此时就会消耗操作系统的资源。IO多路复用是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询的问题,使用一个线程监控多个线程(文件描述符),这样就可以只需要一个线程就可以完成数据状态的询问操作,只要有一个线程的相关数据就绪就可以分配对于的线程获取数据。
在这里插入图片描述

  • 信号驱动IO

  当用户线程发起一个IO请求操作时,会给对应的socket注册一个信号函数,调用sigaltion系统调用,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,提醒用户线程读取数据。

  • 异步IO
      异步IO即经典的Proactor设计模式,也称为异步非阻塞IO。在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,(与信号驱动IO不同,收到通知时数据到达内核)并放在了用户线程指定缓冲区内,内核在IO完成后通知用户线程直接使用即可。

   同步阻塞IO、同步非阻塞IO、IO多路复用、信号驱动IO都是同步IO,用户线程是会阻塞的,内核等待数据直至数据准备好,将数据从内核拷贝到用户空间,内核返回结果,用户线程继续运行。异步IO不需要考虑整个IO操作是如何运行的,只需要发起请求,接收到内核返回成功信号时IO操作已经完成,直接使用数据。
  但相比与IO多路复用模型,信号驱动IO和异步IO并不十分受用,不少高性能并发服务持续使用IO多路复用+多线程任务处理的架构基本可以满足需求。


一、概述

1.1 介绍IO复用的概念和作用

  • 概念

IO复用(Input/Output Multiplexing) 是一种高效的IO模型,一种处理多个输入输出流的方法,它能够在一个线程中同时管理多个IO操作。IO复用通过操作系统提供的机制,使得一个线程能够同时监听多个文件描述符(Socket、文件等),并在有IO事件发生时进行处理。

  • 作用

1.提高系统的资源利用率:传统的IO模型中,每个IO操作都需要创建一个线程进行处理,而线程的创建和销毁会消耗较大的系统资源。使用IO复用模型,多个IO操作可以在同一个线程中进行管理,避免了频繁的线程创建和销毁,从而提高了系统的资源利用率。

2.提高系统的并发性能:传统的IO模型中,一个线程只能同时处理一个IO操作,无法同时处理多个IO操作。使用IO复用模型,一个线程可以同时监听多个IO事件,当有IO事件发生时,可以及时进行处理,从而提高了系统的并发性能。

3.简化系统设计和编程:使用IO复用模型,可以将IO操作的管理和处理逻辑集中到一个线程中,简化了系统的设计和编程。同时,由于只需要维护一个线程,也减少了线程之间的同步和通信的开销。

常见的IO复用机制包括:select、poll、epoll等,它们在实现上有所差异,但都实现了IO复用的功能。

1.1.1 I/O复用具体使用的场景

  • 当客户处理多个描述符(通常是交互式输入、网络套接字)时,必须使用I/O复用。
  • tcp服务器既要处理监听套接字,又要处理已连接套接字,一般要使用I/O复用。
  • 如果一个服务器既要处理tcp又要处理udp,一般要使用I/O复用。
  • 如果一个服务器要处理多个服务或多个服务时,一般要使用I/O复用。

1.1.2 I/O复用常用函数

select()、poll()、epoll()

二、select函数的重要性和用途

  select函数是一个用于多路复用的系统调用,用于监视多个文件描述符的状态,当其中任意一个文件描述符准备就绪时,select函数会返回该文件描述符的信息。其重要性和用途如下:

  • 实现多路复用: 在单线程程序中同时监听多个文件描述符的状态,以实现并发处理多个输入或输出操作。通过select函数,可以将程序从阻塞模式转换为非阻塞模式,提高程序的响应性能。

  • 提高系统资源利用率: 通过多路复用技术,可以使用一个线程同时处理多个客户端连接请求,避免为每个连接创建一个线程,从而减少线程的创建和销毁的开销,提高系统的资源利用率。

  • 网络编程中的应用: 在网络编程中,select函数常用于监听多个套接字的状态变化,如接收客户端的连接请求、读取客户端发送的数据等,从而实现高效的网络通信。

  • IO多路复用框架中的基础组件: 在许多IO多路复用框架(如epoll、kqueue等)的实现中,select函数常作为基础组件,用于监听文件描述符的状态变化。通过select函数,可以实现高效、可扩展的IO多路复用框架,提供高性能的网络服务。

select函数在多路复用网络编程IO多路复用框架中都具有重要的作用,能够提高程序的性能和资源利用率。

2.1 基本的select函数

函数原型:int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
函数功能:轮询扫描多个描述符中的任一描述符是否发生响应,或经过一段时间后唤醒
参	 数:
参数	  名称	                    说明
maxfd	  指定要检测的描述符的范围	所检测描述符最大值+1
readset   可读描述符集               监测该集合中的任意描述符是否有数据可读
writeset  可写描述符集               监测该集合中的任意描述符是否有数据可写
exceptset 异常描述符集               监测该集合中的任意描述符是否发生异常
timeout   超时时间                   超过规定时间后唤醒
——————————————————————————————————————————————————————————————————————————
timeout:指定select函数的超时时间,有以下三种情况:

如果timeout为NULL,表示select函数将一直阻塞,直到有文件描述符就绪。
如果timeout为0,表示select函数立即返回,不阻塞,用于轮询文件描述符的状态。
如果timeout大于0,表示select函数将阻塞指定的时间,超过时间后没有文件描述符就绪,则返回0。

返回值:select函数的返回值是就绪描述符的数目,返回值小于0,出错;返回值等于0,超时;返回值大于0(准备好的文件描述符数量),有事件发生。                        

头文件:

#include <sys/select.h>
#include <sys/time.h>

超时时间:

//该结构体表示等待超时的时间
struct timeval{
	long tv_sec;//秒
	long tv_usec;//微秒
};
 
//比如等待10.2秒
struct timeval timeout;
timeoout.tv_sec = 10;
timeoout.tv_usec = 200000;
 
//将select函数的timeout参数设置为NULL则永远等待

描述符集合的操作:

select()函数能对多个文件描述符进行监测,如果一个参数对应一个描述符,那么select函数的4个参数最多能监测4个文件描述,那他如何实现对多个文件描述符的监测的呢?

大家想一想文件描述符基本具有3种特性(读、写、异常),如果我们统一将监测可读的描述符放入可读集合(readset),监测可写的描述符放入可写集合(writeset),监测异常的描述符放入异常集合(exceptset)。然后将这3个集合传给select函数,是不是就可监测多个描述符呢。

如何将某个描述符加入到特定的集合中呢?这时就需要了解下面的集合操作函数

//初始化描述符集
void FD_ZERO(fd_set *fdset);
 
//将一个描述符添加到描述符集
void FD_SET(int fd, fd_set *fdset);
 
//将一个描述符从描述符集中删除
void FD_CLR(int fd, fd_set *fdset);
 
//检测指定的描述符是否有事件发生
int FD_ISSET(int fd, fd_set *fdset);

2.2 如何使用FD_SET、FD_CLR等宏来设置和清除文件描述符集合

要设置文件描述符集合,可以使用FD_SET宏。这个宏的语法是:

 void FD_SET(int fd, fd_set *set);

其中,fd是要设置的文件描述符,set是要对其进行设置的文件描述符集合。

要清除文件描述符集合,可以使用FD_CLR宏。这个宏的语法是:

void FD_CLR(int fd, fd_set *set);

其中,fd是要清除的文件描述符,set是要对其进行清除的文件描述符集合。

使用这些宏时,首先要创建一个fd_set类型的变量,用来表示文件描述符集合。然后,可以使用FD_SET宏将文件描述符加入集合,或者使用FD_CLR宏将文件描述符从集合中清除。

例如,以下代码示例展示了如何使用FD_SET和FD_CLR宏来设置和清除文件描述符集合:

#include <stdio.h>
#include <sys/select.h>

int main() {
    // 创建一个文件描述符集合
    fd_set fds;
    
    // 初始化文件描述符集合为空
    FD_ZERO(&fds);

    // 将文件描述符 0 和 1 加入集合
    FD_SET(0, &fds);
    FD_SET(1, &fds);
    
    // 从集合中清除文件描述符 0
    FD_CLR(0, &fds);
    
    // 检查文件描述符 0 在集合中是否存在
    if (FD_ISSET(0, &fds)) {
        printf("文件描述符 0 存在于集合中\n");
    } else {
        printf("文件描述符 0 不存在于集合中\n");
    }

    return 0;
}

这段代码首先创建了一个文件描述符集合fds,并使用FD_ZERO宏将其初始化为空集合。

然后,使用FD_SET宏将文件描述符0和1加入集合。

接着,使用FD_CLR宏将文件描述符0从集合中清除。

最后,使用FD_ISSET宏来检查文件描述符0是否存在于集合中,并根据结果打印相应的信息。

2.3 select()函数函数整体使用框架:

示例:检测 0、4、5 描述符是否准备好读

while(1)
{
	fd_set rset;//创建一个描述符集rset
	FD_ZERO(&rset);//对描述符集rset清零
	FD_SET(0, &rset);//将描述符0加入到描述符集rset中
	FD_SET(4, &rset);//将描述符4加入到描述符集rset中
	FD_SET(5, &rset);//将描述符5加入到描述符集rset中
	
	if(select(5+1, &rset, NULL, NULL, NULL) > 0)
	{
		if(FD_ISSET(0, &rset))
		{
			//描述符0可读及相应的处理代码
		}
		
		if(FD_ISSET(4, &rset))
		{
			//描述符4可读及相应的处理代码
		}
		if(FD_ISSET(5, &rset))
		{
			//描述符5可读及相应的处理代码
		}
	}
}

2.4 如何使用select函数同时监听多个文件描述符

在使用select函数同时监听多个文件描述符时,需要按照以下步骤进行操作:

  • 创建一个fd_set变量,并将需要监听的文件描述符添加到fd_set中。可以使用宏FD_ZERO将fd_set清空,并使用宏FD_SET将需要监听的文件描述符添加到fd_set中。
     
  • 设置超时时间。可以创建一个timeval结构体,并设置其成员tv_sec和tv_usec来指定超时的秒数和微秒数。如果不需要设置超时时间,可以将timeval结构体设置为NULL。
     
  • 调用select函数,传入要监听的最大文件描述符值加1,以及被监听的fd_set变量和超时时间。根据函数的返回值来判断哪些文件描述符处于可读状态。
     
  • 使用宏FD_ISSET来检查文件描述符是否在返回的fd_set中,如果在则表示该文件描述符处于可读状态,可以进行相应的读操作。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>

int main() {
    int fd1, fd2;  // 需要监听的文件描述符
    int maxfd;     // 最大文件描述符值加1
    fd_set readfds;      // 监听的文件描述符集合
    struct timeval timeout;   // 超时时间
    int ret;

    // 创建文件描述符
    fd1 = open("file1.txt", O_RDONLY);
    fd2 = open("file2.txt", O_RDONLY);

    // 设置超时时间为5秒
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    // 清空fd_set
    FD_ZERO(&readfds);

    // 将需要监听的文件描述符添加到fd_set中
    FD_SET(fd1, &readfds);
    FD_SET(fd2, &readfds);

    // 获取最大文件描述符值加1
    maxfd = (fd1 > fd2) ? fd1 : fd2;

    // 监听文件描述符
    ret = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
    if (ret == -1) {
        perror("select");
        exit(1);
    } else if (ret == 0) {
        printf("Timeout\n");
    } else {
        // 读取可读文件描述符的数据
        if (FD_ISSET(fd1, &readfds)) {
            // fd1处于可读状态
            // 进行读取操作
        }
        if (FD_ISSET(fd2, &readfds)) {
            // fd2处于可读状态
            // 进行读取操作
        }
    }

    // 关闭文件描述符
    close(fd1);
    close(fd2);

    return 0;
}

在以上示例代码中,我们使用了两个文件描述符fd1和fd2,并将它们添加到fd_set中进行监听。设置了超时时间为5秒,并使用select函数监听这两个文件描述符。在select函数返回后,使用FD_ISSET宏来检查文件描述符是否处于可读状态,并进行相应的读取操作。
 
需要注意的是,在select函数返回后,文件描述符集合readfds会被修改为只包含处于可读状态的文件描述符,因此在下一次调用select函数时需要重新设置监听的文件描述符。

2.5 如何使用select函数处理超时情况

2.5.1 网络编程为什么需要超时检测?

在网络编程中,我们通常使用套接字(sockets)进行网络通信。当进行连接、发送和接收数据等操作时,可能会遇到以下情况导致阻塞:

(1)网络故障: 当网络出现问题时,连接、发送和接收数据可能会永远无法完成,导致程序长时间阻塞。
(2)对端无响应: 当尝试建立连接或发送数据时,对端可能没有响应,导致程序一直等待。
为了避免以上问题,我们需要在进行网络操作时设置合理的超时检测机制,使程序能够在合理的时间内做出响应,增加程序的健壮性。

使用select函数来处理超时情况,可按照以下步骤进行:

  • 创建一个fd_set类型的集合,并将需要监视的文件描述符添加到该集合中。
     
  • 创建一个struct timeval类型的变量,设置超时时间。
     
  • 调用select函数,传入集合最大的文件描述符加1、待监视的集合、空集合和超时时间。
     
  • 检查select函数的返回值,如果返回0,则表示超时;如果返回-1,则表示出现错误;如果返回大于0,则表示有就绪的文件描述符。
     
  • 可以通过FD_ISSET宏来检查特定的文件描述符是否就绪。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd;
    fd_set rfds;
    struct timeval tv;
    int retval;

    // 创建文件描述符
    fd = open("file.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    // 清空集合
    FD_ZERO(&rfds);

    // 将文件描述符添加到集合中
    FD_SET(fd, &rfds);

    // 设置超时时间为5秒
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    // 调用select函数
    retval = select(fd+1, &rfds, NULL, NULL, &tv);

    if (retval == -1) {
        perror("select");
        exit(1);
    } else if (retval == 0) {
        printf("超时\n");
    } else {
        if (FD_ISSET(fd, &rfds)) {
            printf("文件描述符可读\n");
            // 在这里进行读取文件操作
        }
    }

    return 0;
}

以上代码会监视一个文件描述符,如果在5秒内有数据可读,则输出"文件描述符可读";如果超过5秒仍然没有数据可读,则输出"超时"。

三、实例演示

3.1 select函数的应用对比

我们通过udp多线程收发的例子来说明select相较于多线程的妙处。
通过多线程来实现收发

示例1:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

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

#include <unistd.h>
#include <pthread.h>

void *recv_thread(void* arg)
{
    int udpfd = (int)arg;
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);

    bzero(&addr,sizeof(addr));
    while(1)
    {
        char buf[200]  = "";
        char ipbuf[16] = "";
        recvfrom(udpfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &addrlen);
        printf("\r\033[31;1m[%s]:\033[32;1m%s\n",inet_ntop(AF_INET,&addr.sin_addr,ipbuf,sizeof(ipbuf)),buf);
        write(1,"UdpServerRecv:",14);
    }
    return NULL;
}

int main(int argc,char *argv[])
{
    char buf[100] = "";
    int  udpfd = 0;
    pthread_t tid;
    struct sockaddr_in addr;
    struct sockaddr_in cliaddr;

    bzero(&addr,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(8000);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    bzero(&cliaddr,sizeof(cliaddr));
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port   = htons(8001);

    if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0)
    {
        perror("socket error");
        exit(-1);
    }

    if(bind(udpfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
        perror("bind error");
        close(udpfd);
        exit(-1);
    }

    printf("input: \"connect 192.168.111.X\" to sendmsg to somebody\n");
    pthread_create(&tid, NULL, recv_thread, (void*)udpfd);
    printf("\033[32m");
    fflush(stdout);

    while(1)
    {
        write(1,"UdpServerSend:",14);
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';
        if(strncmp(buf, "connect ", 8) == 0)
        {
            char ipbuf[INET_ADDRSTRLEN] = "";
            inet_pton(AF_INET, buf+8, &cliaddr.sin_addr);
            printf("\rconnect %s successful!\n",inet_ntop(AF_INET,&cliaddr.sin_addr,ipbuf,sizeof(ipbuf)));
            continue;
        }
        else if(strncmp(buf, "exit",4) == 0)
        {
            close(udpfd);
            printf("\033[0m\n");
            exit(0);
        }

        sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&cliaddr, sizeof(cliaddr));
    }

    return 0;
}

运行结果:
在这里插入图片描述
使用select来完成上述同样的功能:
示例2:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

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

#include<sys/select.h>

int main()
{
    int udpfd = 0;
    struct sockaddr_in saddr;
    struct sockaddr_in caddr;

    bzero(&saddr,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port   = htons(8000);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bzero(&caddr,sizeof(caddr));
    caddr.sin_family  = AF_INET;
    caddr.sin_port    = htons(8001);

    //创建套接字
    if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0)
    {
        perror("socket error");
        exit(-1);
    }

    //套接字端口绑字
    if(bind(udpfd, (struct sockaddr*)&saddr, sizeof(saddr)) != 0)
    {
        perror("bind error");
        close(udpfd);
        exit(-1);
    }

    printf("input: \"connect 192.168.111.X\" to sendmsg to somebody\033[32m\n");
    while(1)
    {
        char buf[100]="";
        fd_set rset;    //创建文件描述符的聚合变量
        FD_ZERO(&rset); //文件描述符聚合变量清0
        FD_SET(0, &rset);//将标准输入添加到文件描述符聚合变量中
        FD_SET(udpfd, &rset);//将udpfd添加到文件描述符聚合变量中
        //printf("\033[36;1mudpfd = %d\033[0m\n",udpfd);
        write(1,"Udp_Server:",11);

        if(select(udpfd + 1, &rset, NULL, NULL, NULL) > 0)
        {
            if(FD_ISSET(0, &rset))//测试0是否可读写
            {
                fgets(buf, sizeof(buf), stdin);
                buf[strlen(buf) - 1] = '\0';
                if(strncmp(buf, "connect", 7) == 0)
                {
                    char ipbuf[16] = "";
                    inet_pton(AF_INET, buf+8, &caddr.sin_addr);//给addr套接字地址再赋值.
                    printf("\rSend to %s\n",inet_ntop(AF_INET,&caddr.sin_addr,ipbuf,sizeof(ipbuf)));
                    continue;
                }
                else if(strcmp(buf, "exit")==0)
                {
                    close(udpfd);
                    exit(0);
                }
                sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&caddr, sizeof(caddr));
            }
            if(FD_ISSET(udpfd, &rset))//测试udpfd是否可读写
            {
                struct sockaddr_in addr;
                char ipbuf[INET_ADDRSTRLEN] = "";
                socklen_t addrlen = sizeof(addr);

                bzero(&addr,sizeof(addr));

                recvfrom(udpfd, buf, 100, 0, (struct sockaddr*)&addr, &addrlen);
                printf("\r\033[31m[%s]:\033[32m%s\n",inet_ntop(AF_INET,&addr.sin_addr,ipbuf,sizeof(ipbuf)),buf);
            }
        }
    }

    return 0;
}

运行结果:
在这里插入图片描述


总结

select将socket是否就绪检查逻辑下沉到操作系统层面,避免了系统调用,只告诉告诉用户有事件就绪,但是没有告诉用户具体那个FD;

优点:

  • 不需要每个FD都进行依次系统调用,解决了频繁的用户态内核态切换的问题;

缺点:

  • 单进程监听的FD存在限制,默认1024;(可以修改)
  • 每次调用需要将FD从用用户态拷贝到内核态;
  • 不知道具体的那个文件描述符就绪,需要遍历全部文件描述符;
  • 入参的3个fd_set集合每次调用都要重置;

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

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

相关文章

被动防护不如主动出击

自网络的诞生以来&#xff0c;攻击威胁事件不断涌现&#xff0c;网络攻防对抗已然成为信息时代背景下的一场无硝烟的战争。然而&#xff0c;传统的网络防御技术&#xff0c;如防火墙和入侵检测技术&#xff0c;往往局限于一种被动的敌暗我明的防御模式&#xff0c;面对攻击者无…

CSS实现渐变色

渐变色分为线性渐变和径向渐变。 线性渐变linear-gradient(方向, 颜色1, 颜色2, … ,颜色n)径向渐变radial-gradient(颜色1 覆盖区域大小, 颜色2 覆盖区域大小, … ) 线性渐变的方向可以为&#xff1a; ​ 1、一个方向值时&#xff1a; to bottom 表示从上边到下边渐变 ​ 2、…

GO语言核心30讲 实战与应用 (WaitGroup和Once,context,Pool,Map,字符编码,string包,bytes包)

原站地址&#xff1a;Go语言核心36讲_Golang_Go语言-极客时间 一、sync.WaitGroup和sync.Once 1. sync.WaitGroup 比通道更加适合实现一对多的 goroutine 协作流程。 2. WaitGroup类型有三个指针方法&#xff1a;Wait、Add和Done&#xff0c;以及内部有一个计数器。 (1) Wa…

《控制系统实验与综合设计》自控第二次(含程序和题目)

实验五 二阶系统的瞬态响应 一、实验完成任务 1、测试在不同阻尼比的条件下单位阶跃响应曲线&#xff0c;并进行其他动态性能指标测量。 2、通过调节开环增益得到相应K值&#xff0c;并进行其他动态性能指标测量。 3、在阻尼比一定时&#xff0c;测试角频率不同时的单位阶跃…

Service Worker的生命周期和全局对象和API

Service Worker的生命周期和全局对象和API 当我们注册了Service Worker后&#xff0c;它会经历生命周期的各个阶段&#xff0c;同时会触发相应的事件。整个生命周期包括了&#xff1a;installing --> installed --> activating --> activated --> redundant。当Se…

深度剖析进程概念与进程状态

文章目录 1. 前言2. 什么是进程2.1 进程概念2.2 进程描述——PCB 3. 进程的一些基本操作3.1 查看进程3.2 结束进程3.3 通过系统调用获取进程标示符3.4 通过系统调用创建子进程 4. 进程状态4.1 普适的操作系统层面4.2 具体Linux操作系统层面 5. 两种特殊的进程5.1 僵尸进程5.2 孤…

每日OJ题_贪心算法四⑧_力扣767. 重构字符串

目录 力扣767. 重构字符串 解析代码 力扣767. 重构字符串 767. 重构字符串 难度 中等 给定一个字符串 s &#xff0c;检查是否能重新排布其中的字母&#xff0c;使得两相邻的字符不同。 返回 s 的任意可能的重新排列。若不可行&#xff0c;返回空字符串 "" 。 …

【Java基础】枚举类的方法及应用

如何实现让一个类有固定个数的对象 手动封装构造方法&#xff08;private&#xff09; → 创建静态对象 → final修饰静态对象&#xff0c;使其成为常量 class Season { //枚举类public final static Season SPRING new Season();public final static Season SUMMER new Se…

Redis的集群模式——Java全栈知识(20)

1、主从模式 Redis 支持主从模式的集群搭建&#xff0c;这是 Redis 提供的最简单的集群模式搭建方案&#xff0c;目的是解决单点服务器宕机的问题。当单点服务器发生故障的时候保证 Redis 正常运行。 主从模式主要是将集群中的 Redis 节点分为主节点和从节点。然后读和写发生在…

C++11续——智能指针(出现原因至源码模拟)

前言&#xff1a;在C11里面提出了一个新的语法 try catch用来捕捉异常&#xff0c;这样子能不使用return和exit的前提下退出程序就得到错误信息&#xff0c;但是随之而来的就是一个新的问题&#xff0c;try catch退出程序之后可能带来了无法释放的内存泄露问题&#xff0c;原因…

微信小程序 19:小程序分包

对小程序进行分包的好处主要有以下两点 可以优化小程序首次启动的下载时间在多团队共同开发时可以更好的解偶协作 分包前小程序的项目构成 分包前&#xff0c;小程序项目中所有的页面资源都被打包到一起&#xff0c;导致整个项目体积过大&#xff0c;影响小程序首次启动的下…

vue自定义权限指令

定义v-hasPermi指令 /*** v-hasPermi 操作权限处理*/import useUserStore from /store/modules/userexport default {mounted(el, binding, vnode) {const { value } bindingconst all_permission "*:*:*";const permissions useUserStore().permissions&#xff…

算法-卡尔曼滤波之基本数学的概念

1.均值 定义&#xff1a;均值是一组数据中所有数值的总和除以数据的数量。均值是数据的中心趋势的一种度量&#xff0c;通常用符号 xˉ 表示。 &#xff1a;对于包含 n 个数据的数据集 {&#x1d465;1,&#x1d465;2,...,&#x1d465;&#x1d45b;}&#xff0c;均值 xˉ 计…

常见 Web 安全攻防总结

Web 安全的对于 Web 从业人员来说是一个非常重要的课题&#xff0c;所以在这里总结一下 Web 相关的安全攻防知识&#xff0c;希望以后不要再踩雷&#xff0c;也希望对看到这篇文章的同学有所帮助。今天这边文章主要的内容就是分析几种常见的攻击的类型以及防御的方法。 也许你对…

利用CAD绘制角度斜线的简易指南---模大狮模型网

在CAD设计中&#xff0c;绘制角度斜线是常见的需求&#xff0c;尤其在工程、建筑等领域中。正确绘制角度斜线不仅可以提高图纸的清晰度和美观度&#xff0c;还有助于准确表达设计意图。本文将介绍如何利用CAD软件进行角度斜线的绘制&#xff0c;为您提供简明易懂的操作指南。 一…

什么是资源池技术?它有什么用?

在开发应用程序过程中&#xff0c;涉及到对系统资源进行有效管理时往往会用到池化操作。资源池模式的应用场景很多&#xff0c;可以管理那些想要通过重用来分摊昂贵初始化代价的对象&#xff0c;而管理数据库连接就是很好的一种应用场景。数据库连接池作为一种典型的池化技术手…

云端的艺术革命:云渲染如何重塑动画与视觉特效产业

在 2019 年&#xff0c;乔恩费儒&#xff08;Jon Favreau&#xff09;决定重拍迪士尼的经典电影《狮子王》。他的创新构想是以真实动物为模型&#xff0c;在非洲草原上拍摄&#xff0c;由真实动物“出演”的辛巴和其他角色&#xff0c;随后通过配音赋予它们生命。 为了实现这一…

vue前端时间段选择控件

实现效果: 可选具体的某天的某时某分某秒 vue前端代码: <el-form-item label"日期"><el-date-pickerv-model"daterangerq"style"width: 240px"value-format"yyyy-MM-dd HH:mm:ss"type"datetimerange"range-separat…

[笔记] srlua库编译

文章目录 前言一、环境二、编译过程2.1 gcc安装2.2 编译lua2.3 编译srlua库 三、测试srlua库参考总结 前言 一、环境 centos7.9 gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) lua5.1源码 srlua 源码 二、编译过程 2.1 gcc安装 yum install gcc这里gcc安装过程和环…

基础学习-Git(分布式版本控制系统)

学习视频推荐 http://【黑马程序员Git全套教程&#xff0c;完整的git项目管理工具教程&#xff0c;一套精通git】 https://www.bilibili.com/video/BV1MU4y1Y7h5/?p5&share_sourcecopy_web&vd_source2b85bd9be9213709642d908906c3d863 1、Git环境配置 安装Git Git下…