【Linux-进程通信1】管道

news2024/11/20 16:34:51

 🌈进程间通信介绍

🍄进程间通信目的

在操作系统中,每个进程都是独立运行的,它们有自己的地址空间和资源,它们不能直接访问其他进程的资源。然而,在现代计算机系统中,很少有一个进程能够独立完成所有任务,因此需要不同的进程之间进行通信和协作。

进程间通信的目的包括但不限于以下几点:

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

🐖说到底:进程间通信的本质就是让不同进程看到同一份资源。

🍄进程间通信的分类

管道

  • 匿名管道
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

posix这个是关于多线程相关的,后面再讲

🌈管道

👿前言

提起管道,大家都知道输油管道,天然气管道,排污管道等等。但是这些管道是不是都是单向的。而以前的大佬设计管道进行进程交流时也是设计的单向流通。至于双向管道后来才有。这里我们介绍的管道也是单向的。

🍄 什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

🍄管道通信的原理: 

 进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。

当父子进程进行管道交流时:双方关闭各自不需要的文件描述符,父进程关闭读,保留只写,而子进程关闭写,保留只读。

  • 这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。
  • 管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。

🍄匿名管道

  1. 匿名管道(Anonymous Pipes)是一种进程间通信的机制,它可以在同一台计算机上的两个进程之间传递数据,且不需要借助于文件系统。匿名管道是一种半双工通信方式,只能在具有父子关系的进程之间使用。
  2. 在使用匿名管道时,创建一个管道,然后创建两个进程。其中一个进程称为管道的“写端”,另一个进程称为管道的“读端”。写端进程将数据写入管道,而读端进程则从管道中读取数据。管道是一个字节流,没有记录分隔符或消息边界,因此读取端需要自行解析数据。
  3. 匿名管道是一种简单、快速的进程间通信方式,适用于进程间需要高效、低延迟数据交换的场景。

👿pipe函数

pipe函数用于创建匿名管道,pip函数的函数原型如下:

int pipe(int pipefd[2]);

pipe函数的参数是一个输出型参数,数组pipefd用于返回两个指向管道读端和写端的文件描述符:

数组元素含义
pipefd[0]管道读端的文件描述符
pipefd[1]管道写端的文件描述符

 pipe函数调用成功时返回0,调用失败时返回-1。


👿匿名管道的使用步骤

我们写一个代码试一试:Makefile

mypipe:mypipe.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f mypipe
#include<iostream>
#include<unistd.h>
#include<assert.h>

using namespace std;

int main()
{
    int pipefd[2]={0};
    int n= pipe(pipefd);
    assert(n=-1);  //debug下有效
    (void)n;     //这个就是代表n被使用过,没其他意思

    cout<<"pipefd[0]: "<<pipefd[0]<<endl;       //3
    cout<<"pipefd[1]: "<<pipefd[1]<<endl;       //4
     return 0;
}

因为管道需要一个读取端和一个写入端,所以创建一个管道时需要使用长度为2的整型数组来存储管道的读取端和写入端。在这个数组中,第一个元素 pipefd[0] 表示管道的读取端,第二个元素 pipefd[1] 表示管道的写入端。所以是pipefd[2]


