多路复用IO

news2024/12/23 4:13:31

一。进程处理多路IO请求

在没有多路复用IO之前,对于多路IO请求,一般只有阻塞与非阻塞IO两种方式

1.1 阻塞IO

需要结合多进程/多线程,每个进程/线程处理一路IO
在这里插入图片描述
缺点:客户端越多,需要创建的进程/线程越多,相对占用内存资源较多

1.2 非阻塞IO

单进程可以处理,但是需要不断检测客户端是否发出IO请求,需要不断占用cpu,消耗 cpu 资源
在这里插入图片描述

二.多路复用IO简介

  • 本质上就是通过复用一个进程来处理多个IO请求
  • 基本思想:由内核来监控多个文件描述符是否可以进行I/O操作,如果有就绪的文件描述符,将结果告知给用户进程,则用户进程在进行相应的I/O操作
    在这里插入图片描述
    类似于下图的老师检查学生作业
    在这里插入图片描述

三.多路复用I/O方案

目前在Linux系统有三种多路复用I/O的方案

  1. select方案
  2. poll方案
  3. epoll方案

四.select 方案

4.1 设计思想

  • 通过单进程创建一个文件描述符集合,将需要监控的文件描述符添加到这个集合中
  • 由内核负责监控文件描述符是否可以进行读写,一旦可以读写,则通知相应的进程进行相应的I/O操作
    在这里插入图片描述

4.2 实现方式

select多路复用I/O在实现时主要是以调用 select 函数来实现

select 函数
函数头文件
#include <sys/select.h>

函数原型
int select(int nfds, fd_set *readfds,fd_set*writefds,fd_set *exceptfds,struct timeval *timeout);


函数功能
监控一组文件描述符,阻塞当前进程,由内核检测相应的文件描述符是否就绪,一旦有文件描述符就绪,将就绪的文件描述符拷贝给进程,唤醒进程处理


函数参数
nfds:最大文件描述符加1
readfds:读文件描述符集合的指针
writefds:写文件描述符集合的指针
exceptfds:其他文件描述符集合的指针
timeout:超时时间结构体变量的指针


函数返回值
成功:返回已经就绪的文件描述符的个数。如果设置timeout,超时就会返回0
失败:-1,并设置errno

操作文件描述符集合函数

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)
将文件描述符添加到文件描述符集合中


void FD_ZERO(fd_set *set)
将文件描述符集合清空


参数描述:
fd:文件描述符
set:文件描述符集合的指针

在这里插入图片描述
示例代码:使用select函数监控有名管道,如果有输入,则打印相应的信息

write.cpp

using namespace std;
#include<iostream>
#include<string>
#include <cstring>
#include<vector>
#include<deque>
#include<cstdio>
#include<ctime>
#include<deque>
#include<cstdlib>
#include<pthread.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH "/home/linuxfu/pipo"
int main(){
	int ret = access(PATH,F_OK);
	if(ret == -1){
		mkfifo(PATH,0644);
	}
	int fd = open(PATH,O_WRONLY);
	if(fd == -1){
		cout << "create failed" <<endl;
		exit(EXIT_FAILURE);
	} 
	char buf[128] = { 0 };
	while(1){
		memset(buf,0,sizeof(buf));
		cout << "请输入你想要输入的字符串:";
		fgets(buf,sizeof(buf) - 1,stdin);
		buf[strlen(buf) - 1] = '\0';
		ssize_t wtypes = write(fd,buf,size(buf));
		if(wtypes == -1){
			cout << "write failed" << endl;
			close(fd);
			exit(EXIT_FAILURE); 
		}
	}
       	close(fd);
	return 0;      	
}

select.cpp

