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

news2024/11/26 17:00:28

目录

匿名管道

创建管道---pipe()

 命名管道

创建FIFO 

FIFO 操作

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

共享内存

1.创建共享内存函数shmget()

2.获得共享内存地址函数shmat()

3.删除共享内存函数shmdt()

4.共享内存控制函数shmctl()


在Linux下的多个进程间的通信机制叫做IPC,它是多个进程之间相互沟通的一种方法。在Linux下有多种进程间通信的方法:半双工管道、FIFO (命名管道)、消息队列、信号量、共享内存等。使用这些通信机制可以为Linux下的网络服务器开发提供灵活而又坚固的框架。


匿名管道

管道是种把两个进程之间的标准输入和标准输出连接起来的机制。管道是一种历史悠久的进程间通信的办法,自UNIX操作系统诞生,管道就存在了。
1.基本概念
由于管道仅仅是将某个进程的输出和另一个进程的输入相连接的单向通信的办法,因此称其为“半双工”。在shell中管道用“ | ”表示。

ls  -l | grep *.c

把ls -l的输出当做“grep *.c”的输入,管道在前一个进程中建立输入通道,在后一个进程建立输出通道,将数据从管道的左边传输到管道的右边将ls -l的输出通过管道传给“grep *.c”。
进程创建管道,每次创建两个文件描述符来操作管道。其中一个对管道进行写操作,另一个描述符对管道进行读操作。管道将两个进程通过内核连接起来,两个文件描述符连接在一起。如果进程通过管道fda[0]发送数据,它可以从fdb[0]获得信息。

由于进程A和进程B都能够访问管道的两个描述符,因此管道创建完毕后要设置在各个进程中的方向,希望数据向那个方向传输。这需要做好规划,两个进程都要做统一的设置,在进程A中设置为读的管道描述符,在进程B中要设置为写;反之亦然,并且要把不关心的管道端关掉。对管道的读写与一般的I0系统函数一致, 使用write()函数写入数据,read()函数读出数据,某些特定的IO操作管道是不支持的,例如偏移函数lseek()。 

创建管道---pipe()

#include <unistd.h>

        int pipe(int fd[2]);

fd是一个文件描述符的数组,用于保存管道返回的两个文件描述符。数组中的第1个元素(下标为0)是为了读操作而打开的,而第2个元素(下标为1),是为了写操作而创建和打开的。(0看作一张嘴,用来读,1看作一支笔,用来写)。当函数执行成功时返回0,失败返回错误代码。

只建立管道看起来没有什么用处,要使管道有切实的用处,需要与进程的创建结合起来,利用两个管道在父进程和子进程之间进行通信。在父进程和子进程之间建立一个管道, 子进程向管道中写入数据,父进程从管道中读取数据。要实现这样的模型,在父进程中需要关闭写端,在子进程中需要关闭读端。

#include <iostream>
#include <unistd.h>
#include <string>
#include <string.h>
#include <cerrno>
#include <cassert>
#include <sys/types.h>
using namespace std;

