进程间通信(2)--消息队列/内存共享/信号量

最近这两次博客总是讲到一个概念:进程间通信。而且两次总结的内容都不一样,让人有点蒙圈了,哈哈。那我们就将进程间通信的一些相关知识总结一下,然后开始我们今天的知识总结。

1.进程间通信,实际上就是几个进程之间进行数据传输(比如进行读写操作、单向传输数据、双向传输数据等等操作,达到一个信息交流的作用。就好比我们人与人之间交流一样,可以有很多方式:面对面聊天、微信、QQ等等)。

2.进程通信的目的:数据传输、资源共享、通知事件、进程控制

3.进程分类:(如下图)


通过上面知识点的补充,大家一定对“进程间通信”这个概念有了一个整体的了解,接下来我们就来总结一下IPC的三种形式,其中,主要总结消息队列。

一、消息队列

1.消息队列:有类型的数据块

   (前面我们说到了管道,不管是匿名管道还是命名管道,它们只能进行单向的数据传输,结合现实生活,我们不难想到,两个       人聊天,也不可能一个人说,另外一个人听。进程间通信也是如此,所以就有了消息队列,它是由操作系统提供的队列

下面是消息队列的一个简单的原理图


2.消息队列函数

(1)msgget函数

功能:创建和访问一个消息队列

原型:int msgget(key_t key, int msgflg);

参数:key--某个消息队列的名字;   msgflg--由九个权限标志构成,它们的使用和创建文件时使用mode模式一样。

返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1

(2)msgctl函数

功能:消息队列的控制函数

原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数:msqid:由msgget函数返回的消息队列标识码;   cmd:将要采取的动作(有三个可取值),如下图


返回值:成功返回0,失败返回-1

(3)msgsnd函数

功能:把一条消息添加到消息队列中

原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数:msgp:一个指针,指针指向准备发送的消息;   msgsz:是msgp指向的消息长度(该长度不包含保存消息类型的长整型)

          msgflg=IPC_NOWAIT表示队列不等待,返回EAGAIN错误。

返回值:成功返回0,失败返回-1

(4)msgrcv函数

功能:从一个消息队列接收消息

原型:ssize_t msgrcv(int msqid, void * msgp, size_t msgsz, long msgtyp, int msgflg);

参数:msgtyp:它可以实现接收优先级的简单形式

返回值:成功返回实际放到接收缓存区的字符个数,失败返回-1

(5)几点说明:

1)msgtype=0,返回队列第一条信息

2)msgtype>0,返回队列第一条类型等于msgtype的消息

3)msgtype<0,返回队列第一条类型小于等于msgtype绝对值的消息,并且满足条件的消息类型最小的消息

4)msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG

5)msgflg=MSG_NOERROR,消息大小超过msgsz时被截断

6)msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息

3.代码实例

代码测试结构

# ls
client.c  comm.c  comm.h  Makefile  server.c
# cat Makefile
.PHONY:;all
all:client server

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

	   .PHONY:clean
   clean:
rm -f client server

comm.h

#ifdef _COMM_H__
#define _COMM_H__

#include<sdtio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.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 destroyMsgQueue(int msgid);
int sendMsg(int msgid, int who, char *msg);
int recvMsg(int msgid, int recvType, char out[]);

#endif

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, IPC_CREAT | IPC_EXCL);
	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("msgnsnd");
			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 done,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("Plese 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_TYYPE, buf);
		printf("server# %s\n", buf);
	}
	return 0;
}

4.ipcs&ipcrm命令

由于消息队列和管道的生命周期不同,消息队列的生命周期随内核,所以我们需要手动进行其删除操作,这里我们介绍两个很重要的命令。

(1)ipcs:显示ipc的资源

(2)ipcrm:手动删除ipc资源

二、共享内存

1.共享内存是最快的IPC形式(因为两个进程互相通信时,共享内存没有两次数据的拷贝),一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不在涉及到内核(即就是:进程不在通过执行进入内核的系统调用来传递彼此的数据

说明:共享内存的生命周期也是随内核,因此需要用命令、接口来删除

2.共享内存的示意图(以两个进程为例)


说明:共享内存如果没有数据,不会被卡住,并且共享内存没有同步和互斥机制

3.共享内存函数

共享内存也有四个函数,如下:

//shmget函数--创建共享内存
int shmget(key_t key, size_t size, int shmflg);

//shmat函数--将共享内存连接到进程的地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);

//shmdt函数--将共享内存段与当前进程脱离
int shmdt(const void *shmaddr);

//shmctl函数--控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

三、信号量

1.信号量主要用于同步和互斥机制的。上一片博客我们已经提到了同步和互斥的概念,下面我们再整体的说一遍。

(1)进程互斥:由于各个进程间要求资源共享,而有些资源需要互斥使用,因此各进程间竞争使用这些资源称为进程的互斥。

       (其中,系统中某些资源一次只允许一个资源使用,这样的资源称为临界资源或者互斥资源)

举例如图:


(2)进程同步:指所有进程之间相互配合共同完成一项任务。


2.信号量和P、V原语

(1)互斥:P、V在同一个进程中

         同步:P、V在不同的进程中

(2)信号量值含义

         S>0:S表示可用资源的个数

         S=0:表示无可用资源,无进程等待

         S<0:|S|表示等待队列中进程的个数

3.信号量函数

//semget函数--创建和访问一个信号量集
int semget(key_t key, int nsems, int semflg);

//semctl函数--控制信号量集
int semctl(int semid, int semnum, int cmd, ...);

//semop函数--创建和访问一个信号量集
int semop(int semid, struct sembuf *sops, unsigned nsops);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值