Linux进程间通信---->共享内存

news2024/11/18 11:38:10

文章目录

    • 什么是共享内存
    • 共享内存基本原理
    • 和共享内存有关的系统接口
      • ftok
      • shmget
      • ipc相关命令
        • 查看相关共享内存信息
        • 删除相关共享内存信息
      • shmat/shmdt
      • shmctl
    • ipc系列设计思想
    • 总结

什么是共享内存

前面我们学习了管进程间通信的一种方式—>管道。 而我们今天将要介绍的共享内存也是进程间通信的方式。首先,我们要清楚,进程间通信的本质是两个进程看到同一份资源!那么共享内存的本质也是如此。只不过共享内存对于两个进程来说看到的是同一份内存资源! 接下来我们就来详细了解了解共享内存吧。

共享内存基本原理

接下来,我们通过图片来看一看共享内存的基本原理是什么:
在这里插入图片描述
和管道类似。共享内存对于Linux系统来说也是文件,这可真的是一切皆文件了!我们知道进程间通信的本质是两个进程看到同一份资源。对于管道而言是看到同一份管道文件,而对于共享内存看到的就是同一份内存! 但是这个时候有一个问题:管道是通过打开文件,返回文件fd的方式。这个fd是操作系统内核的数据。但是共享内存能不能采用这种策略呢? 答案当然是可以! 但是有一个问题:进程是具有独立性的!如果使用操作系统分配的数字给共享内存的话,那么就会存在一个问题:这个数据是操作系统内核的,如果每一个进程要用必须要通过系统调用获取,但是这么做显然是太麻烦了。 所以共享内存并没有采取和管道一样的策略。共享内存采用的方式是让用户指定对应的共享内存的编号,既然由用户指定生成,那么想让每一个进程看到也就不是什么麻烦的事情了---->主动权转移到用户手中了!

和共享内存有关的系统接口

前面我们铺垫了那么多,就是为了接下来介绍和共享内存相关的一系列接口。接下来我们就来介绍一下和共享内存相关的接口和命令

ftok

前面我们讲了,共享内存的编号是由用户指定的。那么这个编号是随心所欲的吗? 理论上来说是的。不过操作系统的设计者也提供了一个相关的接口给我们生成对应的编号。 这个就是我们要讲的ftok接口。我们先来看看手册里面对于这个函数的说明:
在这里插入图片描述
这个函数会根据对应的文件名和指定的proj_id生成一个对应的编号。生成编号的算法是通过大量的数学算法的验证,这个底层的算法逻辑我们不必关心。直接使用即可。

#include<iostream>
#include<unistd.h>
//ftok所在的头文件
#include<sys/ipc.h>
#include<sys/types.h>
//定义用于生成共享内存编号的路径
#define PATH "/home/chy/test"
//提供proj_id--->随便给
#define PROJ_ID 0x22

key_t createKey()
{
   //使用ftok函数生成
   key_t key=ftok(PATH,PROJ_ID);
   return key;
}
int main()
{ 
  key_t key=createKey();
  log()<<"create key id is "<<key<<" : " <<strerror(errno)<<std::endl;
  return 0;
}

在这里插入图片描述
可以看到,这个ftok函数确实给我们生成了一个key。而且从这个key的值来看,确实很具有随机性!

shmget

有了ftok函数给我们生成的key,接下来我们就可以根据这个key来生成共享内存了。下面正式介绍我们创建共享内存的系统接口:shmget
我们首先先来看手册里面对于shmget函数的介绍:
在这里插入图片描述
前面两个参数一个是key,一个是我们需要的内存大小。这些都没有什么特别的地方,而第三个参数才是我们研究的重头戏第三个参数是shmflg,从名字上来看就是一个标志。而这样类似的参数,我们早在文件操作那块就见过了!所以第三个参数的底层也是使用位图实现的! 而系统的设计者也给我们提供了对应的两个参数:

IPC_CREAT:如果共享内存不存在就创建,如果存在就返回已经存在的共享内存的编号
IPC_EXCL::如果共享内存不存在就创建,如果存在就出错返回!

而通常第二个参数都要配合第一个参数进行使用!那么可能有聪明的读者就会问:既然都要配合第一个参数使用,为什么还要多此一举呢? 实际上,这个第二个选项能够保证我们每次创建的共享内存都是最新的!也就是每次返回的内存的编号都是最新的。接下来,我们来使用一下这个接口。

