【Linux】进程间通信(匿名管道和命名管道通信、共享内存通信)

news2024/11/19 5:54:16

文章目录

    • 1、进程间通信
      • 1.1 进程的通信
      • 1.2 如何让进程间通信?
      • 1.3 进程间通信的本质
    • 2、管道通信
      • 2.1 匿名管道
      • 2.2 匿名管道通信
      • 2.3 命名管道
      • 2.4 命名管道的通信
    • 3、SystemV中的共享内存通信
      • 3.1 共享内存
      • 3.2 共享内存的通信
      • 3.3 共享内存的缺点以及数据保护
      • 3.4 共享内存的优点
      • 3.5 信号量以及与共享内存有关的概念

1、进程间通信

1.1 进程的通信

在一些应用场景下,一定要求着进程之间进行通信,而通信有以下这些:
数据传输:进程间将数据传递给彼此。
资源共享:多个进程共享同一份资源。
通知事件:一个进程向另一个进程发送信息,通知它发生了某种事件。(比如父进程向子进程传递信号,让子进程退出。)
进程控制:一个进程控制另一个进程。(比如debug)

比如在linux中通过命令,ps ajx | grep hello,两个命令分别对应着两个进程,通过管道进行进程通信。(也就是说,为了完成某些业务,我们是需要多进程进行协同的

1.2 如何让进程间通信?

Linux的主流通信标准有以下:

  1. POSIX (让通信过程可以跨主机)
  2. System V (聚焦在本地通信这种方式,这种标准有着三种通信方式:共享内存、消息队列、信号量(这里只谈共享内存,因为SystemV标准不常用)。)

第二套通信方案
管道是最基本的通信方案,管道基于文件系统。
管道有着匿名管道和命名管道。

1.3 进程间通信的本质

首先,进程是具有独立性的,所以如果要让进程进行通信肯定是不能进程与进程直接联系的。
那么就需要以下:

1、OS需要直接或间接的给通信双方的进程提供"共享空间"。
2、要让通信的进程都看到同一份共享空间。
(而不同的通信种类,本质就是这边共享空间的不同,区别在OS哪个模块提供的。比如通过文件系统提供的资源就是管道。)

综上,我们需要做的就是:
1、需要让进程看到同一份资源。(这个是主要的)
2、通信

2、管道通信

2.1 匿名管道

理解管道通信:

在讲文件的时候说到过,一个进程的PCB中有一个指针,指向文件描述符表,被打开的文件被其中文件描述符下标指向。

如果有一个父进程打开了一个文件,那么文件描述符表中对应就会指向这个文件对应的结构体对象,通过这个文件结构体中有着文件的操作方法以及一个内核缓冲区。

父进程创建子进程后,子进程拷贝父进程,对应文件描述符表中对应也会指向这个文件对应结构体对象。

综上,两个进程指向同一文件资源,这个文件就是一个"共享空间",也是一个管道。

在这里插入图片描述

另一个问题,普通文件传输数据是需要经过磁盘的,如果进程间通过文件传输数据经过磁盘,那么就是内存与外设的交互,效率很低。那么OS能不能只创建一个文件结构体对象,只在内存中交互呢?

答案是肯定的,这个文件不会真的存在,OS会申请一个文件结构体对象。
同时,这个文件是一个内存级文件没有名字也没有对应inode,所以称之为匿名管道。

管道只能单向通信
首先创建管道将读写端返回给进程。
在这里插入图片描述
为了让子进程也看到读写端,父进程fork创建子进程。
在这里插入图片描述
因为管道需要单向通信,所以对应读端要关闭写端,对应写端要关闭读端。
在这里插入图片描述

综上,匿名管道就是一个父进程通过读和写的方式打开一个内存级文件后,通过创建子进程,关闭各自读写端,所构成的通信信道,这个信道基于文件,是一个内存级文件,所以称为匿名管道。

2.2 匿名管道通信

pipe接口:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

pipe系统接口,可以创建一个单向管道用于进程间通信,pipefd[2]参数是一个返回型参数,返回两个文件描述符,一个读端fd[0]和一个写端fd[1],指向管道文件的两边。

知道了如何创建管道后,下面需要让两个进程都看到这个"共享空间" 因为管道返回的是文件描述符,所以通过文件描述符就可以访问管道。

综上,思路就很简单:
1. 创建管道,接收管道文件描述符。
2. 父进程创建子进程,进程会获取和父进程一样的文件描述符。
3. 可以让父进程读,子进程写,然后父进程需要关闭写端,子进程关闭读端。
4. 通过文件操作进行通信。

测试代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;
int main()
{
    //第一步:创建管道文件,打开读写端
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);

    //第二步:fork
    pid_t id = fork();
    assert(id >= 0);
    if(id == 0)
    {
        //子进程写入
        close(fds[0]);
        
        //子进程的通信代码
        int cnt = 0;
        while(true)
        {
            char buffer[1024];
            snprintf(buffer, sizeof buffer, "child->parent say: %s[%d][%d]","我是子进程,我正在发消息", cnt++, getpid());
            //一直往管道输入端写,写满也会阻塞
            write(fds[1], buffer, strlen(buffer));
            cout << "count: " << cnt << endl;
            sleep(2); //每隔2s, 写一次 
        }
        close(fds[1]); //写完 子进程关闭写端
        cout << "子进程关闭自己写端" << endl;
        sleep(5);
        exit(0);
    }
    //父进程读取
    close(fds[1]);
    //父进程的通信代码
    while(true)
    {
        char buffer[1024];
        
        //如果管道中没有了数据,读端在读,默认会直接阻塞当前正在读取的进程!
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
        if(s > 0)
        {
            buffer[s] = 0;
            cout << "Get Message# " << buffer << "| my pid:"<< getpid() << endl;
            //break;
        }
        else if(s == 0)
        {
            //读到文件末尾
            cout << "read: " << s << endl;
            break;
        }
        //父进程没有sleep
    }
    close(fds[0]);
    int status = 0;
    n = waitpid(id, &status, 0);
    cout << "waitpid:" << n << " sig:" << (status & 0x7f) << endl;

    assert(n == id);

    return 0;
}


