一、命名管道的定义

  • 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件。

1、创建一个命名管道

  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令,mkfifo也是一个指令,可以直接创建管道:
$ mkfifo pipe


read.cpp

include<iostream>
#include<fcntl.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
int main()
{
    int fd=open("./pipe",O_RDONLY);
    if(fd<0)
    {
        std::cout<<"打开管道文件失败!"<<std::endl;
        return 1;
    }
    char buf[64]={0};
    read(fd,buf,sizeof(buf));
    std::cout<<buf<<std::endl;
    return 0;
}
123456789101112131415161718

write.cpp

1    #include<iostream>
     2    #include<unistd.h>
     3    #include<sys/stat.h>
     4    #include<sys/types.h>
     5    #include<fcntl.h>
     6    int main()
     7    {
     8        int fd=open("./pipe",O_WRONLY);
     9        if(fd<0)
    10        {
    11            std::cout<<"打开管道文件失败!"<<std::endl;
    12            return 1;
    13        }
    14        char str[]="bit education!";
    15        write(fd,str,sizeof(str)-1);
    16        return 0;
    17    }
    18    
123456789101112131415161718

[yyw@VM-0-9-centos 创建命名管道]$ ls / > pipe
[yyw@VM-0-9-centos 创建命名管道]$ cat < pipe

  • 命名管道也可以从程序里创建,相关函数有系统调用mkfifo:
1    #include<stdio.h>
     2    #include<sys/types.h>
     3    #include<sys/stat.h>
     4    int main()
     5    {
     6        mkfifo("pipe",0664);
     7        return 0;
     8    }
12345678

2、匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

3、命名管道的打开规则

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

4、命名管道的应用

实现客户端与服务端的通信:

  • 服务端创建管道,打开管道,从管道读数据:
1    #include<stdio.h>
     2    #include<unistd.h>
     3    #include<sys/stat.h>
     4    #include<fcntl.h>
     5    #include<sys/types.h>
     6    
     7    #define FIFO_FILE "./fifo"
     8    int main()
     9    {
    10        umask(0); 
    11        if(mkfifo(FIFO_FILE,0666)==-1)
    12        {
    13            printf("创建管道失败!\n");
    14            return 1;
    15        }//创建管道
    16    
    17    
    18        int fd=open(FIFO_FILE,O_RDONLY);// 
    19        if(fd<0)
    20        {
    21            printf("打开管道失败!\n");
    22            return 1;
    23        }
    24        else if(fd >=0)
    25        {
    26            char buf[64];
    27            while(1)
    28            {
    29                printf("please wait!\n");
    30                ssize_t s=read(fd,buf,sizeof(buf)-1);//减一是预留\0位置
    31                if(s>0)
    32                {
    33                    buf[s]=0;
    34                    printf("client# %s",buf);
    35                }
    36                else if(s==0)
    37                {
    38                    printf("cilent exit!\n");
    39                    break;
    40                }
    41                else 
    42                {
    43                    printf("文件读取失败!\n");
    44                    break;
    45                }
    46            }
    47        }
    48        return 0;
    49    }
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  • 客户端打开管道,写数据到管道中,发送给服务端:
1    #include<stdio.h>
     2    #define FIFO_FILE "./fifo"
     3    #include<sys/stat.h>
     4    #include<sys/types.h>
     5    #include<fcntl.h>
     6    #include<unistd.h>
     7    int main()
     8    {
     9        int fd=open(FIFO_FILE,O_WRONLY);
    10        if(fd<0)
    11        {
    12            printf("文件打开失败!\n");
    13            return 1;
    14        }
    15        else if (fd>=0)
    16        {
    17            char buf[64];
    18            while(1)
    19            {
    20                printf("please Enter message# ");
    21                fflush(stdout);
    22                ssize_t s =read(0,buf,sizeof(buf)-1);
    23                if(s>0)
    24                {
    25                    buf[s]=0;
    26                    write(fd,buf,s);
    27                }
    28            }
    29        }
    30        return 0;
    31    }
12345678910111213141516171819202122232425262728293031

二、system V共享内存


共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

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 */
};

3.共享内存函数

首先要看到同一份资源,那么必须有标识操作系统上ipc资源的唯一性,就要用到ftok函数:

1.shmget函数


功能:用来创建共享内存
原型:
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段名字。
size:共享内存大小。
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。
1    #pragma once  //防止头文件重复包含
     2    #ifndef __ADD_H_
     3    #define __ADD_H__
     4    #endif
     5    
     6    #define PATHNAME "/tmp"
     7    #define PROJ_ID 0x6688
     8    #define SIZE 4096
     9    
123456789

1    #include<stdio.h>
     2    #include<sys/types.h>
     3    #include"comm.h"
     4    #include<sys/ipc.h>
     5    #include<sys/shm.h>
     6    int main()
     7    {
     8        key_t k=ftok(PATHNAME,PROJ_ID);
     9        printf("key=%d\n",k);
    10    
    11        int shmid=shmget(k,SIZE,IPC_CREAT|IPC_EXCL);
    12        if(shmid<0)
    13        {
    14            printf("创建共享内存失败!\n");
    15        }
    16        return 0;
    17    }
1234567891011121314151617

代码如下(示例):
这里可以使用ipcs -m 查看共享内存:

如果要删除共享内存使用ipcrm -m shmid就可以:

2.shmat函数

功能:将共享内存段连接到进程地址空间
原型:
void shmat(int shmid, const void shmaddr, int shmflg);
参数:
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1