#include<iostream>
#include<string>
#include<ctime>
#include<cstdlib>
#include<cstring>
#include<cerrno>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
//定义用于生成共享内存编号的路径
#define PATH "/home/chy/test"
//提供proj_id--->随便给
#define PROJ_ID 0x22
#define MEM_SIZE 4096
#include "log.hpp"
/*
 * 在生成共享内存之前,需要先生成共享内存的编号
 * */
key_t createKey()
{
   //使用ftok函数生成
   key_t key=ftok(PATH,PROJ_ID);
   return key;
}
int main()
{ 
  key_t key=createKey();
  int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
  log()<<"shmid is " << shmid << "err_msg: "<<strerror(errno)<<std::endl;
  return 0;
}

在这里插入图片描述
接下来我们来看一看这个接口的返回值:man手册是这么说明返回值的:
在这里插入图片描述
如果成功:返回的是共享内存的编号 奇怪,为什么不直接返回我们先前使用的key呢?原因是因为这个key纯粹是给我们用户使用的!就如同文件名和inode的关系一样,文件名只是为了方便我们用户使用。而操作系统底层识别文件用的是inode 由于共享内存的底层是文件,所以这个返回值从某种层面上来说也可以认为是先前我们学习的文件描述符。

ipc相关命令

接下来我们介绍有关共享内存操作的命令。主要包括:使用命令查看共享内存的命令,还有使用命令删除共享内存

查看相关共享内存信息

首先我们先来看第一个命令,查看共享内存的相关命令:

ipcs -m #查看系统中所有的共享内存信息

在这里插入图片描述
这里要特别注意:共享内存的生命周期是不随进程的!换句话说,如果不使用系统调用接口或者是命令回收共享内存,共享内存就会一直存在! 也就是真正意义上的"内存泄漏!" 所以,我们在使用共享内存的时候一定要注意内存泄漏的问题。另外,关于共享内存的大小,我们一般推荐使用4kb或者是4kb的整数倍,因为操作系统都是以4kb为基本单位进行分配的!即使你要4097b的空间,操作系统也是给8kb空间 所以为了能够让空间能够最大化利用,所以我们建议都是给4kb或者4kb的整数倍

删除相关共享内存信息

接下来我们来看一看删除共享内存的相关操作。删除共享内存一般使用以下的命令:

#删除对应的共享内存
ipcrm -m shmid

在这里插入图片描述
补充:ipcs其实是一套标准,不仅有共享内存,还有信号量,消息队列!以下是具体的对应的选项参数:

ipcs -m #共享内存
ipcs -s #信号量
ipcs -q #消息队列

感兴趣的读者可以去额外学习信号量和消息队列。我们这里不额外进行说明了,还是继续以共享内存为主继续讲解

shmat/shmdt

接下来,我们创建了共享内存。但是此时我们并不能直接使用共享内存!原因是虽然是你创建了共享内存,但是你没有共享内存的使用权!如果要使用共享内存需要先把你挂接到共享内存上。 然后接下来就和 使用malloc和new向系统申请的内存一样进行使用就可以了。我们首先先来看man手册里面对于shmat的介绍:
在这里插入图片描述
我们接下来在看看对于返回值的说明:
在这里插入图片描述
第一个参数就是shmget返回的shmid,第二个参数是我们要挂接到共享内存的哪一个地方。对于我们初学者来说,这个参数设置成nullptr即可,让系统自动帮我们挂接到合适的地方就可以了。第三个参数就是读写权限。 我们直接上代码:

#include "log.hpp"
/*
 * 在生成共享内存之前,需要先生成共享内存的编号
 * */
key_t createKey()
{
   //使用ftok函数生成
   key_t key=ftok(PATH,PROJ_ID);
   return key;
}
int main()
{ 
  key_t key=createKey();
  int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
  log()<<"shmid is " << shmid << "err_msg: "<<strerror(errno)<<std::endl;
  //挂接共享内存
  shmat(shmid,nullptr,0666);
  return 0;
}

挂接内存以后,我们就可以正常使用对应的共享内存了,接下来我们就使用共享内存来模拟以以下server和client之间的通信:

#include "log.hpp"
//作为客户端,只要使用共享内存即可
key_t createKey()
{
  key_t key=ftok(PATH,PROJ_ID);
  return key;
}
int main()
{
  key_t key=createKey();
  //获取共享内存 ---这时候存在获取即可
  int shmid=shmget(key,MEM_SIZE,IPC_CREAT);
  //打印日志
  log()<<"shmid is :" <<shmid <<"| "<<strerror(errno)<<std::endl;
  char* str=(char*)shmat(shmid,nullptr,0);
  log()<<"shmat status :" <<strerror(errno)<<std::endl;
  //客户端从键盘读取数据发送给服务端
  while(true)
  {
     printf("getmessage from keyboard is :");
     fflush(stdout);
     ssize_t s=read(0,str,MEM_SIZE);
     str[s]='\0';
     sleep(1);
  }
  return 0;
}
//server
#include "log.hpp"
/*
 * 在生成共享内存之前,需要先生成共享内存的编号
 * */
key_t createKey()
{
   //使用ftok函数生成
   key_t key=ftok(PATH,PROJ_ID);
   return key;
}
int main()
{ 
  key_t key=createKey();
  int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
  log()<<"shmid is " << shmid << "err_msg: "<<strerror(errno)<<std::endl;
  //挂接共享内存 
  char* str=(char*)shmat(shmid,nullptr,0);
  log()<<"shmat status"<<" | "<< "err_msg: "<<strerror(errno)<<std::endl;
  //服务器端从str读取数据并打印到显示器
  while(true)
  { 
    std::cout<<"I am server"<<"I am reading for message from client"<<std::endl;
    write(0,str,MEM_SIZE);
    sleep(1); 
  }
  return 0;
}

在这里插入图片描述
我们可以看到:和管道不同,即使客户端不发送消息,服务器端也是不停地在往显示器写入。也就是共享内存这种通信方式是不会自带任何访问控制的!所以共享内存方式的进程通信也是所有进程间通信速度最快的一种通信方式! 而接下来我们所有写入客户端的消息都会立刻被服务器端读取到!
在这里插入图片描述
如果想要去除关联的话,使用的接口是shmdt接口,对应的说明如下:
在这里插入图片描述
shmdt的参数就是前面shmat返回的共享内存的地址。
而如果想要在共享内存种里面拥有和管道一样的访问控制:我们可以考虑在共享内存中让两个进程使用命名管道进行通信。这样就拥有了访问控制。 感兴趣的读者可以自行尝试添加管道进行访问控制

shmctl

前面我们讲过,共享内存如果不自己手动删除的话是会有很严重的内存泄露问题的。那么前面我们都是使用命令进行删除。 不仅我们会经常忘记做这件事,而且这件事情使用命令也是十分繁琐。那么Linux系统也给我们提供了一个可以删除共享内存的系统调用---->shmctl。 我们先来看一看手册中是如何对shmctl接口进行介绍的。
在这里插入图片描述
shmctl有三个参数:第一个是shmid,第二个参数是我们要执行的命令选项, 第三个我们设置成nullptr即可。 而这个调用的第二个参数非常有讲究,有如下的选项:
在这里插入图片描述
而我们要删除共享内存的话只要把cmd参数设置成IPC_RMID就可以了。

#include "log.hpp"
//作为客户端,只要使用共享内存即可
key_t createKey()
{
  key_t key=ftok(PATH,PROJ_ID);
  return key;
}
int main()
{
  key_t key=createKey();
  //获取共享内存 ---这时候存在获取即可
  int shmid=shmget(key,MEM_SIZE,IPC_CREAT);
  //打印日志
  log()<<"shmid is :" <<shmid <<"| "<<strerror(errno)<<std::endl;
  char* str=(char*)shmat(shmid,nullptr,0);
  log()<<"shmat status :" <<strerror(errno)<<std::endl;
  //客户端从键盘读取数据发送给服务端,现在设置如果客户端输入quit就要退出了
  while(true)
  {
     printf("getmessage from keyboard is :");
     fflush(stdout);
     ssize_t s=read(0,str,MEM_SIZE);
     //处理多余的'\nn'
     str[s-1]='\0';
     if(strcasecmp("quit",str)==0)
     {
         std::cout<<"Client quit!"<<std::endl;
         break;
     }
     sleep(1);  
  }
  //退出以后,客户端去关联即可 
  shmdt(str);
  return 0;
}
#include "log.hpp"
/*
 * 在生成共享内存之前,需要先生成共享内存的编号
 * */
