进程间通信:无名管道+有名管道

news2025/1/16 13:56:41

进程间通信(Inter-Process Communication)

为什么需要进程间通信

当程序是多进程协同工作时,进程间基本都会涉及到数据共享

如何实现进程间数据的共享?

使用进程间通信来实现数据共享

进程间有时需要传递消息
--但是进程在系统有自己的地址空间,os不允许其他进程随意进入该地址空间
--所以内核提供了一种机制:既可以保证通信,又可以保证进程安全
  --这个机制就叫:进程间通信

本章内容和意义

Linux OS所提供的进程间通信机制:

  • 信号
  • 管道(有名、无名)
  • 消息队列
  • 共享内存
  • 信号量

C程序在windows和Linux下想要实现“进程控制”和“进程间通信”的话:

必须调用系统API或者说该系统自己特有C库(不是标准c库)

java提供“进程控制”和“进程间通信”的库接口

+学习意义
1.对开发者来说:了解进程间的互斥、进程的同步、资源保护等这些概念很必要
2.有助于理解其它 OS 的进程间通信(Linux 类似)
3.加深对进程的理解,帮助后续对比学习c线程

+学习重点
理解进程通信的实现原理,通过代码将进程间通信理解清楚

进程间通信其实就是:多进程相互通信,共享信息和交换信息的方法

为什么进程空间完全独立

​ 进程空间完全独立,会使得进程间共享数据很困难,但是为什么OS还是要将进程空间,弄成是完全独立的呢?

什么是进程空间

其实就是程序运行的内存空间

不过OS有提供虚拟内存时,我们所说的进程空间,指的都是虚拟内存空间

虚拟内存是基于物理内存实现的,说虚拟内存时,本身就包含了虚拟内存所对应的底层物理内存空间

虚拟地址与物理地址

  • 虚拟内存有自己的虚拟地址(就是一些编号)

    虚拟地址是因为这些地址并不对应真实物理内存

  • CPU取指运行时,PC是通过虚拟地址来取指的,但是程序指令肯定是放在了真实的物理内存上的,所以虚拟地址最终会被转换为物理地址,然后到真实的物理内存中取出指令,再供CPU执行

每一个进程空间的虚拟地址都是一样的
--原因:如果不一样的话,管理起来会非常的麻烦

对于32位的 OS 来说,虚拟内存的虚拟地址的编码范围为:0~4G-1
--虚拟内存的虚拟地址也不是全用上了,这个范围太大了,OS 做了限制,实际上虚拟地址也只使用了其中的一部分而已

虚拟内存这么大,底层对应的物理内存空间也这么大吗?

当然不可能,如果是这样的话运行40个进程,所占物理内存空间就 == 4G*40(160G)

所以每个进程在运行时,虚拟内存底层实际所需的物理内存空间并不大

为什么要在物理内存上营造出虚拟内存

有一个很重要的原因是:通过虚拟内存机制,可以让每个进程拥有完全独立的进程空间

虚拟内存的一个重要作用就是:可以让每个进程拥有完全独立的进程空间

也就是说:

虽然每个进程空间拥有完全相同的虚拟地址,但是不会相互干扰,各自操作各自的代码和数据

  • 所有进程空间(虚拟内存空间)的虚拟地址(编号)是一样的
  • 但是各自在物理内存上,实际所对应的物理内存空间完全不同
  • 虚拟内存通过特殊的实现机制,它可以严格保证每个虚拟内存,各自对应的是完全独立的物理内存空间

总结:

为什么说每个进程的进程空间是完全独立的

虚拟内存机制可以保证,每个程序完全运行在各自独立的物理内存空间,而且还能保证它们绝对不会误访问得到对方的物理内存空间

进程间通信

IPC原理

所有进程通过同一OS来实现数据的转发

进程间通信的原理:

OS作为所有进程共享的第三方,会提供相关的机制,以实现进程间数据的转发,达到数据共享的目的

