Linux进程间通信之命名管道及SystemV共享内存

news2024/11/18 5:34:55

在这里插入图片描述

命名管道及SystemV共享内存

  • 命名管道
    • 1. 什么是命名管道
    • 2. 用命名管道实现server&client通信
      • Log.hpp
      • comm.hpp
      • server.cpp
      • client.cpp
      • client.cpp
      • Makefile编译
  • system V共享内存
    • 1. 共享内存示意图
    • 2. 共享内存数据结构
    • 3. 共享内存函数
      • 3.1 shmget函数
      • 3.2 shmat函数
      • 3.3 shmdt函数
      • 3.4 shmctl函数
      • 3.5 ftok函数
    • 4. 共享内存示例
      • 4.1 comm.hpp
      • 4.2 Log.hpp
      • 4.3 shmServer.cpp
      • 4.4 shmClient.cpp
    • Makefile编译
  • system V消息队列
    • 1. 什么是system V消息队列
    • 2. 伪代码示例
    • 3. system V消息队列进程通信机制逐渐没落

命名管道

1. 什么是命名管道

命名管道是一种特殊类型的文件,也称为FIFO(First In, First Out)。它允许进程之间进行通信,就像使用管道一样,但不同之处在于它是存在于文件系统中的一种特殊文件

命名管道允许一个进程向管道中写入数据,而另一个进程可以从管道中读取相同的数据。这使得进程之间能够进行通信,无论它们是否在同一台计算机上,只要它们能够访问这个特殊文件。

你可以使用命令mkfifo在Linux中创建一个命名管道。这个特殊文件在文件系统中看起来像其他文件一样,但它被设计用来在进程之间传递数据。

当使用命名管道时,可以创建两个简单的脚本来展示进程间通信。在下面这个例子中,一个脚本会向命名管道写入消息,而另一个脚本则会从管道读取消息。

步骤:

  1. 创建命名管道

    mkfifo my_pipe
    
  2. 脚本 1:写入数据到命名管道 创建一个脚本 writer.sh,它向命名管道写入消息。

    #!/bin/bash
    
    PIPE=my_pipe
    
    echo "Sending message to the pipe..."
    echo "Hello from the writer script" > $PIPE
    
  3. 脚本 2:从命名管道读取数据 创建另一个脚本 reader.sh,它从命名管道读取消息。

    #!/bin/bash
    
    PIPE=my_pipe
    
    echo "Reading message from the pipe..."
    message=$(cat $PIPE)
    echo "Message received: $message"
    
  4. 运行脚本

    • 在一个终端中运行 ./reader.sh
    • 在另一个终端中运行 ./writer.sh

这样,你会看到写入脚本 writer.sh 向命名管道写入消息,而读取脚本 reader.sh 会从命名管道中读取这个消息并显示出来。这个过程展示了两个脚本之间通过命名管道进行的简单通信。

执行writer.sh向管道写入,没有读取就先阻塞

在这里插入图片描述

执行reader.sh读取管道并执行脚本命令输出发送过来的信息
在这里插入图片描述

当谈及命名管道,有几个要点值得深入探讨:

  1. 文件系统中的存在
    • 命名管道在文件系统中以文件的形式存在,它们有一个路径名。这使得多个进程能够通过这个路径名来访问同一个管道,从而实现进程间的通信。其他进程可以打开这个文件,并向其写入数据或从中读取数据。
  2. 持久性
    • 命名管道在文件系统中持久存在,即使通信的进程结束后,该管道文件仍然存在。除非显式地被删除,否则会一直保留在文件系统中。这使得它们适合用于长期的、不相关的进程间通信。
  3. 读写操作
    • 类似于匿名管道,命名管道也是一个先进先出(FIFO)的通道。一个进程可以向管道中写入数据,而另一个进程则可以从管道中读取相同的数据。这种读写操作是阻塞的,也就是说,如果没有数据可用,读取操作会等待,直到有数据可用为止。
  4. 权限和访问控制
    • 像其他文件一样,命名管道也受文件系统的权限控制。这意味着你可以使用权限位掩码(mode 参数)来设置谁可以对管道进行读取、写入以及执行操作。
  5. 多用途性
    • 由于其存在于文件系统中且具有路径名,命名管道在不同程序之间共享数据非常有用。它们可以被不相关的进程使用,只要这些进程可以访问相同的路径名。

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

int mkfifo(const char *filename, mode_t mode);
  • filename 参数是要创建的命名管道的路径名。
  • mode 参数是用于设置文件权限的模式。它类似于 chmod 命令中使用的权限位掩码,用于确定文件的访问权限。