key_t createKey()
{
   //使用ftok函数生成
   key_t key=ftok(PATH,PROJ_ID);
   return key;
}
int main()
{

  key_t key=createKey();
  int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
  log()<<"shmid is " << shmid << "err_msg: "<<strerror(errno)<<std::endl;
  //挂接共享内存 
  char* str=(char*)shmat(shmid,nullptr,0);
  log()<<"shmat status"<<" | "<< "err_msg: "<<strerror(errno)<<std::endl;
  //服务器端从str读取数据并打印到显示器
  while(true)
  { 
    std::cout<<"I am server"<<"I am reading for message from client"<<std::endl;
    //std::cout<<str<<std::endl;
    if(strcasecmp("quit",str)==0)
    {
       std::cout<<"client quit,i will quit now"<<std::endl;
       break;
    }
    write(0,str,MEM_SIZE);
    sleep(1);
  }
  //服务端退出:去关联+删共享内存
   shmdt(str);
   int ret=shmctl(shmid,IPC_RMID,nullptr);
   log()<<"shdel ! the num is "<<ret<<"| "<<"err_msg: "<<strerror(errno)<<std::endl;
  return 0;
}

在这里插入图片描述
从运行结果可以看出:确实使用了shmctl接口以后就把对应的共享内存给删除了。这样我们就可以在代码中回收共享内存,做到一劳永逸。 不过注意:如果共享内存还有和对应的进程关联的时候被删除了,并不会马上被删除!而是对应关联着的进程看见的key值变成了0,不过建议要删除共享内存以前还是要把相关联的进程先取消关联才会更好一点。

ipc系列设计思想

前面我们知道,ipc是一个设计的系列。既然能够谈上是一个系列,那么必然代表设计的结构类似。接下来我们来看一看内核中的ipc系列的结构:

//shmid的结构
struct shmid_ds {
	struct ipc_perm		shm_perm;	/* operation perms */
	int			shm_segsz;	/* size of segment (bytes) */
	__kernel_time_t		shm_atime;	/* last attach time */
	__kernel_time_t		shm_dtime;	/* last detach time */
	__kernel_time_t		shm_ctime;	/* last change time */
	__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */
	__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */
	unsigned short		shm_nattch;	/* no. of current attaches */
	unsigned short 		shm_unused;	/* compatibility */
	void 			*shm_unused2;	/* ditto - used by DIPC */
	void			*shm_unused3;	/* unused */
};
//semid结构
struct semid_ds {
	struct ipc_perm	sem_perm;		/* permissions .. see ipc.h */
	__kernel_time_t	sem_otime;		/* last semop time */
	__kernel_time_t	sem_ctime;		/* last change time */
	struct sem	*sem_base;		/* ptr to first semaphore in array */
	struct sem_queue *sem_pending;		/* pending operations to be processed */
	struct sem_queue **sem_pending_last;	/* last pending operation */
	struct sem_undo	*undo;			/* undo requests on this array */
	unsigned short	sem_nsems;		/* no. of semaphores in array */
};
//msqid
struct msqid_ds {
	struct ipc_perm msg_perm;
	struct msg *msg_first;		/* first message on queue,unused  */
	struct msg *msg_last;		/* last message in queue,unused */
	__kernel_time_t msg_stime;	/* last msgsnd time */
	__kernel_time_t msg_rtime;	/* last msgrcv time */
	__kernel_time_t msg_ctime;	/* last change time */
	unsigned long  msg_lcbytes;	/* Reuse junk fields for 32 bit */
	unsigned long  msg_lqbytes;	/* ditto */
	unsigned short msg_cbytes;	/* current number of bytes on queue */
	unsigned short msg_qnum;	/* number of messages in queue */
	unsigned short msg_qbytes;	/* max number of bytes on queue */
	__kernel_ipc_pid_t msg_lspid;	/* pid of last msgsnd */
	__kernel_ipc_pid_t msg_lrpid;	/* last receive pid */
};
//ipc_perm
struct ipc_perm
{
	__kernel_key_t	key;
	__kernel_uid_t	uid;
	__kernel_gid_t	gid;
	__kernel_uid_t	cuid;
	__kernel_gid_t	cgid;
	__kernel_mode_t	mode; 
	unsigned short	seq;
};

我们看到对应的ipc系列的三个数据结构的第一个字段都是struct ipc_perm,实际上,这里这么设计就是有意而为之,整个内核中的维护关系大致如下图:
在这里插入图片描述
内核里面也是通过数组来维护ipc资源。但是内核维护的是struct ipc_perm类型的指针数组。而每一个系列的ipc系列的资源的第一个字段都是struct ipc_perm。又因为结构体第一个字段的地址就是整个结构体的地址,所以只要拿到了第一个字段的地址,那么就相当于拿到了整个结构体的地址。使用的时候就根据具体类型强制类型转化就可以访问其他字段了。 而对于内核来说,内核看到的永远都是struct ipc_perm类型的地址。这个就是面向对象编程中的切片技术。只不过这里使用的是C语言来实现,而面向对象的编程语言天然支持这种切片转换而已。 不得不说,Linux内核的机制设计的是真的非常优秀!

