进程间的通信

本文详细介绍了进程间通信的多种方式,包括无名管道、有名管道(FIFO)、信号、消息队列、共享内存和信号灯集。无名管道适用于具有亲缘关系的进程通信,有名管道则可用于任意进程间通信。信号是一种异步通信方式,用于进程间的交互。消息队列允许进程按特定格式和优先级交换信息。共享内存允许多个进程直接访问同一块内存区域,提供高效的通信途径。最后,信号灯集用于实现进程间的同步和互斥。文章还提供了相关的代码示例和练习,以加深理解。
摘要由CSDN通过智能技术生成

【1】进程间的通信方式

1.传统的进程间通信方式

1)无名管道 pipe

2)有名管道(命名管道) fifo

3)信号 signal

2.system V操作系统的IPC对象

1)消息队列 message queue

2)共享内存 shared memory

3)信号灯集 semaphore

3.可用于跨主机的通信

1)套接字 socket

【2】管道

1. 管道的原理

在进程的3~4G内核空间中,创建一个管道(特殊的文件),管道中的数据直接保存在内存中

2. 管道的特性

  1. 管道可以看成是一个特殊的文件,一般文件存储在硬盘上,而管道文件存储在内存中;

  2. 管道遵循先进先出的原则;

  3. 管道是一种半双工的通信方式。

    单工:只能A发消息给B,B不能发消息给A

    半双工:同一时间只能A发给B,B不能发给A;

    全双工:同一时间,AB能互相发消息;

  4. 管道的大小:64K = 64*1024 = 65536byte;

  5. 对于管道的读写操作是一次性的,如果对管道进行读操作,那么被读取的数据会从管道中删除。

  6. 从管道中读取数据;

    当写端没有关闭

    1)当管道中没有数据,read函数会阻塞。

    2)当请求的数据a(10)大于管道中存储的数据b(5),则实际读取的数据个数为:b

    3)当请求的读取的数据a(5)小于管道中存储的数据b(12),则实际读取的数据个数为:a

    当所有写端关闭(父子进程都有写端)

    1)从管道中读取数据,当管道中的数据读取完毕后,read函数不阻塞,返回0;

  7. 向管道中写入数据

    当读端没有关闭

    1)管道中,如果有剩余空间,则写进程会向管道中写数据;

    2)如果管道满了,则写函数会阻塞;

    当所有读端关闭(父子进程都有读端)

    1)当读端关闭,尝试向管道中写入数据,会造成管道破裂,退出进程,只要调用write函数就会造成管道破裂;

    2)写进程会收到管道破裂信号(SIGPIPE);

3. 无名管道(pipe)

1)无名管道的特点

  1. 无名管道只能用于具有 亲缘关系 进程间通信。

  2. 对于无名管道的读写,只能使用文件IO,如read write,但是不能使用lseek;

  3. 当管道的读写端均关闭的时候,释放管道空间

为什么无名管道只能用于具有亲缘关系的进程通信

用管道的方式进行进程通信,必须要操作同一个管道。即两个进程必须有同一个管道的读写端。

无名管道在文件系统上没有名字,没有关系的进程无法打开同一个管道;

而子进程克隆父进程的所有资源,所以在父进程中获取到管道的读写端会被拷贝到子进程中,

所以父子进程能能拿到同一管道的读写端。

2)pipe

功能:创建一个管道文件,并打开文件的读写端;
头文件:
       #include <unistd.h>
原型:
       int pipe(int pipefd[2]);
参数:
    int pipefd[2]:传入一个数组,且容量为2;存储打开的两个文件描述符;
            pipefd[0]:读端;
            pipefd[1]:写端;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

练习

使用无名管道实现父子进程对话,顺序如下:

1)父进程发送一句话,子进程接收数据后,打印

2)子进程发送一句话,父进程接收数据后,打印。

3)重复1)2)步骤