using namespace std;
#include<iostream>
#include<string>
#include<vector>
#include<deque>
#include<ctime>
#include<deque>
#include<cstdlib>
#include<pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#define PATH "/home/linuxfu/pipo"
int main(){
	int ret = access(PATH,F_OK);
        if(ret == -1){
                mkfifo(PATH,0644);
        }
        int fd = open(PATH,O_RDONLY);
        if(fd == -1){
                cout << "create failed" <<endl;
                exit(EXIT_FAILURE);
        }
	fd_set rfds;
	FD_ZERO(&rfds);
	FD_SET(0,&rfds);
	FD_SET(fd,&rfds);
	struct timeval tv;
	tv.tv_sec = 5;
	tv.tv_usec = 0;
	fd_set temp_rfds;//备份
	struct timeval temp_tv;//备份
	while(1){
		temp_rfds = rfds;
		temp_tv = tv;
		int ret = select(fd + 1,&temp_rfds,NULL,NULL,&temp_tv);
		if(ret == -1){
			perror("ret");
		}else if(ret == 0){
			cout << "time out" << endl;
		}else{
			for(int i = 0;i < ret;i++){
				if(FD_ISSET(0,&temp_rfds)){
					string temp;
					cin >> temp;
					cout << temp << endl;
				}
				if(FD_ISSET(fd,&temp_rfds)){
					char buf[128] = { 0 };
					ssize_t rtype = read(fd,buf,size(buf));
					if(rtype > 0){
						printf("content:%s",buf);
					}
				}
			}
		}
	}
	
}

答案图片:
请添加图片描述
注意点:

  1. struct timeval结构体
struct timeval {
__kernel_old_time_t tv_sec; /* seconds *///秒数
__kernel_suseconds_t tv_usec; /* microseconds *///毫秒数
};

2.select 函数理解
在这里插入图片描述
3.超时时间的说明

  • 如果timeout之后,文件描述符集合中没有任何就绪的文件描述符,select函数就会返回0
  • 超时之后,timeout会被select函数修改,表示超时时间已经使用完。如果想继续使用超时时间,需要备份之前的struct timeval
  • 超时之后,表示没有就绪的文件描述符,此时文件描述符集合被赋值为空
    在这里插入图片描述
    因此,需要将之前的文件描述符集合进行备份。

4.3多路复用IO-select底层原理分析

文件描述符集合

typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;

数组的类型为 long int 类型,在 64 位系统中 long int 的大小为 8 个字节

typedef long int __fd_mask;

数组的大小为16 __FD_SETSIZE / __NFDBITS

#define __FD_SETSIZE 1024
#define __NFDBITS (8 * (int) sizeof (__fd_mask))

文件描述符集合的数组最终在存储时,使用了位图的方式来记录相应的文件描述符,具体原理如下:

  • 数组中没有直接存储文件描述符,而是使用某一位来表示该文件描述符是否需要监控
  • 需要监控的文件描述符需要转成数组的某一个元素的某一位,然后将对应的位设置为1,例如当 fd = 60 的成员需要监控,则需要将数组的第0个成员的第 [60] bit 设置为1,当 fd = 64时,则需要将数组的第1个成员的第[0] bit 设置为1

在这里插入图片描述

从上面的文件描述符集合内存管理可以分析出,select 最终只能存储1024个文件描述符

select 底层原理分析

在这里插入图片描述

.select() 函数中一共需要使用三个文件描述符集合,分别是

	1.in : 读文件描述符集合,主要包含 需要进行读的文件描述符的集合,反映在底层实际可以从设备中读取数据
	
	2.out : 写文件描述符集合,主要包含 需要进行写的文件描述符的集合,反映在底层实际可以将数据写入到设备中

	3.exp : 其他文件描述符集合,主要包含其他类型的操作的文件描述符集合

