1.Linux——进程间的五种通信方式(管道、消息队列、共享存储、信号、信号量)

文章目录

引言

        进程间通信(IPC,InterProcess Communication)是指在不同的进程之间传播或交换信息。       

        前7种IPC通常限于同一台主机的各个进程间的IPC。然而套接字和STREAMS,是仅有的两种支持不同主机上各个进程间IPC的类型。

1.管道

        管道是UNIX系统IPC的最古老形式,并且所有UNIX系统都提供此种通信机制。

        管道的特点(也是两种局限性):

  1. 历史上,它们是半双工的(即数据只在一个方向上流动)。具有固定的读端与写端。现在,有些系统提供了全双工管道,但是为了最佳的可移植性,我们决不预先假定系统使用此特性。
  2. 只能在具有亲缘关系之间的通信(父子或兄弟之间)。             

       

 1.1 无名管道

  1. 无名管道特点:
    • 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
    • 只能在具有亲缘关系之间的通信
    • 管道是创建在内存中(只存在于内存中),进程结束空间释放,管道不复存在。对于它的读写可以使用普通的read、write等函数.(管道中的数据,读走就没有了)
  2. 函数原型

        经由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输人。

         调用fork之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程则关闭写端(fd[1])。图15-3显示了在此之后描述符的安排。

         为了构造从子进程到父进程的管道,父进程关闭fd[1](写端),子进程关闭fd[0](读端)。 记忆:通常喜欢念0 1对应这里的 读 写。

        3.例子:

        数据流从父进程流入子进程,父进程打开写端,关闭读端。子进程则是打开读端,关闭写端。反之,如果数据流从子进程流向父进程也是一样。

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

int main()
{
	int fd[2];
	int pid;
	char buf[128];

//	int pipe(int pipefd[2]);

	if(pipe(fd) == -1){
		printf("creat pipe failed\n");
	}
	pid = fork();
	
	if(pid<0){
		printf("creat child failed\n");
	}
	else if(pid > 0){
		sleep(3);
		printf("this is father\n");

		close(fd[0]);
		write(fd[1],"hello from father",strlen("hello form father"));
		wait();
	}else{
		printf("this is child\n");
		close(fd[1]);
		read(fd[0],buf,128);
		printf("read from father: %s\n",buf);
		exit(0);
	}
	
	return 0;
}

运行效果:

 1.2 命名管道(FIFO)     

  1. 特点:
    1. 命名管道可以在无关的进程之间交换数据,与无名管道不同。(通过FIFO,不相关的进程也能交换数据)
    2. 命名管道有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。(创建FIFO类似于创建文件。确实,FIFO的路径名存在于文件系统中。)
  2. 函数原型:

          一旦已经用mkfifo创建了一个FIFO,就可用open打开它。其实,一般的文件I/O函数(close、read、write、unlink等)都可用于FIFO。

        FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清楚数据,并且”先进先出“。

    3.  FIFO有下面两种用途;
        (1) FIFO由shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。
        (2) FIFO用于客户进程-服务器进程应用程序中,以在客户进程和服务器进程之间传递数据。我们各用一个例子来说明这两种用途。

代码段:

        先运行 read.c,再运行 write.c ;运行之后读端先阻塞起来等待写端的写入,写入完之后读端开始运行。

        读端代码块:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

//int mkfifo(const char *pathname, mode_t mode);
int main()
{
	char buf[30] = {0};
	int nread = 0;


	if( (mkfifo("./file",0600) == -1) && errno!=EEXIST){
		printf("mkfifo failuer\n");
		perror("why");
	}

	int fd = open("./file",O_RDONLY);
	printf("open success\n");
	
	while(1){
		nread = read(fd,buf,30);
		printf("read %d byte from fifo,context:%s\n",nread,buf);
	}
	close(fd);
	return 0;
}

        写端代码块:

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

//int mkfifo(const char *pathname, mode_t mode);
int main()
{
	int cnt = 0;
	char *str = "message from fifo";

	int fd = open("./file",O_WRONLY);
	printf("write open success\n");
	
	while(1){
		
		write(fd, str, strlen(str));
		sleep(1);
		if(cnt == 5){
			break;	
		}
	}
	close(fd);
	return 0;
}

2.消息队列

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

        2.1 特点:       

  • 消息队列是面向记录的,其中的消息具有特地那个的格式以及特定的优先级。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及内容并不会删除
  • 消息队列可以实现信息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

        2.2 函数原型:

 

        从队列中取消息,并不一定要以先进先出次序,也可以按信息的类型字段取消息。 

         函数msgget中key值很关键,相当于一个索引。进程通过这个键值,在Linux内核中找到相应的队列(确保了队列是同一个) 在以下两种情况中,msgget 将创建一个新的消息队列:

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

        函数msgrcv在读取消息队列时,type参数有下面几种情况:

  • type == 0 ,返回队列中的第一个消息;
  •  type > 0 ,返回队列中消息类型为 type 的第一个消息;
  • type < 0 ,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
     

