当前位置: 首页 > article >正文

Linux-进程间的通信

1、IPC:

        Inter Process Communication(进程间通信):

        由于每个进程在操作系统中有独立的地址空间,它们不能像线程那样直接访问彼此的内存,所以必须通过某种方式进行通信。

        常见的    IPC     方式包括: 2、无名管道:

 管道的概念:

本质:

        1、内核缓冲区;

        2、伪文件-不占用磁盘空间;

特点:

        1、读端,写端对应两个文件描述符;

        2、数据写端流入,读端流出;

        3、操作管理的进程被销毁之后,管道自动被释放;

        4、管道默认式阻塞的;

创建匿名管道:

示例:

int pipe(int fd[2])fd‐传出参数:
fd[0]‐读端
fd[1]‐写端返回值:
0:成功
‐1:创建失败

实验:父子进程使用管道进行通信,实现 ps aux | grep “bash”:

        使用父子进程管道通信时读端和写端不能同时打开,父进程打开写端关闭读端;

#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>int main()
{int ret;int fd[2];pid_t pid;ret = pipe(fd);pid  = fork();if(ret == -1){printf("create pipe failed!");exit(1);}//ps auxif(pid>0){close(fd[0]);//guanbi du duan da kai xie duandup2(fd[1],STDOUT_FILENO);execlp("ps","ps","aux",NULL);exit(1);}//grep bushif(pid==0){close(fd[1]);//guanbi xieduan da kai duduandup2(fd[0],STDIN_FILENO);execlp("grep","grep","bush",NULL);}printf("pipe[0] is %d\n",fd[0]);printf("pipe[1] is %d\n",fd[1]);return 0;
}
~            

结果:

3、有名管道:

示例

int mkfifo(const char \*filename, mode_t mode)功能: 创建管道文件;参数: 管道文件名,权限,创建的文件权限和umask有关;返回值: 创建成功返回0,创建失败返回-1;

特点:

        有名管道;

        伪文件,不占用磁盘空间;

        半双工通信方式;

使用场景:

        用于实现无血缘关系的进程间通信,使用mkfifo函数只会在用户区创建一个节点,不会在内核区创建管道,需要使用IO函数OPEN函数打开管道;

ret = mkfifo("/home/study/ipc/mkfifo",0755);fd = open("./mkfifo",O_RDONLY);

实验:创建一个管道,让进程A写入进程B读出:

读进程:#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>int main()
{int ret;int fd;int nred;char readbuf[50]={0};//ret = mkfifo("/home/study/ipc/mufifo",0755);if(ret == -1){printf("filed!");}printf("create success\n");fd = open("./mufifo",O_RDONLY);if(fd == -1){printf("open filed\n");}printf("open success\n");nred = read(fd,readbuf,50);printf("read %d byte from fifo %s\n",nred,readbuf);printf("second read %d byte from fifo %s\n",nred,readbuf);close(fd);return 0;
}
~    
写进程:#include<stdio.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{char *str = "hello world";int fd;int ret;ret = mkfifo("/home/study/ipc/mufifo",0755);fd = open("./mufifo",O_WRONLY);if(fd < 0){return -1;}printf("open fifo success\n");write(fd,str,strlen(str));close(fd);return 0;}                                                                                                                                                                                                                                                                                                                                                           

结果:

  注意:管道中的信息只能读取一次,读取完之后数据就被移除,但是管道还存在; 

4、消息队列:

概念:

        消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识;

特点:

        消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;

        消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在;

        消息队列可以实现消息的随即查询,消息不一定要先进先出的次序读取,也可以按照消息类型进行读取;

两个进程间的通信:通过识别链表的ID来进行读写实现收发;

相关函数:

