Linux进程间通信的几种方法-半双工管道,命名管道,消息队列

 1、半双工管道

     简单实现

      半双工管道可以实现父进程和子进程之间或者子进程之间(前提是有共同的祖先)的通信

      因为是半双工,所以两端不可能同时读取,而是一端读一端取,而且当一端分配到读任务后,那么他就固定了,不能再担当写的角色了,相反亦然。

      测试程序如下:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/types.h>  
  6.   
  7. int main(void)  
  8. {  
  9.     int fd[2],nbytes;  
  10.     pid_t childpid;  
  11.     char string[] = "Hello, World!\n";  
  12.     char readbuffer[80];  
  13.       
  14.     pipe(fd);  
  15.     int w_fd = fd[1];//写  
  16.     int r_fd = fd[0];//读  
  17.       
  18.     if((childpid = fork()) == -1)  
  19.     {  
  20.         perror("fork");  
  21.         exit(1);      
  22.     }  
  23.       
  24.     if(childpid == 0)  
  25.     {  
  26.         close(r_fd);//关闭读  
  27.         write(w_fd,string,strlen(string));  
  28.         exit(0);          
  29.     }  
  30.     else  
  31.     {  
  32.         close(w_fd);关闭写  
  33.         nbytes = read(r_fd, readbuffer,sizeof(readbuffer));  
  34.         printf("Received string:%s\n",readbuffer);        
  35.     }  
  36.       
  37.     return 0;  
  38.   
  39. }  

          不能看出,为什么半双工管道两端是单一角色了,因为开始读或者写之前必须关闭写或读的fd

      半双工管道的阻塞性

            写端对读端具有依赖性:假如读端被关闭了,那么再写入管道就没有意义的,此时写入管道会返回-1。

            阻塞性:

                  上例程序的这句代码 bytes = read(r_fd, readbuffer,sizeof(readbuffer));

            指明了管道的大小,即为sizeof(readbuffer),写入比该数子大的数据时,先只会写sizeof(readbuffer)个数据到管道,然后写端阻塞,等待读端取走数据,然后按同样的规则写入剩余的部分,这个也体现了写入操作的非原子性。写请求字节数还有一个最大阀值,在/usr/include/linux有文件 limits.h中宏定义

            #define PATH_MAX        4096

            此时,假如指明的管道大小大于PATH_MAX ,系统会按这个PATH_MAX 作为写请求最大字节数。

     

 2、命名管道

         先说说命名管道相比半双工管道的优势,不再需要进程之间有亲属关系了,因为是以一种文件的形式存在,所以对文件的大部分操作都支持。建立命名管道的方法为int makefifo(const char *pathname, mode_t mode);

         先解释下这个函数,pathname为管道名称,mode为建立管道的选项,返回值0为成功,-1为失败。

        命名管道的阻塞性:

        阻塞性可通过mode参数指定:

        1、当以阻塞(未指定O_NONBLOCK)方式只读打开FIFO的时候,则将会被阻塞,知道有其他进程以写方式打开该FIFO。

        2、类似的,当以阻塞(未指定O_NONBLOCK)方式只写打开FIFO的时候,则将会被阻塞,知道有其他进程以读方式打开该FIFO。

        3、当以非阻塞方式(指定O_NONBLOCK)方式只读打开FIFO的时候,则立即返回-1,其errno是ENXIO。

 

        测试程序

        server.c

[cpp]  view plain copy
  1. #include<stdio.h>  
  2. #include<sys/types.h>  
  3. #include<sys/stat.h>  
  4. #include<fcntl.h>  
  5. #include<unistd.h>  
  6.   
  7. #define FIFO_CHANNEL "my_fifo"  /* 宏定义,fifo路径 */  
  8.   
  9. int main()  
  10. {  
  11.  int fd;  
  12.  char buf[80];  
  13.    
  14.  if(mkfifo(FIFO_CHANNEL,0777)==-1) /* 创建命名管道,返回-1表示失败 */  
  15.  {  
  16.   perror("Can't create FIFO channel");  
  17.   return 1;  
  18.  }  
  19.    
  20.  if((fd=open(FIFO_CHANNEL,O_RDONLY))==-1)  /* 以只读方式打开命名管道 */  
  21.  {  
  22.   perror("Can't open the FIFO");  
  23.   return 1;  
  24.  }  
  25.   
  26.  while(1)  /* 不断从管道中读取信息 */  
  27.  {  
  28.   read( fd, buf, sizeof(buf) );  
  29.   printf("Message from Client: %s\n",buf );  
  30.   sleep(3); /* sleep 3s */  
  31.  }  
  32.   
  33.  close(fd);  /* 关闭管道 */  
  34.  return 0;  
  35. }  

          client.c