这个函数会在指定路径创建一个命名管道,返回值为 0 表示成功创建,返回 -1 表示失败,并且会设置适当的错误代码,可以使用 errno 来获取具体的错误信息。
在这里插入图片描述

命名管道(Named Pipes)和匿名管道(Anonymous Pipes)是用于进程间通信的两种不同方式,它们有几个重要的区别

  1. 命名
    • 匿名管道:是一种临时的、单向的通道,只能在相关进程之间使用,无法用于无关的进程间通信。通常用于父子进程之间或者在一个进程内部创建的子进程之间。
    • 命名管道:以文件系统中的文件形式存在,可以被无关的进程访问。命名管道有一个路径名,允许不相关的进程通过这个路径名来访问同一个管道,从而实现进程间通信。
  2. 持久性
    • 匿名管道:进程间通信结束或管道被关闭后就消失,不留下痕迹。
    • 命名管道:作为文件存在于文件系统中,即使通信的进程结束,文件仍然存在,需要显式地被删除。
  3. 用途
    • 匿名管道:通常用于父子进程间或者在一个进程内部创建的子进程之间的通信。
    • 命名管道:适合不相关的进程间通信,比如在不同的程序之间共享数据。

总的来说,匿名管道更适合于有限的、有关的进程间通信,而命名管道更适合于长期、不相关的进程间通信,因为它们可以持久存在并被不同的进程访问。

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

Log.hpp

#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3


const std::string msg[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}


#endif

这个头文件定义了一个简单的日志记录函数 Log 和日志级别常量。

  • #ifndef _LOG_H_#define _LOG_H_ 是头文件的保护宏,避免重复包含。
  • #include 部分引入了所需的标准库头文件。
  • Debug, Notice, Warning, Error 是日志级别的常量,代表不同的日志类型。
  • msg[] 是一个存储日志类型字符串的数组。
  • Log() 函数接受消息字符串和日志级别,然后将消息打印到标准输出,显示时间戳、日志级别和消息内容。

comm.hpp

#ifndef _COMM_H_
#define _COMM_H_

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;

#define MODE 0666
#define SIZE 128

string ipcPath = "./fifo.ipc";


#endif

这个文件包含了一些常量和全局变量,以及必要的头文件。

  • #ifndef _COMM_H_#define _COMM_H_ 是头文件的保护宏。
  • #include 部分引入了所需的标准库头文件和其他自定义的头文件,包括 Log.hpp
  • using namespace std; 是为了方便使用 C++ 标准库中的函数和对象。
  • MODESIZE 是用于管道和缓冲区大小的常量。
  • ipcPath 是命名管道的路径。

server.cpp

#include "comm.hpp"
#include <sys/wait.h>

static void getMessage(int fd)
{
    char buffer[SIZE];
    while (true)
    {
        memset(buffer, '\0', sizeof(buffer));
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            cout <<"["  << getpid() << "] "<< "client say> " << buffer << endl;
        }
        else if (s == 0)
        {
            // end of file
            cerr <<"["  << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl;
            break;
        }
        else
        {
            // read error
            perror("read");
            break;
        }
    }
}

int main()
{
    // 1. 创建管道文件
    if (mkfifo(ipcPath.c_str(), MODE) < 0)
    {
        perror("mkfifo");
        exit(1);
    }

    Log("创建管道文件成功", Debug) << " step 1" << endl;

    // 2. 正常的文件操作
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(2);
    }
    Log("打开管道文件成功", Debug) << " step 2" << endl;

    int nums = 3;
    for (int i = 0; i < nums; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            // 3. 编写正常的通信代码了
            getMessage(fd);
            exit(1);
        }
    }
    for(int i = 0; i < nums; i++)
    {
        waitpid(-1, nullptr, 0);
    }
    // 4. 关闭文件
    close(fd);
    Log("关闭管道文件成功", Debug) << " step 3" << endl;

    unlink(ipcPath.c_str()); // 通信完毕,就删除文件
    Log("删除管道文件成功", Debug) << " step 4" << endl;

    return 0;
}

这个文件包含了从命名管道读取消息的服务端代码。

  • main() 函数中:
    • 使用 mkfifo() 创建一个命名管道。
    • open(ipcPath.c_str(), O_RDONLY) 打开命名管道以进行读取操作。
    • 使用 fork() 创建了多个子进程,每个子进程调用 getMessage() 从管道中读取消息并显示到控制台。
    • 等待所有子进程执行完毕后,关闭文件描述符。
    • 使用 unlink() 删除命名管道文件。

getMessage() 函数:

  • 不断循环读取管道中的数据,并将数据显示在控制台上。

client.cpp

#include "comm.hpp"

