我们学习高级进程通信,指用户可直接利用操作系统所提供的一组通信命令高效地传送大量数据的一种通信方式。
进程间通信的几种主要手段:管道、消息队列、共享内存、套接字
管道
管道,指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,只存在于内存中,是一种两个进程间进行单向通信的机制。在创建管道时,系统为管道分配一个页面作为数据缓冲区,通过管道通信的两个进程通过读写这个缓冲区来进程通信,写在缓冲区尾部,从头部读取数据。
局限性:
数据单向传递,即半双工;管道没有名字,只能用于具有亲缘关系的进程间通信;管道的缓冲区大小受限制;管道传送的是无格式的字节流。
管道的创建:
int pipe(int fd[2]) //调用成功返回0,且数组中包含两个新的fd,失败返回-1
管道两端分别用fd[0]和fd[1]描述,管道两端的任务固定,fd[0]只能用于读,称为管道读端;fd[1]只能用于写,称为管道写端。
管道一旦创建成功,就可以作为一般的文件来使用,对一般文件进行操作的I/O函数也适用于管道。
管道的读写:如果某进程要读取管道中的数据,那么该进程应当关闭fd[1],同时向管道写数据的进程应当关闭fd[0]。向管道中写数据类似。
这两个例子中pipe在fork之前调用,让子进程可以直接共享父进程的文件描述符。但是如果子进程调用exec函数执行另外一个程序时,就不能共享了。这种情况下可以将子进程中的文件描述符重定向到标准输入,当新执行的程序从标准输入获取数据时实际上是从父进程中获取输入数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/*read pipe*/
void read_from_pipe(int fd)
{
char message[100];
read(fd,message,100);
printf("read from pipe:%s\n",message);
}
/*write pipe*/
void write_from_pipe(int fd)
{
char message[100];
printf("input message: ");
scanf("%s",message);
write(fd,message,strlen(message)+1);
}
int main()
{
int fd[2];
pid_t pid;
int stat_val;
if(pipe(fd))
{
printf("create pipe failed\n");
exit(1);
}
pid = fork();
switch(pid)
{
case -1:
printf("fork error!\n");
exit(1);
case 0: /*子进程关闭写端*/
close(fd[1]);
read_from_pipe(fd[0]);
exit(0);
default:/*父进程关闭读端*/
close(fd[0]);
write_from_pipe(fd[1]);
exit(0);
}
return 0;
}
有名管道:nameed pipe或FIFO
有名管道是一个设备文件,它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中,只要进程可以访问该路径,就能够通过FIFO相互通信。与队列类似,先进先出,第一个被写入的数据首先从管道中读出。
有名管道的创建与读写
第一种方式:shell下使用mknod或mkfifo命令创建
第二种方式:系统函数
#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char* path,mode_t mod, dev_t dev);
int mkfifo(const char *path,mode_t mode);
第一个参数是创建的有名管道的全路径名,第二个参数是有名管道的模式,指明权限
有名管道创建后,存在与硬盘上,必须先用open()将其打开。打开有名管道的进程可能会被阻塞,如果同时以读写方式打开,则一定不会阻塞。如果以只读方式打开,则调用open的进程将会被阻塞直到有写方打开管道。
/*fiford.c*/
#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024
int main()
{
int fd;
char buf[BUF_SIZE];
umask(0);
fd = open(FIFO_NAME,O_RDONLY);
read(fd,buf,BUF_SIZE);
printf("Read content: %s\n",buf);
close(fd);
return 0;
}
/*fifowr.c*/
#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024
int main()
{
int fd;
char buf[BUF_SIZE] = "hello fiford,I come from fifowr!";
umask(0);
if(mkfifo(FIFO_NAME,S_IFIFO | 0666) == -1)
{
perror("mkfifo error!");
exit(1);
}
if((fd = open(FIFO_NAME,O_WRONLY)) == -1)
{
perror("open error!");
exit(1);
}
write(fd,buf,strlen(buf)+1);
close(fd);
return 0;
}
消息队列
消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。消息队列存放在内核中,只有在内核重启或者显式地删除一个消息队列时,该消息队列才会被真正地删除。
消息队列的创建:
#incllude <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id); //根据pathname和proj_id这两个参数生成惟一的键值,失败返回-1
#include <sys/msg.h>
int msgget(key_t key,int msgflg); //根据ftok传来的键值,创建一个新的消息队列或者访问一个已存在的消息队列
成功返回消息队列的描述符,失败返回-1
读/写消息队列
#include <sys/msg.h>
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflag);
int msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long ing msgtype, int msgflag);
参数:
msqid:消息队列描述符
msgp:读取的消息存储到msg指向的结构体中
msgsz:消息缓冲区大小
msgflag:操作标志位。一般为0,即消息队列已满的时候,函数将会阻塞
msgtype:请求读取的消息类型 从消息队列中根据消息类型得到对应消息
成功返回0,失败返回-1
/*往消息队列写数据*/
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
int main()
{
/*用户自定义消息缓冲*/
struct mymsgbuf
{
long msgtype;
char ctrlstring[BUF_SIZE];
}msgbuffer;
int qid;
int msglen;
key_t msgkey;
/*获取键值*/
if((msgkey = ftok(PATH_NAME,PROJ_ID)) == -1)
{
perror("ftok error!");
exit(1);
}
/*创建消息队列*/
if((qid = msgget(msgkey,IPC_CREAT | 0660)) == -1)
{
perror("msgget error!");
exit(1);
}
/*填充消息结构,发送到消息队列*/
msgbuffer.msgtype = 3;
strcpy(msgbuffer.ctrlstring,"hello,message queue!");
msglen = sizeof(struct mymsgbuf) - 4;
if((msgsnd(qid,&msgbuffer,msglen,0) == -1))
{
perror("msgsng error!\n");
exit(1);
}
return 0;
}
/*从刚才创建的消息队列中获取数据*/
#define PATH_NAME "."
#define PROJ_ID 32
#define BUF_SIZE 256
int main()
{
key_t key; //键值
int qid; //消息队列标识符
int msglen; //消息大小
struct mymsgbuf
{
long msgtype; //消息类型
char ctrlstring[BUF_SIZE]; //消息内容
}msgbuffer; //自定义消息缓冲区
/*获取键值*/
if((key = ftok(PATH_NAME,PROJ_ID)) == -1)
{
perror("ftok error!\n");
exit(1);
}
/*访问消息队列*/
if((qid = msgget(key,IPC_CREAT | 0660)) == -1)
{
perror("msgget error!\n");
exit(1);
}
/*读取消息队列内容*/
msglen = sizeof(struct mymsgbuf) - 4;
if(msgrcv(qid,&msgbuffer,msglen,3,0) == -1)
{
perror("msgrcv error!\n");
exit(1);
}
printf("Get message: %s\n",msgbuffer.ctrlstring);
return 0;
}