匿名管道读写特性:

之前,主要做的是让进程间看到同一份资源,而通信的过程是可以任意想象的,但是大概就以下几个情况。
1、写的快,读的慢。(那么每次读取的时候,就会从缓冲区读一大堆)
2、写的慢,读的快。
3、写后关闭写端,读端继续。(当读完后,子进程退出,父进程需要回收子进程)
4、一直写,读端关闭。(这种情况的出现,让数据没有意义)

前三个很好理解,如果第四种情况出现,写端一直写,读端却不读,那么写的数据就没有任何意义,造成了空间浪费。

当写端还在,读端关闭这个条件发生时,操作系统为了避免空间浪费,就会给父进程发送SIGPIPE(13)信号,关闭父进程。
在这里插入图片描述


管道特点:

  1. 管道的生命周期随进程。
  2. 管道可以用来进行具有"血缘关系"的进程间进行通信,常用于父子间通信。
  3. 管道是面向字节流的。
  4. 管道只允许单向通信 半双工的。(任何时候都是一方向另一方发送数据)
  5. 互斥和同步机制。(共享资源保护机制)(这个可以和后面共享内存进行比较)

2.3 命名管道

前面的匿名管道,是有"血缘关系"的进程间进行通信的。
如果想让毫不相关的两个进程通过管道通信呢?命名管道就能解决这个问题。

命名管道相较于匿名管道就是在磁盘有一个特殊的管道文件,这个文件在打开后也拥有着自己的struct file。

两个不相关的进程是如何看到同一份资源的呢?
可以让不同进程,通过文件名(路径+文件名)打开。(匿名管道的结构体对象中的地址没有名称,命名管道的结构体对象中的地址拥有名称)

值得注意的是,进程传递到内核缓冲区的数据不会刷新到磁盘上。
在这里插入图片描述

2.4 命名管道的通信

函数调用mkfifo:
在这里插入图片描述
在这里插入图片描述

