linux c++(进程间的通讯方式)

IPC通信的方式有几种

常见的通信方式:单工,半双工,全双工

管道:半双工通信
优点: 简单
缺点: 
      只能单向通信,双向通信需要建立两个管道,
      只能用于父与子,兄弟间通信,该问题后来使用fifo有名管道解决    
管道函数:
int pipe(int pipefd[2]);
	pipefd 读写文件描述符, 0 代表读。1 代表写
	返回值: 失败返回 1 成功返回 0
使用前提前规划好,那个要读,那个要写,关闭不用的一端
读管道:
	写端全部关闭 --read读到0 ,相当于读到文件末尾
	写端没有全部关闭
		有数据---read	读到数据
		没有数据---read 阻塞	fcntl函数可以更改非阻塞
写管道:
	读端全部关闭--- ?产生一个信号SIGPIPE,程序一场终止
	读端未全部关闭
		管道已满 ---write阻塞
		管道未满 ---write正常写入


管道示例

int main()
{
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    if(pid == 0)
    {   
        //son
        close(fd[0]);
        write(fd[1],"hello",5);

    }else if(pid > 0)
    {   
        char buf[12]={0};
        close(fd[1]);
        read(fd[0],buf,sizeof(buf));
        printf("pipe  = %s\n",buf);
    }   
    return 0;
}


有名管道 [实现无血缘关系进程通信]

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
    pid_t pid = 0;
    if((pid=fork()) < 0)
    {   
        perror("fork");
    }else if(pid == 0)
    {   
        // 子进程
        mkfifo("my_fifo",0666);
        int fd = open("my_fifo",O_RDONLY);
        while(1)
        {   
        sleep(1);
        char recv[100] = {0};
        read(fd,recv,sizeof(recv));
        printf("read form my_fifo buf=%s\n",recv);
        }   
    }else{
        mkfifo("my_fifo",0666);
        int fd = open("my_fifo",O_WRONLY);
        while(1)
        {   
        char send[100] = "Hello MiKe";
        write(fd,send,strlen(send));
        sleep(2);
        }   
        // 父进程
    }   
    return 0;                                                                                                            
}

mmap内存映射简单介绍

/dev/zero 可以随意映射

addr:	传NULL
length:	映射区的长度
prot:
	PROT_READ 可读
	PROT_WRITE 可写
flags:
	MAP_SHARED 共享的,对内存的修改会影响原文件 MAP_SHARED|MAP_ANONYMOUS(或者MAP_ANON)匿名映射这两个宏有些unix系统没有
	MAP_PRIVATE 私有的
fd:	文件描述符,open打开一个文件,`文件必须有大小`
offset:	偏移量0或着4K的整数倍
返回值:
	成功 返回可用的内vu你首地址
	失败	返回MAP_FAILED
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

释放映射区
addr:	传mmap的返回值
leng:	mmap创建的长度
返回值:
	成功 0
	失败 -1
int munmap(void *addr, size_t length);


内存映射区 [实现无血缘关系进程通信]

int main()
{
    int *mem = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    if(mem == MAP_FAILED)
    {
        perror("man err");
        return -1; 
    }
    pid_t pid = 0;
    pid=fork();
    if(pid == 0)
    {
     //son
     *mem = 101;
     printf("child,*mem = %d\n",*mem);
     sleep(3);
     printf("child,*mem = %d\n",*mem);
    }else if(pid > 0)
    {
    sleep(1);
    *mem=199;
    printf("father,*mem = %d\n",*mem);
    *mem = 1000;
    printf("father,*mem = %d\n",*mem);
    wait(NULL);
    }
    munmap(mem,4);
    return 0;
}
+ 1.如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗?
  - 不能
+ 2.如果对mem越界操作会怎么样?
   - 文件的大小对应社区操作有影响,尽量避免
 
+ 3.如果文件偏移量随便填个数会怎么样?
   - offset必须是4K的整数倍
 
+ 4.如果文件描述符先关闭,对mmap映射有没有影响?
   - 没有影响
 
+ 5.open的时候,可以新创建一个文件来创建映射区吗?
   - 不可以用大小为0的文件,给文件设置大小ftruncate(fd,offset);
 
+ 6.open文件选择O_WRONLY,可以吗? 
   - 不可以,没有权限
 
+ 7.当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?
   - 不可以,没有权限,SHARED的时候映射区的权限 <= open文件的权限
+ 8.mmap什么情况下会报错? 
   - 以上情况都会报错
