进程间通信——管道,消息队列,共享内存

 
  

 
 

进程间通信的本质是让两个不相干的进程看到同一份资源。这个资源是由操作系统提供的一个文件。


进程间通信的目的

1.数据传输:一个进程需要将它 的数据发送给另一个进程。

2.资源共享:多个进程之间共享同样的资源。

3.通知事件:一个进程需要向另一个(组)进程发送消息,通知它们发生了某种事件。(进程终止时通知父进程)

4.进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有异常,并能够及时知道它的状态改变。

在这里我主要介绍三种进程间通信的方式:管道,消息队列,共享内存

管道: 通过fork()创建出子进程,父子进程共享资源,关闭读/写端以达到单向传输。

  管道分为匿名管道和命名管道。

匿名管道

#include<unistd.h>
功能:创建一无名管道
int pipe(int fd[2]);

fd:文件描述符数组。fd[0]表示读端,fd[1]表示写端。fd[1]的输出是fd[1]的输入。

通常,进程会先调用pipe创建管道,接着调用fork创建子进程,从而创建父进程到子进程的IPC通道。

fork之后做什么取决于我们想要的数据流方向。对于父进程到子进程的管道,若父进程写入,子进程读出,则父进程关闭读端fd[0],子进程关闭写端fd[1]。

对于管道的一端被关闭,有两条规则可以起作用:

(1).当read(读)一个写端已被关闭的管道时,在所有的数据都被读取后,read返回0,表示文件结束。

(2).如果write(写)一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE。


特点1.只能用于具有亲缘关系的进程之间的通信。

           2.管道在通信的时候基于字节流传递。

           3.管道的生命周期随进程。

           4.管道自带同步机制(进程按照某种顺序访问临界资源)。

            5.半双工通信(单向通信)。

下为一个从键盘读取数据,写入管道,读取管道,写到屏幕的例子。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

int main()
{
  int fds[2];
  char buf[100];
  int len;

  if(pipe(fds) == -1)
    perror("make pipe"),exit(1);

  while(fgets(buf,100,stdin)){
     len = strlen(buf);

     if(write(fds[1],buf,len)!=len){
        perror("write to pipe");
        break;
    }
   memset(buf,0x00,sizeof(buf));

   if((len = read(fds[0],buf,100))==-1){
       perror("read from pipe");
       break;
     }
    if(write(1,buf,len)!=len) {
       perror("write to stdout");
       break;
     }
  }
}



命名管道

如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来完成。它被叫做命名管道。

命名管道可以从命令行上创建。

$ mkfifo filename
也可以从程序里创建。
int mkfifo(const char* filename,mode_t mode);

匿名管道和命名管道最大的区别是:匿名管道必须是有亲缘关系才能通信,而命名管道可以在两个不相干的管道之间通信。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

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

int main(int argc,char* argv[])
{
  mkfifo("tp",0644);
  int info;
  infd = open("abc",O_RDONLY);
  if(infd = =1) ERR_EXIT("open");

  int outfd;
  outfd = open("tp",O_ERONLY);
  if(outfd == -1)   ERR_EXIT("open");

  char buf[1024];
  int n;
  while((n = read(infd,buf,1024))>0){
     write(outfd,buf,n);
  }

 close(infd);
 close(outfd);
 return 0;
}

上图是读取文件,写入命名管道的例子:

消息队列:

消息队列提供一个从一个进程向另一个进程发送一个有数据类型的数据块的方法。

消息队列是消息的链接表,存储在内核中,由消息队列标识符标识。

消息队列的生命周期随内核。

消息队列函数:

功能:创建和返回一个消息队列
原型:
int msgget(key_t key,int msgflg);
参数:
key:某个消息队列的名字
msgflg:由九个权限标志组成。
返回值:成功返回一个非负整数,即该消息队列的标识码。失败返回-1.
功能:消息队列的控制函数(增,删,查,改)
原型:
int msgctl(int msqid,int cmd,struct msqid_ds* buf)
参数:
msqid:由msgget函数返回的消息队列标识码
cmd:将要采取的动作。有三个可取。
返回值:成功返回0,失败返回-1

功能:从一个消息队列中接收消息
ssize_t msgrcv(int msqid,void* msgp,sizt_t msgsz,long msgtyp,int msgflg);
返回值:
成功返回实际放到接收缓冲区里去的字符个数,失败返回-1.

实例代码:

makefile:

.PHONY:all

all:client server

client:comm.c client.c
        gcc -o $@ $^
