进程、线程和进程间通信课程 Day6学习要点总结

一、IPC 基本概念

定义:进程间通信(InterProcess Communication,IPC)是指进程与进程之间交换信息的过程。

常用通信方式

  • 无名管道(pipe)
  • 有名管道(fifo)
  • 信号(signal)
  • 共享内存 (mmap)
  • 套接字(socket)

二、无名管道(pipe)

(一)特点

  1. 只能用于具有亲缘关系的进程之间的通信(如父子进程、兄弟进程)
  2. 单工通信模式,具有固定的读端和写端
  3. 创建时返回两个文件描述符:pfd[0]用于读,pfd[1]用于写

(二)创建函数

#include <unistd.h>
int pipe(int pfd[2]);
  • 参数pfd为包含两个整数的数组,用于存储文件描述符
  • 返回值:成功返回 0,失败返回 - 1

(三)代码示例 1:基本父子进程通信

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {
    int pfd[2];  // 文件描述符
    int re;      // 返回值
    char buf[20] = {0};
    pid_t pid;

    re = pipe(pfd);
    if (re < 0) {
        perror("pipe");
        return 0;
    }

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 0;
    } else if (pid == 0) {
        // 子进程:写管道
        while (1) {
            strcpy(buf, "hhahahahah");
            write(pfd[1], buf, strlen(buf));
            sleep(1);
        }
    } else {
        // 父进程:读管道
        while (1) {
            re = read(pfd[0], buf, 20);
            if (re > 0) {
                printf("read pipe=%s\n", buf);
            }
        }
    }
}
运行结果截图说明
read pipe=hhahahahah
read pipe=hhahahahah
read pipe=hhahahahah
read pipe=hhahahahah
  • 截图显示父进程持续从管道读取子进程写入的数据。

(四)代码示例 2:多进程共享管道(父进程读,两子进程写)

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {
    int pfd[2];
    int i;
    int re;
    char buf[40] = {0};
    pid_t pid;

    re = pipe(pfd);
    if (re < 0) {
        perror("pipe");
        return 0;
    }

    printf("%d,%d\n", pfd[0], pfd[1]);

    for (i = 0; i < 2; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork");
            return 0;
        } else if (pid > 0) {
            // 父进程继续创建子进程
        } else {
            // 子进程跳出循环
            break;
        }
    }

    if (i == 2) {  // 父进程
        close(pfd[1]);
        while (1) {
            memset(buf, 0, 40);
            re = read(pfd[0], buf, 40);
            if (re > 0) {
                printf("%s\n", buf);
            }
        }
        return 0;
    }
    if (i == 1) {  // 子进程1
        close(pfd[0]);
        while (1) {
            strcpy(buf, "this is 2 process");
            write(pfd[1], buf, strlen(buf));
            usleep(930000);
        }
        return 0;
    }
    if (i == 0) {  // 子进程0
        close(pfd[0]);
        while (1) {
            strcpy(buf, "this is 1 process");
            write(pfd[1], buf, strlen(buf));
            sleep(1);
        }
        return 0;
    }
}
运行结果截图说明
3,4
this is 1 processthis is 2 process
this is 2 process
this is 1 process
this is 2 process
this is 1 process
this is 2 process
this is 1 process
this is 2 process
this is 1 process
this is 2 process
this is 1 process
this is12 process
this is 1 process
this is 2 process
this is 1 process
  • 截图显示父进程同时读取两个子进程写入管道的数据,数据交替输出。

(五)无名管道读写特性

1. 读管道
  • 管道中有数据read返回实际读到的字节数。
  • 管道中无数据
    • 写端全部关闭:read返回 0(类似文件结尾)。
    • 写端未全部关闭:read阻塞等待。
2. 写管道
  • 读端全部关闭:进程异常终止(可通过捕捉SIGPIPE信号避免终止)。
  • 读端未全部关闭
    • 管道已满(64K):write阻塞。
    • 管道未满:write写入数据并返回实际字节数。

(六)读写特性代码示例

1. 写端关闭时读管道
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {
    int pfd[2];
    int re;
    char buf[20] = {0};
    pid_t pid;

    re = pipe(pfd);
    if (re < 0) {
        perror("pipe");
        return 0;
    }

    printf("%d,%d\n", pfd[0], pfd[1]);

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 0;
    } else if (pid > 0) {
        // 父进程关闭读写端
        close(pfd[0]);
        close(pfd[1]);
        int j = 0;
        while (1) {
            j++;
            strcpy(buf, "hhahahahah");
            sleep(1);
        }
    } else {
        // 子进程关闭写端
        close(pfd[1]);
        sleep(30000);
        exit(0);
        while (1) {
            re = read(pfd[0], buf, 20);
            if (re > 0) {
                printf("read pipe=%s\n", buf);
            } else if (re == 0) {
                printf("re=0\n");
            }
        }
    }
}
运行结果截图说明
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
re=0
  • 截图显示当管道写端全部关闭后,读端read返回 0。