1、int msgget(key_t key, int msgflg);
//创建或打开消息队列,
参数:
key:和消息队列关联的key值
msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或
操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被
忽略,而只返回一个标识符。
返回值:成功返回队列ID,失败则返回‐1,在以下两种情况下,msgget将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志
key参数为IPC_PRIVATE
2.int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//读取消息,成功返回消息数据的长度,失败返回‐1
参数:
msgid:消息队列的ID
msgp:指向消息的指针,常用结构体msgbuf如下:
struct msgbuf
{
long mtype; //消息类型
char mtext[N]; //消息正文
}
size:发送的消息正文你的字节数
flag:
IPC_NOWAIT 消息没有发送完成函数也会立即返回0:知道发送完成函数才返回
返回值:
成功:0
失败:‐1
3.ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//从一个消息队列中获取消息
参数:
msgid:消息队列的ID
msgp:要接收消息的缓冲区
size:要接收的消息的字节数
msgtype:
0:接收消息队列中第一个消息
大于0:接收消息队列中第一个类型为msgtyp的消息
小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。
flag:
0:若无消息函数一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
返回值:
成功:接收到的消息i长度
出错:‐1函数msgrcv在读取消息队列时,type参数有下面几种情况
type ==0,返回队列中的第一消息
type >0,返回队列中消息队列类型为type的第一个消息
type <0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非0时用于以非先进先出次序读取消息,也可以把type看成优先级的权值
4.int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//控制消息队列,成功返回0,失败返回‐1
参数:
msqid:消息队列的队列ID
cmd:
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆
盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构
返回值:
成功:0
失败:‐1
ftok函数
key_t ftok( char * fname, int id )
//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
参数:
fname就时你指定的文件名(该文件必须是存在而且可以访问的)。
id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。
返回值:
当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回

4、共享内存:

概念:

        共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变;

        共享内存会从内核区映射一个地址空间到用户区,需要访问用户区的地址空间才能实现读写功能;

相关函数:

1.int shmget(key_t key, size_t size, int shmflg);
//用来获取或创建共享内存
参数:
key:IPC_PRIVATE 或 ftok的返回值
size:共享内存区大小
shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符
出错:‐1
2.void *shmat(int shm_id, const void *shm_addr, int shmflg);
//把共享内存连接映射到当前进程的地址空间
参数:
shm_id:ID号
shm_addr:映射到的地址,NULL为系统自动完成的映射
shmflg:
SHM_RDONLY共享内存只读
默认是0,表示共享内存可读写
返回值:
成功:映射后的地址
失败:NULL
3.int shmdt(const void *shmaddr);
//将进程里的地址映射删除
参数:
shmid:要操作的共享内存标识符
返回值:
成功:0
出错:‐1
4.int shmctl(int shm_id, int command, struct shmid_ds *buf);
//删除共享内存对象
参数:
shmid:要操作的共享内存标识符
cmd :
IPC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐m
IPC_SET (设置对象属性)
IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐m
buf :指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
成功:0
出错:‐1

实验:生成共享内存,并通过标准输入写入共享内存,并读出来打印屏幕上;

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>      // fork()
#include <sys/ipc.h>     // 共享内存所需
#include <sys/shm.h>     // shmget, shmatint main()
{int shmid;int key;char *p;key = ftok("alarm.c",1);if(key<0){printf("ftok failure\n");return -1;}printf("ftok success key:%x\n",key);shmid = shmget(key,128,IPC_CREAT|0777);if(shmid<0){printf("create failure\n");return -2;}printf("create succ");system("ipcs -m");p = (char *)shmat(shmid,NULL,0);if(p = NULL){printf("shmat funtion failure\n");return -3;}//write tofgets(p,128,stdin);//readprintf("share data is %s",p);return 0;
}

结果:

5、信号:

信号通信的框架:

信号通信的框架:信号的发送(发送信号的进程):kill、raise、alarm信号的接收(接收信号的进程):pause()、sleep、while(1)信号的处理(接收信号的进程):signal
5.1、信号的发送(发送信号的进程):
1、kill:#include<signal.h>
#include<sys/types.h>
函数原型:int kill(pid_t pid, int sig);
参数:
函数传入值:pid
正数:要接收信号的进程的进程号
0:信号被发送到所有和pid进程在同一个进程组的进程
‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号
函数返回值:成功 0 出错 ‐1
2、raise:发送信号给自己 == kill(getpid() , sig)所需头文件:
#include<signal.h>
#include<sys/types.h>
函数原型:
int raise(int sig);
参数:
函数传入值:sig:信号
函数返回值:
成功 0 出错 ‐1

