- 匿名管道:
函数原型:
int pipe(int fd[2]);
参数说明:
fd:文件描述符数组,fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码。
图解:
举一个例子:从键盘读取数据,写入管道,读取管道,写到屏幕。
- read调用阻塞,即进程暂停执行,一直等到有数据来到为止,read调用返回-1,errno值为EAGAIN
- write调用阻塞,直到有进程读走数据,调用返回-1,errno值为EAGAIN。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数说明:pathname:文件名 ,mode:文件的权限掩码,通过umask查看。
命令行:
$ mkfifo pathname
创建一个名为“my”的命名管道
- 如果当前打开操作是为读而打开FIFO时,阻塞直到有相应进程为写而打开该FIFO,立刻返回成功。
- 如果当前打开操作是为写而打开FIFO时,阻塞直到有相应进程为读而打开该FIFO,立刻返回失败,错误码为ENXIO
消息队列其实就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。
消息队列与管道相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序进行接收,而是可以根据自定义条件接收特定的消息。
可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列按照一定的规则添加一条消息,对消息队列有读权限的进程可以从消息队列中读取消息。
问题:怎么保证两个进程看到的是同一个消息队列?
如果给消息队列一个编号,这个编号让两个进程用某一种特定的方式获得,计算之后可以得到相同的数据,将该数据写入消息队列,此时作为该消息队列的编号。往后就可以保证他俩看到的是同一个消息队列。
消息队列的相关函数:
头文件:
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
函数原型:
int msgget(key_t key, int msgflg);
参数:
key:消息队列名字,键值,可设置成常数IPC_PRIVATE,或由ftok函数获取
msgflg:标志位
返回值:成功返回一个非负数,是该消息队列的标识码;失败返回-1.
标志位msflg的取值如下:
IPC_EXCL:与IPC_CREAT一起使用,表示如果要创建的消息队列已经存在,则返回错误
IPC_NOWAIT:读写消息队列要求无法达到满足时,立即返回,不会出现堵塞
参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只是意味着即将参加新的消息队列。
ftok函数:将文件名转换成键值
头文件:
#include<sys/types.h>
#include<sys/ipc.h>
函数原型:
key_t ftok(const char* pathname,int proj_id)
参数:
pathname:文件路径名,这个文件必须是存在的并且是可以访问的
proj_id:子序号,是一个8bits的整数,范围为0-255
返回值:
成功返回与文件相对应的键值;失败返回-1
注意:
- ftok是根据文件路径名,提取文件信息,再根据这些信息和proj_id合成key,该路径可以随便设置
- 该路径是必须存在的,ftok只是根据文件inode在系统内的唯一性来取一个数值,和文件的权限无关
- proj_id是可以自己约定,随意设置的。在Unix系统上,它的取值是1到255
key通过ftok函数获得,key在用户和内核层面上达成了共识。 进程给操作系统一个key,操作系统用这个key创建一个消息队列,另一个进程通过相同的方式也会生成同样的key,这样他们在运算期间后序就可以看到同样的消息队列。
key完成了进程间通信的第一个步骤,让两个进程看到同一份资源。
msgctl函数:消息队列控制函数
(当cmd为IPC_RMID时,作为消息队列删除函数)
函数原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
msqid:有msgget函数返回的标识码
cmd:将要采取的动作(三个取值)
返回值:
成功返回0,失败返回-1
cmd的取值如下:
- IPC_STAT:把msqid_ds结构体中的数据设置为消息队列的当前关联值
- IPC_SET:在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
- IPC_RMID:删除消息队列
buf是执行msqid_ds结构的指针,它指向消息队列模式和访问权限的结构。msqid_ds结构中至少包括以下成员:
struct msqid_ds{
uid_t uid;
gid_t gid;
mode_t mode;
};
msgsnd函数:把一条消息添加到消息队列中
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
msqid:由msgget函数返回的消息队列标识码
msgp:指向准备发送的消息的指针。指针msgp所指向的数据结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型
msgsz:是msgp指向的消息长度,注意这个长度不包含消息类型的哪个long int长整型的长度
msgflg:控制着当前消息队列满或者到达系统上限时将要发生的事情
msgflg=IPC_NOWAIT表示队列满不等待,非阻塞,返回EAGAIN错误。
msgflg=0 ,表示阻塞
返回值:成功,消息数据的一份副本将被放到消息队列中,并返回0,失败返回-1
说明:
1. 消息结构在两方面受到限制:
首先,它必须小于系统规定的上限值;
其次,它必须以一个long int长整型开始,接受者函数将利用这个长整型确定消息的类型
2. 消息结构的参考形式如下:
struct msgbuf{
long int type;
char text[1];
};
msgrcv函数:从一个消息队列接收函数
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数:
msqid:由msgget函数返回的消息队列标识码
msgp:指向准备接收消息的队列
msgsz:msgp指向的消息长度,不包含保存消息类型的哪个long int
msgtyp:可以实现接收优先级的简单形式。
若msgtyp==0,则返回队列的第一条消息
若msgtyp > 0,返回队列第一条类型等于msgtyp的消息
若msgtyp < 0,返回队列第一条类型小于等于msgtyp绝对值的消息,并且是满足条件的消息类型最小的消息
msgflg: 控制着队列中没有相应类型的消息可供接收时将要发生的事
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
msgflg=0表示阻塞
msgtyp>0 且 msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息
一个进程间通信的例子:
客户端发送一条消息,服务器端接收从客户端发送的消息;服务器向客户端发送一条消息(应答),客户端接收消息。
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#define PATHNAME "msg.tmp" //用msg.tmp文件的索引结点号和Proj_id 来生成键值
#define Proj_id 0x20 //可以随意指定
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
struct msgbuf{
long mtype;
char mtext[1024];
};
int CreateMsgQueue();
int GetMsgQueue();
int DestroyMsgQueue(int msqid);
int SendMsg(int msqid,int who,char *msg);
int RecvMsg(int msqid,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 msqid = msgget(key,flags);
if(msqid < 0){
perror("msgget");
//失败返回-1
}
return msqid;
}
int CreateMsgQueue(){
return CommMsgQueue(IPC_CREAT|IPC_EXCL|0666);
//参数含义:创建消息队列,如果该消息队列存在,就返回错误,
//权限为可读可写
}
int GetMsgQueue(){
return CommMsgQueue(IPC_CREAT);
}
int DestroyMsgQueue(int msqid){
if(msgctl(msqid,IPC_RMID,NULL) < 0){
perror("msgctl");
return -1;
}
return 0;
}
int SendMsg(int msqid,int who,char* msg){
struct msgbuf buf;
buf.mtype = who;
strcpy(buf.mtext,msg);
if(msgsnd(msqid,(void*)&buf,sizeof(buf.mtext),0) < 0){
perror("msgsnd");
return -1;
}
return 0;
}
int RecvMsg(int msqid,int recvType,char out[]){
struct msgbuf buf;
if(msgrcv(msqid,(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 msqid = CreateMsgQueue();
char buf[1024];
while(1){
buf[0] = 0;
RecvMsg(msqid,CLIENT_TYPE,buf);
printf("client: %s\n",buf);
printf("Please Enter: ");
fflush(stdout);
//从标准输入中读,fd=0
ssize_t read_size = read(0,buf,sizeof(buf)-1);
if(read_size > 0){
buf[read_size] = '\0';
SendMsg(msqid,SERVER_TYPE,buf);
printf("send done,wait recv...\n");
}
}
DestroyMsgQueue(msqid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int msqid = GetMsgQueue();
char buf[1024];
while(1){
buf[0] = 0;
printf("Please Enter# ");
fflush(stdout);
ssize_t read_size = read(0,buf,sizeof(buf)-1);
if(read_size > 0){
buf[read_size] = '\0';
SendMsg(msqid,CLIENT_TYPE,buf);
printf("send done,wait recv...\n");
}
RecvMsg(msqid,SERVER_TYPE,buf);
printf("server# %s\n ",buf);
}
return 0;
}
Makefile
all:client server
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
运行结果:客户端与服务器之间进行的通信。
ftok参考自:https://blog.csdn.net/u013485792/article/details/50764224