server:comm.c server.c
        gcc -o $@ $^

.PHONY:clean
clean:
        rm -f client server

comm.h:

#ifndef __COM_H__
#define __COM_H__

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

#define PATHNAME "."
#define PROJ_ID 0x6666

#define SERVER_TYPE 1
#define CLIENT_TYPE 2

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

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

#endif

comm.c:

#include"comm.h"

int commMsgQueue(int flags)
{
  key_t _key = ftok(".",0x6666);
  if(_key < 0 ){
    perror("ftok");
    return -1;
  }

  //int msgid = msgget(_key,IPC_CREAT|IPC_EXCL);
    int msgid = msgget(_key,flags);
    if(msgid < 0){
    perror("msgget");
  }
 return msgid;
}


int createMsgQueue()
{
  return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int getMsgQueue()
{
  return commMsgQueue(IPC_CREAT);
}


int destroyMsgQueue(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;
}

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 down,wait recv..\n");
     }
  }
 destroyMsgQueue(msgid);
 return 0;
#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 down,wait recv..\n");
     }
  }
 destroyMsgQueue(msgid);
 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 recv..\n");
     }
    recvMsg(msgid,SERVER_TYPE,buf);
    printf("server:%s\n",buf);
  }
 return 0;
}


 共享内存:

共享内存是最快的IPC形式。它是在物理内存上开辟一块空间让两个进程看到一份公共的资源。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话是说进程不再通过执行进入内核的系统调用来传递彼此的数据。

1.共享内存的效率高是因为避免了来回拷贝。

2.生命周期随内核,需手动删除。

3.共享内存没有同步与互斥机制,要使用必须自己实现互斥和同步。

共享内存函数:

shmget函数:
 功能:创建共享内存
 原型:
   int shmget(key_t key,size_t size,int shmflg);
返回值:成功返回一个非负整数,即该共享内存段的标识码。失败返回-1.

shmat函数:
 功能:将共享内存段连接到进程地址空间。g关联。
 原型:
  void* shmat(int shmid,const void* shmaddr,int shmflg);
 返回值:
  成功返回一个指针,指向共享内存第一节。失败返回-1.

shmdt:
  功能:将共享内存段与当前进程脱离。去关联
  原型:
  int shmdt(const void* shmaddr);
  返回值:成功返回0,失败返回-1.

shmctl函数:
  功能:控制共享内存。
  原型:
  ing shmctl(int shmid,int cmd,struct shmid_ds* buf);
  返回值:成功返回0,失败返回-1.
实例代码:

makefile:

.PHONY:all
all:server client

client:client.c comm.c
        gcc -o $@ $^
server:server.c comm.c
        gcc -o $@ $^
.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/shm.h>


int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);


#endif

comm.c:

#include"comm.h"

int commShm(int size,int flags)
{
  key_t _key = ftok(".",0x6666);
  if(_key < 0 ){
     perror("ftok");
     return -1;
  }
 int shmid = 0;
 if((shmid = shmget(_key,size,flags)) < 0){
     perror("shmget");
     return -2;
  }
 return shmid;
}

int destroyShm(int shmid)
{
  if(shmctl(shmid,IPC_RMID,NULL) < 0){
     perror("shmctl");
     return -1;
  }
 return 0;
}
int createShm(int size)
{
  return commShm(size,IPC_CREAT|IPC_EXCL|0666);
}

int getShm(int size)
{
  return commShm(size,IPC_CREAT);
}

server.c:

#include"comm.h"

int main()
{
  int shmid = createShm(4096);

  char* addr = shmat(shmid,NULL,0);
  sleep(2);
  int i=0;
  while(i++ < 26){
    printf("client: %s\n",addr);
    sleep(1);
  }
 shmdt(addr);
 sleep(2);
 destroyShm(shmid);
 return 0;
}

client.c:

#include"comm.h"

int main()
{
  int shmid = getShm(4096);
  sleep(1);
  char* addr = shmat(shmid,NULL,0);
  sleep(2);
  int i=0;
  while(i<26){
    addr[i] = 'A'+i;
    i++;
    addr[i] = 0;
    sleep(1);
  }
 shmdt(addr);
 sleep(2);
 return 0;
}


值得注意的是,ctrl+c终止进程,再次启动后,需要ipcs -m查看共享内存。再用ipcrm -m shmid删掉共享内存id。

猜你喜欢

转载自blog.csdn.net/weixin_36229332/article/details/79642003