Linux系统编程(三)进程间通信(IPC)

news2024/11/17 21:37:23

本文目录

  • 一、linux 进程之间的通信种类
  • 二、管道
    • 1. 管道的概述
    • 2. 什么是管道文件?
    • 3. 管道的特点
    • 4. 管道类型
      • (1)无名管道(pipe)
      • (2)有名(命名)管道(fifo)
  • 三、信号(signals)
    • 1. 信号发送相关函数
    • 2. 信号接收处理
  • 四、消息队列
    • 1. Linux中的消息队列有两种类型
    • 2. 消息队列与有名管道(FIFO)的异同点
    • 3. System V IPC 机制消息队列相关函数
    • 4. 使用例程
  • 五、共享内存
    • 1. 共享内存原理
    • 2. 共享内存特点
    • 3. 相关函数
    • 4. 使用例程

  

一、linux 进程之间的通信种类

序号通信方式描述
1管道(无名管道、有名管道)无名管道允许亲缘关系进程间的通信。有名命名管道还允许无亲缘关系进程间通信。
2信号 signal在软件层模拟中断机制,通知进程某事发生。它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。
3消息队列 Messge Queue是消息的链接表,包括 posix 消息队列和 SystemV 消息队列。它克服了前两种通信方式中信息量有限的缺点。
4共享内存 Shared memory可以说是最有用的进程间通信方式,是最快的可用 ipc 形式。是针对其他通信机制运行效率较低而设计。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
5信号量Semaphore进程间同步。主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。
6套接字 socket用于网络中不同机器间进程通信。

二、管道

1. 管道的概述

   管道好比一条水管,有两个端口,一端进水,另一端出水。 管道是 Linux 进程间通信的一种方式,如管道命令 ls -l | grep anaconda3,意思是从ls -l中搜索含有anaconda3的文件内容。

2. 什么是管道文件?

   我们软件的管道文件也有两个端口,分别是读端和写端。进水可看成数据从写端被写入,出水可看数据从读端被读出。

3. 管道的特点

(1)管道通信是单向的,有固定的读端和写端;
(2)数据被进程在管道读出后,管道中的数据就不存在了;
(3)当进程去读取空管道的时候,进程会阻塞;
(4)当进程往满管道写入数据时,进程会阻塞;
(5)管道容量为 64KB;

4. 管道类型

   管道类型分为无名管道命名(有名)管道两类。无论是哪种管道我们都用 read、 write 函数来对管道进行读写。对于不同的管道类型有不同的方法。
   对于无名管道,由于读端和写端处于血缘关系的进程中(同一个main函数中),所以必须要知道读写两端分别对应的文件描述符。
   而对于命名(有名)管道,读端和写端处于毫无关系的两个进程中,所以需要创建一个文件作为管道,来对文件进行读写操作。需要注意:管道文件不支持创建在共享目录下,因为共享目录也属于windows。

(1)无名管道(pipe)

   无名管道用于在一个main里的进程中,必须是父子进程或兄弟进程(一个父进程创建的多个子进程之间的关系)。
   例如创建无名管道时,我们使用pipe()来创建无名管道。对于无名管道的读写文件描述符我们通常保存在一个有两个整型元素的数组中,如 int fds[2]。然后调用函数 pipe(fds),这个函数会创建一个管道,并且数组 fds 中的两个元素会成为管道读端和写端对应的两个文件描述符。即 fds[0]为读端文件描述符, fds[1]为写端文件描述符。

无名管道的特点:
① 只能在亲缘关系进程间通信(父子或兄弟)。
② 半双工(固定的读端和固定的写端)。
③ 它是特殊的文件,可以用 read、write 等函数操作,这种文件只能在内存中。

   管道两端的关闭是有先后顺序的。如果先关闭写端则从另一端读数据时,read 函数将返回 0,表示管道已经关闭;但是如果先关闭读端,则从另一端写数据时,将会使写数据的进程接收到 SIGPIPE 信号,如果写进程不对该信号进行处理,将导致写进程终止,如果写进程处理了该信号,则写数据的 write 函数返回一个负值,表示管道已经关闭。

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

//                                进程间通信


int main(int argc,char **argv)
{
     int rec;
	 int pipefd[2];             //pipefd[0]读    pipefd[1]写
     rec= pipe(pipefd);
	 
	 if(rec < 0)
 	 {
       printf("error!\n");
     }
	 
	 pid_t pid;
	 pid=fork();                       //创建进程
	 int i=0;
	 if(pid==0)
	 	{                       //子进程写
	      char buff[64];
		   while(1)
		  	{ 
		  	  i++;
			  memset(buff,0,strlen(buff));             //清空数组
			  fgets(buff, sizeof(buff),stdin);         //从终端获取字符给buff
			  write(pipefd[1], buff, sizeof(buff));    //将buff数组写入管道
			  if(i==3) break;
	        }
		   close(pipefd[1]);                        //关闭管道
		   close(pipefd[0]);
		   exit(0);                                //退出子进程 
	    }
	 
	 else if(pid>0)
	 	{                   //父进程读
	 
	      char data[64];
		  int n;
		  wait(&n);                               //等待子进程结束
		  while(1)
		 	 {    i++;
				  memset(data,0,strlen(data));
				  read(pipefd[0], data, sizeof(data));    //从管道中读取内容传给data数组 
				  printf("%s",data);
				  if(i==3) break;
		 	  }
				  close(pipefd[1]);
				  close(pipefd[0]);
				  exit(0);      
		 }
	 else 
	 	{
         printf("error!\n");
	    }
}

(2)有名(命名)管道(fifo)

   无名管道只能在亲缘关系的进程间通信,这大大限制了管道的使用,有名管道突破了这个限制,通过指定路径名的形式实现不相关进程间的通信。
   这里我们使用两个不相关的进程分别来进行通信,即使用两个命令窗口分别运行管道读端的程序和写端的程序。首先我们需要在写端使用命令mkfifo创建管道。其第一个参数为创建的管道文件名,第二个参数为文件的权限。

问题:既然是创建文件作为管道,那么这个管道文件和普通文件有什么区别呢?
   答:普通文件:用于存储数据,数据可以随机访问,可以读写多次,数据在文件关闭后依然存在。命名管道:用于进程间通信,数据是流式的,主要用于一次性读写,数据在被读取后即被移除,不持久存储。数据以流的形式传输,写入的数据只能按顺序读取,通常一个进程写入数据后,另一个进程立即读取。

●有名管道写端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

//   有名管道:用于两个无亲属关系的进程间的通信。 例如fifo_w 和fifo_r 间的通信。
int main(int argc, char **argv)
{
   int fd; 
   char buff[64];    
   
   //创建一个有名管道文件,文件权限为可读可写
   mkfifo("/tmp/fifo.cmd",0666); 
    
   //系统io来打开文件  ,不是标准io。打开有名管道的写端。
   fd=open("/tmp/fifo.cmd",O_WRONLY);
   
   if(fd < 0) printf("error!\n");
   
  while(1)
  	{  
       memset(buff, 0, sizeof(buff));
       fgets(buff, sizeof(buff), stdin);         //从终端获取字符给buff
       write(fd, buff, strlen(buff));
   }
   
   close(fd);
  
	//删除有名管道文件
   // unlink("/tmp/fifo.cmd");  

   return 0;
}

●有名管道读端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

//管道文件不支持创建在共享目录下,因为共享目录也属于windows
//有名管道:无亲属关系的进程间通信(不在同一个main函数中的进程)
//  fopen:标准io           open:系统io

int main(int argc, char **argv)
{
   int fd; 
   char buff[64];
   
   //打开有名管道的读端(因为写端已经创建了管道文件,所以读端只需要打开就行)
   fd = open("/tmp/fifo.cmd", O_RDONLY);   
   
   if(fd < 0) printf("error!\n");
   
   while(1)
  	{  
       memset(buff,0,sizeof(buff));
       read(fd, buff, sizeof(buff));    //read 必须用 sizeof!!
	   printf("%s",buff);
   }
   
   close(fd);
	
	//删除有名管道文件
	// unlink("/tmp/fifo.cmd");	
}

三、信号(signals)

   在Linux中,信号是一种进程间通信机制,用于通知进程某些事件的发生。信号是一种异步的通知机制,当一个进程接收到信号时,可以选择处理该信号、忽略它或执行默认的操作。信号在系统编程中非常重要,常用于控制进程行为、处理异常情况和执行进程间通信。

信号类型如下:
   对信号进行处理时,使用信号名称和使用信号编号效果相同。在这里插入图片描述

1. 信号发送相关函数

(1)向指定进程发送信号

   在函数中可以使用下面代码,在命令行中可以使用kill -信号编号 进程PID号,来向指定的进程发送指定的信号。我们可以在命令行使用ps -ef来查看当前所有进程的详细信息。

int kill(pid_t pid, int sig); 
/*当 pid>0 将信号发送给指定进程;
当 pid==0 时,将信号发送给同组进程;
当 pid<0 时,将信号发送给进程组 ID 等于 pid 绝对值的进程;
当 pid==-1 时,将信号发送给所有进程;

int sig:信号指令(类型),如 SIGQUIT
*/

(2) 向进程自己发送信号

int raise(int sig);

(3)挂起调用该函数的进程,直到捕获到了一个信号。

int pause(void);

2. 信号接收处理

函数用于设置一个信号处理函数,当特定信号发生时,该函数会被调用。

sighandler_t signal(int signum, sighandler_t handler);
//int signum:要捕获或处理的信号编号。
//sighandler_t handler :接收到指定信号后要执行的函数。

使用例程:当按下Ctrl+c时会触发自定义函数,打印出signal:2。可以使用Ctrl+z结束该进程。

#include <stdio.h>
#include <signal.h>

void fun(int arg)    //自定义函数
{ 
   printf("signal:%d\n",arg);
}

int main(int argc,char **argv)
{
    //当进程收到2号信号时,执行自定义的处理函数
	signal(2, fun);
	while(1)
	{
     
	}
}

四、消息队列

1. Linux中的消息队列有两种类型

System V消息队列(传统消息队列)
POSIX消息队列(现代消息队列)

2. 消息队列与有名管道(FIFO)的异同点

(1)相同点:
   消息队列与 FIFO 很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,多个进程从队列中读取信息。
(2)不同点:
   FIFO 需要读、写的两端事先都打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。

3. System V IPC 机制消息队列相关函数

(0)包含的头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

(1)定义信息包结构体

//数据结构体
typedef struct{
    char buff[10];
	int number;
	float sorce;
} my_data;

//信息包结构体
struct msgbuf{
    long mtype;
	my_data data;
};

(2)创建密钥-关键字
   用于生成一个唯一的键(key),通常用于创建System V IPC对象,如共享内存、信号量和消息队列。 成功的时候,返回密钥值。失败返回-1。

key_t ftok(const char *pathname, int proj_id);
//const char *pathname :文件的路径全称且文件必须存在。
// int proj_id :非0的唯一识别键值id,不要重复。

(3)创建和访问一个消息队列
   成功的时候,返回一个消息队列的唯一标识符 id(跟进程 ID 是一个类型)。失败返回-1。

int msgget(key_t key, int msgflg);
//key_t key:上一步生成的密钥。
//msgflg:指明队列的访问权限和创建标志。创建标志的可选值为 IPC_CREAT 和 IPC_EXCL。队列权限自定义。

(3)将消息添加到消息队列中

int msgsnd(int msqid, struct msgbuf * msgp, size_t msgsz,  int msgflg);
//int msqid :步骤3返回的消息队列的id。
//struct msgbuf * msgp :发送信息的结构体
//size_t msgsz :消息包的大小
//int msgflg:可以为 0(通常为 0)或 IPC_NOWAIT。

(4)从消息队列中读取消息

ssize_t msgrcv(int msqid, struct msgbuf * msgp, size_t msgsz, long msgtyp, int msgflg);
//int msqid :步骤3返回的消息队列的id。
//struct msgbuf * msgp :发送信息的结构体
//size_t msgsz :消息包的大小
// long msgtyp :要接收的消息类型。如果指定为0,则接收队列中的第一条消息。如果大于0,则接收队列中第一个类型字段等于 msgtyp 的消息。如果小于0,则接收队列中第一个类型字段小于或等于 msgtyp 的消息。通常,msgtyp 是一个正整数,用于区分不同类型的消息。
//int msgflg:可以为 0(通常为 0)或 IPC_NOWAIT。

(4)删除消息队列

int msgctl(int msqid, int cmd, struct msqid_ds * buf);
/*
	msqid 是由 msgget 返回的消息队列标识符。
	cmd 通常为 IPC_RMID 表示删除消息队列。
	buf 通常为 NULL 即可。
*/

4. 使用例程

●消息队列写端:每执行一次该程序就把消息发送出去一次,一直累加到消息队列中,等待读取。

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <sys/msg.h>

//用命令:ipcs 查看消息队列内容

int main(int argc, char ** argv)
{
	key_t  key;
	int  ret;
	struct msgbuf mbuf;
	int msgid;
	//创建密匙(文件的路径全称,文件必须存在)
	key=ftok("/mnt/hgfs/Share/7.进程间通信/5.消息队列/key.txt", 1); 
	if(key <0){
	   perror("error\n");
	   return -1;
	}
	
	//创建或访问消息队列
	//消息队列的id,密钥,不存在时创建|读写权限
	msgid = msgget(key,IPC_CREAT | 0666);
	if(msgid <0){
	   perror("msgget error\n");
	return -1;
	}
	
	//消息的类型,用于区分消息包的。
	mbuf.mtype=1;     
	//填充消息体
	mbuf.data.number=121;
	mbuf.data.sorce=12.31;
	strcpy(mbuf.data.buff,"i love you!");

	//消息队列的id,待发送消息地址,消息体的大小,0
	ret = msgsnd(msgid, &mbuf, sizeof(struct msgbuf), 0); 
	if(ret < 0){
	   perror("msgsnd error\n");
	   return -1;
	}
	//删除消息队列
	//msgctl(msgid, IPC_RMID, NULL);
	return 0;
}

●消息队列读端:每执行一次,就从消息队列中读取一次数据,消息队列中消息-1,直至读完。

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <sys/msg.h>

//用命令:ipcs 查看消息队列内容

int main(int argc, char ** argv)
{
	key_t  key;
	int  ret;
	struct msgbuf mbuf;
	int msgid;
	//创建密匙(文件的路径全称,文件必须存在)
	key=ftok("/mnt/hgfs/Share/7.进程间通信/5.消息队列/key.txt", 1); 
	if(key <0){
	   perror("error\n");
	   return -1;
	}
	
	//创建或访问消息队列
	//消息队列的id,密钥,不存在时创建|读写权限
	msgid = msgget(key,IPC_CREAT | 0666);
	if(msgid <0){
	   perror("msgget error\n");
	   return -1;
	}
	
	//消息队列的id,读取消息的缓冲区,消息体的大小,读到第一个消息,0
	ret=msgrcv(msgid,&mbuf, sizeof(struct msgbuf), 0 , 0 );
	if(ret < 0){
		perror("msgsnd error\n");
		return -1;
	}
	
	printf("%d\n",mbuf.data.number);
	printf("%f\n",mbuf.data.score);
	printf("%s\n",mbuf.data.buff);
	
	//删除消息队列
	//msgctl(msgid, IPC_RMID, NULL);
	return 0;
}

五、共享内存

   共享内存也是进程间(进程间不需要有继承关系)通信的一种常用手段。一般 OS 通过内存映射与页交换技术,使进程的内存空间映射到不同的物理内存,这样能保证每个进程运行的独立性,不至于受其它进程的影响。但可以通过共享内存的方式,使不同进程的虚拟内存映射到同一块物理内存,一个进程往这块物理内存中更新的数据,另外的进程可以立即看到这块物理内存中修改的内容。多个进程可以直接读写共享的内存区域,不需要进行数据的复制或者传递。

1. 共享内存原理

(1)进程间需要共享的数据被放在该共享内存区域中。
(2)所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。
(3)这样一个使用共享内存的进程可以将信息写入该空间,而另一个使用共享内存的进程又可以通过简单的内存读操作获取刚才写入的信息,使得两个不同进程之间进行了一次信息交换,从而实现进程间的通信。
(4)共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存进行通信,而这块虚拟内存的页面被每个共享进程的页表条目所引用,同时并不需要在所有进程的虚拟内存都有相同的地址。
(5)进程对象对于共享内存的访问通过 key(键)来控制,同时通过 key 进行访问权限的检查。

2. 共享内存特点

   共享内存是最快的一种通信方式,适合大量数据的传输。只要创建的密钥一样,就可以共享内存空间。如果没有亲缘关系的进程使用共享文件,则需要密钥。如果有亲缘关系的进程使用共享文件,把key改为IPC_PRIVATE

3. 相关函数

(0)包含的头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

(1)定义信息包结构体

//数据结构体
typedef struct{
    char buff[10];
	int number;
	float sorce;
} my_data;

//信息包结构体
struct msgbuf{
    long mtype;
	my_data data;
};