4)当父进程或子进程发送 quit Quit,,结束父子进程。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, const char *argv[])
{
    //父进程向子进程发送数据,父进程写,子进程读
    int ptoc[2] = {0};
    if(pipe(ptoc) < 0)
    {
        perror("pipe");
        return -1;
    }

    //子进程向父进程发送数据,子进程写,父进程读
    int ctop[2] = {0};
    if(pipe(ctop) < 0)
    {
        perror("pipe");
        return -1;
    }


    char str[128] = "";
    ssize_t res = 0;

    //创建子进程
    pid_t pid = fork();
    if(pid > 0)
    {
        //父进程运行
        close(ptoc[0]);      //由于ptoc的读端,父进程不需要,所以关闭    
        close(ctop[1]);     //由于ctop的写端,父进程不需要,所以关闭


        while(1)
        {
            printf("父进程说>>>");
            fgets(str, sizeof(str), stdin);
            str[strlen(str)-1] = 0;

            res = write(ptoc[1], str, sizeof(str));
            if(res < 0)
            {
                perror("write");
                return -1;
            }
        //    printf("父进程成功发送%ld个字节\n\n", res);
            if(strncasecmp(str, "quit", 4) == 0)
            {
                break;
            }


            res = read(ctop[0], str, sizeof(str));
            if(res < 0)
            {
                perror("read");
                return -1;
            }
            else if(0 == res)
            {
                printf("所有写端关闭,并且管道中没有数据了\n");
                return -1;
            }

            printf("父进程接收到子进程:%s\n", str);

            if(strncasecmp(str, "quit", 4) == 0)
            {
                break;
            }
        
        }

        
        wait(NULL);
        close(ptoc[1]);          
        close(ctop[0]);     
    }
    else if(0 == pid)
    {
        //子进程运行
        close(ptoc[1]);          
        close(ctop[0]);     

        while(1)
        {
            res = read(ptoc[0], str, sizeof(str));
            if(res < 0)
            {
                perror("read");
                return -1;
            }
            else if(0 == res)
            {
                printf("所有写端关闭,并且管道中没有数据了\n");
                return -1;
            }

            printf("子进程接收到父进程:%s\n", str);
            if(strncasecmp(str, "quit", 4) == 0)
            {
                break;
            }


            printf("子进程说>>>");
            fgets(str, sizeof(str), stdin);
            str[strlen(str)-1] = 0;

            res = write(ctop[1], str, sizeof(str));
            if(res < 0)
            {
                perror("write");
                return -1;
            }
        //    printf("子进程成功发送%ld个字节\n\n", res);
            if(strncasecmp(str, "quit", 4) == 0)
            {
                break;
            }
        }

        close(ptoc[0]);          
        close(ctop[1]);     
    }
    else 
    {
        perror("fork");
        return -1;
    }

    return 0;
}

4. 有名管道(fifo)

又称为命名管道,顾名思义,就是名字可以被看见的管道文件,但是数据依然存在于内存中,是不可见的

1)有名管道的特点

  1. 可以用在没有亲缘关系的进程间通信

  2. 有名管道的读写可以使用文件IO函数,read,write,但是不能使用lseek;

  3. 有名管道文件需要手动open、close管道文件

  4. 当读写端都关闭后,管道内存被释放;

2)创建有名管道文件

  1. 用shell命令创建

    $ mkfifo 有名管道名
        $ mkfifo fifo
  2. 用mkfifo函数

    功能:创建有名管道文件;
    头文件:
           #include <sys/types.h>
           #include <sys/stat.h>
    原型:
           int mkfifo(const char *pathname, mode_t mode);
    参数:
        char *pathname:指定要创建的有名管道的路径及管道名;
        mode_t mode:管道的8进制权限; the permissions of the created file are (mode & ~umask).
    返回值:
        成功,返回0;
        失败,返回-1,更新errno;        需要排除fifo文件已经存在的情况,errno == EEXIST 
    ​
        if(mkfifo("./myfifo", 0777) < 0)
        {   
            printf("errno = %d\n", errno);
            if(errno != 17)     //EEXIST
            {   
                perror("mkfifo");
                return -1; 
            }   
        } 

3)有名管道的使用

  1. 通过open函数打开有名管道文件

  2. 通过文件IO函数读写有名管道

    与读写普通文件的时候一致;

           #include <sys/types.h>
           #include <sys/stat.h>
           #include <fcntl.h>
    原型:
           int open(const char *pathname, int flags);
  3. 有名管道的open规则

    O_RDONLY    只读
    O_WRONLY    只写
    O_RDWR      读写

    ---以上三种必须包含一种---

1)flags == O_WRONLY

open函数会阻塞,直到有另外一个进程以读的方式打开同一个FIFO文件;

2)flags == O_RDONLY

open函数会阻塞,直到有另外一个进程以写的方式打开同一个FIFO文件;

3)flags == O_RDWR

open函数不阻塞,立即返回;

4)代码范例

i. 写

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
​
int main(int argc, const char *argv[])
{
    //创建有名管道
    if(mkfifo("./myfifo", 0777) < 0)
    {
        if(EEXIST !=errno )
        {
            perror("mkfifo");
            return -1;
        }
    }
    printf("create FIFO success\n");
​
​
    //打开有名管道文件
    int fd = open("./myfifo", O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        return -1;
    }
​
    printf("写端打开成功:%d\n", fd);
​
    ssize_t res = 0;
    char str[20] = "";
    while(1)
    {
        printf("请输入>>>");
        fgets(str, sizeof(str), stdin);
​
        res = write(fd, str, sizeof(str));
        if(res < 0)
        {
            perror("write");
            return -1;
        }
        printf("writ
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值