二.调用了select() 函数,内核做了如下事情:
	1.从用户空间将集合的文件描述符拷贝到内核空间
	2.循环遍历 fd_set 中所有的文件描述符,来检测是否有文件描述符可进行I/O操作
	 (1)如果有文件描述符可进行I/O操作,则设置返回的文件描述符集对应位1(res_in,res_out,res_exp),跳出循环,直接返回。最终会赋值给 in,out,exp 文件描述符集合
	 (2)如果没有文件描述符可进行I/O操作,则继续循环检测。如果设置 timeout ,则在超时后返回,此时select() 函数返回0.select() 函数减少了多进程/多线程的开销,但仍然有很多缺点:
	1.每次调用select()函数都需要将fd集合拷贝到内核空间,这个开销在fd很多时就越大
	2.每次都需要遍历所有的文件描述符集合,这个开销在fd很多时就越大
	3.支持的文件描述符只有1024

五.poll 方案

5.1 基本原理

多路复用poll的方式与select多路复用原理类似,但有很多地方不同,下面是具体的对比

  • 在应用层是以结构体struct pollfd数组的形式来进行管理文件描述符,在内核中基于链表对数组进行扩展;select方式以集合的形式管理文件描述符且最大支持1024个文件描述

  • 在这里插入图片描述

  • poll将请求与就绪事件通过结构体进行分开

  • select将请求与就绪文件描述符存储在同一个集合中,导致每次都需要进行重新赋值才能进行下一次的监控

  • 在内核中仍然使用的是轮询的方式,与 select 相同,当文件描述符越来越多时,则会影响效率

5.2 poll 方案

poll多路复用实现主要调用 poll 函数

函数头文件
#include <poll.h>

函数原型
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

函数功能
监控多个文件描述符的变化

函数参数
fds:sturct pollfd结构体指针
nfds:fds结构体的数量
timeout:超时时间,单位为ms


函数返回值
成功:大于0,返回就绪的文件描述符数量;=0,超时返回,没有文件描述符就绪
失败:-1,并设置errno

参数相关说明

  1. struct pollfd 结构体说明
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
  1. nfds_t 类型定义
typedef unsigned long int nfds_t;

poll 事件说明

在这里插入图片描述
示例代码:使用poll函数监控有名管道,如果有输入,则获取标准输入的内容并打印

write.cpp

using namespace std;
#include<iostream>
#include<string>
#include <cstring>
#include<vector>
#include<deque>
#include<cstdio>
#include<ctime>
#include<deque>
#include<cstdlib>
#include<pthread.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH "/home/linuxfu/pipo"
int main(){
	int ret = access(PATH,F_OK);
	if(ret == -1){
		mkfifo(PATH,0644);
	}
	int fd = open(PATH,O_WRONLY);
	if(fd == -1){
		cout << "create failed" <<endl;
		exit(EXIT_FAILURE);
	} 
	char buf[128] = { 0 };
	while(1){
		memset(buf,0,sizeof(buf));
		cout << "请输入你想要输入的字符串:";
		fgets(buf,sizeof(buf) - 1,stdin);
		buf[strlen(buf) - 1] = '\0';
		ssize_t wtypes = write(fd,buf,size(buf));
		if(wtypes == -1){
			cout << "write failed" << endl;
			close(fd);
			exit(EXIT_FAILURE); 
		}
	}
       	close(fd);
	return 0;      	
}

poll.cpp

using namespace std;
#include<iostream>
#include<string>
#include <cstring>
#include<vector>
#include<deque>
#include<cstdio>
#include<ctime>
#include<deque>
#include<cstdlib>
#include<pthread.h>
#include <unistd.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH "/home/linuxfu/pipo"
int main(){
	int ret = access(PATH,F_OK);
	if(ret == -1){
		mkfifo(PATH,0644);
	}
	int rfd = open(PATH,O_RDONLY);
	if(rfd == -1){
		cout << "create failed" <<endl;
		exit(EXIT_FAILURE);
	} 
	struct pollfd fds[2] = { 0 };
	fds[0].fd = 0;
	fds[0].events = POLLIN;
	fds[1].fd = rfd;
	fds[1].events = POLLIN;
	while(1){
		int ret = poll(fds,2,5000);
		if(ret == -1){
			perror("ret");
		}else if(ret == 0){
			cout << "time out" << endl;
		}else{
			for(int i = 0;i < 2;i++){
				if(fds[0].revents == POLLIN){
					char buf[128] = { 0 };
					fgets(buf,sizeof(buf) - 1,stdin);
					buf[strlen(buf) - 1] = '\0';
					cout << "buf:"<< buf << endl;
				} 
 				if(fds[1].revents == POLLIN){
					char buf[128] = { 0 };
					ssize_t wtypes = read(rfd,buf,sizeof(buf));
					if(wtypes > 0){
						cout << "字符串" << buf << endl;
					}
				}
			}
		}
	}
       	close(rfd);
	return 0;      	
}