[cpp]  view plain copy
  1. #include<stdio.h>  
  2. #include<sys/types.h>  
  3. #include<sys/stat.h>  
  4. #include<fcntl.h>  
  5. #include<unistd.h>  
  6.   
  7. #define FIFO_CHANNEL "my_fifo"  /* 宏定义,fifo路径 */  
  8.   
  9. int main()  
  10. {  
  11.  int fd;  
  12.  char s[]="Hello!";  
  13.   
  14.  if((fd=open(FIFO_CHANNEL,O_WRONLY))==-1)  /* 以读写方式打开命名管道,返回-1代表失败 */  
  15.  {  
  16.   perror("Can't open the FIFO");  
  17.   return 1;  
  18.  }  
  19.   
  20.  while(1)  /* 不断向管道中写信息 */  
  21.  {  
  22.   write( fd, s, sizeof(s) );  
  23.   printf("Write: %s\n",s);  
  24.   sleep(3);  /* sleep 3s */  
  25.  }  
  26.   
  27.  close(fd);  /* 关闭管道 */  
  28.  return 0;  
  29. }  

结果截图:

         此时文件系统中会多一个my_fifo的特殊文件。

3、消息队列

       先介绍消息队列常用函数

      

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/ipc.h>   
  3. key_t ftok(const char* pathname,int proj_id);  

注意: pathname必须是已经存在的目录

系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过该ftok函数得到。

需要用 mkdir -p /ipc/msg

 

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/ipc.h>   
  3. #include <sys/msg.h>  
  4. int msgget(key_t key,int msgflag);  

 获取消息的msgget()函数,第一个参数为键值,msgflag可以指定参数

        IPC_CREATE:如果在内存中不存在该队列,则创建它

        IPC_EXCL:当与IPC_CREATE一起使用时,如果队列早已存在则将出错

 

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/ipc.h>   
  3. #include <sys/msg.h>  
  4. int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);  

发送消息msgsnd()函数,第一个参数为msgget返回值,第二个参数为消息缓冲区,第三个参数为消息长度,msgflg可以设置为0(表示忽略),也可只是为IPC_NOWAIT,如果消息队列已满,则消息将不会被写入队列中,未设IPC_NOWAIT,将会阻塞,直到可以写消息为止。

 

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/ipc.h>   
  3. #include <sys/msg.h>  
  4. ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);  

接受消息msgrcv,第一个参数由msgget指定,第二个参数指定缓冲区,第三个参数代表缓冲区大小,不包括mtype成员的长度,第四个参数指定要从队列中获取的消息类型。msgflg可设置为IPC_NOWAIT功能同上。


 

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/ipc.h>   
  3. #include <sys/msg.h>  
  4. int msgctl(int msqid, int cmd, struct msqid_ds *buf);  

消息控制msgctl函数,该函数向内核发送一个cmd命令,内核根据此来判断进行何种操作。

IPC_STAT 获取队列的msqid _ds结构

IPC_SET 设置队列的msqid_ds结构的ipc_perm成员值

IPC_RMID内核删除队列

 

 

以下为消息队列的一个例子:

[cpp]  view plain copy
  1. <p>#include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <sys/types.h>  
  5. #include <sys/msg.h>  
  6. #include <unistd.h>  
  7. #include <sys/ipc.h></p><p>void msg_show_attr(int msg_id, struct msqid_ds msg_info)  
  8. {  
  9.  int ret = -1;  
  10.  sleep(1);  
  11.  ret = msgctl(msg_id, IPC_STAT, &msg_info);  
  12.  if( -1 == ret)  
  13.  {  
  14.   printf("获得消息信息失败\n");  
  15.   return ;    
  16.  }  
  17.    
  18.  printf("\n");  
  19.  printf("现在队列中的字节数:%d\n",msg_info.msg_cbytes);  
  20.  printf("队列中消息数:%d\n",msg_info.msg_qnum);  
  21.  printf("队列中最大字节数:%d\n",msg_info.msg_qbytes);  
  22.  printf("最后发送消息的进程pid:%d\n",msg_info.msg_lspid);  
  23.  printf("最后接收消息的进程pid:%d\n",msg_info.msg_lrpid);  
  24.  printf("最后发送消息的时间:%s",ctime(&(msg_info.msg_stime)));  
  25.  printf("最后接收消息的时间:%s",ctime(&(msg_info.msg_rtime)));  
  26.  printf("最后变化时间:%s",ctime(&(msg_info.msg_ctime)));  
  27.  printf("消息UID是:%d\n",msg_info.msg_perm.uid);  
  28.  printf("消息GID是:%d\n",msg_info.msg_perm.gid);  
  29. }</p><p>  
  30. int main(void)  
  31. {  
  32.  int ret = -1;  
  33.  int msg_flags, msg_id;  
  34.  key_t key;  
  35.  struct msgmbuf{  
  36.   int mtype;  
  37.   char mtext[10];  
  38.   };   
  39.  struct msqid_ds msg_info;  
  40.  struct msgmbuf msg_mbuf;  
  41.    
  42.  int msg_sflags,msg_rflags;  
  43.  char *msgpath = "/ipc/msg/";  
  44.  key = ftok(msgpath,'b');  
  45.  if(key != -1)  
  46.  {  
  47.   printf("成功建立KEY\n");    
  48.  }  
  49.  else  
  50.  {  
  51.   printf("建立KEY失败\n");    
  52.  }  
  53.    
  54.  msg_flags = IPC_CREAT|IPC_EXCL;  
  55.  msg_id = msgget(key, msg_flags|0x0666);  
  56.  if( -1 == msg_id)  
  57.  {  
  58.   printf("消息建立失败\n");  
  59.   return 0;    
  60.  }   
  61.  msg_show_attr(msg_id, msg_info);  
  62.    
  63.  msg_sflags = IPC_NOWAIT;  
  64.  msg_mbuf.mtype = 10;  
  65.  memcpy(msg_mbuf.mtext,"测试消息",sizeof("测试消息"));  
  66.  ret = msgsnd(msg_id, &msg_mbuf, sizeof("测试消息"), msg_sflags);  
  67.  if( -1 == ret)  
  68.  {  
  69.   printf("发送消息失败\n");    
  70.  }  
  71.  msg_show_attr(msg_id, msg_info);  
  72.    
  73.  msg_rflags = IPC_NOWAIT|MSG_NOERROR;  
  74.  ret = msgrcv(msg_id, &msg_mbuf, 10,10,msg_rflags);  
  75.  if( -1 == ret)  
  76.  {  
  77.   printf("接收消息失败\n");    
  78.  }  
  79.  else  
  80.  {  
  81.   printf("接收消息成功,长度:%d\n",ret);    
  82.  }  
  83.  msg_show_attr(msg_id, msg_info);  
  84.    
  85.  msg_info.msg_perm.uid = 8;  
  86.  msg_info.msg_perm.gid = 8;  
  87.  msg_info.msg_qbytes = 12345;  
  88.  ret = msgctl(msg_id, IPC_SET, &msg_info);  
  89.  if( -1 == ret)  
  90.  {  
  91.   printf("设置消息属性失败\n");  
  92.   return 0;    
  93.  }  
  94.  msg_show_attr(msg_id, msg_info);  
  95.    
  96.  ret = msgctl(msg_id, IPC_RMID,NULL);  
  97.  if(-1 == ret)  
  98.  {  
  99.   printf("删除消息失败\n");  
  100.   return 0;    
  101.  }  
  102.  return 0;  
  103. }</p>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值