🌰例子: 子进程写入数据,父进程读出数据

                                                                                                                                                                                                                                                                                                                                                                                                                                                    
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0){
		//child
		close(fd[0]); //子进程关闭读端
		//子进程向管道写入数据
		const char* msg = "hello father, I am child...";
		int count = 20;
		while (count--){
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //子进程写入完毕,关闭文件
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端
	//父进程从管道读取数据
	char buff[64];
	while (1){
		ssize_t s = read(fd[0], buff, sizeof(buff));
		if (s > 0){
			buff[s] = '\0';
			printf("child send to father:%s\n", buff);
		}
		else if (s == 0){
			printf("read file end\n");
			break;
		}
		else{
			printf("read error\n");
			break;
		}
	}
	close(fd[0]); //父进程读取完毕,关闭文件
	waitpid(id, NULL, 0);
	return 0;
}

🍄管道读写规则 

pipe2函数与pipe函数类似,也是用于创建匿名管道,其函数原型如下:

int pipe2(int pipefd[2], int flags);

该函数创建一个管道,并将其两端文件描述符分别存储在pipefd[0]pipefd[1]中。flags参数可以用来设置管道的行为。

pipe2()函数与pipe()函数的区别在于,pipe2()函数增加了flags参数,可以用于设置管道的行为。flags可以取以下值:

  • O_CLOEXEC将管道的文件描述符设置为close-on-exec,即当调用exec()函数执行一个新程序时,该管道将被关闭。
  • O_DIRECT启用直接IO模式,让数据在传输时避免从内核缓存区复制,直接在应用层和硬件之间进行数据传输,可以提高效率,但只能传输块大小为512字节的倍数的数据。
  • O_NONBLOCK启用非阻塞IO模式,当读取管道中没有数据时,read()函数将立即返回0而不是阻塞等待数据。
  • 当没有数据可读时
  1. O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  2. O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满的时候
  1. O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
  2. O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

🍄管道的特点: 

1.只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

2.管道内部自带同步与互斥机制。

  • 问题思考: 
  1.  如果一个进程向管道写入数据时,另一个进程正在从管道中读取数据,那么这两个操作可能会发生冲突,导致数据读写错误或数据丢失。
  2. 当出现同一时刻有多个进程对同一管道进行操作的情况,会导致同时读写、交叉读写以及读取到的数据不一致等问题。
  • 解决方法:
  1. Linux中的管道内部采用了同步与互斥机制来保证数据传输的正确性。具体来说,管道内部使用了一个缓冲区,当一个进程写入数据时,它会先将数据存储到缓冲区中,等待另一个进程从缓冲区中读取数据。
  2. 在这个过程中,管道内部使用了同步机制来确保缓冲区中的数据不会被同时读写,从而避免了数据的读写冲突。同时,管道内部使用了互斥机制来确保只有一个进程可以访问缓冲区中的数据,从而避免了数据的竞争和混乱。

临界资源:一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行写入或是读取操作,因此管道也就是一种临界资源。

临界资源是需要被保护的,若是我们不对管道这种临界资源进行任何保护机制,那么就可能出现同一时刻有多个进程对同一管道进行操作的情况,进而导致同时读写、交叉读写以及读取到的数据不一致等问题。

为了避免这些问题,内核会对管道操作进行同步与互斥:

  • 同步是指协调多个进程的执行顺序,以便它们按照一定的顺序或时序进行访问和操作。同步可以通过信号量、事件等方式来实现,使得多个进程之间按照一定的顺序进行访问和操作,从而避免数据竞争、死锁等问题的出现。
  • 互斥是指在同一时刻只允许一个进程或线程访问共享资源。多个进程不能同时共享资源。

3.管道提供流式服务。

对于进程A写入管道当中的数据,进程B每次从管道读取的数据的多少是任意的,这种被称为流式服务,与之相对应的是数据报服务:

4.一般而言,进程退出,管道释放,所以管道的生命周期随进程。

管道本质上是通过文件进行通信的,也就是说管道依赖于文件系统,那么当所有打开该文件的进程都退出后,该文件也就会被释放掉,所以说管道的生命周期随进程。

5.管道是半双工的,数据只能向一个方向流动需要双方通信时,需要建立起两个管道

🍎在数据通信中,数据在线路上的传送方式可以分为以下三种:

  • 单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
  • 半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
  • 全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。

管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。

 

 🍄管道的四种特殊情况

在使用管道时,可能出现以下四种特殊情况:

  1. 写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被挂起,直到管道里面有数据后,读端进程才会被唤醒。
  2. 读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
  3. 写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
  4. 读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么操作系统会将写端进程杀掉。

其中前面两种情况就能够很好的说明,管道是自带同步与互斥机制的,读端进程和写端进程是有一个步调协调的过程的,不会说当管道没有数据了读端还在读取,而当管道已经满了写端还在写入。读端进程读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中还有空间,若是条件不满足,则相应的进程就会被挂起,直到条件满足后才会被再次唤醒。

第三种情况也很好理解,读端进程已经将管道当中的所有数据都读取出来了,而且此后也不会有写端再进行写入了,那么此时读端进程也就可以执行该进程的其他逻辑了,而不会被挂起。

第四种情况,既然管道当中的数据已经没有进程会读取了,那么写端进程的写入将没有意义,因此操作系统直接将写端进程杀掉。而此时子进程代码都还没跑完就被终止了,属于异常退出,那么子进程必然收到了某种信号。

那我们就来验证一下他到底收到了几号信号。

#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <cstring>
#include <cstdio>

int main() {
    int fd[2];
    pipe(fd);

    pid_t id = fork();
    if (id == 0){
        //child
        close(fd[0]); //子进程关闭读端
        //子进程向管道写入数据
        const char* msg = "hello father, I am child...";
        int count = 10;
        while (count--){
            write(fd[1], msg, strlen(msg));
            sleep(1);
        }
        close(fd[1]); //子进程写入完毕,关闭文件
        exit(0);
    }

    //father
    close(fd[1]); //父进程关闭写端
    close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)
    int status = 0;
    waitpid(id, &status, 0);

    // 判断子进程是否收到了信号,并打印出来
    if (WIFSIGNALED(status)) {
        printf("Child process exited due to signal %d\n", WTERMSIG(status));
    }

    return 0;
}

 在这段代码中,我们使用WIFSIGNALED宏判断子进程是否因为收到了信号而终止。如果是的话,使用WTERMSIG宏获取信号的编号,并打印出来。在这个例子中,由于子进程直接关闭了写端,因此操作系统会向子进程发送SIGPIPE信号,导致子进程异常退出。

