最近学习进程通信方式网上查找资料学习,根据自己的理解加上网上的参考资料进行了一些总结,其中有些写的好的我直接拿来使用了。
进程通信方式有:
无名管道、有名管道、信号、3种系统IPC(信号量、消息队列、共享内存)、套接字(socket),共7种。
无名管道(pipe):无名管道数据只能单向流动,具有固定的读端和写端,而且只能在具有亲缘关系的进程间使用。
有名管道(fifo) :有名管道数据只能单向流动,但是它允许无亲缘关系进程间的通信。
信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
信号量(sempore):是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列(msgget):就是一个消息的链表。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。
共享内存(share memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
套接字( socket ) :套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
是否能传递消息
其中消息队列,共享内存,套接字,有名管道,无名管道可以传递信息,信号,信号量主要用来同步,不能传递消息。
通信范围
有名管道,共享内存、消息队列、套接字、信号量可以跨进程,不仅可以跨父子进程,还可以在不相关的进程间进行通信。
无名管道只能在具有亲缘关系的进程间进行通信、进程的亲缘关系通常是指父子进程关系。
信号是可以跨进程的但必须知道进程号才可以,大多数发送信号的函数不能跨进程(父子进程都不行),kill(pid_t,sig)函数可以跨进程但必须知道进程号。
使用函数
共享内存:
int shmget(key_t key, int size, int shmflg);//创建共享内存,也可用ftok()函数来创建key_t值。
void *shmat(int shmid, const void *shmaddr, int shmflg);//共享内存映射
int shmdt(const void *shmaddr);取消共享内存映射
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//操作共享内存标识符,设置、获取、删除、内存映射对象
消息队列:
int msgget(key_t key, int flag);//创建消息队列,也可用ftok()函数来创建key_t值。
int msgsnd(int msqid, const void *msgp, size_t size, int flag);//发送消息
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);//接收消息
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );//操作消息队列标识符,设置、获取、删除、对象
信号量:
int semget(key_t key, int nsems, int semflg);//创建信号量,也可用ftok()函数来创建key_t值。
int semop ( int semid, struct sembuf *opsptr, size_t nops);//进行V操作,P操作
int semctl ( int semid, int semnum, int cmd…/*union semun arg/);//设置、获取、删除信号灯集
信号:
int sigemptyset (sigset_t *set);//清空此信号集
int sigaction (int signum, const struct sigaction *restrict action, struct sigaction *restrict old-
action);//重新定义信号处理函数
sighandler_t signal (int signum, sighandler_t action);//重新定义信号处理函数
unsigned int alarm (unsigned int seconds);发送SIGALRM(闹钟信号)不能跨进程
int raise (int signum);给当前进程发送指定的信号。不能跨进程
int kill (pid_t pid, int signum);给进程号为pid的进程发送指定的信号。可以跨进程
void abort(void);发送结束信号,一般用于结束该进程,因为该函数总是会成功所以没有返回值
int pause ();等待知道收到一个信号
有名管道:
int mkfifo (const char *filename, mode_t mode);//创建管道
int open (const char *filename, int flags);//打开管道
ssize_t write (int filedes, const void *buffer, size_t size);//写入数据
ssize_t read (int filedes, void *buffer, size_t size);//读取数据
无名管道:
int pipe (int filedes[2]);//创建管道文件,一个读端filedes[0]和一个写端filedes[1],这个规则不能变。
ssize_t write (int filedes, const void *buffer, size_t size);//写入数据,一个写端filedes[1]
ssize_t read (int filedes, void *buffer, size_t size);//读取数据,filedes=filedes[0]
无名管道实例:
#include "unistd.h"
#include "stdio.h"
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <sys/stat.h>
#include<sys/types.h>
#include <fcntl.h>
using namespace std;
int main() {
int fd[2];
int ret;
char readbuf[128];
ret = pipe(fd);//创建管道文件
//无名管道,只能用于具有亲缘关系的进程之间的通信
//半双工的通信模式,具有固定的读端和写端
if (ret < 0) {
cout << "pipe error\n";
return -1;
}
if (fork() == 0) {//创建子进程
char writebuf[128];
while (1) {
cin >> writebuf;
cin.get();
//向管道中写入数据时,linux将不保证写入的原子性
//只有在管道的读端存在时,向管道中写入数据才有意义。
//否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号(通常Broken pipe错误)
write(fd[1], writebuf, sizeof(writebuf));
if (strcmp(writebuf, "quit") == 0) {
break;
}
}
close(fd[1]);//关闭写端
exit(0);//退出子进程
}else
{
while (1) {
//1.管道中的东西,读完了就删除了;2.没有东西可读,则会阻塞
read(fd[0], readbuf, sizeof(readbuf));
cout << "read:" << readbuf << endl;
if (strcmp(readbuf, "quit") == 0) {
break;
}
}
close(fd[0]);//关闭读端
}
return 0;
}
有名管道实例:
//写端
int main()
{
int pfp;
char buf[128];
//linux从命令行上创建管道,使用下面这个命令:mkfifo pipefile
//./表示在当前目录下创建,pipefile为管道文件的文件名,0644是文件的权限
// if(mkfifo("./pipefile",0644)<0)//如果已经有文件了,创建失败,创建一次就可以了
// {
// cout<<"create fifo failed\n";
// return -1;
// }
cout<<"write\n";
pfp = open("./pipefile",O_WRONLY);//以只写方式打开,注意读端和写端打开的文件名字要一样
while (1) {
cin >> buf;
cin.get();
//向管道中写入数据时,linux将不保证写入的原子性
//只有在管道的读端存在时(不一定是打开的),向管道中写入数据才有意义。
//否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号(通常Broken pipe错误)
if(write(pfp, buf, sizeof(buf))>0)//会阻塞直到数据被读取完
{
cout<<"write sucess\n";
}else
{
cout<<"write falied\n";
}
if (strcmp(buf, "quit") == 0) {
break;
}
}
close(pfp);
return 0;
}
//读端
{
int pfp;
pfp = open( "./pipefile",O_RDONLY);//以只读取方式打开,注意读端和写端打开的文件名字要一样
while (1) {
//1.管道中的东西,读完了就删除了;2.没有东西可读,则会阻塞
if(read(pfp, buf, sizeof(buf))>0)
{
cout << "read:" << buf << endl;
if (strcmp(buf, "quit") == 0) {
break;
}
}
}
close(pfp);
return 0;
}
消息队列实例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<stdlib.h>
int main()
{
int msgid;
msgbuf1 buf1,buf2;
//key:和消息队列关联的key值,0(IPC_PRIVATE):会建立新的消息队列,不同进程通信可以根据ftok()函数创建key
/*
int msgid;
int key ;
//ftok根据路径名,提取文件信息,再根据这些文件信息及project ID合成key.
//该路径是必须存在的,文件也必须存在,ftok只是根据文件inode在系统内的唯一性来取一个数值,和文件的权限无关。
//proj_id是可以根据自己的约定,随意设置。这个数字,有的称之为project ID; 在UNIX系统上,它的取值是1到255
//误解,即只要文件的路径,名称和子序列号不变,那么得到的key值永远就不会变。
//假如存在这样一种情况:在访问同一共享内存的多个进程先后调用ftok()时间段中,如果fname指向的文件或者目录被删除而且又重新创建,那么文件系统会赋予这个同名文件新的i节点信息,
//于是这些进程调用的ftok()都能正常返回,但键值key却不一定相同了, 这是一个很重要的问题,希望能谨记!!
key = ftok("./msg.tmp", 0x01 ) ;
if(key<0)
{
printf("get key error\n");
return -1;
}
msgid=msgget(key,0777|IPC_CREAT);
//flag:消息队列的访问权限,0:取消息队列标识符,若不存在则函数会报错
//IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符
//IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错
//成功:消息队列ID,error:-1
msgid=msgget(IPC_PRIVATE,0666);
// msgget(IPC_PRIVATE,IPC_CREAT|0777);
if(fork()==0)
{
strcpy(buf1.mtext,"hello msg");
buf1.mtype=1L;
//msgid:消息队列的ID
/*buf1:指向消息的指针。
常用消息结构msgbuf
struct msgbuf{
long mtype;// 消息类型,必须大于0
char mtext[N]; //消息正文,可以是其他任何类型
};
*/
//TEXT_SIZE:发送的消息正文的字节数,不含消息类型占用的4个字节,
/* IPC_NOWAIT :消息没有发送完成函数也会立即返回。
0:直到发送完成函数才返回*/
//成功0;出错:-1
for(int i=0;i<3;i++)
{
msgsnd(msgid,&buf1,TEXT_SIZE,0);
sleep(1);
}
exit(1);
}
//msgid:消息队列的ID
//buf2:接收消息的缓冲区,结构体类型要与msgsnd函数发送的类型相同
//TEXT_SIZE:要接收的消息的字节数,不含消息类型占用的4个字节
/* 0:接收消息队列中第一个消息;
* >0:接收类型等于msgtyp的第一个消息
* 小于0:接收类型等于或者小于msgtyp绝对值的第一个消息
*/
/* 0:若无消息函数会一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
*/
for(int i=0;i<3;i++)
{
msgrcv(msgid,&buf2,TEXT_SIZE,1L,0);
//成功:接收到的消息的长度,出错:-1
printf("recv:%s\n",buf2.mtext);
}
int flag;
struct msqid_ds info ;
//msgid:消息队列标识符
//IPC_STAT:获得msgid的消息队列头数据到buf中
//IPC_RMID:从系统中删除消息队列。
/*IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中,
可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes
*/
flag = msgctl(msgid, IPC_RMID, &info ) ;//删除消息队列。
if ( flag < 0 )
{
perror("get message status error") ;
return -1 ;
}
return 0;
}
共享内存实例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
struct sharedata //定义共享内存传递消息的结构体
{
int flag;
char buf[128];
};
int main()
{
int shmid;
void *shm = NULL;
sharedata *sharebuf=NULL;
//有一个特殊的键值IPC_PRIVATE, 它用于创建一个只属于创建进程的共享内存,通常不会用到。
//key_t=ftok("./msg.tmp", 0x01 )
//由IPC_CREAT定义的一个特殊比特必须和权限标志按位或才能创建一个新的共享内存段。
shmid=shmget((key_t)1234,sizeof(sharedata),0666|IPC_CREAT);
if(shmid<0)
{
printf("shmget error\n");
return -1;
}
//第一次创建共享内存段时,它不能被任何进程访问。要想启动对该内存的访问,
//必须将其连接到一个进程的地址空间。这个工作由shmat函数完成:
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
printf("shmat error\n");
return -1;
}
sharebuf=(sharedata*)shm;
sharebuf->flag=0;//初始化可写
while(1)
{
if(sharebuf->flag==1)
{
printf("shmdata=%s\n",sharebuf->buf);
if(strncmp(sharebuf->buf,"quit",4)==0)
{
break;
}
sharebuf->flag=0;
}else
{
printf(" no data to read.\n");
sleep(3);
}
}
if(shmdt(shm)<0)
{
printf("shmdt error\n");
return -1;
}
return 0;
}
//读端
int main()
{
int shmid;
void *shm = NULL;
sharedata *sharebuf=NULL;
//有一个特殊的键值IPC_PRIVATE, 它用于创建一个只属于创建进程的共享内存,通常不会用到。
//key_t=ftok("./msg.tmp", 0x01 )
//由IPC_CREAT定义的一个特殊比特必须和权限标志按位或才能创建一个新的共享内存段。
shmid=shmget((key_t)1234,sizeof(sharedata),0666|IPC_CREAT);
if(shmid<0)
{
printf("shmget error\n");
return -1;
}
//第一次创建共享内存段时,它不能被任何进程访问。要想启动对该内存的访问,
//必须将其连接到一个进程的地址空间。这个工作由shmat函数完成:
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
printf("shmat error\n");
return -1;
}
sharebuf=(sharedata*)shm;
sharebuf->flag=0;//初始化可写
while(1)
{
if(sharebuf->flag==0)
{
printf("please write:\n");
fgets(sharebuf->buf,sizeof(sharebuf->buf),stdin);
sharebuf->flag=1;
printf("write shmdata=%s\n",sharebuf->buf);
if(strncmp(sharebuf->buf,"quit",4)==0)
{
break;
}
}else
{
printf("data not read yet!\n");
sleep(1);
}
}
if(shmdt(shm)<0)//取消映射
{
printf("shmdt error\n");
return -1;
}
if(shmctl(shmid,IPC_RMID,NULL)<0)//删除就只能有一个,读端和写端
{
printf("shmctl error\n");
return -1;
}
return 0;
}
信号实例:
#include <signal.h>
#include <sys/types.h>
#include<stdio.h>
#include<iostream>
#include<unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
//发送信号的四个函数
//int kill(pid_t pid, int sig);
//int raise(int sig);给当前进程发送指定的信号。
//unsigned int alarm(unsigned int seconds);
//在程序运行多少秒之后向所在的进程发送一个闹钟信号SIGALRM也就是14号信号。
//void abort(void);用于结束该进程,因为该函数总是会成功所以没有返回值
//pause()函数是用于将调用进程挂起直到收到信号为止。
//要想使接收的进程能收到信号,这个进程不能结束
//sigaction()函数是POSIX(可移植操作系统接口)的信号接口,而signal()是标准C的信号接口;
void sigactionfun(int sig)//自定义信号处理函数
{
cout<<"sigaction recv singal:"<<sig<<endl;
}
int main()
{
struct sigaction sa,oact;
sa.sa_handler = sigactionfun;//指定新的信号处理函数
sigemptyset(&sa.sa_mask); //清空此信号集
sa. sa_flags = 0;
//参数1:signum可以指定SIGKILL和SIGSTOP以外的所有信号。
sigaction(SIGALRM, &sa,&oact);
//原来的处理函数备份到oact里面,如果不需要重置该给定信号的处理函数为缺省值oact换成NULL
int i=3;
while(i)
{
i--;
alarm(2);//2s后发送SIGALRM信号,向所在的进程(子进程),不会跨进程
pause();
}
sigaction(SIGALRM, &oact, NULL); //恢复成原始处理方式
alarm(2);//2s后发送SIGALRM信号,向所在的进程,不会跨父子进程
pause();
return 0;
}
int testmain()
{
alarm(3);
signal(SIGALRM,sigactionfun);//SIG_IGN:忽略该信号。SIG_DFL:采用系统默认方式处理信号
int i=3;
while(i)
{
i--;
alarm(3);
pause();
}
return 0;
}
信号量实例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main()
{
pid_t pd;
int semid;
int key;
key=ftok(".",'a');
//和信号灯集关联的key值
//信号灯集中包含的信号灯数目
//信号灯集的访问权限,通常为IPC_CREAT | 0666
//return -1 error,成功:信号灯集ID
semid=semget(key,1,0666|IPC_CREAT);
union semun nsem;
nsem.val=0;//初始值0
//要修改的信号灯编号
//GETVAL:获取信号灯的值;SETVAL:设置信号灯的值;IPC_RMID:从系统中删除信号灯集合
//成功:0,eoore:-1
if(semctl(semid,0,SETVAL,nsem)==-1)//设置信号灯的值
{
perror("semctl error\n");
return -1;
}
if(fork()==0)
{
sembuf semb;
semb.sem_num=0;//信号量编号,
semb.sem_op=-1;
//信号操作,-1为分配资源,P操作;0等待,直到信号灯的值变成0;//1释放资源,V操作
semb.sem_flg=SEM_UNDO;// 0, IPC_NOWAIT,SEM_UNDO
//SEM_UNDO:在进程没有释放信号量而退出时,系统自动释放该进程中没释放的信号量
//信号灯集ID
//要操作的信号灯,struct sembuf
//要操作的信号灯的个数
//success:0;error:-1
if(semop(semid,&semb,1)==-1)//进行p操作
{
perror("semop error\n");
return -1;
}
printf("p\n");
exit(1);
}
sembuf semb;
semb.sem_num=0;//信号量编号,
semb.sem_op=1;//信号操作,+1为v操作
semb.sem_flg=SEM_UNDO;
//在进程没有释放信号量而退出时,系统自动释放该进程中没释放的信号量
if(semop(semid,&semb,1)==-1)//进行v操作
{
perror("semop error\n");
return -1;
}
printf("v\n");
sleep(1);
union semun semn;
//从系统删除信号量
if(semctl(semid,0,IPC_RMID,semn)==-1)
{
perror("semctl error\n");
return -1;
}
return 0;
}