请添加图片描述

6.epoll 方案

6.1epoll 基本原理

epoll相对于select与poll有较大的不同,主要是针对前面两种多路复用 IO 接口的不足
select/poll的不足:

  • select 方案使用数组存储文件描述符,最大支持1024个
  • select 每次调用都需要将文件描述符集合拷贝到内核中,非常消耗资源
  • poll 方案解决文件描述符存储数量限制问题,但其他问题没有得到解决
  • select / poll 底层使用轮询的方式检测文件描述符是否就绪,文件描述符越多,则效率越低

**epoll优点:

(1) epoll底层使用红黑树,没有文件描述符数量的限制,并且可以动态增加与删除节点,不用重复拷贝
(2)epoll底层使用callback机制,没有采用遍历所有描述符的方式,效率较高**

在这里插入图片描述

6.2 select/poll 方案图解

在这里插入图片描述

6.3 epoll 方案图解

在这里插入图片描述

6.4epoll 创建

1.epoll创建需要调用epoll_create函数,用于创建epoll实例

函数头文件
#include <sys/epoll.h>

函数原型
int epoll_create(int size);

函数描述
epoll_create() creates a new epoll(7) instance. Since Linux 2.6.8, the size argument is ignored, but must be greater than zero;

函数功能
创建一个epoll实例,分配相关的数据结构空间


函数参数
size:需要填一个大于0的数,从Linux 2.6.8开始,size参数被忽略


函数返回值
成功:返回epoll文件描述符
失败:返回-1,并设置errno

示例代码:创建一个epoll 实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
int main()
{
int epfd = epoll_create(1);
if(epfd == -1)
{
perror("epoll_create");
exit(EXIT_FAILURE);
}
printf("epfd=%d\n",epfd);
return 0;
}
  1. epoll 函数控制
    epoll控制函数主要用于文件描述符集合的管理,包括增加、修改、删除等操作。
    epoll_ctl函数详细信息如下
函数头文件
#include <sys/epoll.h>

函数原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

函数参数
epfd:epoll 实例
op:epoll 操作命令字
	EPOLL_CTL_ADD:在epoll实例中添加新的文件描述符(相当于向红黑树中添加节点),并将事件与fd关联
	EPOLL_CTL_MOD:更改与目标文件描述符fd相关联的事件
	EPOLL_CTL_DEL:从epoll实例中删除目标文件描述符fd ,事件参数被忽略


在系统中定义如下:
#define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */
#define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface.
*/
#define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure.
*/
fd:操作的文件描述符
event:struct epoll_event结构体对象指针

struct epoll_event 结构体定义如下:

ypedef union epoll_data {
	void *ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
} epoll_data_t;
struct epoll_event {
	uint32_t events; /* Epoll events */
	epoll_data_t data; /* User data variable */
};

events : epoll事件,事件具体定义如下:

enum EPOLL_EVENTS
{
EPOLLIN = 0x001, // 读事件有效(非常常用)
#define EPOLLIN EPOLLIN
EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
EPOLLOUT = 0x004, // 写事件有效(非常常用)
#define EPOLLOUT EPOLLOUT
EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
EPOLLERR = 0x008,
#define EPOLLERR EPOLLERR
EPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUP
EPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUP
EPOLLEXCLUSIVE = 1u << 28,
#define EPOLLEXCLUSIVE EPOLLEXCLUSIVE
EPOLLWAKEUP = 1u << 29,
#define EPOLLWAKEUP EPOLLWAKEUP
EPOLLONESHOT = 1u << 30,
#define EPOLLONESHOT EPOLLONESHOT
EPOLLET = 1u << 31 // 将EPOLL设为边缘触发(Edge Triggered)模式
#define EPOLLET EPOLLET
};