(2)创建密钥-关键字
   用于生成一个唯一的键(key),通常用于创建System V IPC对象,如共享内存、信号量和消息队列。 成功的时候,返回密钥值。失败返回-1。

key_t ftok(const char *pathname, int proj_id);
//const char *pathname :文件的路径全称且文件必须存在。
// int proj_id :非0的唯一识别键值id,不要重复。

(3)创建/打开共享内存

成功则返回一个该共享内存段的唯一标识号(唯一的标识了这个共享内存段)。否则返回-1。

int shmget(key_t key, int size, int shmflg);
/*key :是一个与共享内存段相关联的关键字。
Size:指定共享内存段的大小,以字节为单位。
Shmflg:是一掩码合成值,可以是访问权限值与(IPC_CREAT 或 IPC_EXCL)的合成。 
        IPC_CREAT 表示如果不存在该内存段,则创建它。
        IPC_EXCL 表示如果该内存段存在,则函数返回失败结果(-1)。
 */

(4)映射到进程空间地址

如果调用成功,返回映射后的进程空间的首地址,否则返回(void*)-1。

void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
	Shmid:共享内存段的标识 通常应该是 shmget 的成功返回值。
	Shmaddr:共享内存连接到当前进程中的地址位置。通常是 NULL,表示让系统来选择共享内存出现的地址。
	Shmflg:一组位标识,通常为 0 即可。
*/

(5)共享内存段与进程空间分离

将共享内存分离并没删除它,只是使得该共享内存对当前进程不再可用。 成功返回 0,失败时返回-1

int shmdt(const void *shmaddr);
//shmaddr 为 shmat 的成功返回值。

(6)删除共享内存段

成功返回 0,失败时返回-1

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*
Shmid:共享内存段标识 通常应该是 shmget 的成功返回值。
Cmd:对共享内存段的操作方式。
      可选为 IPC_STAT,IPC_SET,IPC_RMID。
     通常为 IPC_RMID,表示删除共享内存段。
Buf:共享内存段的信息结构体数据,通常为 NULL。
*/

4. 使用例程

●写入共享内存

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

#define SHM_MAX 1024

int main(int argc, char **argv)
{
     key_t  key;
	 int shmid;
	 char *p;
	 my_data *data;
      //创建密匙(文件的路径全称,文件必须存在)
	 key=ftok("/home/qjl/1.sh", 0); 
	 if(key <0){
         perror("error\n");
		 return -1;
	  }
	 
	 //所创建/打开的共享内存的id,(密匙,空间大小,打开的空间不存在时创建)
	 shmid=shmget(key, SHM_MAX, IPC_CREAT);
     if(shmid<0) {
         perror("error\n");
		 return -1;
	  }
	 
     //将共享内存映射到本进程的空间中
     //映射后的起止地址,待操作的空间,映射起止地址,0
	 data=(my_data *)shmat(shmid, NULL, 0);
	 if(data ==(my_data *)-1){
         perror("error\n");
		 return -1;
	  }

//方式一:
                  //往共享的地址空间写东西
	              //结构体指针用->访问
/*	 data->n=10;          
	 data->f=12.33;
	 strcpy(data->buff,"hello");
*/
//方式二:(安全,防止数据混乱)
	  my_data d;
      d.n=222;
      d.f=123.1;
	  strcpy(d.buff,"world");
	  memcpy(data,&d,sizeof(my_data));  //将d的数据拷贝到data中

     //将共享内存空间从本进程中分离
	 shmdt(data);
	 
	 //删除共享内存空间
    // shmctl(shmid,IPC_RMID,NULL);
    
	 return 0;
	
}

●读取共享内存

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

#define SHM_MAX 1024

int main(int argc, char **argv)
{
     key_t  key;
	 int shmid;
	 char *p;
	 my_data *data;
	 my_data  d;
	 
	 char buff[64]={0};
	 
      //创建密匙(文件的路径全称,文件必须存在)
	 key=ftok("/home/qjl/1.sh", 0); 
	 if(key <0){
         perror("error\n");
		 return -1;
	  }
	 
	 //所创建/打开的共享内存的id,(密匙,空间大小,打开的空间不存在时创建)
	 shmid=shmget(key,SHM_MAX,IPC_CREAT);
     if(shmid<0) {
         perror("error\n");
		 return -1;
	  }
	 
     //将共享内存映射到本进程的空间中
     //映射后的起止地址,待操作的空间,映射起止地址,0
	 data=(my_data *)shmat(shmid, NULL, 0);
	 if(data== (my_data *)-1){
         perror("error\n");
		 return -1;
	  }
	 //从共享空间读取
	 memcpy(&d, data, sizeof(my_data));  //将共享空间data的数据读到d中
	 
	 printf("%d\n",d.n);
	 printf("%f\n",d.f);
	 printf("%s\n",d.buff);

     //将共享内存空间从本进程中分离
	 shmdt(data);
	 
	 //删除共享内存空间
    // shmctl(shmid,IPC_RMID,NULL);
    
	 return 0;
}

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

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