函数调用mkfifo用于创建命名管道,也是一个特殊的文件。
pathname: 表示创建文件的路径,如果只是一个文件名就和进程在同一路径。
mode: 表示创建的文件名拥有的初始权限。(前提设置umask(0),不然结果就是mode & ~umask
创建成功返回0,失败返回-1。


函数调用unlink:
在这里插入图片描述

unlink在此用于删除管道文件,只需传管道路径,成功返回0,错误返回-1。

对应通信也很简单。
1、创建管道文件,进程1打开文件,向文件里写入。
2、进程2打开文件向文件里读取,读完后删除管道文件。

//comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <fcntl.h>

#define NAMED_PIPE "mypipe"


bool createFifo(const std::string& path)
{
    umask(0);
    int n = mkfifo(path.c_str(), 0666);
    if(n == 0)
    {
        return true;
    }
    else
    {
        std::cout << "errno: " << errno << "err stirng:" << strerror(errno) << std::endl;
        return false;
    }
}

void removeFifo(const std::string& path)
{
    int n = unlink(path.c_str());
    assert(n == 0);
    (void)n;
}

#include "comm.hpp"

int main()
{
    createFifo(NAMED_PIPE);
    
    int wfd = open(NAMED_PIPE, O_WRONLY, 0666);
    int cnt = 10;
    while(cnt--)
    {
        char buffer[1024];
        std::cout << "client begin:" << std::endl;
        fgets(buffer, sizeof buffer, stdin);
        std::cout << "client end:" << std::endl;
        ssize_t w = write(wfd, buffer, strlen(buffer) - 1);
        std::cout << std::endl;

    }
    close(wfd);
    return 0;
}

#include "comm.hpp"

int main()
{
    //createFifo(NAMED_PIPE);
    int rfd = open(NAMED_PIPE, O_RDONLY, 0666);

    while(true)
    {
        std::cout << "server begin:" << std::endl;
        char buffer[1024];
        ssize_t r = read(rfd, buffer, sizeof(buffer));
        if(r > 0)
        {
            buffer[r] = 0;
            std::cout << buffer << std::endl;
            std::cout << "server end:" << std::endl;
            std::cout << std::endl;
        }
        else
        {
            std::cout << "read:" << r << std::endl;
            break;
        }

    }
    close(rfd);

    removeFifo(NAMED_PIPE);

    return 0;
}

3、SystemV中的共享内存通信

3.1 共享内存

共享内存的理解很简单。
用户通过接口,让OS在物理空间申请一块内存。
不同的进程将进程地址空间通过页表和这块内存进行映射。
这就做到了共享同一份空间。

在这里插入图片描述
下面通过接口,看进程是怎么和共享内存联系起来的。

认识接口


shmget:
在这里插入图片描述

shmget让OS在物理空间上申请一个共享内存
key是一个系统层面的标识符,用于标识共享内存唯一性。(这key是由一个函数生成)
size 表示要创建多大的共享内存。
shmflg 是一个二进制标识符,一般为IPC_CREAT 表示共享内存没有时创建,有时不创建;为IPC_CREAT | IPC_EXCL 表示没有时创建,有时会报错。
成功返回一个用户层面的标识符,用于标识共享内存唯一性,错误返回-1。
也正是因为这个返回值和文件描述符没有关联,使得SystemV标准作为自立的一套标准

ftok:

在这里插入图片描述

前面shmget所说的参数key,就是ftok的返回值,用于标识共享内存唯一性。
pathname和proj_id,可以是符合类型的任意值,ftok会根据自己算法将这两个参数转换成返回值key。

这也说明,如果多个进程的pathname和proj_id都用一样的话,就能通过返回值key访问同一个共享内存。


shmctl:
有了创建共享内存的函数就有可以释放共享内存的函数。
在这里插入图片描述

值得注意的是shmclt不仅可以释放共享内存,也有着访问共享内存的属性作用,这里只说怎么释放内存。
shmid :这个值是shmget的返回值,指的是用户层面上标识共享内存唯一性的。
cmd :是个二进制标识符,IPC_RMID 代表释放共享内存。
在这里插入图片描述
buf 是用于访问共享内存属性的参数,这里可以置空。
成功返回0,失败返回-1.


有了shmget和ftok,就可以创建共享内存了,接下来就需要考虑如何将进程和共享内存关联起来。

不过在此之前先再认识下共享内存:

首先前面说过,内存中一定会在一定时刻存在多个共享内存,多个共享内存就一定需要被操作系统管理,管理就一定需要结构化。

共享内存 = 物理空间块 + 自身属性。
而管理共享内存的结构体struct shm其中就保存了作为系统层面的key,用于标识唯一性。

3.2 共享内存的通信

共享内存的系统命令:

通过ipcs -m 可以查看当前开辟的共享内存
在这里插入图片描述
如果要通过命令释放共享内存,可以通过ipcrm -m 对应shmid
在这里插入图片描述
通过while :; do ipcs -m; sleep 2; done 也可以一直看到共享内存信息。



共享内存的通信:

首先进程和共享内存的联系,也是通过函数进行联系的。

shmat(attach 附加) 和 shmdt(detach 分离)
在这里插入图片描述

shmat 将内存段连接到进程地址空间
shmid 是shmget返回的用户层面上的唯一标识符
一般都是shmat(id, nullptr, 0),让它自动连接将当前进程联系到共享内存段。
返回值返回一个指针,指向共享内存的第一个内存段。如果失败返回(void*)-1

shmdt 取消进程与共享内存的关联。
只需要传一个shmat返回的地址就行,就能取消联系。
成功返回0,失败返回-1。

共享内存进行通信

//comm.hpp
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <cassert>

#define SHMSIZE 4026
#define PATHNAME "."
#define PROJ_ID 0x66


key_t getKey(const char* path, int proj_id)
{
    key_t k = ftok(path, proj_id);
    if(k < 0)
    {
        std::cout << "ftok:" << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
    return k;
}

int getShmHelp(key_t k, int shmflg)
{
    int id = shmget(k, SHMSIZE, shmflg);
    if(id < 0)
    {
        std::cout << "shmget: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

int createShm(key_t k)
{
    umask(0);
    return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}

int getShm(key_t k)
{
    return getShmHelp(k, IPC_CREAT);
}


void* attachShm(int shmId)
{
    void* mem = shmat(shmId, nullptr, 0);
    if((long long)mem == -1L) //linux 64位
    {
        std::cout << "shmat: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

void detachShm(void* start)
{
    if(shmdt(start) == -1)
    {
        std::cout << "shmdt: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

void delShm(int shmId)
{
    if(shmctl(shmId, IPC_RMID, nullptr) == -1)
    {
        std::cout << "shmctl: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

///
//client
#include "comm.hpp"

int main()
{
    //先获取key,用于唯一标识共享内存
    key_t key = getKey(PATHNAME, PROJ_ID);
    printf("key:0x%x\n", key);
    //用户创建共享内存
    int shmId = createShm(key);
    printf("shmId:%d\n", shmId);

    //进程与内存关联
    char* start = (char*)attachShm(shmId);
    printf("attach success, address start: %p\n", start);

    //进程通信
    const char* message = "hello server, 我是另一个进程,正在和你通信";
    pid_t id = getpid();
    int cnt = 1;
    int circnt = 5;
    while(circnt--)
    {
        sleep(5);
        snprintf(start, SHMSIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
    }

    //sleep(10);
    //删除前最好取消关联
    detachShm(start);
    //删除共享内存
    //delShm(shmId);
    return 0;
}

///
//server
#include "comm.hpp"

int main()
{
    //先获取key,用于唯一标识共享内存
    key_t key = getKey(PATHNAME, PROJ_ID);
    printf("key:0x%x\n", key);
    //用户创建共享内存
    int shmId = getShm(key);
    printf("shmId:%d\n", shmId);
    //进程与内存关联
    char* start = (char*)attachShm(shmId);
    printf("attach success, address start: %p\n", start);
    //进程通信

    while(true)
    {
        struct shmid_ds sd;
        shmctl(shmId, IPC_STAT, &sd);
        printf("client say : %s, cpid[%d], key[0x%x]\n", 
        start, sd.shm_cpid, sd.shm_perm.__key);            
        sleep(1);
    }

    //sleep(9);
    //删除前最好取消关联
    detachShm(start);
    //删除共享内存
    delShm(shmId);
    return 0;
}

输出结果:
在这里插入图片描述

3.3 共享内存的缺点以及数据保护

根据上一节的输出结果,进行分析

首先client进程每五秒写到共享内存一次,而server每隔一秒向共享内存中读取。

这也说明共享内存方式通信是有缺点的:不给我进行同步和互斥操作的,对数据没有进行保护
也就是不像管道那样,一份数据写完,读端再读。

对共享内存进行保护,需要写完,通知读端,再读。
没通知server的时候,让server进行阻塞状态。
通过在外层再设计一层命名管道,写端写入一个字符,如果读端收到那么就从共享内存中读取,没有收到就进入阻塞状态(read读不到数据)

#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>
#include <string>

#define SHMSIZE 4026
#define PATHNAME "."
#define PROJ_ID 0x66
#define FIFOPATH "mypipe"

void createFifo(const std::string& path)
{
    umask(0);
    int n = mkfifo(path.c_str(), 0666);
    if(n < 0)
    {
        std::cout << "mkfifo:" << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

void removeFifo(const char* path)
{
    if(unlink(path) == -1)
    {
        std::cout << "unlink:" << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}


key_t getKey(const char* path, int proj_id)
{
    key_t k = ftok(path, proj_id);
    if(k < 0)
    {
        std::cout << "ftok:" << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
    return k;
}

int getShmHelp(key_t k, int shmflg)
{
    int id = shmget(k, SHMSIZE, shmflg);
    if(id < 0)
    {
        std::cout << "shmget: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

int createShm(key_t k)
{
    umask(0);
    return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}

int getShm(key_t k)
{
    return getShmHelp(k, IPC_CREAT);
}


void* attachShm(int shmId)
{
    void* mem = shmat(shmId, nullptr, 0);
    if((long long)mem == -1L) //linux 64位
    {
        std::cout << "shmat: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

void detachShm(void* start)
{
    if(shmdt(start) == -1)
    {
        std::cout << "shmdt: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

void delShm(int shmId)
{
    if(shmctl(shmId, IPC_RMID, nullptr) == -1)
    {
        std::cout << "shmctl: " << errno << ":" << strerror(errno) << std::endl;
        exit(-1);
    }
}

///
//client
#include "comm.hpp"

int main()
{
    //建立命名管道
    createFifo(FIFOPATH);
    //先获取key,用于唯一标识共享内存
    key_t key = getKey(PATHNAME, PROJ_ID);
    printf("key:0x%x\n", key);
    //用户创建共享内存
    int shmId = createShm(key);
    printf("shmId:%d\n", shmId);

    //进程与内存关联
    char* start = (char*)attachShm(shmId);
    printf("attach success, address start: %p\n", start);

    //进程通信
    const char* message = "hello server, 我是另一个进程,正在和你通信";
    pid_t id = getpid();
    //向命名管道中写入数据r
    int wfd = open(FIFOPATH, O_WRONLY, 0666);
    int cnt = 1;
    int circnt = 5;
    while(circnt--)
    {
        sleep(5);
        char sig = 'r';
        ssize_t s = write(wfd, &sig, sizeof(sig));
        assert(s == sizeof(char));
        (void)s;
        snprintf(start, SHMSIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
    }

    //sleep(10);
    //删除前最好取消关联
    detachShm(start);
    close(wfd);
    //删除共享内存
    //delShm(shmId);
    return 0;
}

//
//server
#include "comm.hpp"

int main()
{
    //createFifo(FIFOPATH);
    //先获取key,用于唯一标识共享内存
    key_t key = getKey(PATHNAME, PROJ_ID);
    printf("key:0x%x\n", key);
    //用户创建共享内存
    int shmId = getShm(key);
    printf("shmId:%d\n", shmId);
    //进程与内存关联
    char* start = (char*)attachShm(shmId);
    printf("attach success, address start: %p\n", start);
    //进程通信
    //如果从命名管道中读到了r,就从共享内存中读取数据
    int rfd = open(FIFOPATH, O_RDONLY, 0666);

    while(true)
    {
        char sig;
        ssize_t s = read(rfd, &sig, sizeof(sig));
        if(s == 0)
        {
            printf("read:%d\n", s);
            break;
        }
        if(sig == 'r' && s > 0)
        {
            struct shmid_ds sd;
            shmctl(shmId, IPC_STAT, &sd);
            printf("client say : %s, cpid[%d], key[0x%x]\n", 
            start, sd.shm_cpid, sd.shm_perm.__key);            
        }

        sleep(1);
    }

    //sleep(9);
    //删除前最好取消关联
    detachShm(start);
    //删除共享内存
    delShm(shmId);
    close(rfd);
    //删除命名管道
    removeFifo(FIFOPATH);
    return 0;
}

在这里插入图片描述

3.4 共享内存的优点

共享内存是所有通信方式中速度最快的。
因为其能大大降低数据的拷贝次数。

同样的数据通信,管道实现和共享内存实现。考虑键盘、显示器,共享内存会有2+2次拷贝。
只需要将数据从输入拷贝到共享内存,再从内存直接放到标准缓冲区,再到屏幕。
在这里插入图片描述
而管道通信,在进入管道多了要经过用户层面上的缓冲区(FILE),多了两次拷贝。
在这里插入图片描述

所以如果只考虑管道和共享内存通信传输大量的数据的话,共享内存能快不少。

3.5 信号量以及与共享内存有关的概念

什么是信号量?
信号量本身是一个计数器,通常用来表示公共资源中,资源数量多少的问题的。

公共资源:是可以被多个进程访问的资源。
1、值得注意的是公共资源是需要保护的,不然会出现数据不一致的问题。(而对于数据保护,提出了一些方法,但这些方法也会造成问题,所以问题没有解决,最后只是到了被接受的程度)
2、被保护起来的公共资源称为临界资源
3、进程访问临界资源的代码,称为临界区,而其它的称为非临界区
4、共享资源要么是一个整体,要么划分一个一个资源部分。

如何保护共享资源呢?
互斥与同步。
互斥也就是当一方访问时,另一方阻塞。
同步的情况是一种原子性,比如银行的两个账户各有1000元,一个账号向另一个账号转账200时,一方要扣200,另一方要加200,而如果转账失败,要保证各个账户金额不变。

信号量的作用
打个比方,比如电影院买票,一部电影每场次座位的数量就是一种信号量,在人们买票的时候都要先访问这个信号量。
也就是进程在访问公共资源前,都必须申请sem信号量。

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

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

相关文章

13.STM32超声波模块讲解与实战

目录 1.超声波模块讲解 2.超声波时序图 3.超声波测距步骤 4.项目实战 1.超声波模块讲解 超声波传感器模块上面通常有两个超声波元器件&#xff0c;一个用于发射&#xff0c;一个用于接收。电路板上有4个引脚&#xff1a;VCC GND Trig&#xff08;触发&#xff09;&#xff…

gcc的使用,调试工具gdb的使用

gcc编译 gcc编译可以分为四个步骤&#xff0c;预处理、编译、汇编、链接。 预处理命令&#xff1a;gcc -E hello.c -o hello.i编译命令&#xff1a;gcc -S hello.i -o hello.s汇编命令&#xff1a; gcc -c hello.s -o hello.o链接命令&#xff1a;gcc hello.o -o hello gcc…

一个.Net Core开发的,撑起月6亿PV开源监控解决方案

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 项目发布后&#xff0c;对于我们程序员来说&#xff0c;项目还不是真正的结束&#xff0c;保证项目的稳定运行也是非常重要的&#xff0c;而对于服务器的监控&#xff0c;就是保证稳定运行的手段之一。对数据库、…

Navicat16实用小技巧

数据库管理工具是一种用于管理数据库的软件工具&#xff0c;它可以帮助用户创建、修改、维护和查询数据库。数据库管理工具可以为用户提供可视化界面&#xff0c;使得管理数据库变得更加容易。最为一款数据库管理工具&#xff0c;需要具备一下功能&#xff1a; 数据库创建和配…

OAK相机如何将yoloV6模型转换成blob格式?(2.0 及之后版本)

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…

【对比学习论文】SimCLR 视觉表征对比学习的简单框架

文章题目&#xff1a;A Simple Framework for Contrastive Learning of Visual Representations时间&#xff1a;2020 摘要 本文提出了SimCLR:一种用于视觉表征对比学习的简单框架。我们简化了最近提出的对比自监督学习算法&#xff0c;而不需要专门的架构或内存库。为了了解…

websocket报错集锦-不断更新中

问题1&#xff1a;Failed to construct ‘WebSocket’: An insecure WebSocket connection may not be initiated from a page loaded over HTTPS. 问题描述 Mixed Content: The page at https://AAAAAA.com was loaded over HTTPS, but attempted to connect to the insecur…

Linux系统下命令行安装MySQL5.7+详细步骤

一起学编程&#xff0c;让生活更随和&#xff01; 如果你觉得是个同道中人&#xff0c;欢迎关注博主 gzh &#xff1a;【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享&#x1f433;。偶尔认知思考、日常水文&#x1f40c;。 目录1、下载安装包2、检查…

STM32 使用microros与ROS2通信

本文主要介绍如何在STM32中使用microros与ROS2进行通信&#xff0c;在ROS1中标准的库是rosserial,在ROS2中则是microros,目前网上的资料也有一部分了&#xff0c;但是都没有提供完整可验证的demo&#xff0c;本文将根据提供的demo一步步给大家进行演示。1、首先如果你用的不是S…

JUC 之 线程局部变量 ThreadLocal

—— ThreadLocal 基本概念 ThreadLocal 提供线程局部变量。这些变量与正常的变量不同&#xff0c;因为每一个线程在访问 ThreadLocal 实例的时候&#xff08;通过其get 或者 set 方法&#xff09;都有自己的、独立初始化的变副本。ThreadLocal实例通常是类中的私有静态字段&…

104-JVM优化

JVM优化为什么要学习JVM优化&#xff1a; 1&#xff1a;深入地理解 Java 这门语言 我们常用的布尔型 Boolean&#xff0c;我们都知道它有两个值&#xff0c;true 和 false&#xff0c;但你们知道其实在运行时&#xff0c;Java 虚拟机是 没有布尔型 Boolean 这种类型的&#x…

@Autowired和@Resource的区别

文章目录1. Autowired和Resource的区别2. 一个接口多个实现类的处理2.1 注入时候报错情况2.2 使用Primary注解处理2.3 使用Qualifer注解处理2.4 根据业务情况动态的决定注入哪个serviceImpl1. Autowired和Resource的区别 Aurowired是根据type来匹配&#xff1b;Resource可以根…

数据结构栈的经典OJ题【leetcode最小栈问题大剖析】【leetcode有效的括号问题大剖析】

目录 0.前言 1.最小栈 1.1 原题展示 1.2 思路分析 1.2.1 场景引入 1.2.2 思路 1.3 代码实现 1.3.1 最小栈的删除 1.3.2 最小栈的插入 1.3.3 获取栈顶元素 1.3.4 获取当前栈的最小值 2. 有效的括号 0.前言 本篇博客已经把两个关于栈的OJ题分块&#xff0c;可以根据目…

【蓝牙mesh】Upper协议层介绍

【蓝牙mesh】Upper协议层介绍 Upper层简介 Upper协议层用于处理网络层以上的功能&#xff0c;包括设备的应用层数据、安全、群组等信息&#xff0c;是实现蓝牙mesh应用功能的关键协议之一。Upper层接收来自Access层的数据或者是Upper层自己生成的Control数据&#xff0c;并且将…

typing库

typing 库 引入 在日常代码编写中&#xff0c;由于python语言特性&#xff0c;不用像go等编译性语言一样&#xff0c;在定义函数时就规范参数和放回值的类型。 def demo(a, b):return "ab" 此时 a 和 b 可以传入任意类型参数毫无疑问&#xff0c;这一特性&#…

漏洞分析: WSO2 API Manager 任意文件上传、远程代码执行漏洞

漏洞描述 某些WSO2产品允许不受限制地上传文件&#xff0c;从而执行远程代码。以WSO2 API Manager 为例&#xff0c;它是一个完全开源的 API 管理平台。它支持API设计&#xff0c;API发布&#xff0c;生命周期管理&#xff0c;应用程序开发&#xff0c;API安全性&#xff0c;速…

【RockerMQ】001-RockerMQ 概述

【RockerMQ】001-RockerMQ 概述 文章目录【RockerMQ】001-RockerMQ 概述一、MQ 概述1、MQ 简介2、MQ 用途限流削峰异步解耦数据收集3、常见 MQ 产品概述对比4、MQ 常见协议二、RocketMQ 概述1、简介2、发展历史一、MQ 概述 1、MQ 简介 MQ&#xff0c;Message Queue&#xff0…

C++设计模式(22)——状态模式

亦称&#xff1a; State 意图 状态模式是一种行为设计模式&#xff0c; 让你能在一个对象的内部状态变化时改变其行为&#xff0c; 使其看上去就像改变了自身所属的类一样。 问题 状态模式与有限状态机 的概念紧密相关。 有限状态机。 其主要思想是程序在任意时刻仅可处…

【数据库】数据库的完整性

第五章 数据库完整性 数据库完整性 数据库的完整性是指数据的正确性和相容性 数据的正确性是指数据是符合现实世界语义&#xff0c;反映当前实际状况的数据的相容性是指数据库的同一对象在不同的关系中的数据是符合逻辑的 关系模型中有三类完整性约束&#xff1a;实体完整性…

中创公益|中创算力荣获“2022年度突出贡献爱心企业”

公益是什么&#xff1f;不啻微芒造炬成阳萤火虽微愿为其芒公益是持之以恒的努力&#xff0c;中创于2021年1月成立&#xff0c;同年4月中创就开始了公益活动&#xff0c;并对尖山村贫困儿童进行定期捐助。截至2023年&#xff0c;中创先后7次来到被捐助的贫困儿童家中&#xff0c…