epoll_data是一个共用体,主要使用 fd 成员用于存储文件描述符

epoll 等待事件发生(关联的文件描述符就绪),这里调epoll_wait 函数
epoll_wait 函数详细信息如下:

函数头文件
#include <sys/epoll.h>

函数原型
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

函数功能
等待文件描述符关联的事件发生

函数参数
epfd:epoll实例对象
events:存储就绪集合的数组的地址
maxevents:就绪集合的最大值
timeout:超时时间


函数返回值
成功:返回就绪的文件描述符数量,超时返回0
失败:返回-1,并设置errno

6.5示例代码:

将有名管道描述符添加到epoll实例中,等待用户输入数据,如果没有则打印 timeout,否则获取用户输入并输出。

write.cpp

using namespace std;
#include<iostream>
#include<string>
#include <cstring>
#include<vector>
#include<deque>
#include<cstdio>
#include<ctime>
#include<deque>
#include<cstdlib>
#include<pthread.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH "/home/linuxfu/pipo"
int main(){
	int ret = access(PATH,F_OK);
	if(ret == -1){
		mkfifo(PATH,0644);
	}
	int fd = open(PATH,O_WRONLY);
	if(fd == -1){
		cout << "create failed" <<endl;
		exit(EXIT_FAILURE);
	} 
	char buf[128] = { 0 };
	while(1){
		memset(buf,0,sizeof(buf));
		cout << "请输入你想要输入的字符串:";
		fgets(buf,sizeof(buf) - 1,stdin);
		buf[strlen(buf) - 1] = '\0';
		ssize_t wtypes = write(fd,buf,size(buf));
		if(wtypes == -1){
			cout << "write failed" << endl;
			close(fd);
			exit(EXIT_FAILURE); 
		}
	}
       	close(fd);
	return 0;      	
}

epoll.cpp

using namespace std;
#include<iostream>
#include<string>
#include <cstring>
#include<vector>
#include<deque>
#include<cstdio>
#include<ctime>
#include<deque>
#include<cstdlib>
#include<pthread.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PATH "/home/linuxfu/pipo"
int main(){
	int ret = access(PATH,F_OK);
	if(ret == -1){
		mkfifo(PATH,0644);
	}
	int rfd = open(PATH,O_RDONLY);
	if(rfd == -1){
		cout << "create failed" <<endl;
		exit(EXIT_FAILURE);
	} 
	int epfd = epoll_create(1);
	if( epfd == -1){
		perror("epfd");
		exit(EXIT_FAILURE);
	}
	//将标准输入文件描述符添加到epoll中
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = rfd;
	int ret1 = epoll_ctl(epfd,EPOLL_CTL_ADD,rfd,&ev);
	if(ret1 == -1){
		perror("ret1");
		exit(EXIT_FAILURE);
	}
	struct epoll_event ev1;
	ev1.events = EPOLLIN;
	ev1.data.fd = 0;
	int ret2 = epoll_ctl(epfd,EPOLL_CTL_ADD,0,&ev1);
	if(ret2 == -1){
		perror("ret2");
		exit(EXIT_FAILURE);
	}
	struct epoll_event events[2];
	while(1){
		int res = epoll_wait(epfd,events,2,4000);
		if(res == -1){
			perror("res");
		}else if(res == 0){
			printf("timeout\n");
		}else if(res > 0){
			for(int i = 0;i < res;i++){
				if(events[i].data.fd == 0){
					char buf[128] = { 0 };
					fgets(buf,sizeof(buf)-1,stdin);
					buf[strlen(buf) - 1] = '\0';
					cout << "buf:" << buf << endl;
				}
				if(events[i].data.fd == rfd){
					char buf[128] = { 0 };
					ssize_t wtypes = read(rfd,buf,sizeof(buf));
					if(wtypes > 0){
						cout << "str:" << buf << endl;
					}
				}
			}
		}
	}
       	close(rfd);
	return 0;      	
}

