管道模型
传统的模型就是软件开发的瀑布模型(Waterfall Model)。所谓的瀑布模型,其实就是将整个软件开发过程分成多个阶段,往往是上一个阶段完全做完,才将输出结果交给下一个阶段。就像下面这张图展示的一样。
这种模型类似进程间通信的管道模型。Linux 有下面这样一行命令:
ps -ef | grep 关键字 | awk '{print $2}' | xargs kill -9
这里面的竖线“|”就是一个管道。它会将前一个命令的输出,作为后一个命令的输入。从管道的这个名称可以看出来,管道是一种单向传输数据的机制,它其实是一段缓存,里面的数据只能从一端写入,从另一端读出。如果想互相通信,我们需要创建两个管道才行。
管道分为两种类型,“|” 表示的管道称为匿名管道,意思就是这个类型的管道没有名字,用完了就销毁了。就像上面那个命令里面的一样,竖线代表的管道随着命令的执行自动创建、自动销毁。用户甚至都不知道自己在用管道这种技术,就已经解决了问题。所以这也是面试题里面经常会问的,到时候千万别说这是竖线,而要回答背后的机制,管道。
另外一种类型是命名管道。这个类型的管道需要通过 mkfifo 命令显式地创建。
mkfifo hello
hello 就是这个管道的名称。管道以文件的形式存在,这也符合 Linux 里面一切皆文件的原则。这个时候,我们 ls 一下,可以看到,这个文件的类型是 p,就是 pipe 的意思。
# ls -l
prw-r--r-- 1 root root 0 May 21 23:29 hello
接下来,我们可以往管道里面写入东西。例如,写入一个字符串。
# echo "hello world" > hello
这个时候,管道里面的内容没有被读出,这个命令就是停在这里的,这说明当一个项目组要把它的输出交接给另一个项目组做输入,当没有交接完毕的时候,前一个项目组是不能撒手不管的。
这个时候,我们就需要重新连接一个终端。在终端中,用下面的命令读取管道里面的内容:
# cat < hello
hello world
一方面,我们能够看到,管道里面的内容被读取出来,打印到了终端上;另一方面,echo 那个命令正常退出了,也即交接完毕,前一个项目组就完成了使命,可以解散了。
我们可以看出,瀑布模型的开发流程效率比较低下,因为团队之间无法频繁地沟通。而且,管道的使用模式,也不适合进程间频繁的交换数据。
于是,我们还得想其他的办法,例如我们是不是可以借鉴传统外企的沟通方式——邮件。邮件有一定的格式,例如抬头,正文,附件等,发送邮件可以建立收件人列表,所有在这个列表中的人,都可以反复的在此邮件基础上回复,达到频繁沟通的目的。
消息队列模型
这种模型类似进程间通信的消息队列模型。和管道将信息一股脑儿地从一个进程,倒给另一个进程不同,消息队列有点儿像邮件,发送数据时,会分成一个一个独立的数据单元,也就是消息体,每个消息体都是固定大小的存储块,在字节流上不连续。
这个消息结构的定义我写在下面了。这里面的类型 type 和正文 text 没有强制规定,只要消息的发送方和接收方约定好即可。
struct msg_buffer {
long mtype;
char mtext[1024];
};
接下来,我们需要创建一个消息队列,使用msgget 函数。这个函数需要有一个参数 key,这是消息队列的唯一标识,应该是唯一的。如何保持唯一性呢?这个还是和文件关联。
我们可以指定一个文件,ftok 会根据这个文件的 inode,生成一个近乎唯一的 key。只要在这个消息队列的生命周期内,这个文件不要被删除就可以了。只要不删除,无论什么时刻,再调用 ftok,也会得到同样的 key。这种 key 的使用方式在这一章会经常遇到,这是因为它们都属于 System V IPC 进程间通信机制体系中。
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
int main() {
int messagequeueid;
key_t key;
if((key = ftok("/root/messagequeue/messagequeuekey", 1024)) < 0)
{
perror("ftok error");
exit(1);
}
printf("Message Queue key: %d.\n", key);
if ((messagequeueid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}
printf("Message queue id: %d.\n", messagequeueid);
}
vim
:wq mesq.c
gcc mesq.c -o mesq.out
./mesq.out
在运行上面这个程序之前,我们先使用命令 touch messagequeuekey,创建一个文件,然后多次执行的结果就会像下面这样:
# ./a.out
Message Queue key: 92536.
Message queue id: 32768.
System V IPC 体系有一个统一的命令行工具:ipcmk,ipcs 和 ipcrm 用于创建、查看和删除 IPC 对象。
例如,ipcs -q 就能看到上面我们创建的消息队列对象。
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00016978 32768 root 777 0 0
接下来,我们来看如何发送信息。发送消息主要调用msgsnd 函数。第一个参数是 message queue 的 id,第二个参数是消息的结构体,第三个参数是消息的长度,最后一个参数是 flag。这里 IPC_NOWAIT 表示发送的时候不阻塞,直接返回。
下面的这段程序,getopt_long、do-while 循环以及 switch,是用来解析命令行参数的。命令行参数的格式定义在 long_options 里面。每一项的第一个成员“id”“type““message”是参数选项的全称,第二个成员都为 1,表示参数选项后面要跟参数,最后一个成员’i’‘t’'m’是参数选项的简称。
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <getopt.h>
#include <string.h>
struct msg_buffer {
long mtype;
char mtext[1024];
};
int main(int argc, char *argv[]) {
int next_option;
const char* const short_options = "i:t:m:";
const struct option long_options[] = {
{ "id", 1, NULL, 'i'},
{ "type", 1, NULL, 't'},
{ "message", 1, NULL, 'm'},
{ NULL, 0, NULL, 0 }
};
int messagequeueid = -1;
struct msg_buffer buffer;
buffer.mtype = -1;
int len = -1;
char * message = NULL;
do {
next_option = getopt_long (argc, argv, short_options, long_options, NULL);
switch (next_option)
{
case 'i':
messagequeueid = atoi(optarg);
break;
case 't':
buffer.mtype = atol(optarg);
break;
case 'm':
message = optarg;
len = strlen(message) + 1;
if (len > 1024) {
perror("message too long.");
exit(1);
}
memcpy(buffer.mtext, message, len);
break;
default:
break;
}
}while(next_option != -1);
if(messagequeueid != -1 && buffer.mtype != -1 && len != -1 && message != NULL){
if(msgsnd(messagequeueid, &buffer, len, IPC_NOWAIT) == -1){
perror("fail to send message.");
exit(1);
}
} else {
perror("arguments error");
}
return 0;
}
-----------------------
消息队列代码重新跑完再继续写
----------------------