进程间通信的几种常用方式:
管道、命名管道、信号、信号量、共享内存、消息队列、套接字。
管道( pipe ):一种半双工的通信方式,数据只能单向流动且只能在具有共同祖先的进程间使用。
命名管道 (name pipe):也是一种半双工的通信方式,但他允许不相关进程间的通信。
信号( sinal ):一个进程通过信号通知其他进程某事件已经发生,其他进程的反应如何及何时反应他都不管。
信号量( message queue ):信号量只是一个计数器,通过控制多个进程对共享资源的访问实现用于进程同步。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针
对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小
受限等缺点。
套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
教材:Linux程序设计(第三版) Neil Mathhew Richard Stones
一.进程管道
最简单的两个进程之间的传递数据的方法就是使用popen、pclose。
#include <stdio.h>
FILE *popen(const char *command,const char *open_mode);
int pclose(FILE *stream_to_close);
popen通过command调用另一个程序并传递参数。open_mode是管道的打开方式,只能是"r"或"w",因此不能同时进行读写;如果要实现双向通信,最普通的方法是同时打开两个管道分别负责一个方向的数据流。
pclose不再使用该管道时,可以用它关闭。
一个例子:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
FILE *read_fp;
char buffer[BUFSIZ + 1]; //最新的linux一般将BUFSIZ设为8192或更大
int chars_read;
memset(buffer, 0, sizeof(buffer));
read_fp = popen("ps -ax", "r"); //ubuntu中为ps ax,带"-"会有报警
if ( NULL != read)
{
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while(chars_read > 0)
{
buffer[chars_read - 1] = '\0';
printf("Reading: - \n %s \n", buffer);
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
程序运行的结果是将ps -ax命令(也就是运行ps程序,参数是ax)的结果写入管道,再从管道读出来显示在终端。
二.pipe调用
上例中popen要通过shell来解释ps ax命令,调用ps程序,pipe则可以省过这一步并提供更多的控制。
#include <unistd.h>
int pipe(int file_descroptor[2]);
pipe函数在往它的参数中填入两个文件描述符,成功返回0,失败返回-1并设置errno以表明失败原因。
EMFILE:进程使用的文件描述符过多
ENFILE:系统的文件表已满
EFAULT:文件描述符无效
pipe()使用两个文件描述符以一种特殊的方式连接起来。写到int file_descroptor[1]的所有数据都可以从int file_descroptor[0]以先进先出的方式读出来。
这里使用的是文件描述符而不是流,因此不用fread/fwrite,而是使用底层的read/write 。
例子:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "lishenfutongxue";
char buffer[BUFSIZ + 1];
pid_t fork_result;
memset(buffer, 0, sizeof(buffer));
if( 0 == pipe(file_pipes) )
{
fork_result = fork();
if( -1 == fork_result ) //创建子进程失败
{
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if( 0 == fork_result ) //创建子进程成功,返回0表示我们在子进程中
{
printf("goto child process \n");
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf("read %d bytes: %s \n", data_processed, buffer);
exit(EXIT_SUCCESS);
}
else // 创建子进程成功,返回子进程号(这里没有表现具体进程号,仅用非-1且非0表示),表示我们在父进程中
{
printf("still in father process \n");
data_processed = write(file_pipes[1], some_data, strlen(some_data));
printf("Wrote %d bytes \n", data_processed);
}
}
exit(EXIT_FAILURE);
}
这个程序用两个文件描述符 file_pipes创建一个管道,父进程向 file_pipes[1]写入数据,子进程从 file_pipes[0]读出。
三.一些细节的处理
将管道用作标准输入输出
到此,我们一直让读进程简单的读取一些数据然后直接退出,且假设不用手动清理使用过的资源。但大多数从标准输入输出请填写数据的程序用的是另一种做法,通常它们并不知道有多少数据要读写而往往用循环的方法:读取-处理-读取...没有数据可读时,read调用阻塞,如果另一边管道关闭,就让read返回一个0。
可以使用文件描述符复制函数将标准输入/输出作为管道文件描述符:
#include <unistd.h>
int dup(int file_descriptor);
dup函数打开一个新的文件描述符,与参数file_descriptor指向同一个文件(或管道),新文件描述符总是取最小值--如果在调用dup函数前关闭了输入流,那么它返回新描述符就是0,因为新描述符与参数中的描述符指向同一文件或管道,所以标准输入流改为指向一个我们传递给dup函数的参数对应的文件或管道。