进程间通信(IPC)
前言
管道
匿名管道 pipe:适用于亲缘关系进程间的、一对一的通信
具名管道 fifo :适用于任何进程间的一对一、多对一的通信
套接字 socket:适用于跨网络的进程间通信
信号:异步通信方式
system-V IPC对象
共享内存:效率最高的通信方式
消息队列:相当于带标签的增强版管道
信号量组:也称为信号灯,用来协调进程间或线程间的执行进度
POSIX信号量
POSIX匿名信号量:适用于多线程,参数简单,接口明晰
POSIX具名信号量:适用于多进程,参数简单,接口明晰
父子进程使用匿名管道通信
#include <unistd.h>
//读fd[0],一个专用于写fd[1]
int pipe( int fd[2] ); //匿名管道
示例代码如下:
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
// 创建匿名管道
int fd[2];
pipe(fd);
// 子进程
if(fork() == 0)
{
// 向父进程打招呼
char *msg = "hello parent!";
write(fd[1], msg, strlen(msg));
exit(0);
// 关掉不必要的读端
close(fd[0]);
}
// 父进程
else
{
char buf[50];
bzero(buf, 50);
// 静静地等待子进程的消息 阻塞
read(fd[0], buf, 50);
printf("来自子进程: %s\n", buf);
exit(0);
// 关掉不必要的写端
close(fd[1]);
}
}
1普通文件,默认是非阻塞的,且不可修改。
2管道文件,默认是阻塞的,可修改。
// 1,将管道设置为非阻塞
long flag = fcntl(fd[0], F_GETFL);
flag |= O_NONBLOCK; //某位设置为O_NONBLOCK
fcntl(fd[0], F_SETFL, flag);
// 2,将管道重新设置为阻塞
flag = fcntl(fd[0], F_GETFL);
flag &= ~O_NONBLOCK; //还原flag的值,某位清零
fcntl(fd[0], F_SETFL, flag);
具名管道FIFO更接近普通文件,有文件名、可以open打开、支持read()/write()等读写操作。
与PIPE一样不支持定位操作lseek()
与PIPE一样秉持相同的管道读写特性
使用专门的接口来创建:mkfifo()(匿名管道是pipe())
在文件系统中有对应节点,支持使用 open() 打开管道(匿名管道不具备)
支持多路同时写入(匿名管道不具备)
#include <sys/types.h>
#include <sys/stat.h>
//mode是文件权限模式,例如0666
int mkfifo(const char *pathname, mode_t mode);
示例代码:
// 进程A,创建管道并向管道写入字符串"data from FIFO."
int main()
{
// 创建具名管道
mkfifo("/tmp/fifo", 0666);
// 向管道写入数据
int fd = open("/tmp/fifo", O_RDWR);
char *msg = "data from FIFO";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
// 进程B,从管道读出数据
int main()
{
// 从管道读出数据
int fd = open("/tmp/fifo", O_RDWR);
char buf[50];
bzero(buf, 50);
read(fd, buf, 50);
printf("%s\n", buf);
close(fd);
return 0;
}
编程实现下述命令的执行效果,查看系统进程列表中的指定进程信息:
gec@ubuntu:~$ ps ajx | grep 'xxx' --color
253
gec@ubuntu:~$
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
int fd[2];
pipe(fd);
if(argc != 2)
{
printf("用法: %s 要搜索的进程名\n", argv[0]);
exit(0);
}
if(fork() == 0) //创建进程
{
//fd[1]:写:设置为标准输出
dup2(fd[1], STDOUT_FILENO);
// 子进程执行 "ps ajx"
execlp("ps", "ps", "ajx", NULL);
}
else
{
close(fd[1]); //fd[0]:读:设置为标准输入
dup2(fd[0], STDIN_FILENO);
// 父进程执行 "grep" argv[1]: |
execlp("grep", "grep", argv[1], "--color", NULL);
}
exit(0);
}
精灵进程、有名管道FIFO
【2】编程实现两个程序:一个服务器server,一个客户机client。要求:
服务器创建并监视有名管道FIFO,一旦发现有数据则将其保存到一个指定的地方。
客户机每隔一段时间产生一个子进程。
客户机的这些子进程将当前系统时间和自身的PID写入有名管道FIFO就退出。
解答:
// 服务端程序示例代码:
int main(int argc, char **argv)
{
mkfifo("/tmp/fifo", 0666);
int fifofd = open("/tmp/fifo", O_RDWR);
int logfd = open("/tmp/log.txt", O_WRONLY|O_CREAT|O_APPEND, 0777);
char buf[1024];
while(1)
{
bzero(buf, 1024);
read(fifofd, buf, 1024);
write(logfd, buf, strlen(buf));
}
return 0;
}
//客户端代码:
int main(int argc, char **argv)
{
mkfifo("/tmp/fifo", 0777);
int fd = open("/tmp/fifo", O_WRONLY);
char buf[1024];
time_t t;
while(1)
{
bzero(buf, 1024);
time(&t);
snprintf(buf, 1024, "[%-6d] %s",
getpid(), ctime(&t));
write(fd, buf, strlen(buf));
sleep(1);
}
return 0;
}
【3】根据管道的读写特性,编写一个程序,测试你系统管道文件的缓冲区大小。
int main()
{
// 管道默认为阻塞
int fd[2];
pipe(fd);
// 将管道写端设置为非阻塞
long flag = fcntl(fd[1], F_GETFL);
flag |= O_NONBLOCK;
fcntl(fd[1], F_SETFL, flag);
// 持续不断写入数据,直到报错为止
int total = 0;
while(1)
{
if(write(fd[1], "x", 1) < 0)
{
perror("写入失败");
break;
}
total++;
}
printf("缓冲区大小:%d个字节\n", total);
}