欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

进程间通信(一)

程序员文章站 2022-05-08 17:38:03
...

进程间通信的目的

1、数据传输
一个进程将数据发送给另一个进程
2、资源共享
多个进程之间共享相同的资源
3、通知事件
第一个进程需要向另一个进程发送消息,通知发生的事件
4、进程控制
有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够知道它的状态改变

进程间通信的本质就是两个互不相干的进程看到同一份资源,该资源一定是操作系统提供的

进程间通信分类

管道

* 匿名管道
* 命名管道

System V IPC

* System V 消息队列
* System V 共享内存
* System V 信号量

POSIX IPC

* 消息队列
* 共享内存
* 信号量
* 互斥量
* 条件变量
* 读写锁

一、管道

本质上,管道也是一种文件,我们把一个进程连接到另一个进程的数据流称为管道

*匿名管道*
具有亲缘关系的进程进行数据间通信
我们创建一个子进程,通过管道实现子进程写的数据父进程可以读出来

1、父进程创建管道
进程间通信(一)
2、父进程fork出子进程
进程间通信(一)
3、父进程关闭写端、子进程关闭读端
进程间通信(一)
代码实现:
进程间通信(一)
匿名管道特点:
1、只能用于具有亲缘关系的进程
2、进程退出,管道释放,所以管道的生命周期随进程
3、内核会对管道操作进行同步与互斥(管道自带同步 )
4、单向传输,半双工的
5、管道是基于字节流的

四种情况:
* 写段关闭,读端一直在读,直到0值
进程间通信(一)
进程间通信(一)
* 写端不写也不关闭,读端一直在读
进程间通信(一)
进程间通信(一)
* 写端一直写,读端不读,管道满,等待读
* 写端一直写,读端关闭,写端被系统终止

命名管道
可以用于没有亲缘关系的进程
创建一个命名管道
方法一:
进程间通信(一)
方法二:
进程间通信(一)
进程间通信(一)

匿名管道与命名管道的区别:

* 匿名管道由pipe函数创建并打开
* 命令管道由mkfifo函数创建,打开用open
* 匿名管道与匿名管道的唯一区别在于它们创建与打开方式不同

用命名管道实现客户端与服务器端之间的通信
server.c(服务器端)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define ERR_EXIT(m) \
    do{ \
        perror(m); \
        exit(EXIT_FAILURE); \
    }while(0)

 int main()
 {
     umask(0);
     if(mkfifo("ppipe",0644) == -1)
     {
         ERR_EXIT("mkfifo");
     }

     int fd = open("ppipe",O_RDONLY);
     if(fd == -1){
         ERR_EXIT("open");
     }

     char buf[1024];
     while(1){
         buf[0] = 0;
         ssize_t s = read(fd, buf, sizeof(buf)-1);
         if(s > 0){
             buf[s-1] = 0;
             printf("client : %s\n",buf);
         }else if(s == 0){
             printf("client quit,exit now\n");
         }else{
             ERR_EXIT("read");
         }
     }

     close(fd);
     return 0;
 }

client.c(客户端)

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define ERR_EXIT(m) \
     do{ \
         perror(m); \
         exit(EXIT_FAILURE); \
     }while(0)

 int main()
 {
     int fd = open("ppipe",O_WRONLY);
     if(fd == -1)
     {
         ERR_EXIT("open");
     }                                                                                                                                          22
     char buf[1024];
     while(1){
         buf[0] = 0;
         printf("Please Enter:");
         fflush(stdout);
         ssize_t s= read(0, buf, sizeof(buf)-1);
         if(s > 0){
             buf[s] = 0;
             write(fd, buf, strlen(buf));
         }else if(s <= 0){
             ERR_EXIT("read");
         }
     }

     close(fd);
     return 0;

 }

打开两个终端,客户端输入一条信息,服务器端接受到信息后在屏幕上显示。
进程间通信(一)
进程间通信(一)

二、消息队列

* 一个进程向另一个进程发送有类型数据块的方法
* 可以实现相互通信的功能
* 进程的生命周期随内核
* 消息队列是基于消息的

系统为每一个IPC对象保存一个ipc_perm结构体,该结构体说明了IPC对象的权限和所有者,每一个版本的内核有不同的ipc_perm结构成员,最重要的是key成员,表示队列的编号。
进程间通信(一)
消息队列结构
从第一个成员,我们可以知道,消息队列中也含有队列的编号。
进程间通信(一)
因为消息队列的生命周期随内核,所以我们需要手动删除一个消息队列
ipcs -q:显示IPC资源
ipcrm -q [消息队列的key值]:删除IPC资源