广义上的进程间通信

A进程——————文件———————B进程				
A进程—————数据库——————B进程

一般来说,这种广义的进程间通信,并不被算作真正的“进程间通信”

只有OS所提供的专门的通信机制,才能算作是真正的“进程间通信”

LINUX提供的进程间通信

Linux的进程间通信,其实都是继承于Unix

  • 信号

前面讲的信号其实也是进程间通信的一种,只不过信号是非精确通信

本章讲的IPC是精确通信
---精确通信,就是能告诉你详细信息
---而信号这种非精确通信,只能通知某件事情发生了,但是无法告诉详细信息

进程间通信

  • 管道

    • 无名管道
    • 有名管道

system V IPC(系统5)

  • 消息队列:通过消息队列机制来通信
  • 共享内存:通过共享内存机制来通信
  • 信号量:借助通信来实现资源的保护(一种加锁机制)

无名管道

无名管道的通信原理

​ 具体来说就是,内核会开辟一个“管道”,通信的进程通过共享这个管道,从而实现通信

操作无名管道

文件的方式来读写管道

  • 有读写用的文件描述符
  • 读写时会用write、read等文件Io函数

为什么叫无名管道

无名管道文件比较特殊,它没有文件名,但可以通过“文件描述符”来操作管道,它就是一个文件(管道文件)

无名管道API

#include <unistd.h>
 /* On all other architectures */
int pipe(int pipefd[2]);
功能

创建一个用于亲缘进程(比如:父子进程)之间通信的无名管道(本质上是:缓存),并将管道与两个读写文件描述符关联起来

无名管道只能用于亲缘进程之间通信

参数

缓存地址:缓存用于存放读写管道的文件描述符

这个缓存就是一个拥有两个元素的int型数组

1)元素[0]:里面放的是读管道的读文件描述符
2)元素[1]:里面放的是写管道的写文件描述符

这里的读和写文件描述符,是两个不同的文件描述符

并不是所有的文件描述符,都是通过open函数打开文件得到的

这里无名管道的读、写文件描述符,就是直接在创建管道时得到的,与open没有任何关系

这里也没办法使用open函数,因为open函数需要文件路径名,无名管道没有文件名

无名管道特点

只能用于亲缘进程之间通信

由于没有文件名,因此进程没办法使用open打开管道文件,从而得到文件描述符
----方法----
父进程先调用pipe创建出管道,并得到读写管道的文件描述符。
然后再fork出子进程,让子进程通过继承父进程打开的文件描述符
父子进程就能操作同一个管道,从而实现通信。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xlWUjn8l-1668593716547)(/home/guojiawei/.config/Typora/typora-user-images/image-20221116180900282.png)]

内核在调用无名管道函数,会创建出一个缓存,也就是管道,一端用于读一端用于写

子进程被父进程fork后,会继承父进程的文件描述符,实现亲缘通信关系

只要是存在继承关系的进程就是亲缘进程:

(1)直接继承关系
			父进程————>子进程
(2)间接继承关系
			父进程————>子进程————>子进程————>...

读管道时,如果没有数据的话,读操作会休眠(阻塞)

无名管道代码

父子进程单向通信

(a)父进程在fork之前先调用pipe创建无名管道----获取读写文件描述符
(b)fork创建出子进程,子进程继承无名管道读写文件描述符
(c)父子进程使用各自管道的读写文件描述符进行读写操作,实现通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
/*单向通信---父进程写数据,子进程读数据并打印*/
int main(void){
    /*fork前优先创建管道*/
    int pipefd[2]={0};//用于存放管道读写文件描述符 [0]:读 [1]:写
    int ret=0;
    ret=pipe(pipefd);//int pipe(int pipefd[2]);
    if(ret==-1){
        perror("pipe fail\n");
        exit(-1);
    }
    /*调用fork函数*/
    ret=fork();
    if(ret>0){
       while(1){
             write(pipefd[1],"hello",5);//写
             sleep(0.5);
       }
    }
    else if(ret==0){
             while(1){
                char buf[30]={0};
                //清空缓存
                bzero(buf,sizeof(buf));
                read(pipefd[0],buf,sizeof(buf));
                printf("child,data=%s",buf);
             }
    }
    return 0;
}