相关文章

自定义Linux命令,显示docker镜像、容器信息

1、修改环境变量&#xff08;仅对当前用户有效&#xff09; vim ~/.bashrc2、给命令取别名 alias dpsdocker ps --format "table{{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}" alias disdocker images#保存并退出 :wq3、让配置重新生效 source ~/.bashrc4、测试&…

前端设计模式学习记录

设计模式介绍 概念 设计模式是我们在解决问题的时候针对特定的问题给出的简洁而优化的处理方案在JS设计模式中&#xff0c;最核心的思想&#xff1a;封装变化将变与不变分离&#xff0c;确保变化的部分灵活、不变的部分稳定 注意&#xff1a;下面文章介绍的设计模式&#xff…

shell脚本实战--批量修改文件名

字符串截取 先来了解一下shell字符串相关操作的变量 # 从开头删除匹配最短 ## 从开头删除匹配最长 % 从结尾削除匹配最短 %% 从结尾删除匹配最长#指定字符内容截取 a*c 匹配开头为a&#xff0c;中间任意个字符&#xff0c;结尾为c的字符串 a*C 匹配…

​​​【收录 Hello 算法】10.4 哈希优化策略

目录 10.4 哈希优化策略 10.4.1 线性查找&#xff1a;以时间换空间 10.4.2 哈希查找&#xff1a;以空间换时间 10.4 哈希优化策略 在算法题中&#xff0c;我们常通过将线性查找替换为哈希查找来降低算法的时间复杂度。我们借助一个算法题来加深理解。 Question 给…

LINGO:存贮问题

存贮模型中的基本概念 模型&#xff1a; 基本要素&#xff1a; &#xff08;1&#xff09;需求率&#xff1a;单位时间内对某种物品的需求量&#xff0c;用D表示。 &#xff08;2&#xff09;订货批量&#xff1a;一次订货中&#xff0c;包含某种货物的数量&#xff0c;用 Q表…

AI网络爬虫-从当当网批量获取图书信息

工作任务和目标&#xff1a;用户输入一个图书名称&#xff0c;然后程序自动从当当网批量获取图书信息 查看相关元素在源代码中的位置&#xff1a; 第一步&#xff1a;在deepseek中输入提示词&#xff1a; 你是一个Python爬虫专家&#xff0c;一步步的思考&#xff0c;完成以下…

Linux定时计划

定时计划 一、计划任务种类 突发性&#xff1a;临时决定只执行一次的任务 at&#xff1a;处理执行一次任务就结束定时性&#xff1a;每隔一定时间需要重复执行此命令 crontab&#xff1a;指定任务&#xff0c;按照设定的周期一直循环执行二、作用 定时任务可以用于自动备份…

AI办公自动化:用kimi批量将word文档部分文件名保存到Excel中

文件夹中有很多个word文档&#xff0c;现在只要英文部分的文件名&#xff0c;保存到一个Excel文件中。 可以在kimi中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个编写Python脚本的任务&#xff0c;具体步骤如下&#xff1a; 打开文件夹&#xff1a;…

51单片机-实机演示(LED,呼吸,蜂鸣器)

一&#xff0c;LED点灯 #include "REG52.H"sbit LED P0^0;void main() {LED 1;while (1){} }控制P00输出。 烧录 重启 再换个引脚插入的位置。 二&#xff0c;蜂鸣器 代码&#xff0c;和烧录步骤同上。 只需要换一下&#xff0c;引脚位置即可 三&#xff0c;呼…

jmeter之MD5加密接口请求教程

前言&#xff1a; 有时候在项目中&#xff0c;需要使用MD5加密的方法才可以登录&#xff0c;或者在某一个接口中遇到 登录获取token后才可以进行关联&#xff0c;下面介绍下遇到的常见使用 一、第一种方法&#xff1a;使用jmeter自带的函数助手digest 选择工具&#xff0c;选择…