🍄匿名管道的缺陷 

匿名管道是一种用于实现进程间通信的管道,但它也存在一些不足之处:

  1. 只能用于相关进程间通信:匿名管道只能用于直接相关的进程之间的通信,即父进程和子进程之间的通信。如果需要在不相关的进程之间进行通信,就需要使用其他的进程间通信方式,如命名管道、套接字等。

  2. 只能传递一次性数据:匿名管道只能传递一次性数据,即一旦一个进程读取了管道中的数据,这些数据就会被删除,不能被其他进程再次读取。如果需要传递多次数据,就需要重新创建一个新的匿名管道。

  3. 无法在文件系统中查找:匿名管道不像命名管道那样在文件系统中有一个唯一的名称,因此无法在文件系统中查找。这也使得在使用匿名管道时需要注意,以免在不同的进程之间出现管道名称冲突的情况。

 🌈命名管道  

为了改善上面匿名管道的缺陷之处,出现了命名管道。

  1. 命名管道是一种在文件系统中创建的特殊文件,用于实现进程间通信。它也被称为FIFO(First-In-First-Out),因为它遵循先进先出的原则,类似于一个队列。
  2. 与匿名管道不同,命名管道具有一个独特的名字,它们可以通过这个名字被多个进程共享和访问。这使得命名管道更加灵活,可以被用于在不同的进程之间传输数据,即使这些进程不是直接相关的。
  3. 在Linux和Unix系统中,使用mkfifo命令可以创建一个命名管道。一旦创建,进程可以像读写普通文件一样,使用打开和关闭文件的方式来访问命名管道。当进程向管道写入数据时,该数据将被缓存到管道中,并可以被其他进程读取。这种方式使得命名管道成为一种非常常见的进程间通信方式之一。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mkfifo(const char* pathname, mode_t mode);

创建出来的文件的类型是p,代表该文件是命名管道文件。 

🍄创建命名管道

在程序中创建命名管道使用mkfifo函数,mkfifo函数的函数原型如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mkfifo(const char* pathname, mode_t mode);

参数说明:

  • pathname:命名管道的路径,通常是一个文件路径,例如 /tmp/my_pipe
  • mode:命名管道的访问权限,例如 0666 表示所有用户都有读写权限。

mkfifo函数的返回值。

  • 命名管道创建成功,返回0。
  • 命名管道创建失败,返回-1。
#include<iostream>
using namespace std;
#include<sys/types.h>
#include<sys/stat.h>
#define FILE_NAME "myfifo"
int main()
{
    if (mkfifo(FILE_NAME, 0666) < 0)
    {
        cerr << "mkfifo error" << endl;
        return 1;
    }
    //create success
    cout << "hello world" << endl;
}