实验raise==kill(getpid(),sig):

#include<stdio.h>
#include <signal.h>int main()
{printf("before sig\n");raise(9);printf("after sig\n");return 0 ;
}
~    

结果:  打印完“before sig”后进程执行kill动作,结束进程,不会打印“after sig”;

 5.2、alarm:发送闹钟信号的函数:

5.2.1、alarm与raise函数的比较:

相同点:让内核发送信号给进程

不同点:alarm只会发送SIGALARM信号;

               alarm会让内核定时一段时间在发送信号,raise会立即发送信号;

所需头文件#include <unistd.h>
函数原型 unsigned int alarm(unsigned int seconds)
参数:
seconds:指定秒数
返回值:
成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则 返回上一个闹钟时间的剩余时间,否则返回0。
出错:‐1

 实验:定时7秒向进程发送信号:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{int i = 0;printf("before alarm\n");alarm(7);printf("after alarm\n");while(i<20){i++;sleep(1);printf("process %d\n",i);}return 0;}

结果:

        在第7秒时想进程发送信号,但是由于没有设置信号处理函数对这个信号进行处理,所以这个信号会让进程终止;

 

 信号的含义及默认操作:

信号名含义默认操作
SIGHUP
该信号在用户终端连接 ( 正常或非正常 ) 结束时发出,通常是在终端的控制进程结束 时,通知同一会话内的各个作业与控制终端不再关联。
终止
SIGINT
该信号在用户键入 INTR 字符 ( 通常是 Ctrl-C) 时发出,终端驱动程序发送此信号并送到 前台进程中的每一个进程。
终止
SIGQUIT
该信号和 SIGINT 类似,但由 QUIT 字符 ( 通常是 Ctrl-) 来控制。
终止
SIGILL
该信号在一个进程企图执行一条非法指令时 ( 可执行文件本身出现错误,或者试图执 行数据段、堆栈溢出时) 发出。
终止
SIGFPE
该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出 及除数为0 等其它所有的算术的错误。
终止
SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略
终止
SIGALRM
该信号当一个定时器到时的时候发出。
终止
SIGSTOP
该信号用于暂停一个进程,且不能被阻塞、处理或忽略
暂停进程
SIGTSTP
该信号用于暂停交互进程,用户可键入 SUSP 字符 ( 通常是 Ctrl-Z) 发出这个信号
暂停进程
SIGCHLD
子进程改变状态时,父进程会收到这个信号
忽略
SIGABORT
该信号用于结束进程
终止
5.3、信号的处理:

收到信号的进程,应该怎样处理?

处理的方式:

        1、进程的默认处理(内核为用户进程设置的默认处理方式)

         A:忽略、B:终止进程、C:暂停

        2、自己的处理方式:自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式

所需头文件 #include <signal.h>
函数原型 void (*signal(int signum, void (*handler)(int)))(int);
函数传入值
signum:指定信号
handler
SIG_IGN:忽略该信号。
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数指针
函数返回值
成功:设置之前的信号处理方式
出错:‐1
signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函
数;这个函数的返回值是一个函数指针。

6、信号灯 

信号灯集合(可以包含多个信号灯)IPC对象是一个信号的集合(多个信号量)

1、semget函数原型: int semget(key_t key, int nsems, int semflg);
//创建一个新的信号量或获取一个已经存在的信号量的键值。
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
key:和信号灯集关联的key值
nsems: 信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限
函数返回值:
成功:信号灯集ID
出错:‐1
2、semctl:函数原型:int semctl ( int semid, int semnum, int cmd,…union semun arg(不是地址));
//控制信号量,删除信号量或初始化信号量
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
semid:信号灯集ID
semnum: 要修改的信号灯编号
cmd :
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
函数返回值:
成功:0
出错:‐1
3、P V 操作:int semop(int semid ,struct sembuf *sops ,size_t nsops);
//用户改变信号量的值。也就是使用资源还是释放资源使用权
包含头文件:
include<sys/sem.h>
参数:
semid : 信号量的标识码。也就是semget()的返回值
sops是一个指向结构体数组的指针。
struct sembuf{
unsigned short sem_num;//信号灯编号;
short sem_op;//对该信号量的操作。‐1 ,P操作,1 ,V操作
short sem_flg;0阻塞,1非阻塞
};
sem_op : 操作信号灯的个数
//如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负
数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用
权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

