linux msgsend 头文件,Linux进程间通信之管道、消息队列实践

1、进程间通信简述

进程间通信的几种方式:无名管道、有名管道、消息队列、共享内存、信号、信号量、套接字(socket)。

进程间通信是不同进程直接进行的一些接触,这种接触有简单,有复杂。机制不同,复杂度也不同。通信是一个广义上的意 义,不仅指大批量数据传送,还包括控制信息的传送,但是使用的方法都是大同小异的。

ea72b21c617f6414a2b3cb6dc49aba71.png

如图所示进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信。

2、管道

管道分为无名管道和有名管道两种方式。管道是一种半双工的通信方式,数据只能单向流动,但是无名管道和有名管道的区别是无名管道只能在具有亲缘关系的进程间通信,有名管道则是在无亲缘关系进程间通信。进程的亲缘关系通常是指父子进程关系。管道是Linux支持的最初Unix IPC形式之一,管道与管道之间通信其实就是一个文件,但它不是一个普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统而且只存在内存中。当一个进程向管道中写的内容被管道另一端的进程读出;写入的内容每次都会被添加到管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。如下图所示。

7d2848ccfcf4783a8b7770ba7c210adf.png

那么,如何创建一条管道呢?下面,我们就来了解下FIFO函数。

FIFO不同于pipe函数,因为它提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径就能够彼此通过FIFO互相通信,因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进后出,和栈的原则一样,对管道以及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

需要包含的头文件如下:

int mkfifo(const char *pathname,mode_t mode);

函数返回值 :

成功0,失败-1

参数含义:

pathname为路径名,创建管道的名字(该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字)。mode为创建fifo的权限(第二个参数与打开普通文件的open()函数中的mode参数相同)。

注:如果mkfido的第一个参数已经是一个已经存在的路径名时,就会返回EEXIST错误,所以当我们调用的时候首先会检查是否返回该错误,如果返回该错误那我们只需要直接调用打开FIFO的函数即可。

FIFO比pipe函数打开的时候多了一个打开操作open;如果当时打开操作时为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将返回成功;否则,可能阻塞到有相应进程为写而打开该FIFO;或者,成功返回。另一种情况就是为写而打开FIFO时,若已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则可能会阻塞直到有相应进程为读而打开该FIFO;或者,返回ENIO错误。

下面我们使用FIFO实现进程间的通信。

(1)打开一个文件,管道的写入端向文件写入数据;管道的读取端从文件中读取出数据。

fifo_write.c

#include

#include

#include

#include

#include

#include

#define P_FIFO "txt"

int main()

{

int ret;

int fd;

char buf[20];

//打开有名管道

//第一个参数为有名管道文件路径

//第二个参数表明是以读取方式并以非阻塞方式打开有名管道

//O_RDONLY读取模式

//O_NONBLOCK非阻塞方式

fd = open(P_FIFO,O_RDONLY);

if(fd<0)

{

printf("open fail\n");

return -1 ;

}

//循环读取有名管道

while(1)

{

memset(buf,0,sizeof(buf));

if(read(fd,buf,sizeof(buf)) == 0)

{

printf("nodata.\n");

}

else

{

printf("getdata:%s\n",buf);

break;

}

}

close(fd);

return 0;

}

下面先将fifo_write.c和fifo_read.c分别编译成fifo_write和fifo_read两个可执行程序:

62db9b610fe0dfb732a8c8e2eb456af1.png

接下来,先运行fifo_write,然后打开另一个终端,接着运行fifo_read,运行fifo_write的时候,可以看到程序阻塞在终端:

2d83f7670b094f09867831bbfd26ac2a.png

下面打开另外一个终端运行fifo_read

切换到另外一个终端,在终端输入ls –l可以看到由于fifo_write中创建了管道文件txt,从前面的字串prwxr-xr-x中的p可以知道,这是一个管道文件,如下图所示:

14f780f2b6f23c22fd8f13cf1ad42591.png

运行fifo_read,这时候,可以看到从管道中获取的字符串hello write_fifo,如下图所示:

cdee849bc45b8e8e9542f579065d92a4.png

管道读取结束后,fifo_write这个程序也就不会在阻塞在终端了,如下图所示:

5a5302fda95ea59b3103bb82d6f22c72.png

写管道程序还要注意,一旦我们创建了FIFO,就可以用open去打开它,可以使用open、read、close等去操作FIFO和pipe有相同之处,当打开FIFO时,非阻塞标志(O_NONBLOCK)将会对读写产生如下影响:

1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞;

2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,errno是ENXIO。

3、消息队列

消息队列(也叫做报文队列)提供了一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。

打开或者创建消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,只需要提供该消息队列的键值即可。

消息读写操作非常简单,对于开发人员来说,每个消息都类似如下的数据结构:

int msgget(key_t key, int msgflg);

与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,成功则返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。

3.2、msgsnd函数

该函数用来向消息队列发送一个消息。

int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

msgrcv函数前面三个参数和msgsnd函数的三个参数一样不做讲解。msgtype可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。当调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中对应的消息;失败则返回-1.

3.4、msgctl函数

该函数用来控制消息队列。

#include

#include

#include

int main(void)

{

int msgid ;

//创建消息队列,注意,创建后面要有IPC_CREAT标志

msgid = msgget(0x123456 , IPC_CREAT | 0777);

if(msgid < 0)

{

perror("msgget fail");

return -1 ;

}

printf("success ... ! \n");

return 0 ;

}

运行结果:

8f1f962e5c6d86d2df2138c2de868046.png

那消息队列呢?怎么查看?使用ipcs –q命令可以查看到刚刚我们创建的消息队列0x123456。

ac768d90602287617d15ec4267c45018.png

(2)向消息队列发送消息 msgsend.c

#include

#include

#include

#include

int main(void)

{

int msgid ;

msgid = msgget(0x123456 , 0);

if(msgid == -1)

{

perror("create msg queue fail");

return -1 ;

}

printf("open msg success ... \n");

int ret ;

char buffer[1024] = {0};

//接收消息队列中的信息

ret = msgrcv(msgid , buffer , 11 , 0 , 0);

if(ret == -1)

{

perror("recv msgid fail");

return -2 ;

}

printf("ret: %d buffer:%s \n" , ret , buffer);

return 0 ;

}

运行结果,如图所示:

0b834fee6dd8fb2f79b018d40f1a0ec1.png

那么,如何删除一个消息队列呢?先用ipcs –q查看消息队列,如图所示:

ab3c752cb8327f679aaa30e1bc16b1dd.png

有两种方法:

1、使用命令ipcrm –q msqid 删除消息队列,如图所示

54a25ce4d1d0e9885c4407415ee4bc29.png

2、使用msgctl函数,写IPC_RMID标志删除消息队列

(4)删除消息队列 msgrm.c

#include

#include

#include

int main(void)

{

int msgid ;

msgid = msgget(0x123456 , 0);

if(msgid < 0)

{

perror("msgget fail");

return -1 ;

}

printf("success ... ! msgid:%d \n" , msgid);

//写IPC_RMID标志

if(msgctl(msgid , IPC_RMID , NULL) == 0)

{

printf("remove success ... \n");

}

return 0 ;

}

运行结果,如图所示:

6b01c2d8011af32145b9c6b8b34bb3fc.png

使用系统提供的API的方式,可以将消息队列删除。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点,相对于管道通信有很大的改观,而且消息队列对数据的顺序处理也是非常有条理性的不会产生混杂性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值