int main()
{
    // 1. 获取管道文件
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }

    // 2. ipc过程
    string buffer;
    while(true)
    {
        cout << "Please Enter Message Line :> ";
        std::getline(std::cin, buffer);
        write(fd, buffer.c_str(), buffer.size());
    }

    // 3. 关闭
    close(fd);
    return 0;
}

client.cpp

这个文件包含了向命名管道写入消息的客户端代码。

  • main() 函数中:
    • open(ipcPath.c_str(), O_WRONLY) 打开命名管道以进行写入操作。
    • 使用 std::getline() 获取用户输入的消息,然后使用 write() 将消息写入到管道中。
    • close(fd) 关闭文件描述符。

Makefile编译

.PHONY:all
all:client mutiServer

client:client.cpp
	g++ -o $@ $^ -std=c++11
mutiServer:server.cpp
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f client mutiServer

编译完成后,我们打开终端,两个终端分别执行clientmutiServer两个程序

先执行mutiServer 等待client发送

在这里插入图片描述

在另一终端执行client,打开管道

在这里插入图片描述

我们在client端输入消息,mutiServer 端接收

在这里插入图片描述

client端终止程序,管道关闭并删除

在这里插入图片描述

system V共享内存

System V共享内存是一种用于在Linux系统中进程间共享内存的机制。它是System V IPC(Inter-Process Communication,进程间通信)机制的一部分,与信号量和消息队列一起组成了System V IPC

特点包括:

  1. 共享内存段: System V共享内存允许多个进程访问相同的逻辑内存部分。这些内存段通过一个唯一的标识符来标识,允许多个进程将同一个共享内存段映射到它们的地址空间中。
  2. 高效性: 与管道、消息队列等其他IPC机制相比,共享内存的效率更高。它可以用于需要频繁、大量数据交换的场景,因为进程可以直接读写共享内存而无需进行复制或通过内核来中转数据。
  3. 操作简单: System V共享内存提供了一组系统调用,如shmgetshmatshmdtshmctl等,用于创建、连接、断开和管理共享内存段。
  4. 需要显式清理: 与文件映射内存不同,System V共享内存在进程结束后并不会自动清理。因此,程序员需要确保适当地断开连接并删除共享内存段,以防止资源泄漏。

共享内存允许不同进程之间通过共享相同的内存区域来交换数据,适用于需要高性能和频繁数据交换的应用场景,但同时也需要程序员进行显式的内存管理和同步。

1. 共享内存示意图

在这里插入图片描述

2. 共享内存数据结构

struct shmid_ds {
    struct ipc_perm shm_perm; /* operation perms */
    int shm_segsz; /* size of segment (bytes) */
    __kernel_time_t shm_atime; /* last attach time */
    __kernel_time_t shm_dtime; /* last detach time */
    __kernel_time_t shm_ctime; /* last change time */
    __kernel_ipc_pid_t shm_cpid; /* pid of creator */
    __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
    unsigned short shm_nattch; /* no. of current attaches */
    unsigned short shm_unused; /* compatibility */
    void *shm_unused2; /* ditto - used by DIPC */
    void *shm_unused3; /* unused */
};

System V 共享内存中用于维护共享内存段信息的结构体 shmid_ds。这个结构体中包含了关于共享内存段的多项信息,例如:

  • shm_perm: 是一个 struct ipc_perm 类型的结构体,包含了关于共享内存段权限的信息,比如操作权限等。
  • shm_segsz: 表示共享内存段的大小(以字节为单位)。
  • shm_atime, shm_dtime, shm_ctime: 分别代表最后一次附加(attach)、分离(detach)和更改(change)的时间。
  • shm_cpid: 是创建者进程的PID。
  • shm_lpid: 是最后操作共享内存的进程的PID。
  • shm_nattch: 表示当前附加到这个共享内存段的进程数。
  • 其他未使用的字段,如 shm_unused, shm_unused2, shm_unused3

这个结构体主要用于跟踪和管理共享内存段的属性和状态,例如谁创建了这个共享内存段、谁最后访问了它、它的大小等。这些信息对于维护和控制共享内存的生命周期和访问权限非常重要。

3. 共享内存函数

3.1 shmget函数

shmget 函数是用于创建或获取 System V 共享内存段的函数,它在Linux系统中的 System V IPC 机制中使用。

声明

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

int shmget(key_t key, size_t size, int shmflg);

参数

  • key: 用于唯一标识共享内存段的键值。通常使用 ftok() 函数生成一个键。
  • size: 指定要创建或获取的共享内存段的大小(以字节为单位)。
  • shmflg: 是一个标志参数,用于指定一些附加的操作,比如权限标志和行为选项。可以与 | 操作符结合使用多个标志。

返回值

  • 若成功,返回一个标识共享内存段的标识符(非负整数),称为共享内存标识符(shmid)。
  • 若失败,返回 -1,并设置 errno 指示失败的具体原因。