2. 写端未关闭时读管道(阻塞)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {
    int pfd[2];
    int re;
    char buf[20] = {0};
    pid_t pid;

    re = pipe(pfd);
    if (re < 0) {
        perror("pipe");
        return 0;
    }

    printf("%d,%d\n", pfd[0], pfd[1]);

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 0;
    } else if (pid > 0) {
        // 父进程写管道
        close(pfd[0]);
        while (1) {
            strcpy(buf, "hhahahahah");
            write(pfd[1], buf, strlen(buf));
            sleep(1);
        }
    } else {
        // 子进程关闭写端并休眠
        close(pfd[1]);
        sleep(30000);
        exit(0);
        while (1) {
            re = read(pfd[0], buf, 20);
            if (re > 0) {
                printf("read pipe=%s\n", buf);
            } else if (re == 0) {
                printf("re=0\n");
            }
        }
    }
}
3. 读端关闭时写管道(进程终止)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {
    int pfd[2];
    int re;
    char buf[20] = {0};
    pid_t pid;

    re = pipe(pfd);
    if (re < 0) {
        perror("pipe");
        return 0;
    }

    printf("%d,%d\n", pfd[0], pfd[1]);

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 0;
    } else if (pid > 0) {
        // 父进程写管道
        close(pfd[0]);
        while (1) {
            strcpy(buf, "hhahahahah");
            write(pfd[1], buf, strlen(buf));
            sleep(1);
        }
    } else {
        // 子进程关闭读写端
        close(pfd[1]);
        close(pfd[0]);
        sleep(30000);
        exit(0);
        while (1) {
            re = read(pfd[0], buf, 20);
            if (re > 0) {
                printf("read pipe=%s\n", buf);
            } else if (re == 0) {
                printf("re=0\n");
            }
        }
    }
}
4. 管道已满时写阻塞
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {
    int pfd[2];
    int re;
    char buf[20] = {0};
    pid_t pid;

    re = pipe(pfd);
    if (re < 0) {
        perror("pipe");
        return 0;
    }

    printf("%d,%d\n", pfd[0], pfd[1]);

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 0;
    } else if (pid > 0) {
        // 父进程大量写入数据
        close(pfd[0]);
        int j = 0;
        while (1) {
            j++;
            strcpy(buf, "hhahahahah");
            for (int i = 0; i < 1000; i++) {
                write(pfd[1], buf, strlen(buf));
            }
            printf("write %d times\n", j);
            sleep(1);
        }
    } else {
        // 子进程关闭写端并休眠
        close(pfd[1]);
        sleep(30000);
        exit(0);
        while (1) {
            re = read(pfd[0], buf, 20);
            if (re > 0) {
                printf("read pipe=%s\n", buf);
            } else if (re == 0) {
                printf("re=0\n");
            }
        }
    }
}

三、有名管道(FIFO)

(一)创建函数

#include <unistd.h>
#include <fcntl.h>
int mkfifo(const char *path, mode_t mode);
  • 参数
    • path:管道文件路径
    • mode:管道文件权限(如 0666)
  • 返回值:成功返回 0,失败返回 - 1

(二)打开方式

  1. open(const char *path, O_RDONLY);
  2. open(const char *path, O_RDONLY | O_NONBLOCK);
  3. open(const char *path, O_WRONLY);
  4. open(const char *path, O_WRONLY | O_NONBLOCK);

(三)特点

  1. 支持非亲缘关系进程通信
  2. 通过路径名操作,文件系统中可见但内容存于内存
  3. 使用文件 IO 操作
  4. 遵循先进先出(FIFO)规则
  5. 不支持lseek操作
  6. 单工读写

(四)代码示例 1:写端程序

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main() {
    int re;
    int fd;
    char buf[32];

    // 创建有名管道
    re = mkfifo("/myfifo", 0666);
    if (re < 0) {
        perror("mkfifo");
    }

    // 以只写方式打开管道
    fd = open("/myfifo", O_WRONLY);
    if (fd < 0) {
        perror("open");
        return 0;
    }

    printf("after open\n");

    // 从标准输入读取数据并写入管道
    while (1) {
        fgets(buf, 32, stdin);
        write(fd, buf, strlen(buf));
    }
}

(五)代码示例 2:读端程序

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main() {
    int re;
    int fd;
    char buf[32];

    // 以只读方式打开管道
    fd = open("/myfifo", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 0;
    }

    printf("after open\n");

    // 从管道读取数据并输出
    while (1) {
        re = read(fd, buf, 32);
        if (re > 0) {
            printf("read fifo=%s\n", buf);
        } else if (re == 0) {
            exit(0);
        }
    }
}

(六)注意事项

  1. 不建议以O_RDWR模式打开 FIFO 文件,行为未明确定义。
  2. O_NONBLOCK选项
    • 读打开时:非阻塞模式下,若无写端打开仍返回成功;阻塞模式下,等待写端打开。
    • 写打开时:非阻塞模式下,若无读端打开返回 - 1;阻塞模式下,等待读端打开。
  3. 数据完整性:多个进程写同一管道时,若写入数据长度≤PIPE_BUF(4K),系统确保数据不交错。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值