实际创建出来文件的权限为:mode&(~umask)。umask的默认值一般为0002,当我们设置mode值为0666时实际创建出来文件的权限为0664。若想创建出来命名管道文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0。

umask(0); //将文件默认掩码设置为0

🍄命名管道的打开规则

  • 如果当前打开操作是为读而打开FIFO时
  1. O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO。
  2. O_NONBLOCK enable:立刻返回成功。
  • 如果当前打开操作是为写而打开FIFO时
  1. O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO。
  2. O_NONBLOCK enable:立刻返回失败,错误码为ENXIO。

🍄用命名管道实现server & client间的通信 

实现服务端(server)和客户端(client)之间的通信之前,我们需要先让服务端运行起来,让服务端运行后创建一个命名管道文件,然后再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了。

  • 注意:如果一个进程已经把文件创建好了,那么另一个进程不需要创建这个文件了,直接用就可以了。

server客户端代码如下:

//读取
#include"comm.h"
using namespace std;
int main()
{
    umask(0);//将文件默认掩码设置为0
    if (mkfifo(IPC_PATH, 0600) != 0)
    {
        cerr << "mkfifo error" << endl;
        return 1;
    }
    int pipefd = open(IPC_PATH, O_RDONLY);//以读的方式打开命名管道文件
    if (pipefd < 0)
    {
        cerr << "open fifo error" << endl;
        return 2;
    }
    //正常的通信过程
    #define NUM 1024
    char buffer[NUM];
    while (true)
    {
        ssize_t s = read(pipefd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = '\0';//手动设置'\0',便于输出
            cout << "客户端->服务器# " << buffer << endl;//输出客户端发来的信息
        }
        else if (s == 0)
        {
            cout << "客户退出啦,我也退出啦" << endl;
            break;
        }
        else
        {
            //do nothing
            cout << "read: " << strerror(errno) << endl;
            break;
        }
    }
    close(pipefd);
    cout << "服务端退出啦" << endl;
    unlink(IPC_PATH);//通信完毕后,自动帮我们删除管道文件
    return 0;
}

client服务端代码如下:

//写入
#include"comm.h"
using namespace std;
int main()
{
    int pipefd = open(IPC_PATH, O_WRONLY);//以写的方式打开命名管道文件
    if (pipefd < 0)
    {
        cerr << "open: " << strerror(errno) << endl;
        return 1;
    }
 
    #define NUM 1024
    char line[NUM];
    while (true)
    {
        printf("请输入你的消息# ");
        fflush(stdout);
        memset(line, 0, sizeof(line));//每次读取之前将line清空
        //fgets -》C语言接口 -》line结尾自动添加\0
        if (fgets(line, sizeof(line), stdin) != nullptr)
        {
            //abcd\n\0
            line[strlen(line) - 1] = '\0';//除去回车后多余的\0
            write(pipefd, line, strlen(line));
        }
        else 
        {
            break;
        }
    }
    close(pipefd);//通信完毕,关闭命名管道文件
    cout << "客户端退出啦" << endl;
}

makefile代码如下:

.PHONY:all
all: clientfifo serverfifo
clientfifo:clientfifo.cpp
	g++ -Wall -o $@ $^ -std=c++11
serverfifo:serverfifo.cpp
	g++ -Wall -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f clientfifo serverfifo .fifo

头文件comm.h代码如下:

#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cstring>
#include<cerrno>
#include<cstdio>
 
#define IPC_PATH "./.fifo"

我们操作一下让客户端和服务器进行交流: 

 🍄命名管道和匿名管道的区别

命名管道和匿名管道是两种不同类型的管道,用于进程间通信。

  1. 匿名管道:
  • 匿名管道是一种无名的管道,只能用于具有亲缘关系的进程间通信(父子进程或兄弟进程)。
  • 它只能单向传输数据,也就是只能从一个进程的输出流传输到另一个进程的输入流。
  • 匿名管道在创建时需要调用 pipe() 系统调用,并通过文件描述符进行访问。

     2.命名管道:

  • 命名管道是一种有名字的管道,可以被不相关的进程之间使用。
  • 它可以双向传输数据,允许多个进程同时读写管道。
  • 命名管道在创建时需要调用 mkfifo() 系统调用,并在文件系统中创建一个特殊的文件,用于标识这个管道。

