目录
一、进程间通信(IPC机制:interprocess communicate)
一、进程间通信(IPC机制:interprocess communicate)
同一主机进程间通信:
1.无名管道
2.有名管道
3.信号
4.共享内存
5.消息队列
6.信号灯
不同主机进程间通信:7.网络套接字
二、管道
无名管道:用于同一主机下,具有亲缘关系的父子进程间通信。
有名管道:用于同一主机下,具有任意关系的进程间通信。
三、无名管道
无名管道的使用步骤
1.创建一个无名管道并打开
#include <unistd.h> int pipe(int pipefd[2]); /* 参数:pipefd[0]->管道的读端 pipefd[1]->管道的写端 返回值: 成功:0 失败:-1 */
2.读管道read
3.写管道write
4.关闭管道close
练习1
父进程从终端读数据,子进程打印父进程读到的数据,读到".quit",两进程退出
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
// 定义一个数组来存储管道的两个文件描述符
// pipefd[0] 用于读操作,pipefd[1] 用于写操作
int pipefd[2];
// 调用 pipe 函数创建一个管道
// 若创建成功,pipefd 数组会被填充相应的文件描述符
// 若失败,ret 会为 -1
int ret = pipe(pipefd);
if (ret < 0)// 检查管道是否创建成功
{
perror("fail to pipe\n");
return -1;
}
pid_t pid = fork();
if (pid > 0)
{
close(pipefd[0]);// 当前为父进程,关闭管道的读端,因为父进程只负责写数据
char buff[1024] = {0};
while (1)
{
// 从标准输入读取一行内容到 buff 数组
fgets(buff, sizeof(buff), stdin);
// 将 buff 数组中的内容写入管道的写端
// 写入的字节数为 buff 字符串的长度
write(pipefd[1], buff, strlen(buff));
// 检查用户输入的是否为 ".quit"
// 若输入 ".quit",则跳出循环
if (0 == strcmp(buff, ".quit\n"))
{
break;
}
}
wait(NULL);// 父进程等待子进程结束
}
else if (0 == pid)
{
close(pipefd[1]);// 当前为子进程,关闭管道的写端,因为子进程只负责读数据
char buff[1024] = {0};
ssize_t n;
while (1)
{
// 将 buff 数组清零,以便存储新的读取内容
memset(buff, 0, sizeof(buff));
// 从管道的读端读取数据到 buff 数组
// n 存储实际读取的字节数
n = read(pipefd[0], buff, sizeof(buff));
// 检查读取的字节数
// 若 n 小于等于 0,说明管道关闭或出现错误,跳出循环
if (n <= 0)
{
// 管道关闭或出错,跳出循环
break;
}
if (0 == strcmp(buff, ".quit\n"))
{
break;
}
printf("buff = %s\n", buff);
}
}
else
{
perror("fail fork");
return -1;
}
// 关闭管道的读端和写端,释放资源
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
无名管道的特性
1.默认64K大小
测试代码
#include<stdio.h> #include <unistd.h> int main(int argc, char const *argv[]) { int pipefd[2]; int ret = pipe(pipefd); if (ret < 0) { perror("fail to pipe!\n"); return -1; } // pipefd[0]->read // pipefd[1]->write int cnt = 0; while (1) { ssize_t size = write(pipefd[1], "a", 1); if (size < 0) { perror("fail to write!\n"); break; } cnt++; printf("cnt = %d\n", cnt); } close(pipefd[0]); close(pipefd[1]); return 0; }
测试结果:
65536 / 1024 = 64K
2.管道存储数据时,按照FIFO的方式存储
3.写阻塞:管道读写端都存在时,向管道中写入数据,当管道满时,发生写阻塞
读阻塞:管道读写端都存在时,向管道中读数据,如果管道中有数据,read返回实际读到的字节数;如果管道中无数据,read发生读阻塞
读到0返回:管道的写端关闭,只保留读端,从管道读数据,若有数据,则读到数据;若无数据,则read返回0,不阻塞
管道破裂:管道的读端关闭,只保留写端,向管道中写入数据,发生管道破裂(异常)
练习2
父进程将"1.txt"文件通过管道发送给子进程"2.txt"
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int pipefd[2];
int ret = pipe(pipefd);
if (ret < 0)
{
perror("fail pipe!\n");
return -1;
}
pid_t pid = fork();
if (pid > 0)
{
close(pipefd[0]);
int fd = open("1.txt", O_RDONLY);// 打开文件 "1.txt" 以只读模式,返回文件描述符
if (fd < 0)
{
perror("fail open!\n");
return -1;
}
char buff[1024] = {0};
while (1)
{
size_t size = read(fd, buff, sizeof(buff));
if (size <= 0)
{
break;// 如果读取的字节数小于等于 0,表示文件读取完毕,退出循环
}
write(pipefd[1], buff, size);// 将缓冲区中的数据写入管道的写端,写的字节数等于读取的字节数
}
close(pipefd[1]);
close(fd);
wait(NULL);
}
else if (0 == pid)
{
close(pipefd[1]);
// 打开文件 "2.txt" 以只写模式,如果文件不存在则创建,如果文件已存在则截断,权限为 0664
int fd = open("2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd < 0)
{
perror("fail open\n");
return -1;
}
char buff[1024] = {0};
while (1)
{
// 从管道的读端读取数据到缓冲区,返回实际读取的字节数
size_t size = read(pipefd[0], buff, sizeof(buff));
if (size <= 0)
{
// 如果读取的字节数小于等于 0,表示管道中没有数据了,退出循环
break;
}
// 将缓冲区中的数据写入文件
write(fd, buff, size);
}
close(pipefd[0]);
close(fd);
}
else
{
perror("fail fork!\n");
return -1;
}
return 0;
}
四、有名管道
有名管道在同一主机下,用于任意进程间通信
有名管道使用步骤
1.创建管道文件
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); /* 参数: pathname:文件描述符 mode:读写执行权限(一般为0664) */
2.打开管道文件open
3.读写管道文件read
4.关闭管道文件close
5.删除管道文件
#include <stdio.h> int remove(const char *pathname);
父子进程间使用有名管道通信
#include"head.h" int main(int argc, char const *argv[]) { mkfifo("./myfifo", 0664); pid_t pid = fork(); if (pid > 0) { int fd = open("./myfifo", O_WRONLY); if (fd < 0) { perror("fail open"); return -1; } write(fd, "hello word", strlen("hello word")); close(fd); wait(NULL); } else if (0 == pid) { int fd = open("./myfifo", O_RDONLY); if(fd < 0) { perror("fail open"); return -1; } char buff[512] = {0}; read(fd, buff, sizeof(buff)); printf("buff = %s\n", buff); close(fd); } else { perror("fail fork\n"); } return 0; }
练习1
使用有名管道实现两个没有亲缘关系的进程间通信
进程a,发送
#include"head.h"
int main(int argc, char const *argv[])
{
mkfifo("./myfifo", 0664);
int fd = open("./myfifo", O_WRONLY);
if (fd < 0)
{
perror("fail open");
return -1;
}
char buff[1024] = {0};
while (1)
{
fgets(buff, sizeof(buff), stdin);
write(fd, buff, strlen(buff));
}
close(fd);
return 0;
}
进程b,接收并打印
#include"head.h"
int main(int argc, char const *argv[])
{
mkfifo("./myfifo", 0664);
int fd = open("./myfifo", O_RDONLY);
if (fd < 0)
{
perror("fail open");
return -1;
}
char buff[1024] = {0};
while (1)
{
memset(buff, 0, sizeof(buff));
size_t size = read(fd, buff, sizeof(buff));
if(size <= 0)
{
break;
}
printf("buff = %s\n", buff);
}
return 0;
}
练习2
两个线程间发送文件
思路:先把要发送的文件读入到发送进程中的缓冲区,再由缓冲区写入到管道,接收进程读取管道中的内容,写入到文件
图解:
发送进程
#include"head.h"
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("Usage:./send <srcfile>");
return -1;
}
mkfifo("./myfifo", 0664);
int fdfifo = open("./myfifo", O_WRONLY);
if (fdfifo < 0)
{
perror("fail open fdfifo");
return -1;
}
int fdsrc = open(argv[1], O_RDONLY);
if(fdsrc < 0)
{
perror("fail open fdsrc");
return -1;
}
char buff[1024] = {0};
while (1)
{
size_t size = read(fdsrc, buff, sizeof(buff));
if (size <= 0)
{
break;
}
write(fdfifo, buff, size);
}
close(fdfifo);
close(fdsrc);
return 0;
}
接收进程
#include"head.h"
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("Usage:./recv <dstfile>");
return -1;
}
mkfifo("./myfifo", 0664);
int fdfifo = open("./myfifo", O_RDONLY);
if (fdfifo < 0)
{
perror("fail open fdfifo");
return -1;
}
int fddst = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(fddst < 0)
{
perror("fail open fddst");
return -1;
}
char buff[1024] = {0};
while (1)
{
size_t size = read(fdfifo, buff, sizeof(buff));
if (size <= 0)
{
break;
}
write(fddst, buff, size);
}
close(fdfifo);
close(fddst);
remove("./myfifo");
return 0;
}
memset函数介绍
将内存清成指定字节
#include <string.h>
void *memset(void *s, int c, size_t n);