总结

1.共享内存是进程间通信的一种方式
2.共享内存需要程序员显式使用接口或者命令回收,否则会造成内存泄露问题
3.共享内存没有任何访问机制控制,所以在所有进程间通信中共享内存最快。
4.共享内存创建了以后不能马上使用,必须使用shmat接口挂接。
5.删除共享内存之前需要使用shmdt取消所有的关联。

以上就是本文的主要内容,如有不足之处还望指出。希望大家一起共同进步。

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

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

相关文章

10天,几万字,源码深度解析之 Spring IOC

历时 10 天&#xff0c;终于把 Sping 源码系列写完了&#xff0c;该系列一共 5 篇&#xff0c;后续会整理成 PDF 教程&#xff0c;本文是最后一篇。 这篇文章主要讲解 IOC 容器的创建过程&#xff0c;让你对整体有一个全局的认识&#xff0c;文章没有复杂嵌套的 debug 流程&am…

SA实战 ·《SpringCloud Alibaba实战》第06章-快速搭建三大微服务并完成交互开发与测试

作者:冰河 星球:http://m6z.cn/6aeFbs 博客:https://binghe.gitcode.host 文章汇总:https://binghe.gitcode.host/md/all/all.html 大家好,我是冰河~~ 在《SpringCloud Alibaba实战》专栏中前面的文章,我们为开发用户微服务、商品微服务和订单微服务做了充分的准备。今天…

微服务 SpringBoot 整合 Redis GEO 实现附近商户功能

文章目录⛄引言♨️广播站一、Redis GEO 数据结构用法⛅GEO基本语法、指令⚡使用GEO存储经纬度、查询距离二、SpringBoot 整合Redis 导入 店铺数据 到GEO三、SpringBoot 整合 Redis 实现 附近商户功能☁️需求介绍⚡核心源码✅附近商户效果图⛵小结⛄引言 本文参考黑马 点评项…

Spring之xml方式整合第三方框架

目录 一&#xff1a;概述 二&#xff1a;代码演示 二&#xff1a;Spring整合MyBatis的原理剖析 三&#xff1a;案例演示 一&#xff1a;概述 xml整合第三方框架有两种整合方案&#xff1a; 不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如&…

VScode连接本地Docker

一、安装VScode和Docker 1、vscode的安装 官网下载到最新的X64安装包&#xff0c;然后使用下述命令进行安装&#xff1a; dpkg -i code_1.73.1-1667967334_amd64.deb 安装成功之后&#xff0c;可以在应用的安装列表中查看到。 2、docker的安装 同上 二、创建docker的用户…

使用nginx临时搭建rtmp服务器

使用nginx临时搭建rtmp服务器 文章目录使用nginx临时搭建rtmp服务器系统环境搭建步骤RTMP服务验证由于需要研究rtmp协议交互方式及报数据格式&#xff0c;使用nginx临时搭建一个rtmp服务器&#xff0c;主要通过nginx的rtmp扩展模块实现接收RTMP推送的音视频流&#xff0c;同时提…

【C++】缺省参数

其实在C基础一文中已经介绍过了缺省参数&#xff0c;但是每次用这玩意都是很迷&#xff0c;今天趁着复习c知识&#xff0c;再来总结一下缺省参数。 &#x1f308;1.缺省参数知识图&#xff1a; 看来看去也就这么多的知识点&#xff0c;接下来就一一介绍一下&#xff1a; &…

prometheus监控微服务端口和主机存活

简介&#xff1a; BlackBox Exporter 顾名思义就是在应用程序的外部对其进行探测&#xff0c; 支持 HTTP、HTTPS、DNS、TCP、ICMP等方式对目标进行检测。 官方下载链接 https://github.com/prometheus/blackbox_exporter/releases/download/v0.21.1/blackbox_exporter-0.21.…

CRM管理系统软件哪家好?

规模不大的企业&#xff0c;往往抗风险能力较差、资金不足、员工也相对比较少&#xff0c;此时&#xff0c;客户资源&#xff0c;客户开发往往成为企业生存的基础。 对于企业&#xff0c;一款合适的CRM客户管理系统&#xff0c;绝对是小规模企业的必备工具&#xff0c;可以帮助…