工作原理

  • 如果传入的 key 对应的共享内存段已经存在,则 shmget 将返回其标识符(shmid),不会创建新的共享内存段。
  • 如果传入的 key 没有对应的共享内存段,且 shmflg 包含了 IPC_CREAT 标志,shmget 将会创建一个新的共享内存段,并返回其标识符。
  • shmflg 参数可以包含其他标志,如权限标志 IPC_CREAT | 0666 确定了共享内存段的权限。

这个函数是创建或获取共享内存段的第一步。创建成功后,接下来需要使用 shmat 将其连接到进程的地址空间,才能进行读写操作。

3.2 shmat函数

shmat 函数用于将共享内存附加到调用进程的地址空间,允许进程访问和操作共享内存段。

声明

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

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

参数

  • shmid: 共享内存段的标识符,由 shmget 返回。
  • shmaddr: 期望共享内存段连接的地址。通常为 NULL,表示让操作系统选择合适的地址。
  • shmflg: 标志参数,可以包括一些特定的选项,比如 SHM_RDONLY 用于只读方式连接共享内存。

返回值

  • 若成功,返回共享内存段的起始地址;若失败,返回 (void *) -1,并设置 errno 指示失败的具体原因。

工作原理

  • shmat 将共享内存段连接到调用进程的地址空间。
  • 如果 shmaddr 为 NULL,操作系统将自动选择一个适当的地址将共享内存连接到调用进程的地址空间。
  • 连接成功后,进程可以使用返回的地址指针进行对共享内存的读写操作。

注意事项

  • 一旦共享内存连接到进程的地址空间,进程就可以直接读写共享内存。因此,需要小心处理共享内存中的数据,以避免数据损坏或不一致。
  • 当不再需要共享内存时,需要使用 shmdt 函数将其从进程的地址空间分离。

3.3 shmdt函数

shmdt 函数用于将共享内存从进程的地址空间中分离,使得进程无法再访问共享内存段。

声明

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

int shmdt(const void *shmaddr);

参数

  • shmaddr: 指向共享内存段附加地址的指针。

返回值

  • 若成功,返回 0。
  • 若失败,返回 -1,并设置 errno 指示失败的具体原因。

工作原理

  • shmdt 用于将共享内存从调用进程的地址空间中分离,但不会删除共享内存段。
  • 进程使用这个函数将共享内存从其地址空间分离后,就无法再访问这段共享内存的内容。

注意事项

  • 分离共享内存后,其他仍然附加到共享内存的进程仍然可以访问和操作共享内存。
  • 分离共享内存后,通常不会释放共享内存,只是取消了进程对共享内存的访问权限。通常需要在不再需要共享内存时使用 shmctl 函数来删除共享内存段。

3.4 shmctl函数

shmctl 函数是用于控制 System V 共享内存的函数,可以用来执行各种对共享内存段的控制操作。

声明

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数

  • shmid: 共享内存段的标识符,由 shmget 返回。

  • cmd: 控制命令,用于指定执行的操作。常用的命令包括:

    • IPC_STAT: 获取共享内存的状态,将信息写入 buf 参数指向的结构体中。

    • IPC_SET: 设置共享内存的状态,通过 buf 参数中提供的信息进行设置。

    • IPC_RMID: 从系统中删除共享内存段。
      在这里插入图片描述

  • buf: 一个指向 struct shmid_ds 结构体的指针,用于存储或传递共享内存段的信息。

返回值

  • 若成功,返回一个非负整数,具体返回值取决于执行的命令。
  • 若失败,返回 -1,并设置 errno 指示失败的具体原因。

工作原理

  • shmctl 函数用于执行与共享内存段相关的控制操作。
  • 通过 cmd 参数来指定需要执行的操作,如获取共享内存的状态、设置共享内存的状态或者删除共享内存段。

注意事项

  • 使用 IPC_RMID 命令可以删除共享内存段。删除后,所有附加到该共享内存段的进程将不再能够访问共享内存,但直到所有进程都断开连接,系统才会回收该共享内存。

3.5 ftok函数

声明

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

key_t ftok(const char *pathname, int proj_id);

参数

  • pathname: 是一个指向路径名的字符串,通常是一个现有的文件路径。ftok 使用此路径名来生成键。
  • proj_id: 是一个用户指定的整数,用于生成唯一的键。

返回值

  • 若成功,返回一个由 pathnameproj_id 生成的唯一的键。
  • 若失败,返回 -1,并设置 errno 指示失败的具体原因。

工作原理

  • ftok 根据 pathnameproj_id 来创建一个键。
  • 它使用 st_devst_ino 两个文件属性(stat 结构体中的设备编号和节点编号)以及 proj_id 来创建唯一的键。