代码端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//        int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
//                      int msgflg);
struct msgbuf {              
			long mtype;       /* message type, must be > 0 */
			char mtext[256];    /* message data */
};

int main()
{
	//1.huoqu
	struct msgbuf readBuf;	
	
	//获取key值
	key_t key;
	key = ftok(".",'m');// “.” 表示当前路径,第二个参数随意,可以是数字,可以是字符
	printf("key=%x\n",key);
	// 获取/创建消息队列
	int msgId = msgget(key, IPC_CREAT|0777);//0777 可读可写可执行
	if(msgId == -1 ){
		printf("get que failuer\n");
	}
	memset(&readBuf,0,sizeof(struct msgbuf));
	//接收数据
	msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),888,0); //第三个参数:消息类型,第四个参数:0 的意思是默认的方式接收消息,接收不到消息会一直阻塞	
	printf("read from que:%s\n",readBuf.mtext);
	//发送数据
    struct msgbuf sendBuf = {988,"thank you for reach"};
    msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
	//关闭数据
	msgctl(msgId,IPC_RMID,NULL);//第三个参数,通常设置为NULL

	return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
//                      int msgflg);
struct msgbuf {
			long mtype;       /* message type, must be > 0 */
			char mtext[256];    /* message data */
};

int main()
{
	//1.huoqu
	struct msgbuf sendBuf = {888,"this is message from quen"};	//第一个参数:消息的类型
	struct msgbuf readBuf;

	memset(&readBuf,0,sizeof(struct msgbuf));
	//获取key值
	key_t key;
    key = ftok(".",'m');// “.” 表示当前路径
    printf("key=%x\n",key);
	//创建消息队列
    int msgId = msgget(key, IPC_CREAT|0777);//0777 可读可写可执行

	if(msgId == -1 ){
		printf("get que failuer\n");
	}
	//发送数据
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
	printf("send over\n");
	//接收数据
    msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),988,0);//第四个参数:0 的意思是默认的方式接收消息,接收不到消息会一直阻塞
	printf("reaturn from get:%s\n",readBuf.mtext);
	//关闭消息队列
	msgctl(msgId,IPC_RMID,NULL);
	
	return 0;
}

运行效果:

3.共享存储

        共享存储允许两个或更多进程共享给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。

  1.  特点:
    1. 共享存储是最快的一种IPC,因为进程是直接对内存进行存储,而不需要任何数据的拷贝。
    2. 它有一个特性:只能单独一个进程写或读,如果A和B进程同时写,会造成数据的混乱,(所以需要搭配信号量来使用,比较好)

        2. 函数原型

//创建或获取一个共享内存:成功返回共享内存ID,失败返回 -1
int shmget(key_t key, size_t size, int shmflg);
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回 -1
void *shmat(int shmid, const void *shmaddr, int shmflg);
//断开与共享内存的连接:成功返回0,失败返回 -1
int shmdt(const void *shmaddr);
//控制共享内存的相关信息:成功返回0,失败返回 -1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

部分参数解释

 代码段:

        编程思路:          

  1. 创建/打开共享存储
  2. 连接映射共享存储
  3. 写入数据 strcpy
  4. 断开释放共享存储
  5. 干掉共享存储

 shm_write

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//int shmget(key_t key, size_t size, int shmflg);
int main()
{	
	int shmid;
	char *shmaddr;
	
	key_t key;
	key = ftok(".",1);
	
	shmid = shmget(key,1024*4,IPC_CREAT|0666);
	if(shmid == -1){
		printf("shmget noOk\n");
		exit(-1);
	}
	shmaddr = shmat(shmid,0,0);

	printf("shmat ok\n");
	strcpy(shmaddr,"chenlichen");

	sleep(5);
	shmdt(shmaddr);
	shmctl(shmid, IPC_RMID, 0);

	printf("quit\n");
	return 0;
}

 shm_get

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//int shmget(key_t key, size_t size, int shmflg);
int main()
{	
	int shmid;
	char *shmaddr;

	key_t key;
	key = ftok(".",1);
	
	shmid = shmget(key,1024*4,0);
	if(shmid == -1){
		printf("shmget noOk\n");
		exit(-1);
	}
	shmaddr = shmat(shmid,0,0);

	printf("shmat ok\n");
	printf("data: %s\n:",shmaddr);

	shmdt(shmaddr);

	printf("quit\n");
	return 0;
}

 运行效果:

 

前3种通信方式概况图:

4.信号

2.Linux——信号_单片机爱好者之家的博客-CSDN博客

5.信号量

6.Linux——信号量_单片机爱好者之家的博客-CSDN博客

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值