1    #include<stdio.h>
     2    #include<unistd.h>
     3    #include<sys/types.h>
     4    #include"comm.h"
     5    #include<sys/ipc.h>
     6    #include<sys/shm.h>
     7    int main()
     8    {
     9        key_t k=ftok(PATHNAME,PROJ_ID);
    10        printf("key=%p\n",k);
    11    
    12        int shmid=shmget(k,SIZE,IPC_CREAT|0664);
    13        if(shmid<0)
    14        {
    15            printf("创建共享内存失败!\n");
    16            return 0;
    17        }
    18        char* addr=(char*)shmat(shmid,NULL,0);
    19        if(addr==NULL)
    20        {
    21            printf("关联失败!\n");
    22            return 0;
    23        }
    24        while(1)
    25        {
    26            printf("%s\n",(char*)addr);
    27            sleep(1);
    28        }
    29        //shmdt(addr);
    30        shmctl(shmid,IPC_RMID,NULL);
    31        return 0;
    32    }
1234567891011121314151617181920212223242526272829303132

3.shmdt函数

功能:将共享内存段与当前进程脱离
原型:
int shmdt(const void *shmaddr);
参数:
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

4.shmctl函数


功能:用于控制共享内存
原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
1    #include<stdio.h>
     2    #include<unistd.h>
     3    #include<sys/types.h>
     4    #include"comm.h"
     5    #include<sys/ipc.h>
     6    #include<sys/shm.h>
     7    int main()
     8    {
     9        key_t k=ftok(PATHNAME,PROJ_ID);
    10        printf("key=%p\n",k);
    11    
    12        int shmid=shmget(k,SIZE,IPC_CREAT|0664);
    13        if(shmid<0)
    14        {
    15            printf("创建共享内存失败!\n");
    16            return 0;
    17        }
    18        char* addr=(char*)shmat(shmid,NULL,0);
    19        if(addr==NULL)
    20        {
    21            printf("关联失败!\n");
    22            return 0;
    23        }
    24        while(1)
    25        {
    26            printf("%s\n",(char*)addr);
    27            sleep(1);
    28        }
    29        //shmdt(addr);
    30        shmctl(shmid,IPC_RMID,NULL);
    31        return 0;
    32    }
1234567891011121314151617181920212223242526272829303132


完整的客户端与服务端通信的代码
server.c:

1    #include<stdio.h>
     2    #include"head.h"
     3    #include<unistd.h>
     4    #include<sys/types.h>
     5    #include<sys/ipc.h>
     6    #include<sys/shm.h>
     7    int main()
     8    {
     9        key_t k=ftok(PATHNAME,PROJ_ID);
    10        printf("key=%d\n",k);
    11        int shmid=shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0666);//创建共享内存
    12    
    13        if(shmid==-1)
    14        {
    15            printf("创建共享内存失败!\n");
    16            return 1;
    17        }
    18        printf("shmid=%d\n",shmid);
    19        sleep(10);
    20    
    21        char* str=(char*)shmat(shmid,NULL,0);//关联共享内存
    22       // sleep(5);
    23       while(1)
    24       {
    25           sleep(1);
    26           printf("%s\n",str);
    27       }
    28    
    29        shmdt(str);//去除关联的共享内存
    30       // sleep(5);
    31    
    32        shmctl(shmid,IPC_RMID,NULL);//删除共享内存
    33        sleep(5);
    34        return 0;
    35    }
1234567891011121314151617181920212223242526272829303132333435

client.c:

1    #include<stdio.h>
     2    #include"head.h"
     3    #include<unistd.h>
     4    #include<sys/types.h>
     5    #include<sys/ipc.h>
     6    #include<sys/shm.h>
     7    int main()
     8    {
     9        key_t k=ftok(PATHNAME,PROJ_ID);
    10        printf("key=%p\n",k);
    11        int shmid=shmget(k,SIZE,0);//创建共享内存
    12    
    13        if(shmid==-1)
    14        {
    15            printf("创建共享内存失败!\n");
    16            return 1;
    17        }
    18        printf("shmid=%d\n",shmid);
    19       // sleep(10);
    20    
    21        char* str=(char*)shmat(shmid,NULL,0);//关联共享内存
    22       // sleep(5);
    23    
    24    
    25        char c='a';
    26        for(c;c<='z';c++)
    27        {
    28            str[c-'a']=c;
    29            sleep(3);
    30        }
    31        shmdt(str);//去除关联的共享内存
    32        sleep(5);
    33    
    34       // shmctl(shmid,IPC_RMID,NULL);//删除共享内存
    35       // sleep(10);
    36        return 0;
    37    }
12345678910111213141516171819202122232425262728293031323334353637

makefile:

1    .PHONY:all
     2    all:server client
     3    client:client.c
     4        gcc $^ -o $@
     5    server:server.c
     6        gcc $^ -o $@
     7    .PHONY:clean
     8    clean:
     9        rm -f client server
123456789

head.h:

1    #pragma once 
     2    #define PATHNAME "/tmp"
     3    #define PROJ_ID 0x6688
     4    
     5    #define SIZE 4096
12345


ipcs 命令的扩展:

三、system V消息队列 - 选学了解即可

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。

四、system V信号量P和V - 选学了解即可

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。

  • [ ] 进程互斥
  • 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
  • 在进程中涉及到互斥资源的程序段叫临界区。
  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。

总结

以上就是今天要讲的内容,本文仅仅简单介绍了Linux进程间通信中后两种方法的使用,而进程间通信提供了大量能使我们快速便捷地处理数据的函数和方法。我们务必掌握。到这里,进程间通信就结束了,后序将会有更重要的文章陆续更新,希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。

————————————————
版权声明:本文为CSDN博主「森明帮大于黑虎帮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44918090/article/details/118392232

最后修改:2021 年 08 月 06 日
如果觉得我的文章对你有用,请随意赞赏