总的来说,匿名管道主要用于单向、有亲缘关系的进程间通信,而命名管道则可以实现双向、不相关进程之间的通信。

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

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

相关文章

yolov5读取单通道图像会怎样?

通过上图打印可知输入是固定3通道&#xff0c;那么意味着在读取图像中会对图像进行处理。 opencv在默认情况下会读取3个通道的图像&#xff0c;如果是灰度图会将图层复制三次(BGR缺省&#xff0c;BGR)&#xff0c;因此读出来的图片是三通道。

xcode打包导出ipa

转载&#xff1a;xcode打包导出ipa 众所周知&#xff0c;在开发苹果应用时需要使用签名&#xff08;证书&#xff09;才能进行打包安装苹果IPA&#xff0c;作为刚接触ios开发的同学&#xff0c;只是学习ios app开发内测&#xff0c;并没有上架appstore需求&#xff0c;对于苹果…

【Mybatis】Mybatis之xml开发—用户角色权限关联案例

目录 要求&#xff1a;使用xml开发完成需求查询。 数据库 需求 要求&#xff1a;使用xml开发完成需求查询。 数据库 -- 用户表 create table sys_user(user_id int primary key auto_increment comment 用户ID,user_name varchar(50) comment 用户名,password varchar(32)…

软件测试——性能指标

登录功能示例&#xff1a; 并发用户数500&#xff1b; 响应时间2S&#xff1b; TPS到500&#xff1b; CPU不得超过75%&#xff1b; 性能指标有哪些&#xff1f; 响应时间 并发用户数 TPS CPU 内存 磁盘吞吐量 网络吞吐量 移动端FPS 移动端耗电量 APP启动时间 性能…

windows11 安装多个mysql8

安装一个mysql请参考&#xff1a;windows系统安装mysql8 解压缩版安装顺序_csdn_aspnet的博客-CSDN博客 下载mysql&#xff1a;MySQL :: Download MySQL Community Server 下载后解压到你指定的目录&#xff0c;我下载的非最新版&#xff0c;如图&#xff1a; 在文件夹下面建一…

【SpringMVC】| 控制器异常处理机制及实现流程

MVC目录 一. &#x1f981; 前言二. &#x1f981; 控制器异常处理Ⅰ. 单个控制器异常处理Ⅱ. 全局异常处理Ⅲ. 自定义异常处理 三. &#x1f981; 最后 一. &#x1f981; 前言 咱们来探索一下控制器异常处理流程&#xff0c;以及如何来实现它。 二. &#x1f981; 控制器异…

烈日炎炎的夏天骑行,怎么预防中暑及中暑后怎么处理?

随着天气的逐渐炎热&#xff0c;夏季骑行逐渐成为了人们喜爱的一项运动。但是&#xff0c;在享受骑行乐趣的同时&#xff0c;我们也要时刻关注身体健康&#xff0c;预防中暑等意外情况的发生。下面&#xff0c;本文将从多个角度为大家讲解夏季骑行中暑的预防和处理方法。 一、选…

音频环回实验

音频环回实验 一、WM8978简介 WM8978是一个低功耗、高质量的立体声多媒体数字信号编译码器&#xff0c;它结合了一个高质量的立体声音DAC和ADC&#xff0c;带有灵活的音频线输入、麦克风输入和音频输出处理 WM8978内部有58个寄存器。每一个寄存器的地址位为7位&#xff0c;数…

SpringCloud基础知识

1、什么是SpringCloud SpringCloud分布式微服务架构的一站式解决方案&#xff0c;是多种微服务架构落地技术的集合体&#xff08;微服务全家桶&#xff09;。 查看官网&#xff1a;https://spring.io/ Spring Cloud本身不是新的框架&#xff0c;是一个全家桶式的技术栈&…

销售管理系统哪种好?