关闭没有用到的文件描述符

  • 两个close
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
/*单向通信---父进程写数据,子进程读数据并打印*/
int main(void){
    /*fork前优先创建管道*/
    int pipefd[2]={0};//用于存放管道读写文件描述符 [0]:读 [1]:写
    int ret=0;
    ret=pipe(pipefd);//int pipe(int pipefd[2]);
    if(ret==-1){
        perror("pipe fail\n");
        exit(-1);
    }
    /*调用fork函数*/
    ret=fork();
    if(ret>0){
        close(pipefd[0]);
       while(1){
             write(pipefd[1],"hello",5);//写
             sleep(0.5);
       }
    }
    else if(ret==0){
        close(pipefd[1]);
             while(1){
                char buf[30]={0};
                //清空缓存
                bzero(buf,sizeof(buf));
                read(pipefd[0],buf,sizeof(buf));
                printf("child,data=%s",buf);
             }
    }
    return 0;
}

SIGPIPE信号

写管道时,如果管道的读端被close了话,向管道“写”数据的进程会被内核发送一个SIGPIPE信号
------发这个信号的目的就是想通知你,管道所有的“读”都被关闭了。

水管的出口(读)给堵住了,结果你还一直往里面灌水(写),别人跟定会警告你,因为你这样可能会对水管造成损害

这个信号的默认动作是终止,所以收到这个信号的进程会被终止

注意只有两个读端被关闭后,才会产生这个信号(父子进程都没有出口)

父子进程双向通信

单个无名管道无法实现双向通信


因为父子进程会争夺读数据

自己发送给对方的数据,就被自己给抢读到

解决

  • 创建两个管道,父子进程各有四个读写端

  • 关闭一个管道的子进程读写端,和另一个管道的读进程读写端即可达到目的

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
/*双向通信---父子进程读写数据*/
int main(void){
    /*fork前优先创建管道*/
    //创建两个管道
    int pipefd1[2]={0};
    int pipefd2[2]={0};
    pipe(pipefd1);
    pipe(pipefd2);
    /*调用fork函数*/
    int ret=0;
    ret=fork();
    /*父进程写hello子进程接收*/
    if(ret>0){
        //关闭父进程的管道1的读文件描述符
       close(pipefd1[0]);
        //关闭父进程管道2的写文件描述符
       close(pipefd2[1]);
       char buf1[30]={0};
       while(1){
             write(pipefd1[1],"hello",5);
             /*父进程通过管道二读数据*/
             bzero(buf1,sizeof(buf1));
             read(pipefd2[0],buf1,sizeof(buf1));
             printf("parent=%s\n",buf1);
       }
    }
    else if(ret==0){
        //关闭子进程管道1的写文件描述符
        close(pipefd1[1]);
        //关闭子进程管道1的读文件描述符
        close(pipefd2[0]);
        char buf2[30]={0};
             while(1){
                /*子进程通过管道2写文件*/
                write(pipefd2[1],"world",5);

                bzero(buf2,sizeof(buf2));//清空缓存
                read(pipefd1[0],buf2,sizeof(buf2));
                printf("child=%s\n",buf2);
             }
    }
    return 0;
}

有名管道

有名管道之所以叫“有名管道”,是因为它有文件名

不管是有名管道,还是无名管道,本质其实都是一样的:

管道都是内核所开辟的一段缓存空间。
---进程间通过管道通信时,本质上就是通过共享操作这段缓存来实现
---只不过操作这段缓存的方式,是以读写文件的形式来操作的。