请添加图片描述

6.6 epoll 底层结构

在这里插入图片描述

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

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

相关文章

python 图片加文字 文字自动上下左右居中 自动换行居中对齐

一.实现效果展示 二.代码 # -*- coding: utf-8 -*- # Time : 2024/9/26 17:22 # Author : Cocktail_pyfrom PIL import Image, ImageFont, ImageDrawdef split_string(s, num_parts):length len(s)chunk_size length // num_partsremainder length % num_partsparts […

数智化引领传媒新变革,又一场技术的盛宴!

文 | 智能相对论 作者 | 陈泊丞 2024巴黎奥运会在塞纳河畔为世界呈现了一场精彩绝伦的体坛盛宴。回顾今年的直播&#xff0c;每一个观众或许都可以在屏幕前感受到更胜往届的体育表现力和竞赛节目效果。 在超高清制播技术支持下&#xff0c;不仅制播画质变高清了&#xff0c;…

ASO关键词研究-9个步骤帮您找到应用商店优化的完美关键词

应用商店优化是一种让您的应用展示给更多用户的有效方法。为此&#xff0c;您需要在应用元数据中使用正确的关键字&#xff0c;以帮助 Apple 和 Google 的搜索算法知道何时在搜索结果中显示您的应用。如果您做得正确&#xff0c;他们会向更多人展示您的应用&#xff0c;而更多的…

python全栈学习项目案例(一)atm+购物车

atm购物车 文章目录 atm购物车一、项目需求二、项目构架图三、视图的实现四、登录认证五、转账功能六、购物功能 一、项目需求 二、项目构架图 其中用户功能层中有呈现给用户的视图、各个功能的简单函数(这些简单函数需要调用接口层中函数的具体功能) 接口层存放各个功能的具体…

自动化测试数据管理问题

在自动化测试中&#xff0c;关于数据管理的更好实践确实存在&#xff0c;这些实践可以帮助提高测试的效率和质量。以下是一些推荐的最佳实践&#xff1a;1. 使用Test Data Builder模式&#xff1a;这种模式可以帮助隐藏测试数据准备的细节&#xff0c;在测试代码中只显示地对测…

每日OJ题_牛客_NC1大数加法_高精度加法

目录 牛客_NC1大数加法_高精度加法 题目解析 C代码 Java代码 牛客_NC1大数加法_高精度加法 题目解析 模版类型的算法题&#xff0c;模拟加法列竖式运算的过程即可。 假定算法流程&#xff1a; 设定 i&#xff0c;j 两指针分别指向 s&#xff0c;t 尾部&#xff0c;模拟…

2024网络安全面试指南(非常详细)收藏这一篇就够了

1.1 网络安全行业现状 安全行业起步晚。安全行业整体起来才没几年&#xff0c;多数企业因为资源投入和建设时间原因导致覆盖面和深入度都不够&#xff0c;这其中甚至包括一些国内大厂&#xff0c;并没有想象的那么安全。其安全水位仅能应付一些白帽子级别&#xff0c;针对专业…

记录一次学习--委派攻击学习

目录 为什么要使用委派 什么账号可以使用委派 非约束性委派 这里有一张图 利用 流程 约束性委派 这里有一张图 如何利用 条件 具体流程 为什么要使用委派 这个是因为可能A服务需要B服务的支持&#xff0c;但是A服务的权限不可以使用B服务。然后这时就可以让域用户将…