注意事项

  • 由于 ftok 使用文件属性来生成键值,因此传递给 ftok 的文件必须存在并且可访问,否则可能会导致生成的键不唯一或者失败。
  • 生成的键值通常用于标识 System V IPC 中的共享内存、消息队列或信号量等资源。

4. 共享内存示例

4.1 comm.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std; 

#define PATH_NAME "/home/kingxzq"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍

#define FIFO_NAME "./FIFO"

class Init
{
public:
    Init()
    {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("create fifo success",Notice) << "\n";
    }
    ~Init()
    {
        unlink(FIFO_NAME);
        Log("remove fifo success",Notice) << "\n";
    }
};

#define READ O_RDONLY
#define WRITE O_WRONLY

int OpenFIFO(std::string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void Wait(int fd)
{
    Log("等待中....", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}

void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("唤醒中....", Notice) << "\n";
}

void CloseFifo(int fd)
{
    close(fd);
}
  • Init 类包含了创建和删除命名管道的方法。在构造函数中创建了一个命名管道,并在析构函数中删除了这个管道。
  • OpenFIFO 函数用于打开一个命名管道,并返回文件描述符。
  • Wait 函数在文件描述符中等待并接收信号。
  • Signal 函数用于向文件描述符中写入数据,发送信号。
  • CloseFifo 函数用于关闭文件描述符。

4.2 Log.hpp

#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3


const std::string msg[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}


#endif
  • 定义了几个日志级别(Debug、Notice、Warning、Error),以及每个级别对应的描述字符串。
  • Log 函数用于接收消息和消息级别,然后在控制台上打印该消息,包括时间戳和级别。
  • 这个函数返回 std::ostream 对象,允许像使用 std::cout 一样使用这个函数打印日志。

4.3 shmServer.cpp

#include "comm.hpp"

Init init; 

string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

int main()
{
    // 1. 创建公共的Key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);

    Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;

    // 2. 创建共享内存
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); 
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << " shmid : " << shmid << endl;

    // 3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " shmid : " << shmid << endl;

    
    int fd = OpenFIFO(FIFO_NAME, READ);
    for(;;)
    {
        Wait(fd);

        printf("%s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
    }
    // 4. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << " shmid : " << shmid << endl;

    // 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << " shmid : " << shmid << endl;

    CloseFifo(fd);
    return 0;
}

这段代码负责创建共享内存、连接共享内存、从共享内存中读取数据,并最终清理共享内存资源和命名管道。

  • Init init;:调用了 Init 类的构造函数,创建了一个命名管道。
  • TransToHex 函数:将 key_t 类型的键值转换为十六进制字符串。
  • main 函数中执行的关键步骤包括:
    1. 通过 ftok 函数创建共享内存的键值。
    2. 使用 shmget 创建一个共享内存段,设置了 IPC_CREAT 和 IPC_EXCL 标志以确保不存在同样的共享内存。
    3. 通过 shmat 连接到该共享内存,获得共享内存的地址。
    4. 使用 OpenFIFO 函数打开命名管道。
    5. 循环中使用 Wait 函数等待从命名管道接收到信号,然后从共享内存中读取数据并打印,直到读取到 “quit” 消息。
    6. 使用 shmdt 解除对共享内存的连接。
    7. 使用 shmctl 删除共享内存。
    8. 关闭命名管道。

整个过程实现了服务端的功能,通过共享内存和命名管道实现了与客户端的通信。

4.4 shmClient.cpp

#include "comm.hpp"

int main()
{
    Log("child pid is : ", Debug) << getpid() << endl;
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0)
    {
        Log("create key failed", Error) << " client key : " << k << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << k << endl;

    // 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if(shmid < 0)
    {
        Log("create shm failed", Error) << " client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client key : " << k << endl;


    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if(shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << " client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Error) << " client key : " << k << endl;

    int fd = OpenFIFO(FIFO_NAME, WRITE);
    while(true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE-1);
        if(s > 0)
        {
            shmaddr[s-1] = 0;
            Signal(fd);
            if(strcmp(shmaddr,"quit") == 0) break;
        }
    }

    CloseFifo(fd);
    // 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success", Error) << " client key : " << k << endl;

    return 0;
}

这段代码实现了一个客户端程序,用于与服务端通过共享内存和命名管道进行通信。

  • 首先,程序打印出自己的进程 ID。
  • 接着使用 ftok 函数创建一个键值,用于获取共享内存。
  • 通过 shmget 获取共享内存段。
  • 使用 shmat 连接到共享内存,获得共享内存的地址。
  • 打开命名管道,并进入一个无限循环。
  • 在循环中,程序从标准输入中读取数据,将数据写入共享内存,并发送信号给服务端,以此实现数据的传输。
  • 如果输入 “quit”,则退出循环。
  • 关闭命名管道,解除对共享内存的连接并退出程序。

这个程序的作用是向服务端发送数据,并接收来自服务端的信号,在读取数据后将其写入共享内存,通过命名管道实现了与服务端的同步和通信。

Makefile编译

.PHONY:all
all:shmClient shmServer

shmClient:shmClient.cpp
	g++ -o $@ $^ -std=c++11
shmServer:shmServer.cpp
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f shmClient shmServer

编译完成后,我们打开终端,两个终端分别执行shmClientshmServer两个程序

先执行shmServer 等待shmClient发送,shmClient发送消息后,shmServer接收再次进行等待,直到客户端输入quit
在这里插入图片描述

如果我们在客户端造成非法退出,可能会导致再次开启服务端失败

在这里插入图片描述

再次启动服务端
在这里插入图片描述

这是因为ipc资源没有被删除,导致资源占用,无法初始化新的ipc资源

我们使用命令ipcs -m查看ipc资源

在这里插入图片描述

输入指令ipcrm -m [shmid号]删除shm ipc资源,需要注意的是

IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

这里除了删除ipc资源还需要重新生成命名管道文件,这里命名管道的作用主要是实现进程间的同步和控制。在这个示例中,命名管道用于实现服务端和客户端之间的同步,以协调它们对共享内存的访问和操作。

具体来说:

  1. 服务端等待信号: 服务端通过命名管道等待来自客户端的信号,以指示何时从共享内存中读取数据。
  2. 客户端发送信号: 客户端在向共享内存写入数据后,通过命名管道向服务端发送信号,告知服务端可以读取共享内存中的数据。

这种设计允许两个进程以同步的方式共享数据,避免了竞争条件和数据不一致的问题。命名管道充当了同步的信号桥梁,协调了进程间的操作,使得共享内存能够在适当的时候被读取和写入。

共享内存本身并不包含进程同步和控制的机制。它只是提供了一个共享的内存区域,允许多个进程访问相同的内存空间。因此,需要其他机制来协调和控制进程对共享内存的访问以避免数据竞争和不一致性

通常,要确保共享内存的安全访问,需要结合其他形式的同步和控制机制,比如:

  • 信号量: 用于控制对共享资源的访问,确保同一时间只有一个进程可以访问共享内存。
  • 互斥锁: 可以确保同时只有一个进程能够执行临界区代码,避免多个进程同时写入共享内存。
  • 条件变量: 用于实现进程间的等待和通知机制,允许进程等待某个条件成立后才执行操作。

这些机制能够协调进程之间的行为,确保数据的一致性和安全访问。命名管道在这个示例中就是用来辅助实现了进程间的同步和控制。

system V消息队列

1. 什么是system V消息队列

System V 消息队列是 Linux 系统中一种 IPC(进程间通信)机制,它提供了一种进程间通信的方式,允许在不同进程之间传递数据。

特点包括:

  1. 消息队列结构: 消息队列是一组消息的集合,每个消息都有一个类型和一个正文。
  2. 独立于发送者和接收者: 发送者可以发送消息,接收者可以接收消息,彼此之间不需要直接连接。这使得进程可以异步地进行通信。
  3. 消息类型: 每个消息都有一个类型,接收者可以选择特定类型的消息进行接收。
  4. 持久性: 消息队列是持久的,它们会一直存在直到被显式地删除。
  5. 有限大小: 每个消息队列有限制大小,不同操作系统和配置可能有不同的限制。

System V 消息队列通过一系列函数来实现,比如 msgget(创建或获取一个消息队列)、msgsnd(发送消息到队列)、msgrcv(从队列接收消息)等。这些函数允许进程进行消息的发送、接收和控制消息队列的属性。

这种通信机制适用于需要异步通信、数据传递、以及进程间解耦的场景。它提供了一种可靠的进程间通信方式,使得不同的进程能够以较低的耦合度进行数据交换。

2. 伪代码示例

以下示例中的代码只是概念性的示范,并不是可直接运行的代码,因为 System V IPC 函数需要正确的参数和错误处理。另外,System V IPC 函数使用的是 C 的函数接口,不同操作系统之间可能会有细微的差异

// Sender Process

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct msgbuf {
    long mtype;
    char mtext[100];
};

int main() {
    key_t key = ftok("path", 'A');
    int msgid = msgget(key, IPC_CREAT | 0666);

    struct msgbuf message;
    message.mtype = 1; // 设置消息类型为1
    strcpy(message.mtext, "FileTransfer: file.txt, size: 1024");
    
    msgsnd(msgid, &message, sizeof(message), 0);

    // 继续其他任务或等待接收者的确认
    return 0;
}


// Receiver Process

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct msgbuf {
    long mtype;
    char mtext[100];
};

int main() {
    key_t key = ftok("path", 'A');
    int msgid = msgget(key, 0666);

    struct msgbuf message;
    msgrcv(msgid, &message, sizeof(message), 1, 0);

    printf("Received message: %s\n", message.mtext);

    // 提取文件名和大小
    // 创建文件
    // 发送确认消息

    return 0;
}

这段示例代码展示了如何使用 System V 消息队列在两个进程之间进行简单的通信。发送者发送一个包含文件信息的消息,接收者从消息中提取数据并执行相应操作,然后发送确认消息。

3. system V消息队列进程通信机制逐渐没落

System V 消息队列虽然是一种有效的 IPC(进程间通信)机制,但它逐渐被其他更现代化的进程间通信方式所取代,主要有以下几个原因:

  1. 复杂性: 使用 System V 消息队列需要处理 IPC API,需要调用诸如 msggetmsgsndmsgrcv 等函数,而这些操作对于开发者来说可能比较繁琐,容易出错,不够直观。
  2. 性能: 与其他 IPC 机制相比,消息队列的性能可能相对较低。比如,相对于共享内存,消息队列需要进行数据拷贝,这可能导致额外的开销。
  3. 限制: System V 消息队列具有固定的消息大小限制,这会限制消息的大小和传输的灵活性。
  4. 可移植性: 不同操作系统对 System V 消息队列的支持程度和细节可能不同,这可能导致不同系统上的兼容性问题。
  5. 替代技术: 随着时间的推移,出现了更现代化、更方便的进程间通信方式,比如 POSIX 消息队列、管道、套接字等,它们可能更易于使用,并提供了更好的性能和灵活性。

基于这些原因,开发者在选择 IPC 机制时可能更倾向于使用其他更简单、更高效的方式,这导致 System V 消息队列逐渐被替代或淘汰。

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

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

相关文章

8年经验之谈 —— 记一次接口压力测试与性能调优!

经验总结 1. 如果总的CPU占用率偏高&#xff0c;且基本都被业务线程占用时&#xff0c;CPU占用率过高的原因跟JVM参数大小没有直接关系&#xff0c;而跟具体的业务逻辑有关。 2. 当设置JVM堆内存偏小时&#xff0c;GC频繁会导致业务线程停顿增多&#xff0c;TPS下降&#xff…

【Unity】 场景优化策略

Unity 场景优化策略 GPU instancing 使用GPU Instancing可以将多个网格相同、材质相同、材质属性可以不同的物体合并为一个批次&#xff0c;从而减少Draw Calls的次数。这可以提高性能和渲染效率。 GPU instancing可用于绘制在场景中多次出现的几何体&#xff0c;例如树木或…

【分享】Excel“只读方式”的两种模式

查阅Excel表格的时候&#xff0c;担心不小心修改了内容&#xff0c;可以给Excel设置以“只读方式”打开&#xff0c;这样就算修改了内容也不能直接保存表格。Excel表格可以设置两种“只读方式”&#xff0c;一起来看看吧&#xff01; “只读方式” 1&#xff1a; 打开Excel表…

CCF ChinaSoft 2023 论坛巡礼 | 顶会顶刊论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

Netty入门指南之Reactor模型

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言单线程…

网银转账虚拟生成器在线制作,工商农业邮政建设招商,标签+对话框+画板+快照实现

标签对话框画板快照实现就实现了一个虚拟截图生成器&#xff0c;当然我加了水印了&#xff0c;这个图片你根本盗用不了&#xff0c;图片模版的话网上真的太多了&#xff0c;我这个也是网上找的&#xff0c;自己百度图库搜一下&#xff0c;然后标签记得一定用黑月的透明标签&…

c语言-数据结构-链表分割

链表分割实际上是给定一个值&#xff0c;遍历链表把链表中小于该值的节点与大于该值的节点分开&#xff0c;一般是将小于该值的节点放到链表的前面部分&#xff0c;大于该值的节点放在链表的后面部分。 链表分割示意图如下&#xff1a; 思路&#xff1a; 首先创建两条带哨兵位节…

Topk问题!(面试高频常考)

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; 剑指offer &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言&#x1f324;️什么是Top-k问题&#xff1f;&#x1f324;️常见的Top-K问题类型☁️寻找…

Global_Mapper_Pro_25.0安装教程大全

一. 下载&#xff1a; http://dt1.8tupian.net/2/29913a55b1000.pg3二. 介绍&#xff1a; Global Mapper Pro 25是领先的GIS数据处理解决方案&#xff01;提供了一整套符合标准的功能来提升您的操作和技能&#xff0c;您可以最合理的利用您的工具集来完成以前复杂的工作任务&a…

深度学习AIR-PolSAR-Seg图像数据预处理

文章目录 深度学习sar图像数据预处理一.图片预处理操作1.log(1x)处理2.sqrt平方化处理 二.原网络训练效果展示原始数据训练效果展示&#xff1a; 三.对比实验1.采用原始数据2.采用取log(1x)后的数据3.采用取平方后归一化处理&#xff1a; 四.总结&#xff1a;五.思考 深度学习s…

揭秘 DCNN——AlexNet

来源 — gifs.com 一、说明 还记得 2012 年的 ImageNet 视觉识别挑战赛吗&#xff1f;当然&#xff0c;你知道&#xff01;经过大量的反复试验和实验&#xff0c;研究员 Alex Krizhevsky 及其合著者 Ilya Sutskever 和 Geoffrey E. Hinton&#xff08;他真正理解了深度学习中…

Windows没有USB启动选项很常见,但解决方法更常见

当试图在计算机上重新安装Windows 11/10操作系统,或从安装介质启动时,一些用户看到错误–系统没有任何USB启动选项,请在启动管理器菜单中选择其他启动选项。此错误出现在不同OEM的多个设备,原因包括启用了安全引导、禁用了Legacy/CSM支持、联想服务引擎、未正确制作可引导U…

万宾科技智能传感器EN100-C2有什么作用?

在日常生活中井盖是一种常见的城市设施&#xff0c;但井盖出现问题可能会对人们的生活造成什么影响呢&#xff1f;移位或老化的井盖可能会威胁人们的安全&#xff0c;同时也会影响城市生命线的正常运行。然而智能井盖的出现为解决这些问题提供了有效的应对方案。 WITBEE万宾智能…

Day44 力扣动态规划 : 300.最长递增子序列|674. 最长连续递增序列 | 718. 最长重复子数组

Day44 力扣动态规划 : 300.最长递增子序列&#xff5c;674. 最长连续递增序列 &#xff5c; 718. 最长重复子数组 300.最长递增子序列第一印象看完题解的思路dp递推公式遍历顺序初始化 实现中的困难感悟代码 674. 最长连续递增序列第一印象dp状态转移公式遍历顺序初始化 看完题…

能够定时发送朋友圈的软件

此款软件提供便捷的网页端登录方式&#xff0c;让您轻松管理多个账号&#xff0c;实现多账号聚合管理&#xff0c;只需一个界面即可解决所有问题。 朋友圈内容编辑功能强大&#xff0c;让您在输入框内输入文本内容&#xff0c;点击表情图标选择表情&#xff0c;还能通过“”图标…

Go利用反射实现一个ini文件的解析器程序

package mainimport ("bufio" // 逐行读取配置文件"fmt""log""os""reflect""strconv""strings" )type Config struct { // 定义配置结构体Section1 Section1 ini:"section1" // 嵌套结构体1…

洗地机哪个牌子最好用?洗地机怎么选?2023洗地机选购推荐

家里有小孩或者是养有宠物的都有一个深刻的体验&#xff0c;那就是房子每天都很乱&#xff0c;隔三岔五就得做一次卫生清理、地板杀菌等。如果是房屋面积太大的话&#xff0c;只靠自己手动清洁是非常的耗时间并且还很劳累。洗地机的出现可谓是造福人类&#xff0c;解脱了家庭劳…

EM@一次双绝对值不等式

文章目录 一次双绝对值不等式求解步骤去绝对值情况分析&#x1f47a;例例代数法几何方法比较 例 一次双绝对值不等式求解步骤 设 f ∣ f 1 ∣ ∣ f 2 ∣ f|f_1||f_2| f∣f1​∣∣f2​∣, f 1 , f 2 f_1,f_2 f1​,f2​都是一次多项式,则原不等式 f ⩾ a f\geqslant{a} f⩾a或 …

跨越编程界限:C++到JavaSE的平滑过渡

JDK安装 安装JDK 配置环境变量&#xff1a; Path 内添加 C:\Program Files\Java\jdk1.8.0_201\bin 添加 JAVA_HOME C:\Program Files\Java\jdk1.8.0_201 添加 CLASSPATH .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar 第一个Java程序 HelloWorld.java public class…

记录--让我们来深入了解一下前端“三清”是什么

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前端“三清” 在前端开发中&#xff0c;我们经常听到关于“三清”的说法&#xff0c;即 window、document、Object。这三者分别代表了 BOM(浏览器对象模型)、DOM(文档对象模型)以及 JS 的顶层对象。在…