成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],
error被设为以下的某个值:
 1 EACCES:访问出错
 2 EAGAIN:文件已被锁定,或者太多的内存已被锁定
 3 EBADF:fd不是有效的文件描述词
 4 EINVAL:一个或者多个参数无效
 5 ENFILE:已达到系统对打开文件的限制
 6 ENODEV:指定文件所在的文件系统不支持内存映射
 7 ENOMEM:内存不足,或者进程已超出最大内存映射数量
 8 EPERM:权能不足,操作不允许
 9 ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
10 SIGSEGV:试着向只读区写入
11 SIGBUS:试着访问不属于进程的内存区

进阶精华


共享内存 [实现无血缘关系进程通信]

#include <iostream>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
    pid_t pid = 0;
    if((pid=fork()) < 0)
    {   
        perror("fork");
    }else if(pid == 0)
    {   
        // 创建共享内存
        int shmid = shmget(100,0,0);
        // 和当前进程关联
        void* ptr = shmat(shmid,NULL,0);
        while(1)
        {   
            printf("child = %s\n",(char*)ptr);
            sleep(1);
        }   
        // 解除关联
        shmdt(ptr);
        // 删除共享内存
        shmctl(shmid,IPC_RMID,NULL);
        // 子进程
    }else{
        // 创建共享内存
        int shmid = shmget(100,4096,IPC_CREAT|0664);
        // 和当前进程关联
        void* ptr = shmat(shmid,NULL,0);
        while(1)
        {   
            std::string str= "这是一块共享内存";
            memcpy(ptr,str.c_str(),str.size());
            sleep(2);
        }
        // 解除关联                                                                                                      
        shmdt(ptr);
        // 删除共享内存
        shmctl(shmid,IPC_RMID,NULL);
        // 父进程
    }   
    return 0;
}
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

在以下两种情况下,msgget将创建一个新的消息队列:

1.如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
2.key参数为IPC_PRIVATE。

函数msgrcv在读取消息队列时,type参数有下面几种情况:
type == 0,返回队列中的第一个消息;
type > 0,返回队列中消息类型为 type 的第一个消息;
type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值


消息队列

  #include <iostream>                                                                         
  #include <unistd.h>
  #include <sys/msg.h>

  const std::string FILE_MSG = "/dev/null";
  // 消息结构
  struct msg_form
  {   
      long mtype;   
      char mtext[256];
  };
  int main()
  {
      pid_t pid = 0;
      if((pid=fork()) < 0)
      {
          perror("fork");
      }else if(pid == 0)
      {
          int msqid;
          key_t key;
          struct msg_form msg;
          // 获取key值
          if((key = ftok(FILE_MSG.c_str(),'z')) < 0)
          {
              perror("ftok error");
              exit(1);
          }
          // 打开消息队列
          if((msqid = msgget(key,IPC_CREAT|0777)) == -1)
          {
              perror("msgget error");
              exit(1);
          }
          // 打印消息队列ID及进程ID
          printf("MyClient msqid is: %d\n",msqid);
          printf("MyClient pid is: %d\n",getpid());
          // 添加消息,类型为888
          for(;;)
          {   
          msg.mtype = 888;
          sprintf(msg.mtext,"hello,I'm client %d",getpid());
          msgsnd(msqid,&msg,sizeof(msg.mtext),0);
              
          msgrcv(msqid,&msg,256,999,0);
         printf("Client: receive msg.mtype is: %d\n",msg.mtype);
          printf("Client: receive msg.mtype is: %s\n",msg.mtext);
          sleep(2);
          }   
          // 子进程
      }else{
          int msqid;
          key_t key;
          struct msg_form msg;
          // 获取key值
          if((key = ftok(FILE_MSG.c_str(),'z')) < 0)
          {   
              perror("ftok error");
              exit(1);
          }   
          // 创建消息队列
          if((msqid = msgget(key,IPC_CREAT|0777)) == -1) 
          {   
              perror("msgget error");
              exit(1);
          }   
          // 打印消息队列ID及进程ID
          printf("MyServer msqid is: %d\n",msqid);
          printf("MyServer pid is: %d\n",getpid());
          // 循环读取消息
          for (;;)
          {   
              msgrcv(msqid,&msg,256,888,0);
              printf("Server: receive msg.mtext is: %s\n",msg.mtext);
             printf("Server: receive msg.mtype is: %d\n",msg.mtype);
  
              msg.mtype = 999;
              sprintf(msg.mtext,"hello,I'm server %d",getpid());
              msgsnd(msqid,&msg,sizeof(msg.mtext),0);
          }   
          // 父进程
      }   
      return 0;                                                                               
  }


消息队列

共享内存

  • 共享内存用来传递数据;
  • 信号量用来同步;
  • 消息队列用来 在客户端修改了共享内存后 通知服务器读取。

server.cpp

#include <iostream>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>                                                                                               
#include <sys/shm.h>
#include <sys/msg.h>