int main()
{
    // 任何一种任何一种进程间通信中,一定要先保证不同的进程之间看到同一份资源
    int pipefd[2] = {0};
    //1. 创建管道
    int n = pipe(pipefd);
    if(n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }
    //2.创建子进程
    pid_t id=fork();
    assert(id != -1);
    if(id==0)
    {
        //子进程
        //3.关闭不需要的fd,让父进程进行读取,子进程进行写入
        close(pipefd[0]);

        //4.开始通信
        string strmessage="hello,我是子进程";
        char buffer[1024];
        int count=1;
        while(true)
        {
            snprintf(buffer,sizeof buffer,"%s,计数器:%d,我的PId:%d",strmessage.c_str(),count++,getpid());
            write(pipefd[1],buffer,strlen(buffer));
            sleep(1);
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    //3.关闭不需要的fd,让父进程进行读取,子进程进行写入
    close(pipefd[1]);

    //4.开始通信
    char buffer[1024];
    while(true)
    {
        int n=read(pipefd[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]='\0';
            cout<<"我是父进程,child give me message:"<<buffer<<endl;
            
        }
    }

    close(pipefd[0]);
    return 0;
}

特点

  1. 单向通信
  2. 管道的本质是文件,因为fd的生命周期随进程,管道的生命周期是随进程的。
  3. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。 (pipe打开管道,并不清楚管道的名字---匿名管道)。
  4. 在管道通信中,写入的次数,和读取的次数,不是严格匹配的读写次数的多少没有强相关。
  5. 具有一定的协同能力,让reader和writer能够按照一定的步骤进行通信---自带同步机制。
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int pipefd[2] = {0};
    //1. 创建管道
    int n = pipe(pipefd);
    if(n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "pipefd[0]: " << pipefd[0] << std::endl; // 读端, 0->嘴巴->读书
    std::cout << "pipefd[1]: " << pipefd[1] << std::endl; // 写端, 1->笔->写东西的

    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1); 

    if(id == 0)// 子进程
    {
        //3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
        close(pipefd[0]);

        //4. 开始通信 -- 结合某种场景
        // const std::string namestr = "hello, 我是子进程";
        // int cnt = 1;
        // char buffer[1024];
        int cnt = 0;
        while(true)
        {
            char x = 'X';
            write(pipefd[1], &x, 1);
            std::cout << "Cnt: " << cnt++<<std::endl;
            sleep(1);
            // break;

            // snprintf(buffer, sizeof buffer, "%s, 计数器: %d, 我的PID: %d", namestr.c_str(), cnt++, getpid());
            // write(pipefd[1], buffer, strlen(buffer));
        }

        close(pipefd[1]);
        exit(0);
    }

    //父进程
    //3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
    close(pipefd[1]);

    //4. 开始通信 -- 结合某种场景
    char buffer[1024];
    int cnt = 0;
    while(true)
    {
        // sleep(10);
        // sleep(1);
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = '\0';
            std::cout << "我是父进程, child give me message: " << buffer << std::endl;
        }
        else if(n == 0)
        {
            std::cout << "我是父进程, 读到了文件结尾" << std::endl;
            break;
        }
        else 
        {
            std::cout << "我是父进程, 读异常了" << std::endl;
            break;
        }
        sleep(1);
        if(cnt++ > 5) break;
    }
    close(pipefd[0]);

    int status = 0;
    waitpid(id, &status, 0);
    std::cout << "sig: " << (status & 0x7F) << std::endl;

    sleep(100);

    return 0;
}

 

  • 如果我们read读取完毕了所有的管道数据,如果对方不发,我就只能等待。read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • 如果我们writer端将管道写满了,我们就不能写了。write调用阻塞,直到有进程读走数据。
  • 如果我关闭了写端,读取完毕管道数据,再读,就会read返回0,表明读到了文件结尾。
  • 写端一直写,读端关闭,这是没有意义的。OS不会维护无意义、低效率或者浪费资源的事情。OS会杀死一直在写入的进程。OS会通过信号来终止进程(SIGPIPE--信号13)。

 命名管道

命名管道的工作方式与普通的管道非常相似,但也有一些明显的区别。

  • 在文件系统中命名管道是以设备特殊文件的形式存在的。
  • 不同的进程可以通过命名管道共享数据。

创建FIFO 

有许多种方法可以创建命名管道。其中,可以直接用shell来完成。例如,在当前目录下建立一一个名字为namedfifo的命名管道:

mkfifo  namedfifo

 

 可以看出namedfifo的属性中有一个p,表示这是个管道。

命名管道也可以从程序里创建,相关函数有:

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

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

创建命名管道: 

int main(int argc, char *argv[]) {

        mkfifo("p2", 0644);

        return 0;

FIFO 操作

对命名管道FIFO来说,IO操作与普通的管道IO操作基本上是一样的,二者之间存在着一个主要的区别。在FIFO中,必须使用一个open()函数来显式地建立连接到管道的通道。一般来说FIFO总是处于阻塞状态。也就是说,如果命名管道FIFO打开时设置了读权限,则读进程将一直“阻塞”,一直到其他进程打开该FIFO并且向管道中写入数据。这个阻塞动作反过来也是成立的,如果一个进程打开一个管道写入数据,当没有进程冲管道中读取数据的时候,写管道的操作也是阻塞的,直到已经写入的数据被读出后,才能进行写入操作。如果不希望在进行命名管道操作的时候发生阻塞,可以在open()调用中使用O_NONBLOCK标志,以关闭默认的阻塞动作。

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

comm.hpp(client.cc和server.cc的命名管道)

#pragma once
#include <iostream>
#include <string>

#define NUM 1024

const std::string fifoname="./fifo";
uint32_t mode = 0666;

server.cc

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"

int main()
{
    // 1. 创建管道文件,只需要一次创建
    umask(0); //这个设置并不影响系统的默认配置,只会影响当前进程
    int n = mkfifo(fifoname.c_str(), mode);
    if(n != 0)
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "create fifo file success" << std::endl;
    // 2. 让服务端直接开启管道文件
    int rfd = open(fifoname.c_str(), O_RDONLY);//只读方式打开
    if(rfd < 0 )
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 2;
    }
    std::cout << "open fifo success, begin ipc" << std::endl;

    // 3. 正常通信
    char buffer[NUM];
    while(true)
    {
        buffer[0] = 0;
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "client# " << buffer << std::endl;
            //printf("%c", buffer[0]);
            //fflush(stdout);
        }
        else if(n == 0)
        {
            std::cout << "client quit, me too" << std::endl;
            break;
        }
        else 
        {
            std::cout << errno << " : " << strerror(errno) << std::endl;
            break;
        }
    }

    // 关闭不要的fd
    close(rfd);

    unlink(fifoname.c_str());

    return 0;
}