有名管道的特点

  1. 能够用于非亲缘进程之间的通信

    • 因为有文件名,所以进程可以直接调用open函数打开文件,从而得到文件描述符
    • 无名管道,必须在通过继承的方式才能获取到文件描述符
    有名管道通信:
    任何两个进程调用open函数打开同一个“有名管道”文件,然后对同一个“有名管道文件”进行读写操作,即可实现通信  A进程 —————————> 有名管道 ————————> B进程
    
  2. 读管道时,如果管道没有数据的话,读操作同样会阻塞(休眠)

    就是等待数据的写入

  3. 当进程写一个所有读端都被关闭了的管道时,进程会被内核返回SIGPIPE信号

    如果不想被该信号终止的话,我们需要忽略、捕获、屏蔽该信号

有名管道的使用

  1. 进程调用mkfifo创建有名管道
  2. open打开有名管道
  3. read/write读写管道进行通信
  • 对于通信的两个进程来说,创建管道时,只需要一个人创建,另一个直接使用即可

  • 为了保证管道一定被创建,最好是两个进程都包含创建管道的代码,谁先运行就谁先创建

    后运行的发现管道已经创建好了,那就直接open打开使用

有名管道API

#include <sys/types.h>
#include <sys/stat.h>
//创建有名管道文件,创建好后便可使用open打开
int mkfifo(const char *pathname, mode_t mode);
/*
   pathname:被创建管道文件的文件路径名
   mode:指定被创建时原始权限,一般为0664(110110100),三组读写权限
         创建新文件时,文件被创建时的真实权限=mode & (~umask)
*/
//返回值:成功返回0,失败则返回-1,并且errno被设置
  • 如果是创建普通文件的话,我们可以使用open的O_CREAT选项来创建,比如:
    open(“./file”, O_RDWR|O_CREAT, 0664);
  • 但是对于“有名管道”这种特殊文件,这里只能使用mkfifo函数来创建。

单向通信

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FIFONAME "./fifo"
void print_err(char* str){
	perror(str);
	exit(-1);
}
int main(void){
	/*使用mkfifo创建管道文件*/
	int ret=0;
	ret=mkfifo(FIFONAME,0664);
	if(ret==-1) print_err("mkfifo fails\n");
	/*使用open打开管道文件*/
	int fd=-1;
	fd=open(FIFONAME,O_WRONLY);//只写打开
	if(fd==-1) print_err("open fails\n");
	/*从键盘获取*/
	char buf[100]={0};
	while(1){
		bzero(buf,sizeof(buf));
		scanf("%s",buf);
		write(fd,buf,sizeof(buf));
	}
}

用于写的管道

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define FIFONAME "./fifo"
void print_err(char* str){
	perror(str);
	exit(-1);
}
//封装创建管道+打开管道
int create_open_fifo(char *fifoname,int open_mode){
	int ret=0;
	int fd=-1;
	ret=mkfifo(FIFONAME,0664);
	if(ret==-1 && errno!=EEXIST) print_err("mkfifo fails\n");
	/*使用open打开管道文件*/
	fd=open(FIFONAME,open_mode);//以只写方式打开
	if(fd==-1) print_err("open fails\n");
	return fd;
}
/*信号捕获,通信结束,删除管道文件,退出进程*/
void signal_fun(int signo){
	//unlink()
	remove(FIFONAME);
	exit(-1);
}
int main(void){
	signal(SIGINT,signal_fun);//捕获:必须在create_open_fife前
	/*因为读端堵塞,以只写方式打开*/
	int fd=create_open_fifo(FIFONAME,O_WRONLY);
	
	while(1){
		char buf[30]={0};
		bzero(buf,sizeof(buf));
		scanf("%s",buf);
		write(fd,buf,sizeof(buf));
	}

}

