Linux 3.进程间通信(IPC)
进程间通信:
不同进程之间传数据、交换信息。
进程间方式:
- 管道(无名管道和命名管道)
- 消息队列
- 信号量
- 共享内存
- socket
- steams等。
其中socket 和 steams 支持不同主机上的两个进程通信。
pipe 管道(无名管道)
通常指无名管道,是Unix 系统 IPC最古老的形式。
头文件及原型
#include <unistd.h>
int pipe(int pipefd[2]);
特点
- 半双工,只能要么接收数据,要么传输数据。一个读,一个写。
- 只能用于具有亲缘关系的进程通信,比如:父子进程。
- 可以看成是特殊文件,它读写可以使用 read、write等函数,但它不是普通文件和系统文件,它是只存在内存中的。
pipe 示例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
int fd[2];
char readBuf[128]={0};
if(pipe(fd)==-1)
{
printf("pipe fail!\n");
}
else
{
printf("pipe successfully!\n");
}
int retpid=fork();
if(retpid>0)
{
sleep(3);
printf("this is father print...\n");
close(fd[0]);
write(fd[1],"hello word",strlen("hello word"));
wait(NULL);
}
else if(retpid==0)
{
printf("this is child print...\n");
close(fd[1]);
read(fd[0],readBuf,128);
printf("readBuf=%s\n",readBuf);
exit(0);
}
else
{
printf("fork fail!\n");
}
return 0;
}
运行结果:
FIFO(有名管道)
有名管道也叫命名管道,在文件系统目录中存在一个管道文件。
管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。
管道文件的创建
- 在shell中使用 mkfifo 命令
mkfifo filename
- mkfifo 函数 (在代码中使用其创建管道文件)
mkfifo 头文件及原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename,mode_t mode);
mkfifo 参数
pathname:文件名/文件路径
mode:权限 比如 0600 就是可读可写。
mkfifo 返回值
成功:则返回 0
失败:返回 -1 , 错误原因存于 errno 中.
mkfifo 创建有名管道
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
int mk=mkfifo("./fifo",0600);
if(mk==0)
{
printf("mkfifo successfully!\n");
}
else if(mk==-1)
{
printf("mkfifo fail!\n");
perror("why");
}
return 0;
}
运行结果:
注意:如果已经存在这个文件,创建会失败的。
FIFO 使用示例
fifoRead.out
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
char readBuf[32]={0};
int mk=mkfifo("./fifo",0600);
if(mk==0 && errno!=EEXIST)
{
printf("mkfifo successfully!\n");
}
int fd=open("./fifo",O_RDONLY);
int n_read=0;
while(1)
{
n_read=read(fd,readBuf,32);
printf("read %d from writeFifo,readBuf=\n%s\n",n_read,readBuf);
memset(readBuf,'\0',sizeof(readBuf));
if(n_read==0)
{
break;
}
}
close(fd);
return 0;
}
fifoWrite.out
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
char* str="Yinyuer is a pretty girl!";
int mk=mkfifo("./fifo",0600);
if(mk==0 && errno!=EEXIST)
{
printf("mkfifo successfully!\n");
}
int fd=open("./fifo",O_WRONLY);
int i=0;
for(i=0;i<5;i++)
{
write(fd,str,strlen(str));
sleep(3);
}
close(fd);
return 0;
}
运行结果:
消息队列
消息队列是消息的链接表,是存在内核中的,一个消息队列是由一个标识符(ID号)来标识。
ftok
ftok 功能
系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok 头文件及原型
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok( const char * fname, int id );
ftok 参数
fname:指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
key = ftok(".", 1);
这样就是将fname设为当前目录。
id:子序号。虽然是int类型,但是只使用8bits(1-255)。
ftok 返回值
成功:返回key_t值(即IPC 键值)
出错:-1,错误原因存于error中
msgget
msgget 功能
用于创建一个新的或打开一个已经存在的消息队列,此消息队列与key相对应。
msgget 头文件及原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
msgget 参数
key: 函数ftok的返回值(ID号)或IPC_PRIVATE。
msgflag:
IPC_CREAT:创建新的消息队列。
IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。
IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。返回值: 调用成功返回队列标识符,否则返回-1.
msgget 返回值
成功:返回消息队列的标识符
出错:-1,错误原因存于error中
msgrcv
msgrcv 功能
从标识符为msqid的消息队列读取消息并存于msgp中,读取后把此消息从消息队列中删除。
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);
msgrcv 参数
msqid:消息队列的识别码(ID)。
msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下。
sgsz:消息的大小。
msgtyp: 消息类型,这个typ和结构体msgbuf内的msgtyp是一样的,
如果需要接受到想要的消息,则msgsnd发送的结构体中msgtyp应该和msgrcv的msgtyp相同。
msgflg:这个参数依然是控制函数行为的标志,取值可以是:0,表示忽略。
在 msgrcv 函数中,最后一个参数 flag 控制着接收消息的行为。这个参数的值可以是 0 或者一些其他的标志,它们影响着函数的阻塞行为以及消息的接收方式。
当 flag 参数为 0 时,表示接收消息的行为是阻塞的。也就是说,如果当前消息队列中没有符合条件的消息可供接收,msgrcv 函数会一直等待,直到有合适的消息到达为止。这样的阻塞模式通常用于需要等待消息到达的场景,使得进程能够在没有消息时挂起,直到有消息可用。
除了 0 之外,还有其他的一些标志可以传递给 flag 参数,例如:
IPC_NOWAIT: 表示非阻塞模式,如果没有符合条件的消息可供接收,函数会立即返回,并返回一个错误码(例如 -1),而不会等待消息到达。
MSG_NOERROR: 表示在接收消息时,如果消息长度超过了缓冲区的大小,则截断消息而不会产生错误。
其他一些标志,具体取决于系统的实现和特性。
因此,当 flag 参数为 0 时,表示 msgrcv 函数以阻塞模式接收消息,直到有消息到达为止。
msgrcv 返回值
成功:实际读取到的消息数据长度
出错:-1,错误原因存于error中
msgsnd
msgsnd 功能
将msgp消息写入到标识符为msqid的消息队列。
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);
msgsnd 参数
msqid:消息队列标识符
msgp:发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下:
struct s_msg{ /*msgp定义的参照格式*/
long type; /* 必须大于0,消息类型 */
char mtext[256]; /*消息正文,可以是其他任何类型*/
} msgp;
msgsz:要发送消息的大小,不含消息类型占用的4个字节,即mtext的长度
msgflg:
0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回
IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。
msgsnd 返回值
成功:0
出错:-1,错误原因存于error中
msgctl
msgctl 功能
获取和设置消息队列的属性。
msgctl 头文件原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf)
msgctl 参数
msqid:消息队列标识符
cmd:
IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。
IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
IPC_RMID:从系统内核中移走消息队列。
buf:消息队列管理结构体,请参见消息队列内核结构说明部分。
msgctl 返回值
成功:0
出错:-1,错误原因存于error中
消息队列 示例
msgrcv.out
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key=ftok(".",66);
printf("key=%x\n",key);
// int msgget(key_t key, int msgflg);
int msgId=msgget(key,IPC_CREAT|0777);
if(msgId==-1)
{
printf("msgget fail!\n");
}
struct msgbuf readBuf;
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
printf("msg from msgsnd:\nreadBuf=%s\n",readBuf.mtext);
struct msgbuf sendBuf={666,"Yeah!I agree that!"};
msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
msgsnd.out
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key=ftok(".",66);
printf("key=%x\n",key);
// int msgget(key_t key, int msgflg);
int msgId=msgget(key,IPC_CREAT|0777);
if(msgId==-1)
{
printf("msgget fail!\n");
}
struct msgbuf writeBuf={888,"Yinyuer is a pretty girl!"};
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msgId,&writeBuf,sizeof(writeBuf.mtext),0);
printf("send over!\n");
struct msgbuf readBuf;
msgrcv(msgId,&readBuf,sizeof(readBuf),666,0);
printf("return from msgrcv:\n%s\n",readBuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
运行结果: