本文内容:
1.进程通信的目的
2.介绍Linux下进程间的4种通信方式:管道,消息队列,共享内存,信号量
ps:套接字也可以用于进程间的通信,不过是不同物理机器上的进程通信,本章讨论是是同一台物理机器上的通信,套接字本章暂不讨论
一.进程间通信的目的
1)数据的传输
2)数据的共享
3)事件的通知
4)资源的共享
5)进程的控制
二.进程间的通信方式
1.管道
概念:管道是一种两个进程间进行单向通信的机制,因为管道传递数据的单向性,管道又称之为半双工管道
分类:匿名管道和有名管道
特点:
1)数据只能由一个进程流向另一个进程,如果要进行双工通信,则需要建立两个管道(一个读管道,一个写管道)
2)匿名管道只能用于父子进程或者兄弟进程间的通信,有名管道则不受这个限制
3)管道缓冲区的大小是有限制的
4)管道传输的是无格式的字节流,需要传输双方事先约定传输格式
5)管道也是一种文件,但这个文件只存在于内存中
管道创建:
pipe()函数
int pipe(int fd[2])
1.管道两端分别用fd[0]和fd[1]描述
2.管道两端是固定了任务的,即一端只能用来读,一端只能用来写,规定fd[0]为读端,fd[1]为写端
样例程序:建立一个子进程读,父进程写的匿名管道
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
using namespace std; #define input 0
#define output 1 int main()
{
int fd[2];
pid_t pid; pipe(fd);
pid=fork(); if(pid<0)
{
cout<<"fork error"<<endl;
return 0;
}else if(pid==0)
{
cout<<"in child process"<<endl;
close(fd[input]);
char str[]="hello,my parents";
write(fd[output],str,strlen(str));
exit(0);
}else
{
cout<<"in parent process"<<endl;
close(fd[output]);
char str[256];
int num=read(fd[input],str,sizeof(str));
str[num]='\0';
cout<<num<<" bytes,"<<str<<endl;
exit(0);
}
return 0;
}分析:要求子进程写,父进程读,那么关闭父进程的写端,打开父进程读端,关闭子进程的读端,关闭子进程写端,这样一条子进程写父进程读的匿名管道就建立好了,然后直接用read,write函数传递进行读写就好了
值得注意的是,匿名管道是存在于内核中的,而有名管道则是以FIFO的文件形式存在于文件系统中,这样即使是不具有亲缘关系的进程,只有可以访问文件系统,都可以进行通信
有名管道的特点:
1)可以使互不相关的两个进程实现彼此通信
2)该命名管道可以通过路径名来指出,并且在文件系统中是可见的,在建立了有名管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用方便
3)数据严格遵循FIFO先进先出原则
有名管道的创建:
int mkfifo(const char* pathname,mode_t mode)
第一个参数为路径名,也就是其创建后的名字
第二个参数为文件权限掩码,表示那些用户对此文件具有读写权限
样例程序:两个毫无关系的进程通过有名管道进行通信:
进程1:读端
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
using namespace std; #define P_FIFO "/tmp/p_fifo"
#define max_v 105 int main()
{
char str[max_v]; if(access(P_FIFO,F_OK)==0)//管道文件存在
{
execlp("rm","-f",P_FIFO,NULL);//删除
cout<<"the FIFO file have existed,delete it";
} if(mkfifo(P_FIFO,0777)<0)//创建命名管道,fifo文件
{
cout<<"create fifo file error"<<endl;
return 0;
} int fd=open(P_FIFO,O_RDONLY|O_NONBLOCK);//打开fifo文件 while(1)
{
memset(str,0,sizeof(str));//清空
int num=read(fd,str,max_v);//读取数据
if(num==0)
{
cout<<"no data"<<endl;
}else
{
str[num]='\0';
cout<<str<<endl;
}
sleep(1);//sleep 1s继续读
}
close(fd);//关闭fd
return 0;
}进程二:写端
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
using namespace std; #define P_FIFO "/tmp/p_fifo"
#define max_v 105 int main()
{ int fd=open(P_FIFO,O_WRONLY|O_NONBLOCK);//打开fifo文件
char str[]="give you data";
str[strlen(str)]='\n'; write(fd,str,strlen(str));//写入数据
sleep(1); close(fd);
return 0;
}分析:先运行读端程序,然后多次运行写端程序向命名管道中写入数据,写入的数据的读端的终端中会有显示
2.消息队列
定义:用于运行于同一台机器上的进程通信,和管道很相似,是在一个系统的内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现的
相关函数:
1)创建新消息队列或者取得已经存在的消息队列
int msgget(key_t key,int msgflag)
key:键值,一个公共的标识来标识一个通讯通道,当标识和消息队列绑定之后,则内核可以通过改标识找到对应的那个队列,key值可以由ftok函数生成
【函数ftok将一个已经存在的路径和一个整数标识转化为一个key键】
msgflag:
若msgflag=IPC_CREAT,若没有消息队列,则创建一个,若存在在返回原标识符
若msgflag=IPC_EXCL,若没有该队列,则返回-1.若已存在则返回0
2)向消息队列读取/写入消息
读取消息:
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,int msgflag)
写入消息
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflag)
字段含义:
# msqid:消息队列标识符
# msgp:指向消息缓冲区的指针,用来暂时存储和发送消息,是一个用户可定义的通用结构
struct msgstru
{
long type;//大于0
char mtext[512];
};# msgsz:消息的大小
# msgtyp:消息的形态,等于0则标识消息队列中所有的消息都会被读取
# msgflag:
1.等于0表示阻塞式接收/发送消息,没有该类型的消息/消息队列满了,那么一直阻塞等待
2.等于IPC_NOWAIT,在发送消息时若果队列满了,则不阻塞,直接返回-1,在接收消息时,如果消息队列为空,则不阻塞等待,直接返回-1
3)设置消息队列属性
int msgctl(int msgqid,int cmd,struct msqid_ds *buf)
# msqid:消息队列标识符
# cmd=IPC_STAT:获取消息队列对应的msqid_ds(消息队列属性)数据结构,并且将其存放到buf指定的看见中
# cmd=IPC_SET:设置消息队列的属性,要设置的属性放在buf中
# cmd=IPC_RMID:删除消息队列
样例程序:使用消息队列来进行两个进程间的数据传输
接收消息端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
using namespace std; #define max_v 512
struct msg_st
{
long type;//>0
char mtext[max_v];
}; int main()
{
int msgid=-1;
struct msg_st data;
long int msgtype=0; //建立消息队列
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
cout<<"msgget function faild with error"<<endl;
exit(EXIT_FAILURE);
} //从队列中获取消息,直到遇到end
while(1)
{
if(msgrcv(msgid,(void*)&data,max_v,msgtype,0)==-1)
{
cout<<"msgrcv failed with error"<<endl;
exit(EXIT_FAILURE);
} //打印一下
cout<<"you get message:"<<data.mtext<<endl; if(strncmp(data.mtext,"end",3)==0)
{
return 0;
}
} //删除消息队列
if(msgctl(msgid,IPC_RMID,0)==-1)
{
cout<<"msgctl IPC_RMID faild error"<<endl;
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}写入消息端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
using namespace std; #define max_v 512
struct msg_st
{
long type;//>0
char mtext[max_v];
}; int main()
{
struct msg_st data;
char buff[max_v];
int msgid=-1; //建立消息队列,若已经存在则直接获取
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
cout<<"msgget faild with error";
exit(EXIT_FAILURE);
} //向消息队列中写入信息,知道写入end
while(1)
{
cout<<"enter some text:"<<endl; //输入信息
fgets(buff,max_v,stdin);
data.type=1;
strcpy(data.mtext,buff); //写入信息
if(msgsnd(msgid,(void*)&data,max_v,0)==-1)
{
cout<<"msgsnd faild with error"<<endl;
exit(EXIT_FAILURE);
} //end跳出
if(strncmp(buff,"end",3)==0)
{
break;
}
sleep(1);
}
exit(EXIT_SUCCESS);
}和命名管道相比,消息队列的优势在于:
1)消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难
2)可以同时发通过发送消息来避免命名管道的同步和阻塞问题,而不需要进程自己提供同步方法
3)接收程序可以通过消息类型有选择的接收数据,而不是像命名管道那样只能默认接收
3.共享内存
定义:允许两个不相关的进程访问同一个逻辑内存
1.共享内存是两个正在运行的进程之间传递和共享数据的一种非常有效的方式
2.不同进程之间共享的内存通常安排在同一段物理内存中
3.进程可以将同一段共享内存链接到自己的地址空间中
4.共享内存没有提供同步机制!所以通过需要其他的机制来进行同步
1)创建共享内存:shmget函数
int shmget(key_t kry,int size,int flag)
# key:共享内存段的id
# size:共享内存的容量,单位字节
# flag:权限标志
成功则返回共享内存标识符,失败则返回-1
2)链接共享内存到自身的地址空间:shmat函数
void *shmat(int shmid,void *addr,int flag)
# shmid:共享存储标识符
# *addr和flag:决定以什么方式来确定链接的地址
成功则返回进程数据段所连接的实际地址
3)将共享内存从当前进程的地址空间分离:shmdt函数
int shmdt(const void *shmaddr);
# shmaddr:为shmat函数返回的地址指针
成功返回0,失败返回-1
样例程序:使用共享内存进行进程间通信
写入端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
using namespace std; #define max_v 2048
struct shared_use_st
{
int flag;//0代表可写,1代表可读
char text[max_v];
}; int main()
{
//创建共享内存
int shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
cout<<"shmget failed with error"<<endl;
exit(EXIT_FAILURE);
} void *share_memory=(void*)0; //将共享内存链接到本进程的地址空间
share_memory=shmat(shmid,(void *)0,0);
if(share_memory==(void*)-1)
{
cout<<"shmat failed with error"<<endl;
exit(EXIT_FAILURE);
}
//打印共享内存在进程数据段的地址
cout<<"memory attached at "<<(long)share_memory<<endl; struct shared_use_st *shared_stuff=(struct shared_use_st *)share_memory;
shared_stuff->flag=0;
char buff[max_v]; while(1)
{
//当前共享内存处于不可读写状态,等待
if(shared_stuff->flag==1)
{
sleep(1);
cout<<"wait for client..."<<endl;
} //输入
cout<<"exter some text"<<endl;
fgets(buff,max_v,stdin);
strncpy(shared_stuff->text,buff,max_v);
shared_stuff->flag=1; //遇到end跳出
if(strncmp(buff,"end",3)==0)
{
break;
}
} //将共享内存从本进程地址空间分离
if(shmdt(share_memory)==-1)
{
cout<<"shmdt failed with error"<<endl;
exit(EXIT_FAILURE);
} exit(EXIT_SUCCESS);
}读取端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
using namespace std; #define max_v 2048
struct shared_use_st
{
int flag;//0代表可写,1代表可读
char text[max_v];
}; int main()
{
//创建共享内存
int shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
cout<<"shmget failed with error"<<endl;
exit(EXIT_FAILURE);
} void *share_memory=(void*)0; //将共享内存链接到本进程的地址空间
share_memory=shmat(shmid,(void *)0,0);
if(share_memory==(void*)-1)
{
cout<<"shmat failed with error"<<endl;
exit(EXIT_FAILURE);
} //打印共享内存在进程数据段的地址
cout<<"memory attached at "<<(long)share_memory<<endl; struct shared_use_st *shared_stuff=(struct shared_use_st *)share_memory;
shared_stuff->flag=0; while(1)
{
//可写
if(shared_stuff->flag)
{
cout<<"you wrote :"<<shared_stuff->text<<endl;
sleep(1);
shared_stuff->flag=0; //遇到end跳出
if(strncmp(shared_stuff->text,"end",3)==0)
{
break;
}
}
} //将共享内存从本进程地址空间分离
if(shmdt(share_memory)==-1)
{
cout<<"shmdt failed with error"<<endl;
exit(EXIT_FAILURE);
} //删除共享内存
if(shmctl(shmid,IPC_RMID,0))
{
cout<<"shmctl IPC_RMID faild with error";
exit(EXIT_FAILURE);
} exit(EXIT_SUCCESS);
}共享内存优劣分析:
优点:
1.方便,函数接口简单
2.数据不用进行传送,可以之间访问内存,加快了效率
3.对通信的进程没有要求,不像匿名管道一样,要求父子/兄弟进程才能进行通信
缺点:
1.没有提供同步机制!导致我们在通信时需要通过其他机制来实现同步
四.信号量
用于多线程的信号量是POSIX信号量,而用于进程的信号量是SYSTEM_V信号量
相关函数
1)创建和打开信号量:semget函数
int semget(key_t key,int nsems,int semflag)
# key:键值
# nsems:创建信号量的个数,一旦创建了该信号量,就不能改变信号量的个数,只有不删除该信号量,就可以重新调用该函数创建该键值的信号量
# semflag:指定该信号量的读写权限
成功则返回信号量的标识符,失败则返回-1
2)改变信号量的值:semop函数
int semop(int semid,struct sembuf *sops,unsigned nsops);
# sem_id:信号量标识符
# *sops:指向存储信号操作结构的数组指针
# nsops:信号操作结构的数量,>=1
共享内存是进程间最快的通信方式,但是共享内存的同步问题共享内存无法解决,可以采用信号量解决共享内存的同步问题
在进程访问临界资源之前,需要测试信号量,如果为正数,则信号量-1并且进程可以进入临界区,若为非正数,则进程挂起放入等待队列,直至有进程退出临界区,释放资源并+1信号量,此时唤醒等待队列的进程。
样例程序:信号量+共享内存,共享内存解决进程间的通信,而信号量解决共享内存的同步问题
读取端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h> using namespace std; #define sem_key 4001
#define shm_key 5678 union semnu
{
int val;
}; int main()
{ //创建共享内存
int shmid=shmget(shm_key,sizeof(int),IPC_CREAT|0666);
if(shmid<0)
{
cout<<"create shm error";
return -1;
} //将共享内存链接到当前进程的地址空间
void *shmptr=shmat(shmid,NULL,0);
if(shmptr==(void*)-1)
{
cout<<"shmat error:"<<strerror(errno)<<endl;
return -1;
} int *data=(int*)shmptr;
int semid=semget(sem_key,2,0666);//创建信号量 union semnu s;
struct sembuf sbuf;
struct sembuf sb[1] = {{0, 1, 0}}; while(1)
{
//sleep(1);
cin>>*data;//往共享内存写入数据
semop(semid, sb, 1);//信号量+1
}
return 0;
}读取端:
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h> using namespace std; #define sem_key 4001
#define shm_key 5678 union semnu
{
int val;
}; int main()
{
//创建共享内存
int shmid=shmget(shm_key,sizeof(int),IPC_CREAT|0666);
if(shmid<0)
{
cout<<"create shm error";
return -1;
} //将共享内存链接到当前进程的地址空间
void *shmptr=shmat(shmid,NULL,0);
if(shmptr==(void*)-1)
{
cout<<"shmat error:"<<strerror(errno)<<endl;
return -1;
} int *data=(int*)shmptr;
int semid=semget(sem_key,2,IPC_CREAT|0666);//创建信号量 union semnu s; struct sembuf sbuf;
struct sembuf sb[1] = {{0, -1, 0}}; while(1)
{
semop(semid, sb, 1);//信号量减1,当信号量<0则会阻塞等待写入端写入数据,>0则输出数据
cout<<"the NUM="<<*data<<endl;
}
return 0;
}分析:信号负责共享内存的同步,而共享内存负责进程间的通信!将共享内存和信号量结合的方法非常好,因为共享内存是进程间通信最快的方式,而信号量又可以很好的解决共享内存的同步问题!
写入数据后,将信号量+1
将信号量-1,然后读取数据
因为信号量<0的话当前进程会阻塞
总结:进程间的四种通信方式总结完毕:管道(匿名管道和命名管道),消息队列,共享内存,信号量
【Linux】Linux下进程间的通信方式的更多相关文章
-
Linux下进程间管道通信小作业
在进行这次作业之前,我们先来看看什么是管道吧! 管道是Linux中很重要的一种通信方式,是把一个程序的输出直接连接到另一个程序的输入,常说的管道多是指无名管道,无名管道只能用于具有亲缘关系的进程之间, ...
-
Linux进程间的通信方式和原理
进程的概念 进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放.可以认为进程是一个程序的一次执行过程. 进程通信的概念 进程用户空间是 ...
-
linux进程间的通信方式
linux进程间的通信 进程间的通信就是不同的进程之间传播或交换信息,进程的用户空间是互相独立,进程之间可以利用系统空间交换信息. 管道 允许将一个进程的标准输出和另一个进程的标准输入连接在一起,主要 ...
-
linux信号量之进程间同步
概念 linux信号量: 允许多个线程同时进入临界区,可以用于进程间的同步. 和互斥锁(mutex)的区别: 互斥锁只允许一个线程进入临界区. 所在头文件: semaphore.h 主要函数 初始化函 ...
-
Linux环境下进程的CPU占用率
阿里云服务器网站:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=qqwovx6h 文字来源:http://www.s ...
-
Linux系统编程—进程间同步
我们知道,线程间同步有多种方式,比如:信号量.互斥量.读写锁,等等.那进程间如何实现同步呢?本文介绍两种方式:互斥量和文件锁. 互斥量mutex 我们已经知道了互斥量可以用于在线程间同步,但实际上,互 ...
-
Windows进程间各种通信方式浅谈
1 Windows进程间通信的各种方法 进程是装入内存并准备执行的程序,每个进程都有私有的虚拟地址空间,由代码.数据以及它可利用的系统资源(如文件.管道等)组成. 多进程/多线程是Windows操作系 ...
-
Windows进程间各种通信方式浅谈(转)
转自 https://blog.csdn.net/microzone/article/details/7044266 权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原 ...
-
Linux c 管道文件-进程间的通信 mkfifo、pipe
管道文件: 1. 创建管道mkfifo(命名管道) #include<sys/stat.h> int mkfifo( const char *pathname, mode_ ...
随机推荐
-
SQL Server技术问题之索引优缺点
索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息. 优点: 正确的索引会大大提高数据查询.对结果排序.分组的操作效率. 缺点: 1.存储空间,每个索引都要空间 ...
-
ORACLE函数详解【weber出品】
一.什么是函数 一个函数: 1. 是命名的PL/SQL块,必须返回一个值 2. 可以存储到数据库中重复执行 3. 可以作为表达式的一部分或者提供一个参数值 二.创建函数的语法 必须至少有一个返回值,创 ...
-
php批量删除,批量操作
批量删除多条记录,对于比较多的信息,如果没有批量删除功能是非常麻烦的. 1.从数据库中拿一张表过来,写个复选框进行选择 可以加全选复选框 连接数据库什么的都不写啦 代码: <form act ...
-
使用Git与Github创建自己的远程仓库
原因 早就想创建一个自己的远程仓库,方便发布到Nuget上,自己用也好,项目组用也好,都方便. 今天抽了个时间建了个仓库,随便记下溜方便后来的人. 流程 1,创建自己的GitHub仓库 首先需要到 G ...
-
ActiveMQ (一) 初识ActiveMQ
了解JMS JMS即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进 ...
-
Interface Comparable<;T>;
Interface Comparable<T> : 该接口对实现它的每个类的对象强加一个整体排序. 这个排序被称为类的自然排序 ,类的compareTo方法被称为其自然比较方法 . 参数 ...
-
jQuery Gantt Edit:(一)参数以及方法说明
jQuery Gantt editor jQuery Gantt Edit(以下简称GE)是一款开源的基于jQuery的甘特图插件,作者:robicch. GitHub地址:https://githu ...
-
Asp.Net_Mvc3.5语法_<;%%>;的用法
一. <%%>这种格式实际上就是和asp的用法一样的,只是asp中里面是vbscript或 者javascript代码,而在asp.net中用的是.net平台下支持的语言.特别 注意:服务 ...
-
hdu 1159 Common Subsequence (最长公共子序列 +代码)
Problem Description A subsequence of a given sequence is the given sequence with some elements (poss ...
-
会话跟踪session
会话跟踪 HTTP是“无状态”协议:客户程序每次读取Web页面,都打开到web服务器的单独的连接,而且,服务器也不自动维护客户的上下文信息.类似客户决定结账时,如何确定之前创建的购物车中哪个属于此客户 ...