进程间通信的方式主要是有:
- 管道
- 消息队列
- 信号量
- 共享内存
- 信号
- Socket套接字
半双工方式是最常用的IPC形式(数据只能是在一个方向上流动)
#include
int pipe(int fd[2]);
单个进程中的管道意义不大,通常是
pipe
之后调用fork
来创建父子之间的ipc管道。我们可以通过关闭父进程或者子进程的读写的端口来实现 控制数据流的方向
:从父进程流向子进程的管道, 父进程关闭管道的
读端(fd[0])
, 子进程关闭管道的写端(fd[1])
如果想从子进程向父进程的流向,关闭的顺序相反就行
#include "apue.h"
int main()
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];
if (pipe(fd) < 0)
err_sys("pipe error");
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0) { //parent
close(fd[0]); //close read port
write(fd[1], "hello from father\n", 18);
} else { //child
close(fd[1]);
sleep(1);
n = read(fd[0], line, n);
write(STDOUT_FILENO, line, n); //将从管道读取的数据写到标准输出中
}
exit(0);
}
#include "apue.h"
static int pfd1[2], pfd2[2];
void TELL_WAIT() {
if (pipe(pfd1) < 0 || pipe(pfd2) < 0)
err_sys("pipe error");
}
void TELL_PARENT(pid_t pid) {
if (write(pfd2[1], "c", 1) != 1)
err_sys("write error");
}
void TELL_CHILD(pid_t pid) {
if (write(pfd1[1], "p", 1) != 1)
err_sys("write error");
}
void WAIT_PARENT() {
char c;
if (read(pfd1[0], &c, 1) != 1)
err_sys("read error");
if (c != 'p')
err_quit("wait parent");
}
void WAIT_CHILD() {
char c;
if (read(pfd2[0], &c, 1) != 1)
err_sys("read error");
if (c != 'c')
err_quit("wait parent");
}
常见的使用管道的方式是:创建一个管道同一个进程进行交互,向另一个进程的管道的端口进行交互
popen
:创建管道,fork子进程,关闭未使用的管道端口,执行shell命令,等待命令终止
#include
FILE* popen(const char* cmdstring, const char *type);
int pclose(FILE* fp);
函数
popen
首先fork
,然后调用exec
执行cmdstring
,返回一个标准IO文件指针。如果type 是 “r”, 文件指针连接到cmdstring 的标准输出,为“w”则反之
Trans.cpp
#include "apue.h"
#include
int main()
{
int c;
while ((c = getchar()) != EOF) {
if (isupper(c))
c = tolower(c);
if (putchar(c) == EOF)
err_sys("output error");
if (c == '\n')
fflush(stdout);
}
exit(0);
}
main.cpp
#include "apue.h"
#include
int main()
{
char line[MAXLINE];
FILE *fpin;
if ((fpin = popen("./mytrans", "r")) == NULL) //将文件流连接到trans程序的标准输出上面(也就是能够读取到字符转换后的结果)
err_sys("popen error");
for (;;) {
//下面都是父进程的工作,子进程调用mytrans,用户就是同mytrans进行交互,父进程来进行调度
fputs("prompt> ", stdout); //用户的标准输出上打印prompt >
fflush(stdout);
if (fgets(line, MAXLINE, fpin) == NULL) //从trans的标准输出中读取
break;
if (fputs(line, stdout) == EOF)
err_sys("fputs error to pipe");
}
if (pclose(fpin) == -1) {
err_sys("pclose error");
}
putchar('\n');
exit(0);
}
解析:用户的输入是同
trans
程序进行交互的,父进程进行总体的调度
当一个过滤的程序既产生某个过滤程序的输入又读取该过滤程序的输出的时候,就变成了协同进程
协同进程从标准输入读取两个数字,将和写到标准输出中(简单实例,实际上协同进程做更加有意义的事情)
#include "apue.h"
int main()
{
int n, num1, num2;
char line[MAXLINE];
while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0) {
line[n] = 0; //null terminate
if (sscanf(line, "%d%d", &num1, &num2) == 2) {
sprintf(line, "%d\n", num1 + num2);
n = strlen(line);
if (write(STDOUT_FILENO, line, n) != n)
err_sys("write error");
} else {
if (write(STDOUT_FILENO, "invalid args\n", 13) != 13)
err_sys("write error");
}
}
exit(0);
}
进行协同进程的样例演示,父子进程关闭不需要的端口, 子进程使用
dup2
让管道描述符号移至标准输入和标准输出,最终调用execl
#include "apue.h"
static void sig_pipe(int); /* our signal handler */
int
main(void)
{
int n, fd1[2], fd2[2];
pid_t pid;
char line[MAXLINE];
if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
err_sys("signal error");
if (pipe(fd1) < 0 || pipe(fd2) < 0)
err_sys("pipe error");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid > 0) { /* parent */
close(fd1[0]);
close(fd2[1]);
while (fgets(line, MAXLINE, stdin) != NULL) {
n = strlen(line);
if (write(fd1[1], line, n) != n)
err_sys("write error to pipe");
if ((n = read(fd2[0], line, MAXLINE)) < 0)
err_sys("read error from pipe");
if (n == 0) {
err_msg("child closed pipe");
break;
}
line[n] = 0; /* null terminate */
if (fputs(line, stdout) == EOF)
err_sys("fputs error");
}
if (ferror(stdin))
err_sys("fgets error on stdin");
exit(0);
} else { /* child */
close(fd1[1]);
close(fd2[0]);
if (fd1[0] != STDIN_FILENO) {
if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
err_sys("dup2 error to stdin");
close(fd1[0]);
}
if (fd2[1] != STDOUT_FILENO) {
if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
err_sys("dup2 error to stdout");
close(fd2[1]);
}
if (execl("./add2", "add2", (char *)0) < 0)
err_sys("execl error");
}
exit(0);
}
static void
sig_pipe(int signo)
{
printf("SIGPIPE caught\n");
exit(1);
}
fifo
称为命名管道,未命名的管道只能是在两个相关的进程之间使用.通过fifo,不相关的进程之间也是可以进行交换数据
#include
int mkfifo(const char* path, mode_t mode);
int mkfifoat(int fd, const char* path, mode_t mode);
阻塞与非阻塞:(O_NOBLOCK)
- 没有指定非阻塞的情况下, 只读open要阻塞到 某个进程为了写而打开这个fifo。只写open类似
- 如果指定了非阻塞,那么open之后立即返回,如果没有可读就返回-1, 设置error : ENXIO
用途:
- shell命令使用FIFO将数据从一条管道传送到另一条管道,无需创建中间临时文件
- 客户进程-服务器进程应用程序中,FIFO用作汇聚点,在两者进程之间传递数据
信号量、消息队列、共享内存
我看书上的解释也不是太多,这里就贴上小林Coding的解析好了地址
Socket
章节内容等到unp栏目再记笔记