client.cc 

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// #include <ncurses.h>
#include "comm.hpp"

int main()
{
    //1. 不需创建管道文件,我只需要打开对应的文件即可!
    int wfd = open(fifoname.c_str(), O_WRONLY);//只写方式打开
    if(wfd < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        return 1;
    }

    // 可以进行常规通信了
    char buffer[NUM];
    while(true)
    {
        std::cout<<"请输入你的消息#:";
        char *msg = fgets(buffer,sizeof(buffer),stdin);
        assert(msg);
        (void)msg;

        buffer[strlen(buffer)-1]=0;
        if(strcasecmp(buffer,"quit") == 0) break;
        ssize_t n = write(wfd,buffer,strlen(buffer));
        assert(n > 0);
        (void)n;
    }
    
    close(wfd);

    return 0;
}

共享内存

共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的。这是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换;与此相反,共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅是地址不同而已,因此不需要进行复制,可以直接使用此段空间。

1.创建共享内存函数shmget()

函数shmget()用于创建一个新的共享内存段, 或者访问一个现有的共享内存段。函数shmget()的原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_ t size, int shmflg);

key:这个共享内存段名字

size:共享内存大小

shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmget()的第一个参数是关键字的值。然后,这个值将与内核中现有的其他共享内存段的关键字值相比较。在比较之后,打开和访问操作都将依赖于shmflg参数的内容。

  • IPC_CREAT:如果在内核中不存在该内存段,则创建它。
  • IPC_EXCL:当与IPC CREAT 一起使用时,如果该内存段早已存在,则此次调用将失败。

如果只使用IPC_CREAT, shmget()或者 将返回新创建的内存段的段标识符,或者返回早已存在于内核中的具有相同关键字值的内存段的标识符。如果同时使用IPC_CREAT和IPC_EXCL,则可能会有两种结果:如果该内存段不存在,则将创建一个新的内存段;如果内存段早已存在,则此次调用失败,并将返回-1。IPC_EXCL本身是没有什么用处的,但在与IPC_CREAT组合使用时,它可用于防止一个现有的内存段为了访问而打开着。一旦进程获得了给定内存段的合法IPC标识符,它的下一步操作就是连接该内存段,或者把该内存段映射到自己的寻址空间中。

单独使用 IPC_CREAT :创建一个共享内存,如果共享内存不存在 ,就创建它;如果存在,就获取已经存在的共享内存并且返回
IPC_EXCT不能单独使用,一般都要配合IPC_CREAT
 IPC_CREAT | IPC_EXCT:创建一个共享内存,如果共享内存不存在,就创建它,如果存在,出错并返回(如果共享内存创建成功,则这个共享内存一定是最新的)

2.获得共享内存地址函数shmat()

函数shmat()用来获取共享内存的地址,获取共享内存成功后,可以像使用通用内存一样对其进行读写操作。函数的原型如下:

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

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid: 共享内存标识

shmaddr:指定连接的地址

shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个字节;失败返回-1

如果shmaddr参数值等于0,则内核将试着查找一个未映射的区域。用户可以指定个地址,但通常该地址只用于访问所拥有的硬件,或者解决与其他应用程序的冲突。SHM_RND标志可以与标志参数进行OR操作,结果再置为标志参数,这样可以让传送的地址页对齐。
此外,如果把SHM_RDONLY标志与标志参数进行OR操作,结果再置为标志参数,这样映射的共享内存段只能标记为只读方式。
当申请成功时,对共享内存的操作与一般内存一样,可以直接进行写入和读出,以及偏移的操作。 

3.删除共享内存函数shmdt()

函数shmdt()用于删除一段共享内存。 函数的原型如下:

#include <sys/types.h>
#include <sys/shm.h>
int shmdt (const void *shmaddr) ;

shmaddr: 由shmat所返回的指针

返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

当某进程不再需要一个共享内存段时,它必须调用这个函数来断开与该内存段的连接。这与从内核删除内存段是两回事。在成功完成了断开连接操作以后,相关的shmid ds结构的shm nattch成员的值将减去1。如果这个值减到0,则内核将真正删除该内存段。

4.共享内存控制函数shmctl()

共享内存的控制函数shmctl的使用类似iocl()的方式对共享内存进行操作:向共享内存的句柄发送命令来完成某种功能。函数shmcl()的原型如下,其中shmid是其享内存的句柄,emd是向共享内存发送的命令,最后一个参数 buf则是向共享内存发送命令的参数。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf) ;

shmid:由shmget返回的共享内存标识码

cmd:将要采取的动作(有三个可取值)

buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0;失败返回-1

  •  IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值。
  •  IPC_SET:获取内存段的shmid_ds结构,并把它存储在buf参数所指定的地址中。IPC_ SET设置内存段shmid_ds 结构的ipc_perm 成员的值,此命令是从buf参数中获得该值的。
  • IPC_ RMID:标记某内存段,以备删除。该命令并不真正地把内存段从内存中删除。相反,它只是标记上该内存段,以备将来删除。只有当前连接到该内存段的最后一个进程正确地断开了与它的连接,实际的删除操作才会发生。当然,如果当前没有进程与该内存段相连接,则删除将立刻发生。为了正确地断开与其共享内存段的连接,进程需要调用shmdt()函数。

小实验: 

 comm.hpp(对方法的封装)

#ifndef __COMM_HPP__
#define __COMM_HPP__

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

using namespace std;

// IPC_CREAT and IPC_EXCT
// 单独使用 IPC_CREAT :创建一个共享内存,如果共享内存不存在 ,就创建它;如果存在,就获取已经存在的共享内存并且返回
// IPC_EXCT不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT |IPC_EXCT:创建一个共享内存,如果共享内存不存在,就创建它,如果存在,出错并返回(如果共享内存创建成功,则这个共享内存一定是最新的)

#define PATHNAME "."
#define PROJID 0x6666

const int gsize = 4096;