const std::string __MSG_FILE__ = "/dev/null";

sem_t blank,xfull;

struct msg_form{
    long mtype;
    char mtext;
};

int main()
{
    struct msg_form msg; // 消息队列用于通知对方更新了共享内存
    // 获取key值
    key_t key;
    if((key = ftok(__MSG_FILE__.c_str(),'z')) < 0)
    {   
        perror("ftok error");
        exit(1);
    }   
    // 创建共享内存
    int shmid;
    if((shmid = shmget(key,4096,IPC_CREAT|0664)) == -1) 
    {   
        perror("ftok error");
        exit(1);
    }   
    // 和当前进程关联
    char* shm;
    shm = (char*)shmat(shmid,0,0);
    if((int)*shm == -1) 
    {   
        perror("Attach Shared Memory Error");
        exit(1);
    }   

    // 创建消息队列
    int msqid;
    if((msqid = msgget(key,IPC_CREAT|0777)) == -1) 
    {
        perror("msgget error");
        exit(1);
    }

    // 创建信号量
    sem_init(&blank,0,1);
    sem_init(&xfull,0,0);

    // 读数据
    while(1)
    {
            sem_wait(&blank);
        msgrcv(msqid,&msg,1,888,0);
            sem_post(&xfull);
        if(msg.mtext == 'q')
            break;
        else if(msg.mtext == 'r')
        {
            sem_wait(&xfull);
            printf("%s\n",shm);
            sem_post(&blank);
        }
    }
    
    // 解除关联
    shmdt(shm);
    // 删除共享内存,消息队列
    shmctl(shmid,IPC_RMID,NULL);
    msgctl(msqid,IPC_RMID,NULL);
    sem_destroy(&blank);
    sem_destroy(&xfull);

    return 0;
}   

client.cpp

#include <iostream>                                                                                                
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
        
const std::string __MSG_FILE__ = "/dev/null";
        
sem_t blank,xfull; 
            
struct msg_form{
    long mtype; 
    char mtext; 
};              
                
int main()      
{               
    struct msg_form msg; // 消息队列用于通知对方更新了共享内存
    int flag = 1; // while循环条件
    // 获取key值
    key_t key;
    if((key = ftok(__MSG_FILE__.c_str(),'z')) < 0)
    {       
        perror("ftok error");
        exit(1);
    }           
                
    // 创建共享内存
    int shmid;
    if((shmid = shmget(key,4096,0)) == -1)
    {           
        perror("shmget error");
        exit(1);
    }
    // 和当前进程关联
    char* shm;
    shm = (char*)shmat(shmid,0,0);
    if((int)*shm == -1)
    {
        perror("Attach Shared Memory Error");
        exit(1);
    }
    
    // 创建消息队列
    int msqid;
    if((msqid = msgget(key,0)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // 创建信号量
    sem_init(&blank,0,111);
    sem_init(&xfull,0,0);

// 写数据
printf("***************************************\n");
printf("*                 IPC                 *\n");
printf("*    Input r to send data to server.  *\n");
printf("*    Input q to quit.                 *\n");
printf("***************************************\n");



    // 读数据
    while(flag)
    {
        char c;
        printf("Please input command: ");
        scanf("%c",&c);
        switch (c) {
            case 'r':
                printf("Data to send: \n");
                sem_wait(&blank);
                scanf("%s",shm);
                sem_post(&xfull);
                // 清空标准输入缓冲区
                while((c = getchar())!='\n' && c!=EOF);
                msg.mtype = 888;
                msg.mtext = 'r';
                sem_wait(&xfull);
                msgsnd(msqid,&msg,sizeof(msg.mtext),0);
                sem_post(&blank);
                break;
            case 'q':
                msg.mtype = 888;
                msg.mtext = 'q';
                msgsnd(msqid,&msg,sizeof(msg.mtext),0);
                flag = 0;
                break;
            default:
                  printf("Wrong input!\n");
                while((c=getchar())!='\n'&&c!=EOF);
        }
    }
    
    // 解除关联
    shmdt(shm);
    // 删除共享内存,消息队列
    shmctl(shmid,IPC_RMID,NULL);
    msgctl(msqid,IPC_RMID,NULL);
    sem_destroy(&blank);
    sem_destroy(&xfull);

    return 0;
}

注意:当scanf()输入字符或字符串时,缓冲区中遗留下了\n,所以每次输入操作后都需要清空标准输入的缓冲区。但是由于 gcc 编译器不支持fflush(stdin)(它只是标准C的扩展),所以我们使用了替代方案:

while((c=getchar())!='\n' && c!=EOF);

返回顶部

posted on 2021-05-04 21:31  lodger47  阅读(468)  评论(0)    收藏  举报

导航