用于读的管道

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define FIFONAME "./fifo"
void print_err(char* str){
	perror(str);
	exit(-1);
}
//封装创建管道+打开管道
int create_open_fifo(char *fifoname,int open_mode){
	int ret=0;
	int fd=-1;
	ret=mkfifo(FIFONAME,0664);
	if(ret==-1 && errno!=EEXIST) print_err("mkfifo fails\n");
	/*使用open打开管道文件*/
	fd=open(FIFONAME,open_mode);//以只写方式打开
	if(fd==-1) print_err("open fails\n");
	return fd;
}
/*信号捕获,通信结束,删除管道文件,退出进程*/
void signal_fun(int signo){
	//unlink()
	remove(FIFONAME);
	exit(-1);
}
int main(void){
	signal(SIGINT,signal_fun);//捕获:必须在create_open_fife前
	/*因为读端堵塞,以只读方式打开*/
	int fd=create_open_fifo(FIFONAME,O_RDONLY);
	char buf[30]={0};
	while(1){
		bzero(buf,sizeof(buf));
		read(fd,buf,sizeof(buf));
		printf("buf=%s\n",buf);
	}

}

双向通信

  • 同样的,使用一个“有名管道”是无法实现双向通信的,因为也涉及到抢数据的问题。
  • 所以双向通信时需要两个管道

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UQQBlgMi-1668838782776)(/home/guojiawei/.config/Typora/typora-user-images/image-20221119141656507.png)]

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"
void print_err(char* str){
	perror(str);
	exit(-1);
}
/*封装创建管道+打开管道*/
int create_open_fifo(char *fifoname,int open_mode){
	int ret=0;
	int fd=-1;
	ret=mkfifo(fifoname,0664);
	if(ret==-1 && errno!=EEXIST) print_err("mkfifo fails\n");
	//使用open打开管道文件
	fd=open(fifoname,open_mode);
	if(fd==-1) print_err("open fails\n");
	return fd;
}
/*信号捕获,通信结束,删除管道文件,退出进程*/
void signal_fun(int signo){
	remove(FIFONAME1);
	remove(FIFONAME2);
	exit(-1);
}
int main(void){
	int fd1=-1;
	int fd2=-1;
	//fd1管道去写,fd2管道去读
	fd1=create_open_fifo(FIFONAME1,O_WRONLY);
	fd2=create_open_fifo(FIFONAME2,O_RDONLY);
    char buf[30]={0};
	int ret=fork();
	if(ret>0){//父进程管道2只读
		signal(SIGINT,signal_fun);
	    while(1){
		   bzero(buf,sizeof(buf));
		   read(fd2,buf,sizeof(buf));
		   printf("从管道2读出的数据:%s\n",buf);
	 }
	}
	else if(ret==0){//子进程管道1只写
		while (1){
		   bzero(buf,sizeof(buf));
		   scanf("%s",buf);
		   write(fd1,buf,sizeof(buf));
		}
	}

}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"
void print_err(char* str){
	perror(str);
	exit(-1);
}
/*封装创建管道+打开管道*/
int create_open_fifo(char *fifoname,int open_mode){
	int ret=0;
	int fd=-1;
	ret=mkfifo(fifoname,0664);
	if(ret==-1 && errno!=EEXIST) print_err("mkfifo fails\n");
	//使用open打开管道文件
	fd=open(fifoname,open_mode);
	if(fd==-1) print_err("open fails\n");
	return fd;
}
/*信号捕获,通信结束,删除管道文件,退出进程*/
void signal_fun(int signo){
	remove(FIFONAME1);
	remove(FIFONAME2);
	exit(-1);
}
int main(void){
	int fd1=create_open_fifo(FIFONAME1,O_RDONLY);
	int fd2=create_open_fifo(FIFONAME2,O_WRONLY);
	char buf[30]={0};
	int ret=fork();
	if(ret>0){//父进程从管道2只写
	    signal(SIGINT,signal_fun);
	    while(1){
		   bzero(buf,sizeof(buf));
		   scanf("%s",buf);
		   write(fd2,buf,sizeof(buf));
	}}//子进程从管道1只读
	else if(ret==0){
		while(1){
			bzero(buf,sizeof(buf));
            read(fd1,buf,sizeof(buf));
			printf("从管道1读出数据:%s\n",buf);
		}
	}
}