实验: 通过信号灯操作和共享内存来共同实现父子进程通信:

 实现思路:

1、声明 P V 操作并进行初始化:p v操作需要配置函数,在使用P  V都可以这样进行配置

void Poperation(int index,int semid)
{struct sembuf sop;sop.sem_num = index;sop.sem_op = -1;sop.sem_flag = 0;semop(semid,&sop,1);}void Voperation(int index,int semid)
{struct sembuf sop;sop.sem_num = index;sop.sem_op = 1;sop.sem_flg = 0;semop(semid,&sop,1);}

2、使用union semun初始化信号量:常与semctl()函数一起使用

union semun
{int val;
}

3、生成共享内存:

 int shmid; char *shmaddr;       shmid = shmget(key,128,IPC_CREAT|0755);

4、宏定义读写两个信号量:

#define SEM_WRITE 1
#define SEM_READ 0

5、 使用semctl函数来控制读写两个信号量:

 //init semaphoreunion semum myun;//init semaphore readmyun.val=0;semctl(semid,SEM_READ,SETVAL,myun);//init semaphore writemyun.val = 1;semctl(semid,SEM_WRITE,SETVAL,myun);

6、生成父子进程,并分别实现父子进程的动作:

父进程实现从键盘输入,并执行写操作,读操作暂停:

子进程执行读操作,写操作暂停;

       pid_t pid;pid = fork();if(pid == 0)//child process{while(1){shmaddr = (char *)shmat(shmid,NULL,0);Poperation(SEM_READ,semid);printf("get share memory is %s\n",shmaddr);Voperation(SEM_WRITE,semid);}}else if(pid>0)//father process{while(1){shmaddr =( char *)shmat(shmid,NULL,0);Poperation(SEM_WRITE,semid);printf("please input to share memory\n");fgets(shmaddr,32,stdin);Voperation(SEM_READ,semid);}}