要想实现一个消息队列,首先认识一些函数
1、msgget:创建和访问一个消息队列
第二个参数:
IPC_CREAT|IPC_EXCL:如果没有该消息队列,则创建一个;如果有,则出错返回。如此创建的消息队列是全新的。
进程间通信(一)
2、ftok函数
说明:
* ftok函数根据路径名,提取文件信息,再根据这些文件信息及proj_id合成key,该路径可以随便设置
* pathname必须存在,和文件的权限无关
* proj_id是随意设置的,在UNIX系统上,取值为1~255
进程间通信(一)
3、msgsnd函数:把一条消息添加到消息队列中
msgrcv函数:从一个消息队列接受消息
进程间通信(一)

代码实现:
makefile

.PHONY:all
 all:client server

 client:client.c comm.c
         gcc -o [email protected] $^

 server:server.c comm.c
         gcc -o [email protected] $^

 .PHONY:clean
 clean:
         rm -f client server

comm.h

#ifndef __COMM__H_
#define __COMM__H_

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

 #define PATHNAME "."
 #define PROJ_ID 0x664

 #define SERVER_TYPE 0                                                     
 #define CLIENT_TYPE 200

 struct msgbuf{
     long mtype;
     char mtext[1024];
 };

 int createMsgQueue();
 int getMsgQueue();
 int destroyMsgQueue(int msgid);
 int sendMsg(int msgid, int who, char *msg);
 int recvMsg(int msgid, int recvType, char out[]);


 #endif //_COMM_H_  

comm.c

#include"comm.h"                                                         
static int commMsgQueue(int flags)
{
    key_t _key=ftok(PATHNAME, PROJ_ID);
    if(_key < 0){
        perror("ftok");
        return -1;
    }
    int msgid = msgget(_key,flags);
     if(msgid < 0){
         perror("msgget");
     }

     return msgid;
 }

 int createMsgQueue()
 {
     return commMsgQueue(IPC_CREAT|IPC_EXCL|0x664);
 }

 int getMsgQueue()
 {
     return commMsgQueue(IPC_CREAT);
 }

 int destoryMsgQueue(int msgid)
 {
     if(msgctl(msgid, IPC_RMID, NULL)<0){
         perror("msgctl");
         return -1;
     }
     return 0;
 }

 int sendMsg(int msgid, int who,char *msg)
 {
     struct msgbuf buf;
     buf.mtype = who;
     strcpy(buf.mtext,msg);

     if(msgsnd(msgid, (void*)&buf,sizeof(buf.mtext),0)<0){
         perror("msgsnd");
         return -1;
     }
     return 0;
 }

 int recvMsg(int msgid, int recvType, char out[])
 {
     struct msgbuf buf;
     if(msgrcv(msgid,(void*)&buf, sizeof(buf.mtext),recvType, 0)<0){
         perror("msgrcv");
         return -1;
     }
     strcpy(out,buf.mtext);
     return 0;
 }

client.c

#include "comm.h"

int main()
{
    int msgid = getMsgQueue();

    char buf[1024];
    while(1){
        buf[0] = 0;
        printf("Please Enter:>");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf));
        if(s>0){
            buf[s-1] = 0;
            sendMsg(msgid, CLIENT_TYPE, buf);
            printf("send done,wait...\n");
        }

        recvMsg(msgid, SERVER_TYPE, buf);
        printf("server:>%s\n", buf);
    }
    return 0;
 }

server.c

#include "comm.h"

 int main()
 {
     int msgid = createMsgQueue();

     char buf[1024];
     while(1){
         buf[0] = 0;
         recvMsg(msgid, CLIENT_TYPE, buf);
         printf("client:>%s\n",buf);

         printf("Please Enter:>");
         fflush(stdout);
         ssize_t s = read(0, buf,sizeof(buf));
         if(s > 0){
             buf[s-1] = 0;
             sendMsg(msgid, SERVER_TYPE, buf);
             printf("send done, wait...\n");
         }
     }

     destoryMsgQueue(msgid);      
     return 0;
 }   

运行后,可以看到实现了进程间消息的互相传输。
进程间通信(一)
进程间通信(一)
可以查看当前消息队列的信息,以及删除消息队列
进程间通信(一)
共享内存和信号量会在接下来的博客中介绍到,谢谢关注哦!