ssm整合教程

目录 写在前面 目录结构 添加依赖 web.xml jdbc.properties spring.xml mybatis.xml springmvc.xml pom.xml额外配置 last&#xff1a;写个测试样例看看整合成功没。 User.java UserDao.java(接口) UserController.java UserService.java UserServiceImpl.java …

人工智能在鼻咽癌领域的最新应用|【医学AI·论文速递·05-27】

小罗碎碎念 2024-05-27&#xff5c;文献速递 接下来打算把人工智能在主流癌种治疗中的应用&#xff0c;每天和大家做一期推送&#xff0c;方便大家了解各自领域最新的一个进展。 因为小罗的课题是鼻咽癌相关的&#xff0c;所以这一期推文就先从人工智能在鼻咽癌中最新的应用开…

MyBatis框架的使用:mybatis介绍+环境搭建+基础sql的使用+如何使用Map传入多个参数+返回多个实体用List或者Map接收+特殊sql的使用

MyBatis框架的使用&#xff1a;mybatis介绍环境搭建基础sql的使用如何使用Map传入多个参数返回多个实体用List或者Map接收特殊sql的使用 一、MyBatis介绍1.1 特性1.2 下载地址1.3 和其它持久层技术对比 二、搭建环境2.1配置maven2.2 创建mybatis配置文件2.3 搭建测试环境 三、基…

【CTF Web】CTFShow web7 Writeup(SQL注入+PHP+进制转换)

web7 1 阿呆得到最高指示&#xff0c;如果还出问题&#xff0c;就卷铺盖滚蛋&#xff0c;阿呆心在流血。 解法 注意到&#xff1a; <!-- flag in id 1000 -->拦截很多种字符&#xff0c;连 select 也不给用了。 if(preg_match("/\|\"|or|\||\-|\\\|\/|\\*|\…

宿舍管理系统代码详解(操作界面)

目录 一、前端代码 1.样式展示 2.代码详解 <1>主页面列表部分 &#xff08;1&#xff09;template部分 &#xff08;2&#xff09;script部分 <2>新增页面 &#xff08;1&#xff09;template部分 &#xff08;2&#xff09;script部分 <3>修改页面…

C++初阶学习第九弹——探索STL奥秘(四)——vector的深层挖掘和模拟实现

string&#xff08;上&#xff09;&#xff1a;C初阶学习第六弹——探索STL奥秘&#xff08;一&#xff09;——标准库中的string类-CSDN博客 string&#xff08;下&#xff09;&#xff1a;C初阶学习第七弹——探索STL奥秘&#xff08;二&#xff09;——string的模拟实现-CS…

访问tomcat的webapps下war包,页面空白

SpringBootvue前后端分离项目&#xff0c;Vue打包到SpringBoot中 常见问题 错误一&#xff1a;war包访问页面空白 前提&#xff1a;项目在IDEA里配置tomcat可以启动访问项目 但是&#xff0c;打成war包拷贝到tomcat webapps下能启动却访问不了&#xff0c;页面显示空白 原…

孜然多程序授权系统V2.0开源

源码介绍 孜然一款多程序授权系统&#xff0c;支持自定义权限价格/新增程序配置等支持自动生成授权代码在线签到在线充值多支付接口IP/域名云黑文章系统&#xff08;富文本编辑器&#xff09;卡密功能一键云黑&#xff08;挂个大马/一键黑页/一键删库/一键删源码&#xff09; …

linux 阿里云服务器安装ImageMagick和php扩展imagick

操作系统版本 Alibaba Cloud Linux 3.2104 LTS 64位 # 1.安装ImageMagick yum install -y ImageMagick ImageMagick-devel # 没有pecl要先安装pecl 和头文件 sudo yum install php-devel # 2.pecl 安装扩展 pecl install imagick #寻找所有php.ini文件 find / -name php.…

基于EV54Y39A PIC-IOT WA的手指数量检测功能开发(MPLAB+ADC)

目录 项目介绍硬件介绍项目设计开发环境及工程参考总体流程图硬件基本配置光照传感器读取定时器检测逻辑 功能展示项目总结 &#x1f449; 【Funpack3-2】基于EV54Y39A PIC-IOT WA的手指数量检测功能开发 &#x1f449; Github: EmbeddedCamerata/PIC-IOT_finger_recognition 项…