2024年【烟花爆竹经营单位主要负责人】免费试题及烟花爆竹经营单位主要负责人考试技巧

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 烟花爆竹经营单位主要负责人免费试题是安全生产模拟考试一点通总题库中生成的一套烟花爆竹经营单位主要负责人考试技巧&#xff0c;安全生产模拟考试一点通上烟花爆竹经营单位主要负责人作业手机同步练习。2024年【烟…

吉林大学微机接口实验五:D/A转换

1.实验内容 2.实验原理/预备知识 D/A转换器TLC7528是关键&#xff0c;其用法参见&#xff1a; 芯片部件汇总&#xff1a;常用功能部件大全-CSDN博客 直接找"TLC7528 D/A数模转换器"&#xff08;实际上学校的讲义已经讲的很清楚&#xff0c;我只是给搬到了博客里&…

C++ | Leetcode C++题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; class Solution { public:Node* flatten(Node* head) {function<Node*(Node*)> dfs [&](Node* node) {Node* cur node;// 记录链表的最后一个节点Node* last nullptr;while (cur) {Node* next cur->next;// 如果有子节点…

【源码+文档+调试讲解】重庆旅游景点数据分析系统python

摘 要 重庆旅游景点数据分析系统是一个专门为旅游管理部门和景点运营商设计的信息化工具&#xff0c;它通过集成和分析各种数据来优化景点管理和提升游客体验。该系统能够实时收集游客流量、景点信息、满意度反馈等关键信息&#xff0c;帮助管理者洞察游客行为和市场趋势。系统…

C++ const成员函数

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 C const引用常量 使用规则 引用常量对象&#xff1a;可以引用一个常量对象&#xff0…

Unity 的Event的Use()方法

对于Event的Use方法&#xff0c;其在调用后将不会再判断同类型的事件 这种情况下&#xff0c;第二个MosueDown不会进入&#xff0c;因为已经Use 如果把Use注释掉 依旧能进入第二个MosueDown 也就是说当使用了Use方法&#xff0c;相同的事件类型不会进第二遍

【文心智能体】从零到一的优质智能体构建全攻略

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 创作平台&#xff1a;文心智能体平台&#xff1a; 基本配置智能体名称与简介人物设定回复逻辑prompt提示词的编写开场白设定与开场白…

利士策分享,领导者素养自检:五维审视法

利士策分享&#xff0c;领导者素养自检&#xff1a;五维审视法 在人生的广阔舞台上&#xff0c;每个人都蕴藏着成为领导者的潜力&#xff0c;但如何知晓自己是否已具备那份独特的素养&#xff1f; 这需要我们进行一次深刻而诚实的自我审视。 以下五个维度&#xff0c;或许能为你…

K8s Calico替换为Cilium,以及安装Cilium过程(鲁莽版)

迁移CNI插件的3种办法&#xff1a; 1、创建一个新的集群&#xff0c;通过Gitops的方式迁移负载&#xff0c;然而&#xff0c;这可能涉及大量的准备工作和潜在的中断。 2、另一种方法是重新配置/etc/cni/net.d/指向Cilium。但是&#xff0c;现有的pod仍将由旧的…

AI 驱动的数据库 TDSQL-C 实战与电商可视分析

目录 一、背景介绍二、实验介绍三、效果展示四、实操指导4.1 系统设计4.2 环境搭建4.2.1 购买 TDSQL-C Mysql Serverless 实例4.2.2 部署HAI高算力服务器本地python环境搭建 4.3 应用构建4.3.1 搭建项目框架4.3.2 TDSQL-C 数据库&HAI云算力配置4.3.3 应用开发4.3.4 运行程序…

将本地文件上传至虚拟机

1、查看虚拟机ip地址 ip addr 2、xshell连接上虚拟机 连接root连接不上的解决办法更改配置文件vim /etc/ssh/sshd_config 重启&#xff08;sudo service ssh restart&#xff09;并查看是否开启ssh服务&#xff08;sudo ps -e | grep ssh&#xff09; 即可连接成功 3、复制文…