管道又名匿名管道,这是一种最基本的IPC机制,由pipe函数创建:
#include
int pipe(int pipefd[2]);
调用pipe函数时在内核中开辟一块缓冲区用于通信,它有一个读端,一个写端:pipefd[0]指向管道的读端,pipefd[1]指向管道的写端。所以管道在用户程序看起来就像一个打开的文件,通过read(pipefd[0])或者write(pipefd[1])向这个文件读写数据,其实是在读写内核缓冲区。
一般使用管道的时候是使用fork来创建一个子进程,一个进程用于写,一个进程用于读
#include
#include
#include
#include
int main(){
int _pipe[2]={0,0};
int ret=pipe(_pipe); //创建管道
if(ret == -1){
perror("create pipe error");
return 1;
}
printf("_pipe[0] is %d,_pipe[1] is %d\n",_pipe[0],_pipe[1]);
pid_t id=fork(); //父进程fork子进程
if(id < 0){
perror("fork error");
return 2;
}
else if(id == 0){ //child,写
printf("child writing\n");
close(_pipe[0]);
int count=5;
const char *msg="i am from XATU";
while(count--){
write(_pipe[1],msg,strlen(msg));
sleep(1);
}
close(_pipe[1]);
exit(1);
}
else{ //father,读
printf("father reading\n");
close(_pipe[1]);
char msg[1024];
int count=5;
while(count--){
ssize_t s=read(_pipe[0],msg,sizeof(msg)-1);
if(s > 0){
msg[s]='\0';
printf("client# %s\n",msg);
}
else{
perror("read error");
exit(1);
}
}
if(waitpid(id,0,NULL) != -1){
printf("wait success\n");
}
}
return 0;
}
上一种进程间通信的方式是匿名的,所以只能用于具有亲缘关系的进程间通信,命名管道的出现正好解决了这个问题。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储文件系统中。命名管道是一个设备文件,因此即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。
命名管道的特点:
1.命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。所以当使用命名管道的时候必须先open将其打开。
2.命名管道可以用于任何两个进程之间的通信,不管这两个进程是不是父子进程,也不管这两个进程之间有没有关系。
server.c
#include
#include
#include
#include
#include
void testserver(){
int namepipe=mkfifo("myfifo",S_IFIFO|0666); //创建一个存取权限为0666的命名管道
if(namepipe == -1){
perror("mkfifo error");
exit(1);
}
int fd=open("./myfifo",O_RDWR); //打开该命名管道
if(fd == -1){
perror("open error");
exit(2);
}
char buf[1024];
while(1){
printf("sendto# ");
fflush(stdout); //清空缓存区
ssize_t s=read(0,buf,sizeof(buf)-1); //从标准输入获取消息
if(s > 0){
buf[s-1]='\0'; //过滤掉从标准输入中获取的换行
if(write(fd,buf,s) == -1){ //把该消息写入到命名管道中
perror("write error");
exit(3);
}
}
}
close(fd);
}
int main(){
testserver();
return 0;
}
client.c
#include
#include
#include
#include
#include
void testclient(){
int fd=open("./myfifo",O_RDWR);
if(fd == -1){
perror("open error");
exit(1);
}
char buf[1024];
while(1){
ssize_t s=read(fd,buf,sizeof(buf)-1);
if(s > 0){
printf("client# %s\n",buf);
}
else{ //读失败或者是读取到字符结尾
perror("read error");
exit(2);
}
}
close(fd);
}
int main(){
testclient();
return 0;
}
消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了一个数据结构,向消息队列中写数据,实际上是向这个数据结构中插入一个新结点;从消息队列汇总读数据,实际上是从这个数据结构中删除一个结点。消息队列也有管道一样的不足,就是每个数据块的最大长度是有上限的,系统上全体队列的最大总长度也有一个上限。
无论发送进程还是接收进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下
struct msgbuf {
long mtype; /* 消息的类型 */
char mtext[1]; /* 消息正文 */
};
可通过mtype区分数据类型,同过判断mtype,是否为需要接收的数据
mtext[]为存放消息正文的数组,可以根据消息的大小定义该数组的长度
#include
#include
#include
int msgget(key_t key, int msgflg);
key: 某个消息队列的名字
msgflg:由九个权限标志构成,用法和创建文件时使用的mode模式标志是一样的,这里举两个来说明
IPC_CREAT
如果消息队列对象不存在,则创建之,否则则进行打开操作
IPC_EXCL
如果消息对象不存在则创建之,否则产生一个错误并返回
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msg_ptr:是一个指针,指针指向准备发送的消息,
msg_sz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
如:
msgflg = IPC_NOWAIT 表示队列满不等待,返回EAGAIN错误
int msgrcv(int msgid, void *msg_ptr, size_t msgsz,long int msgtype, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msg_ptr是一个指针,指针指向准备接收的消息,
msgsz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
msgtype:它可以实现接收优先级的简单形式
msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
int msgctl(int msqid, int command, strcut msqid_ds *buf);
msqid: 由msgget函数返回的消息队列标识码
command:是将要采取的动作,(有三个可取值)分别如下
#include
#include
#include
#include
#include
#include
#include
#define MSGKEY 123
//消息的数据结构是以一个长整型成员变量开始的结构体
struct msgstru{
long msgtype;
char msgtext[2048];
};
int main(){
struct msgstru msgs;
char str[256];
int msg_type;
int ret_value;
int msqid;
int pid;
//检查消息队列是否存在
msqid = msgget(MSGKEY, IPC_EXCL);//(键名,权限)
if (msqid < 0){
//创建消息队列
msqid = msgget(MSGKEY, IPC_CREAT | 0666);
if (msqid <0){
printf("failed to create msq | errno=%d [%s]\n", errno, strerror(errno));
exit(-1);
}
}
pid = fork();//创建子进程
if (pid > 0){
//父进程
while (1){
printf("input message type:\n");//输入消息类型
scanf("%d", &msg_type);
if (msg_type == 0)
break;
printf("input message to be sent:\n");//输入消息信息
scanf("%s", str);
msgs.msgtype = msg_type;
strcpy(msgs.msgtext, str);
//发送消息队列(sizeof消息的长度,而不是整个结构体的长度)
ret_value = msgsnd(msqid, &msgs, sizeof(msgs.msgtext), IPC_NOWAIT);
if (ret_value < 0){
printf("msgsnd() write msg failed,errno=%d[%s]\n", errno, strerror(errno));
exit(-1);
}
}
}
else if (pid == 0){
//子进程
while (1){
msg_type = 1;//接收的消息类型为1
msgs.msgtype = msg_type;
//发送消息队列(sizeof消息的长度,而不是整个结构体的长度)
ret_value = msgrcv(msqid, &msgs, sizeof(msgs.msgtext), msgs.msgtype, IPC_NOWAIT);
if (ret_value > 0){
printf("read msg:%s\n", msgs.msgtext);
}
}
}
else{
printf("fork error\n");
//删除消息队列
msgctl(msqid, IPC_RMID, 0);
exit(1);
}
return 0;
}
转载自:进程间的通信方式(一):共享内存 - 围城的文章 - 知乎
https://zhuanlan.zhihu.com/p/37808566
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
上图描述的内容一样,共享内存实际上就是进程通过调用shmget(Shared Memory GET 获取共享内存)来分配一个共享内存块,然后每个进程通过shmat(Shared Memory Attach 绑定到共享内存块),将进程的逻辑虚拟地址空间指向共享内存块中。 随后需要访问这个共享内存块的进程都必须将这个共享内存绑定到自己的地址空间中去。当一个进程往一个共享内存快中写入了数据,共享这个内存区域的所有进程就可用都看到其中的内容。
进程通过调用shmget(Shared Memory GET,获取共享内存)来分配一个共享内存块。
int shmget(key_t key, size_t size,int shmflg);
key:进程间通信键值,ftok() 的返回值。
size:该共享存储段的长度(字节)。
shmflg:标识函数的行为及共享内存的权限
返回值:
成功:共享内存标识符。
失败:-1。
要让一个进程获取对一块共享内存的访问,这个进程必须先调用 shmat(SHared Memory Attach,绑定到共享内存)。将 shmget 返回的共享内存标识符 SHMID 传递给这个函数作为第一个参数。该函数的第二个参数是一个指针,指向您希望用于映射该共享内存块的进程虚拟内存地址;如果您指定NULL则Linux会自动选择一个合适的地址用于映射。第三个参数是一个标志位
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:共享内存标识符,shmget() 的返回值。
shmaddr:共享内存映射地址(若为 NULL 则由系统自动指定),推荐使用 NULL。
shmflg:共享内存段的访问权限和映射条件( 通常为 0 ),具体取值如下:
0:共享内存具有可读可写权限。
SHM_RDONLY:只读。
SHM_RND:(shmaddr 非空时才有效)
返回值:
成功:共享内存段映射地址( 相当于这个指针就指向此共享内存 )
失败:-1
int shmdt(const void *shmaddr);
shmaddr:共享内存映射地址。
返回值:
成功:0
失败:-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享内存标识符。
cmd:函数功能的控制,其取值如下:
IPC_RMID:删除。(常用 )
IPC_SET:设置 shmid_ds 参数,相当于把共享内存原来的属性值替换为 buf 里的属性值。
IPC_STAT:保存 shmid_ds 参数,把共享内存原来的属性值备份到 buf 里。
SHM_LOCK:锁定共享内存段( 超级用户 )。
SHM_UNLOCK:解锁共享内存段。
SHM_LOCK 用于锁定内存,禁止内存交换。并不代表共享内存被锁定后禁止其它进程访问。其真正的意义是:被锁定的内存不允许被交换到虚拟内存中。这样做的优势在于让共享内存一直处于内存中,从而提高程序性能。
buf:shmid_ds 数据类型的地址(具体类型请点此链接 ),用来存放或修改共享内存的属性。
返回值:
成功:0
失败:-1
做这么一个例子:创建两个进程,在 A 进程中创建一个共享内存,并向其写入数据,通过 B 进程从共享内存中读取数据。
写端代码如下:
#include
#include
#include
#include
#include
#include
#include
#define BUFSZ 512
int main(int argc, char *argv[])
{
int shmid;
int ret;
key_t key;
char *shmadd;
//创建key值
key = ftok("../", 2015);
if(key == -1)
{
perror("ftok");
}
//创建共享内存
shmid = shmget(key, BUFSZ, IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
exit(-1);
}
//映射
shmadd = shmat(shmid, NULL, 0);
if(shmadd < 0)
{
perror("shmat");
_exit(-1);
}
//拷贝数据至共享内存区
printf("copy data to shared-memory\n");
bzero(shmadd, BUFSZ); // 共享内存清空
strcpy(shmadd, "how are you, lh\n");
return 0;
}
读端代码如下:
#include
#include
#include
#include
#include
#include
#include
#define BUFSZ 512
int main(int argc, char *argv[])
{
int shmid;
int ret;
key_t key;
char *shmadd;
//创建key值
key = ftok("../", 2015);
if(key == -1)
{
perror("ftok");
}
system("ipcs -m"); //查看共享内存
//打开共享内存
shmid = shmget(key, BUFSZ, IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
exit(-1);
}
//映射
shmadd = shmat(shmid, NULL, 0);
if(shmadd < 0)
{
perror("shmat");
exit(-1);
}
//读共享内存区数据
printf("data = [%s]\n", shmadd);
//分离共享内存和当前进程
ret = shmdt(shmadd);
if(ret < 0)
{
perror("shmdt");
exit(1);
}
else
{
printf("deleted shared-memory\n");
}
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
system("ipcs -m"); //查看共享内存
return 0;
}