什么时候使用有名管道

当两个进程需要通信时,不管是亲缘的还是非亲缘的,我们都可以使用有名管道来通信。

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

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

相关文章

msdn下载的系统怎么安装

有小伙伴们不知道安装msdn系统的具体操作&#xff0c;那么小编就教大家下载的系统怎么安装吧。 工具/原料&#xff1a; 系统版本&#xff1a;win10 专业版 品牌型号&#xff1a;联想小新Air 13 Pro 软件版本&#xff1a;小鱼一键重装系统v3.1.329.319 方法/步骤&#xff1…

UNIAPP实战项目笔记41 收货地址页面布局和省市县三级联动

UNIAPP实战项目笔记41 收货地址页面布局和省市县三级联动 my-add-path.vue 设置页面布局 具体内容图片自己替换哈&#xff0c;随便找了个图片的做示例 用到了vue的默认组件 城市选择器mpvueCityPicker,从uniappDemo中复制过来即可,具体位置见目录结构 代码 my-add-path.vue 页…

数据结构二叉排序树应用一

2022.11.19 二叉排序树应用一任务描述相关知识编程要求测试说明C/C代码任务描述 本关任务&#xff1a;输入一个无序序列&#xff0c;创建一棵二叉排序树。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.二叉排序树定义&#xff0c;2.如何创建一棵二叉排序…

黑*头条_第6章_admin端功能开发通用后端封装

黑*头条_第6章_admin端功能开发&通用后端封装 文章目录黑*头条_第6章_admin端功能开发&通用后端封装admin端功能开发&通用后端封装今日目标1 admin端的登录功能实现1.1 admin项目搭建1.2 登录接口-后端1.2.1接口定义1.2.2mapper定义1.2.3 代码编写1.3 前端项目导入…

FCN的代码解读

目录 模型初始化 VGG初始化 FCN初始化 图片的预处理 图片处理 图片编码 计算相关参数 模型训练 一个小问题 完整代码 参考 最近浅研究了一下关于图像领域的图像分割的相关知识&#xff0c;发现水还是挺深的&#xff0c;因为FCN差不多也是领域的开山鼻祖&#xff0c;所以就先从…

Postman进阶篇(十)-在pre-request script或test script中使用pm对象访问变量

在之前的文章中介绍过postman中的两个脚本——pre-request script或test script&#xff0c;在这两个脚本中都有使用到pm对象。&#xff08;pre-request script详细介绍、Test script详细介绍&#xff09;pm对象是在postman的脚本中非常重要&#xff0c;也是十分常用的方法。本…

Tomcat使用与Servlet

目录 Tomcat Get与Post请求的区别 Servlet 体系 请求流程 生命周期 配置loadOnStartup 线程安全问题 问题原因 解决方案 Servlet核心类 请求 响应 转发与重定向 转发 重定向 Cookie与HttpSession Cookie HttpSession ServletConfig ServletContext 总结 F…

基于javaweb,ssm学生宿舍管理系统(带论文)

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat8.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;非前后端分离 前端技术&#xff1a;vue.jselementUI等框架实现 服务端技术&#xff1a;springspringmvcmybat…

Java--Lambda(1)简介

文章目录0 写在前面1 优点2 实现3 写在末尾0 写在前面 阅读公司前辈写的代码的时候&#xff0c;有一些地方总不理解&#xff0c;后来才知道是Lambda表达式。 所以学习了一下&#xff0c;在此记录一下。 通过百度搜索得知&#xff1a;Lambda 表达式也叫作匿名函数&#xff0c…

简单易懂的 全景图高清下载方法以及原理简要解析(支持下载建E、720yun、酷雷曼、景站、酷家乐、百度街景原图)

