(1)匿名管道和FIFO有名管道。
(2)消息队列、信号量和共享存储。
(3)套接字。
管道——匿名管道
在命令行中使用:$cat file | grep "pipe" | more。这句话用了两个管道,分别使cat的标准输出成为grep的标准输入,grep的标准输出成为more的标准输入。
创建管道:
#include <unistd.h>
int pipe(int fdes[2]);
该函数在系统内部创建一条管道,并设置该管道使用的两个已打开文件描述字于数组fdes中。fdes[0]是输入端,fdes[1]是输出端。
管道的作用主要体现在父子进程间的通信:在调用fork之前用pipe()创建一个管道,那么fork之后父子进程都能访问这两个文件描述字。但是只能有一个通信方向,所以要关闭父子进程中各自不使用的那个描述字。
//父进程通过管道向子进程发送数据,子进程通过管道接收父进程发送的数据
#include<iostream>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
#define BUFSIZE 100
int main()
{
pid_t pid;
int mypipe[2], n;
char buffer[BUFSIZE+1], some_data[] = "Hello world!";
//创建管道
if(pipe(mypipe))
{
cout << "Pipe failed.\n" << endl;
exit(1);
}
//派生子进程
if((pid = fork()) == (pid_t)0){
close(mypipe[1]);
n = read(mypipe[0], buffer, BUFSIZE);
printf("child %d:read %d bytes:%s\n", getpid(), n, buffer);
}else{
close(mypipe[0]);
n = write(mypipe[1], some_data, strlen(some_data));
printf("parent %d:write %d bytes:%s\n", getpid(), n, some_data);
}
exit(EXIT_SUCCESS);
}
popen()函数和pclose()函数
#include <stdio.h>
FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);
popen()函数在调用进程和命令command之间建立一个内部半双工管道,然后派生一个子进程,并调用exec()启动shell程序执行command给出的命令。pclose()等待由popen()执行的进程终止。
管道——命名管道(FIFO)
匿名管道只能用于父子进程之间的通信,但是用FIFO特别文件,非父子关系的进程也可以交换数据。半双工,先进先出。命名管道本质上是不相关的进程读写同一个特殊文件。必须同时有一个进程为读而打开他,另一个进程为写而打开它。为读而打开FIFO将阻塞直到另一个进程为写而打开同一个FIFO,为写而打开FIFO也同样会阻塞直到另一个进程为读而打开同一个FIFO。
创建FIFO两种方法
(1)$mknod myfifo p 或者 $mkfifo a=rw myfifo。mkfifo是专门用来创建FIFO文件的,mknod除了FIFO文件还可以创建别的特殊文件
(2)程序中创建FIFO
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mknod(const char *path, mode_t mode, dev_t dev);
mkfifo创建一个新的FIFO特别文件,path是文件名字,mode是权限,取值同open函数的mode参数。
mknod创建一个给定文件类型的新文件,path是文件名,mode是文件类型,dev是设备号,此处指定为0。
打开FIFO:open函数,注意不能以O_RDWR方式打开FIFO既读又写,因为FIFO是半双工的。指定O_NONBLOCK标识,说明对该文件的读写是非阻塞的,并且打开的动作也是非阻塞的,否则open将阻塞直到另一个进程以相反的读写方式打开同一个FIFO文件。但是对于普通文件来说,无论是否设置O_NONBLOCK,open均不阻塞。
//写.cpp
int resfd=mkfifo(“c:/my_fifo”,0777); //创建命名管道
int pipefd=open(“c:/my_fifo”,O_WRONLY); //打开命名管道
write(pipefd,buffer,buffer_size); //往管道中写东西
//读.cpp
int pipefd=open(“c:/my_fifo”,O_RDONLY); //打开已经创建的命名管道
read(pipefd,buffer,buffer_size); //往管道中读东西
消息队列
i. 优点:
1. 传输数据块,而不是无格式字节流;可以有选择的接收数据,而不是所有数据都接收(管道)
2. 没有缓冲区大小受限这一说
3. 不相关进程通信
4. 无管道的同步阻塞问题
ii. 缺点:
1. 消息队列中的消息不接收,就会一直存在在那儿(管道在最后一个进程引用结束后会自动删除(至少是里面的数据是这样)),直至显式的删除消息队列或者删除,接收完消息
iii. 使用示例
//接收.进程
int msgfd=msgget((key_t)1234, 0666 | IPC_CREAT); //创建或获取管道
msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0); //获取消息
//发送.进程
int msgfd=msgget((key_t)1234, 0666 | IPC_CREAT); //创建或获取管道
msgsnd(msgid, (void*)&data, BUFSIZ, msgtype, 0); //发送消息
共享内存
将同一块内存映射到不同进程的地址。不同进程通过操作同一块内存来满足进程间通信的需求。因此共享内存上做的操作将立即反应到其他进程,所以需要使用信号量,来同步的使用共享内存。
(1)mmap函数mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
该函数中的addr可以指定描述符fd应被映射到的进程内空间的起始地址。len是映射到调用进程地址空间中的字节数,他从被映射文件开头起第offset个字节处开始算。prot设置内存映射区的保护(PROT_READ,PROT_WRITE,PROT_EXEC,PROT_NONE),flag取值MAP_SHARED表示调用进程对被映射数据所做的修改只对该进程可见,MAP_PRIVATE表示变动是共享的。
(2)用shmget创建共享内存区(IPC内存区);用shmat将共享内存区映射到具体的进程空间;用完之后,用shmdt撤销映射操作。
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT); //创建共享内存
Void *shm = shmat(shmid, (void*)0, 0);//将共享内存连接到当前进程的地址空间
struct shared_use_st *shared=(struct shared_use_st*)shm;//转换格式
strncpy(shared->text, buffer, TEXT_SZ);//就像文件一样写
shmdt(shm);//把共享内存从当前进程中分离