自除数判断,除自身以外数组乘积,[ ]操作符,二维数组内存存储计算,有关进制转换与取数字每一位的问题

tips 1. 表达式求值的时候&#xff0c;首先当然是从左往右看&#xff0c;确定优先级&#xff08;只针对相邻操作符才有意义&#xff09;&#xff0c;相邻操作符按照优先级高低计算&#xff0c;如果&#xff08;相邻&#xff09;操作符的优先级相同&#xff08;也就是两个操作符…

4G低功耗摄像头模组如何快速唤醒拍照

对于应用在野外恶劣环境&#xff0c;无电无网络的情况下&#xff0c;需要一款能支持太阳能供电或者电池供电&#xff0c;不过前提是&#xff0c;功耗需要足够低&#xff0c;还需要能支持无线网络&#xff0c;能上传图片回到服务器&#xff0c;用于监测一些野外作业的数据&#…

Vue.set()的使用,以及对其进行深入解析

目录 Vue.set()使用 Vue.delete()的使用 Vue.set()方法原理解析 总结 Vue.set()使用 vue 在实例上添加新的属性的时候&#xff0c;该属性&#xff0c;并不是响应式的。同样删除某一属性的时候&#xff0c;也不会实时渲染到页面上。 比如&#xff1a; <p> 年龄&#x…

Python开发案例之用Python子进程关闭Excel自动化中的弹窗

利用Python进行Excel自动化操作的过程中&#xff0c;尤其是涉及VBA时&#xff0c;可能遇到消息框/弹窗&#xff08;MsgBox&#xff09;。此时需要人为响应&#xff0c;否则代码卡死直至超时 [^1] [^2]。根本的解决方法是VBA代码中不要出现类似弹窗&#xff0c;但有时我们无权修…

在专网建设场景,LoRa和NB的技术优劣对比

先说结论&#xff1a;运营商在大铺NB&#xff0c;LoRa更适用于专网。 对于某个企业或者组织的实际应用来说&#xff0c;最后很可能是nb做骨架&#xff0c;lora做补充&#xff0c;混合应用。除非是nb在覆盖继续完善做到无死角 其实&#xff0c;对于物联网复杂的应用场景来说&am…

国产的内网穿透工具也很优秀,这10款工具推荐正在寻找的你!

什么是内网穿透&#xff1f; 首先&#xff0c;我们生活中的网络从应用上可以分为内网和外网&#xff1b; 内网就是你自己的网络环境&#xff0c;就你自己能访问&#xff0c;比如你本地测试进行的localhost&#xff1b; 外网就不言而喻了&#xff0c;你看网页&#xff0c;视频…

利用vite创建vue3工程

目录 什么是vite 优势&#xff1a; 简单理解&#xff1a; 1、创建工程 2、进入工程目录&#xff0c;安装依赖 3、启动​编辑 什么是vite 官方创建的前端构建工具 优势&#xff1a; 1开发环境中&#xff0c;无需打包操作&#xff0c;可快速冷启动 2轻量快速的热重载 3真…

Word文件加密的方法有哪些?两种方法告诉你

日常生活工作中&#xff0c;我们经常会使用到Word文档。有时里面有些比较重要的内容&#xff0c;我们不想别人随便可以更改我们输入的内容、窥探我们的隐私&#xff0c;我们该怎么做&#xff1f;建议给你的word文件加密&#xff0c;这样就能更好保护我们的信息。 操作环境&…

C语言论坛系统[2023-01-03]

C语言论坛系统[2023-01-03] 论坛系统设计 课程说明 需要提交的内容包括两个部分。 第一部分&#xff0c;对代码功能的讲解。 课设要求最后每个同学录制一个讲解视频&#xff0c;对着自己代码的功能进行讲解。 讲解时&#xff0c;主要涉及一个几个标准步骤&#xff1a; 步骤一…

【实操篇】Linux定时任务调度

目录 ●crond任务调度 简要介绍 基本语法 常用选项 参数细节说明 典型案例 应用实例 ●crond任务调度 简要介绍&#xff1a; 任务调度&#xff0c;它是指系统在某个特定时间去执行的特定命令或程序。它分为两类&#xff0c;第一类为系统工作&#xff08;一些周…

自动驾驶数据集(一):KITTI数据集介绍

如有错误&#xff0c;恳请指出。 文章目录0. 数据集下载1. 标注数据label_22. 校准数据calib3. 点云数据velodyne4. 图像数据image_20. 数据集下载 KITTI数据集的下载地址&#xff1a;https://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark3d&#xff0c;下载…