完整代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>#define SEM_WRITE 1
#define SEM_READ 0//ding yi xin hao lian he ti 
// 必须手动定义 semun union,因为它不在标准头文件中定义
union semun {int val;                // 用于 SETVALstruct semid_ds *buf;   // 用于 IPC_STAT 和 IPC_SETunsigned short *array;  // 用于 GETALL 和 SETALLstruct seminfo *__buf;  // 用于 IPC_INFO(可选,可以忽略)
};void Poperation(int index,int semid)
{struct sembuf sop;sop.sem_num = index;sop.sem_op = -1;sop.sem_flg = 0;semop(semid,&sop,1);}void Voperation(int index,int semid)
{struct sembuf sop;sop.sem_num = index;sop.sem_op = 1;sop.sem_flg = 0;semop(semid,&sop,1);}void Voperation(int index,int semid)
{struct sembuf sop;sop.sem_num = index;sop.sem_op = 1;sop.sem_flg = 0;semop(semid,&sop,1);}int main()
{key_t key;key = ftok(".",123);if(key == -1){perror("ftok");return -1;}int semid;int shmid;char *shmaddr;semid = semget(key,2,IPC_CREAT|0755);if(semid<0){perror("semget");return -1;}shmid = shmget(key,128,IPC_CREAT|0755);if(shmid<0){perror("shmget");return -2;}//init semaphoreunion semun myun;//init semaphore readmyun.val=0;semctl(semid,SEM_READ,SETVAL,myun);//init semaphore writemyun.val = 1;semctl(semid,SEM_WRITE,SETVAL,myun);pid_t pid;pid = fork();if(pid == 0)//child process{while(1){shmaddr = (char *)shmat(shmid,NULL,0);Poperation(SEM_READ,semid);printf("get share memory is %s\n",shmaddr);Voperation(SEM_WRITE,semid);}}else if(pid>0)//father process{while(1){shmaddr =( char *)shmat(shmid,NULL,0);Poperation(SEM_WRITE,semid);printf("please input to share memory\n");fgets(shmaddr,32,stdin);Voperation(SEM_READ,semid);}}return 0;}

 结果:父子进程就会交替执行读写操作,并不会同时占用共享内存:

相关文章:

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法 大家好,我是Echo_Wish。最近刷短视频、看直播,有没有发现,越来越多的应用都开始“懂你”了——它们能感知你的情绪,推荐更合适的内容,甚至帮客服识别用户情绪,提升服务体验。这背后,神经网络在悄悄发力,撑起…...

门静脉高压——表现

一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构&#xff1a;由肠系膜上静脉和脾静脉汇合构成&#xff0c;是肝脏血液供应的主要来源。淤血后果&#xff1a;门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血&#xff0c;引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

React核心概念:State是什么?如何用useState管理组件自己的数据?

系列回顾&#xff1a; 在上一篇《React入门第一步》中&#xff0c;我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目&#xff0c;并修改了App.jsx组件&#xff0c;让页面显示出我们想要的文字。但是&#xff0c;那个页面是“死”的&#xff0c;它只是静态…...

用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章

用 Rust 重写 Linux 内核模块实战&#xff1a;迈向安全内核的新篇章 ​​摘要&#xff1a;​​ 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言&#xff0c;受限于 C 语言本身的内存安全和并发安全问题&#xff0c;开发复杂模块极易引入难以…...

【51单片机】4. 模块化编程与LCD1602Debug

1. 什么是模块化编程 传统编程会将所有函数放在main.c中&#xff0c;如果使用的模块多&#xff0c;一个文件内会有很多代码&#xff0c;不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里&#xff0c;在.h文件里提供外部可调用函数声明&#xff0c;其他.c文…...

Java并发编程实战 Day 11:并发设计模式

【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天&#xff0c;今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案&#xff0c;它们不仅提供了优雅的设计思路&#xff0c;还能显著提升系统的性能…...

DeepSeek越强,Kimi越慌?

被DeepSeek吊打的Kimi&#xff0c;还有多少人在用&#xff1f; 去年&#xff0c;月之暗面创始人杨植麟别提有多风光了。90后清华学霸&#xff0c;国产大模型六小虎之一&#xff0c;手握十几亿美金的融资。旗下的AI助手Kimi烧钱如流水&#xff0c;单月光是投流就花费2个亿。 疯…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)

目录 &#x1f50d; 若用递归计算每一项&#xff0c;会发生什么&#xff1f; Horners Rule&#xff08;霍纳法则&#xff09; 第一步&#xff1a;我们从最原始的泰勒公式出发 第二步&#xff1a;从形式上重新观察展开式 &#x1f31f; 第三步&#xff1a;引出霍纳法则&…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor

1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...

跨平台商品数据接口的标准化与规范化发展路径:淘宝京东拼多多的最新实践

在电商行业蓬勃发展的当下&#xff0c;多平台运营已成为众多商家的必然选择。然而&#xff0c;不同电商平台在商品数据接口方面存在差异&#xff0c;导致商家在跨平台运营时面临诸多挑战&#xff0c;如数据对接困难、运营效率低下、用户体验不一致等。跨平台商品数据接口的标准…...

电脑桌面太单调,用Python写一个桌面小宠物应用。

下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡&#xff0c;可以响应鼠标点击&#xff0c;并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!

今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等&#xff0c;设置经线、纬线都以10间隔显示。 2、需要插入背会归线&#xf…...

MySQL体系架构解析(三):MySQL目录与启动配置全解析

MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录&#xff0c;这个目录下存放着许多可执行文件。与其他系统的可执行文件类似&#xff0c;这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中&#xff0c;用…...

从实验室到产业:IndexTTS 在六大核心场景的落地实践

一、内容创作&#xff1a;重构数字内容生产范式 在短视频创作领域&#xff0c;IndexTTS 的语音克隆技术彻底改变了配音流程。B 站 UP 主通过 5 秒参考音频即可克隆出郭老师音色&#xff0c;生成的 “各位吴彦祖们大家好” 语音相似度达 97%&#xff0c;单条视频播放量突破百万…...

boost::filesystem::path文件路径使用详解和示例

boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类&#xff0c;封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解&#xff0c;包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...

Python爬虫实战:研究Restkit库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...

DAY 45 超大力王爱学Python

来自超大力王的友情提示&#xff1a;在用tensordoard的时候一定一定要用绝对位置&#xff0c;例如&#xff1a;tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾&#xff1a; tensorboard的发展历史和原理tens…...

UE5 音效系统

一.音效管理 音乐一般都是WAV,创建一个背景音乐类SoudClass,一个音效类SoundClass。所有的音乐都分为这两个类。再创建一个总音乐类&#xff0c;将上述两个作为它的子类。 接着我们创建一个音乐混合类SoundMix&#xff0c;将上述三个类翻入其中&#xff0c;通过它管理每个音乐…...

轻量级Docker管理工具Docker Switchboard

简介 什么是 Docker Switchboard &#xff1f; Docker Switchboard 是一个轻量级的 Web 应用程序&#xff0c;用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器&#xff0c;使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...

如何通过git命令查看项目连接的仓库地址?

要通过 Git 命令查看项目连接的仓库地址&#xff0c;您可以使用以下几种方法&#xff1a; 1. 查看所有远程仓库地址 使用 git remote -v 命令&#xff0c;它会显示项目中配置的所有远程仓库及其对应的 URL&#xff1a; git remote -v输出示例&#xff1a; origin https://…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...

边缘计算网关提升水产养殖尾水处理的远程运维效率

一、项目背景 随着水产养殖行业的快速发展&#xff0c;养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下&#xff0c;而且难以实现精准监控和管理。为了提升尾水处理的效果和效率&#xff0c;同时降低人力成本&#xff0c;某大型水产养殖企业决定…...

echarts使用graphic强行给图增加一个边框(边框根据自己的图形大小设置)- 适用于无法使用dom的样式

pdf-lib https://blog.csdn.net/Shi_haoliu/article/details/148157624?spm1001.2014.3001.5501 为了完成在pdf中导出echarts图&#xff0c;如果边框加在dom上面&#xff0c;pdf-lib导出svg的时候并不会导出边框&#xff0c;所以只能在echarts图上面加边框 grid的边框是在图里…...

goreplay

1.github地址 https://github.com/buger/goreplay 2.简单介绍 GoReplay 是一个开源的网络监控工具&#xff0c;可以记录用户的实时流量并将其用于镜像、负载测试、监控和详细分析。 3.出现背景 随着应用程序的增长&#xff0c;测试它所需的工作量也会呈指数级增长。GoRepl…...

麒麟系统使用-进行.NET开发

文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的&#xff0c;如果需要进行.NET开发&#xff0c;则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET&#xff0c;所以要进…...

游戏开发中常见的战斗数值英文缩写对照表

游戏开发中常见的战斗数值英文缩写对照表 基础属性&#xff08;Basic Attributes&#xff09; 缩写英文全称中文释义常见使用场景HPHit Points / Health Points生命值角色生存状态MPMana Points / Magic Points魔法值技能释放资源SPStamina Points体力值动作消耗资源APAction…...

GraphRAG优化新思路-开源的ROGRAG框架

目前的如微软开源的GraphRAG的工作流程都较为复杂&#xff0c;难以孤立地评估各个组件的贡献&#xff0c;传统的检索方法在处理复杂推理任务时可能不够有效&#xff0c;特别是在需要理解实体间关系或多跳知识的情况下。先说结论&#xff0c;看完后感觉这个框架性能上不会比Grap…...