简单易懂的 全景图高清下载方法以及原理简要解析&#xff08;支持下载建E、720yun、酷雷曼、景站、酷家乐、百度街景原图&#xff09; 文章目录简单易懂的 全景图高清下载方法以及原理简要解析&#xff08;支持下载建E、720yun、酷雷曼、景站、酷家乐、百度街景原图&#xff09…

【Linux】在Xilinx平台上实现UVC Gadget(1)

【Linux】在Xilinx平台上实现UVC Gadget&#xff08;1&#xff09;前言&#xff1a;关于UVC一、创建Petalinux工程并修改设备树1) 创建一个基本的petalinux工程2) 配置sstate和downloads3) 配置内核4) 修改设备树二、在petalinux下添加uvc-gadget测试程序1) 创建一个空应用程序…

Python爬虫采集框架——Scrapy初学入门

一、安装Scrapy依赖包 pip install Scrapy 二、创建Scrapy项目&#xff08;tutorial&#xff09; scrapy startproject tutorial 项目目录包含以下内容 tutorial/scrapy.cfg # deploy configuration filetutorial/ # projects Python module, youl…

Deformable Attention学习笔记

Deformable Attention学习笔记 Vision Transformer with Deformable Attention Abstract Transformer 最近在各种视觉任务中表现出卓越的表现。大的(有时甚至是全局的)接受域使Transformer模型比CNN模型具有更高的表示能力。然而&#xff0c;单纯扩大接受野也会引起一些问题…

Linux【进程间通信】

目录 一、什么是进程间通信 管道 管道的原理 匿名管道 1.简单写一个管道 2.总结管道的特点&#xff0c;理解以前的管道 3.扩展 如何写一个进程池&#xff1f; 创建Makefile文件 创建我们的任务头文件Task.cpp 创建我们的主程序文件 一、什么是进程间通信 进程的运…

java项目-第149期ssm师生交流平台_java毕业设计_计算机毕业设计

java项目-第149期ssm师生交流平台-java毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm师生交流平台》 该项目分为3个角色&#xff0c;管理员、学生和老师。 学生可以浏览前台查看教学资源、申请做作业、论坛信息、新闻资讯等信息查看。 同时可以跳转…

[MQ] 延迟队列/延迟插件下载

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

Linux基本指令3——文件操作

Linux内核&#xff1a;Centos 7.6 64位 find指令 按文件名查找文件的用法&#xff1a;find [路径] -name [文件名] 作用&#xff1a;可以查找目标文件 找到后用nano&#xff0c;通过绝对路径打开目标文件。目前只需要知道这种程度就行了。 grep指令 语法&#xff1a;gre…

浅谈非线性回归(non-linear regression)

文章目录浅谈非线性回归&#xff08;non-linear regression&#xff09;引言最小二乘多项式拟合非线性拟合Gauss–NewtonGauss–NewtonGauss–Newton算法[1]Levenberg–MarquardtLevenberg–MarquardtLevenberg–Marquardt算法[2]Quasi−NewtonQuasi-NewtonQuasi−Newton方法&a…

这样做框架结构图,让你的PPT更有创意!

已剪辑自: https://zhuanlan.zhihu.com/p/58834710 嗨&#xff0c;各位木友们好呀&#xff0c;我是小木。 昨天&#xff0c;有个跟我一样鸟人的鸟人让我帮忙做个框架结构图&#xff1a; 可惜当时我不在办公室&#xff0c;不然我真的一分钟就能把图做给他… ▼ 在文本框里输入…

RabbitMQ_交换机

简单理解交换机在RabbitMQ中扮演的角色 交换机在RabbitMQ中扮演消息系统中枢&#xff0c;将从生产者处收集的消息转发至对应的消息队列处&#xff0c;等待消费者消费 提前说明交换机 与 routing key 与 消息队列的关系 channel.queueBind(queueName, exchangeName, routingKey)…