一、如何选择销售管理系统 销售管理软件其实就是我们常说的CRM软件&#xff0c;在激烈的市场竞争下&#xff0c;传统的销售管理模式不能满足有效跟进和及时维护客户的需求&#xff0c;在挖掘新客户和增强客户忠诚度方面也出现了一定弊端&#xff0c;因此销售管理软件应运而生&…

作者等级与权益说明

「创收计划」3.0上线&#xff0c;全面助力资源优质创作者 创&#xff1a;代表创作者收&#xff1a;代表收获 截止目前&#xff0c;文库资源频道已开放20细分领域&#xff0c;每个领域又进行详细的细分&#xff0c;在每个细分的品类上&#xff0c;我们已经收获了来自广大创作者…

基于AT89C51单片机的简易计算器的设计与仿真

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87755299?spm1001.2014.3001.5503 源码获取 本设计是以单片机AT89C51为核心的简易计算器设计&#xff0c;要通过芯片AT89C51实现计算器程序运行来完成加、减、乘…

python--读取TRMM-3B43月平均降水绘制气候态空间分布图(陆地区域做掩膜)

python–读取TRMM-3B43月平均降水绘制气候态空间分布图&#xff08;陆地区域做掩膜&#xff09; 成果展示 TRMM降水数据介绍 热带降雨测量任务(The Tropical Rainfall Measuring Mission&#xff0c;TRMM)是美国国家航空航天局(NASA)和日本国家太空发展署(National Space Dev…

刚转岗做项目经理,无从下手,怎么办?

01 背景 最近在知乎平台看到一个问题是这么说的&#xff1a; 或许很多人都不是从工作开始就是项目专员再到项目经理这里一步一步过来&#xff0c;而是从其他岗位比如售前、销售、产品经理、程序员等转到项目经理岗位的。 那么对于这些人来说&#xff0c;做项目经理会有什么问…

第一次找实习, 什么项目可以给自己加分(笔记)

什么样的项目能简历加分、对找工作有帮助 基本特征&#xff1a; 一个特征是“硬核基础软件”&#xff0c;另一个为很实用的APP。 硬核基础软件 独立实现一个操作系统的kerne内核&#xff08;操作系统的内部引擎&#xff09; 北美计算机名校会让学生用一个学期的时间实现一个…

冒险岛私人服务器详细架设教程

冒险岛Online   《冒险岛Online》是由韩国WIZET和NEXON制作开发的一款2D横版卷轴网络游戏&#xff0c;于2004年7月24日在中国大陆正式上线&#xff0c;由盛大游戏负责运营。   故事以被“黑暗力量”不断入侵&#xff0c;因而进入了“浑沌期”的世界为背景&#xff0c;勇士们…

微服务知识3

Gateway核心概念 路由(route) 网关中最基础的部分&#xff0c;路由信息包括一个ID&#xff0c;一个目的URI&#xff0c;一组谓词工厂&#xff0c;一组Filter组成。如果谓词为真&#xff0c;则说明请求的URL和配置的路由匹配 谓词(preducates) 即java.util.function.Predica…

atbf中imu数据的读取与处理方式

一、说明 本文为作者在阅读atbf源码的过程中&#xff0c;对atbf中imu数据的读取和处理方式的个人理解&#xff0c;可能存在不对之处&#xff0c;意在抛砖引玉&#xff0c;请各位老师多多指正&#xff1b; 二、数据读取流程图 1、target NEUTRONRCF435SE 不同的target所定义…

C++中的queue与priority_queue

文章目录 queuequeue的介绍queue的使用 priority_queuepriority_queue介绍priority_queue使用 queue queue的介绍 队列是一种容器适配器&#xff0c;专门用于上下文先进先出的操作中。队列的特性是先进先出&#xff0c;从容器的一端插入&#xff0c;另一端提取元素。   队列…

Java17的性能优势是否足以让它取代Java8?

随着时间的推移&#xff0c;Java不断地进行更新和发展&#xff0c;以满足不断变化的业务需求。目前&#xff0c;Java8已经成为了一个非常成熟的版本&#xff0c;并且在各个领域广泛应用。但是&#xff0c;Java17也早已发布&#xff0c;并且是Java 11以来又一个LTS(长期支持)版本…