key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJID);
    if (k == -1)
    {
        cerr << "error:" << errno << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

// 十六进制转换
string toHex(int x)
{
    char buffer[64];
    snprintf(buffer, sizeof buffer, "0x%x", x);
    return buffer;
}

static int createShmHelper(key_t k, int size, int flag)
{
    int shmid = shmget(k, size, flag);
    if (shmid == -1)
    {
        cerr << "error:" << errno << ":" << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

int createShm(key_t k, int size)
{
    umask(0);
    return createShmHelper(k, size, IPC_CREAT | IPC_EXCL | 0666);
}

int getShm(key_t k, int size)
{
    umask(0);
    return createShmHelper(k, size, IPC_CREAT);
}
char *attachShm(int shmid)
{
    char *start = (char *)shmat(shmid, nullptr, 0);
    return start;
}

void detachShm(char *start)
{
    int n = shmdt(start);
    assert(n != -1);
    (void)n;
}
void delShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
}
#define SERVER 1
#define CLIENT 0
class Init
{
public:
    Init(int t)
    :type(t)
    {
        key_t k = getKey();
        if(type == SERVER)
            shmid = createShm(k,gsize);
        else
        shmid = getShm(k,gsize);

        start = attachShm(shmid);

    }
    char* getStart(){return start;}
    ~Init()
    {
        detachShm(start);
        if(type == SERVER)
            delShm(shmid);
    }

private:
    char*start;
    int type;//server or client
    int shmid;
};
#endif

server.cc

#include "comm.hpp"
#include <unistd.h>
int main()
{
    Init init(SERVER);
    char* start = init.getStart();

    int n = 0;
    while(n <= 26)
    {
        cout<<"client -> server#"<<start<<endl;
        sleep(1);
        n++;
    }
    // //1.创建key
    // key_t k = getKey();
    // cout<<"server key:"<<toHex(k)<<endl;

    // //2.创建共享内存
    // int shmid = createShm(k,gsize);
    // cout<<"server shmid:"<<shmid<<endl;

    // //3.将自己和共享内存关联起来
    // char* start = attachShm(shmid); 
    // sleep(5);
    // //4.将自己和共享内存去关联
    // detachShm(start);
    // //删除共享内存
    // delShm(shmid);
    return 0;
}

client.cc

#include "comm.hpp"
#include <unistd.h>
#include <string>
#include <vector>
int main()
{
    Init init(CLIENT);
    char* start = init.getStart();

    char c = 'A';

    while(c <= 'Z')
    {
        start[c - 'A'] = c;
        c++;
        start[c - 'A'] = '\0';
        sleep(1);
    }
    // //1.创建key
    // key_t k = getKey();
    // cout<<"client key:"<<toHex(k)<<endl;

    // //2.创建共享内存
    // int shmid = createShm(k,gsize);
    // cout<<"client shmid:"<<shmid<<endl;

    // //3.将自己和共享内存关联起来
    // char* start = attachShm(shmid);

    // sleep(10);
    // //4.将自己和共享内存去关联
    // detachShm(start);
    return 0;
}

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

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

相关文章

Unittest单元测试框架之unittest的第三方库paramunittest参数化基础及应用

一、unittest参数化介绍 参数化测试用于当需要使用多组不同的测试数据测试同一个方法的时候 paramunittest是unittest实现参数化的一个专门的模块&#xff0c;可以传入多组参数&#xff0c;自动生成多个用例 实现unittest参数化的方式有多种&#xff0c;如ddt、paramunittest等…

Linux-进程信号

Linux-进程信号 一&#xff0c;信号入门信号的概念生活中的信号技术应用角度的信号使用kill -l 查看信号列表信号的处理方式 二&#xff0c;信号产生通过终端按键产生信号Core Dumpcode dump标志位 通过系统调用向进程发信号killraiseabort 由软件条件产生信号SIGPIPESIGALRM 硬…

三届跻身世界级,长沙国际工程机械展的突破之路

文 | 智能相对论 作者 | 易不二 长沙正在成为全球工程机械企业争相奔赴的产业“盛宴”。 近日完美落幕的第三届长沙国际工程机械展&#xff0c;留下了全球30多个国家、1500余家企业“同台竞技”的精彩亮相&#xff0c;并达成了536亿元的现场交易额。 卡特彼勒、日立、沃尔沃…

2022年蓝桥杯:第十三届蓝桥杯大赛软件赛省赛C/C++大学B组真题(考后回顾,文末附真题链接)

目录 第十三届蓝桥杯大赛软件赛省赛C/C大学B组真题&#xff08;考后回顾&#xff09;试题 A: 九进制转十进制试题 B: 顺子日期试题 C: 刷题统计试题 D: 修剪灌木试题 E: X 进制减法试题 F: 统计子矩阵试题 G: 积木画试题 H: 扫雷试题 I: 李白打酒加强版试题 J: 砍竹子 第十三届…

【JavaStript】

目录 &#x1f437;1. JavaScript 的书写形式 &#x1f6e9;1.1 行内式 &#x1f49b; 1.2 内嵌式 &#x1f357;1.3 外部式 &#x1f37f;2. JavaScricpt 的一些常用语句 &#x1f6f4;2.1 输入&#xff1a;prompt &#x1f47d;2.2 输出&#xff1a;alert &#x1f…

Linux线程间的同步和互斥 进程间传统通信方式 5.16

Linux线程间的同步和互斥 同步&#xff1a;有顺序的考虑 按照约定的顺序相互配合完成一件事情&#xff08;红绿灯&#xff09; {释放 产生 资源1&#xff08;V操作&#xff09;&#xff1b;&#xff1b;申请 资源-1&#xff08;p操作&#xff09;} 信号量代表某一类资源&am…

RocketMQ整理

RocketMQ在阿里云上的商业版本,集成了阿里内部一些更深层次的功能及运维定制。开源版本,功能上略有缺失,但大体上是一样的。 使用Java开发,便于深度定制。最早叫MetaQ。消息吞吐量虽然依然不如Kafka,但是却比RabbitMQ高很多。在阿里内部,RocketMQ集群每天处理的请求数超过…

监控需求来源及主流方案对比

我们从开始了解监控系统来说&#xff0c;首先我们要先了解监控的需求来源&#xff0c;即监控系统都可以用于做什么? 监控需求来源 其实最初的需求很简单&#xff0c;即"系统出问题了我们要能及时感知"。后面随着技术的不断发展&#xff0c;我们对监控系统提出了更…

Linux的超级用户及权限

目录 一:Linux下的两个用户 二&#xff1a;权限 1&#xff1a;目录文件 文件创建的默认权限 2&#xff1a;普通文件 一:Linux下的两个用户 在使用Linux的时候会有两个身份,第一个是普通用户,普通用户在很多方面是受阻的,原因就是权限不够,在这种情况下就有一个超级用户,也…

iOS图片系列一 图片的基本属性

图片在项目的开发中使用频率很高&#xff0c;但是绝大部分都是作为普通的展示或者偶尔需要裁剪&#xff0c;并不需要对图片做什么特别的处理&#xff0c;最近做了一个项目对于图片的需求功能比较多&#xff0c;踩了很多坑的同时也对图片的使用有了更深的理解&#xff0c;整理下…

C++面经:初始化全局变量和未初始化全局变量有什么区别

全局变量初始化而且初始值不为0&#xff0c;那么这样的全局变量是放在内存的.data段的&#xff0c;如果全局变量初始值为0或者未初始化&#xff0c;那么这样的全局变量是放在.bss段的。 考点&#xff1a; 考察C/C语言内存模型&#xff0c;.data&#xff0c;.bss段存放的内容。 …

Windows shell环境: 从git bash切换到msys2

文章目录 1. 目的2. msys2 环境 (Environment)3. 升级 MSYS2: 使用 pacman 滚动式升级整个系统4. 在 Windows Terminal 中增加显示 MSYS25. 使用 zsh6. VSCode 中的配置增加 MSYS2 终端配置 git 路径 7. 安装 C/C 依赖库安装 ag查询 bison 和 flex 的安装目录 8. References 1.…

ES6模块化规范

在没有ES6模块化规范前&#xff0c;有像AMD、CMD这样的浏览器模块化规范&#xff0c;还有像CommonJS这样的服务端模块化规范。 2015年&#xff0c;JS终于推出了官方的模块化规范&#xff0c;为了统一各种规范&#xff0c;我们简称ES6 模块化。 ES6目前作为JS的内置模块化系统&a…

Spring的创建和使用,存储和读取Bean总结

目录 Spring项目创建和使用流程 1.创建一个 Spring 项目 2.存储 Bean 3.读取 Bean ApplicationContext和BeanFactory的区别 通过注解存储 Bean对象 五大类注解的关系 Java程序标准分层 方法注解Bean 注入Bean对象的三种方式 1.属性注入 2.Setter注入 3.构造方法注入…

【Java数据结构】Map和Set

Map和Set 搜索树概念操作 - 查找操作 - 插入操作 - 删除cur没有左树&#xff08;cur.left null&#xff09;cur没有右树&#xff08;cur.right null&#xff09;cur既有左树也有右树&#xff08;最困难的情况&#xff09;替罪羊删除法 操作代码性能分析和 java 类集的关系 搜…

程序员困局:去大城市进大厂却买不了房,回老家又没有高薪工作…

对于在外打拼的程序员来说&#xff0c;难的是进大厂&#xff0c;而不是买不起房。 进大厂的程序员&#xff0c;能不能买得起房&#xff1f; 进大厂的程序员的薪资&#xff0c;还是相当可观的。以阿里P6为例&#xff0c;年薪50万&#xff0c;到手40万左右&#xff0c;刨去10万…

【C++学习】C++11——新特性 | 右值引用 | 完美转发

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; C11——新特性 | 右值引用 | 完美转发 &#x1f440;列表初始化&#x1f9b4; std::initializer_list…

收藏!网络行业主流的六大技术认证全科普

大家好&#xff0c;我是老杨。你的年终总结做完了没&#xff1f;还没做完&#xff0c;点击“年终总结”&#xff0c;拿个模板&#xff0c;快速完成。 很多人在年末都会有列一个新年愿望清单&#xff0c;写写来年想要完成的事情。 不少网工在这两年的就业环境之下&#xff0c;…

Nginx-部署2个vue项目(多个项目)-二级域名设置代理

前言 最近在实际开发过程中&#xff0c;需要在服务器部署2个项目。需要nginx二级域名。 开始时候在网上查了一圈&#xff0c;各有说法&#xff0c;不是很全&#xff0c;很头大。这里把自己成功的二级域名代理记录一下。 网上有很多文章说要该router.js文件&#xff0c;要该vu…

自动备份交换机的配置到远程服务器

环境 交换机配置修改后及时备份相关配置&#xff0c;每次配置变化后需要在1分钟后自动进行保存&#xff0c;并且将配置上传至FTP服务器&#xff1b;每隔30分钟&#xff0c;交换机自动把配置上传到FTP服务器 配置命令&#xff1a; [huawei]set save-configuration delay 1 //…