进程间通信(1)
进程间通信是指进程之间的消息交换。
进程间通信的实质是多个进程看到同一块资源.
本文将介绍Linux系统下关于进程间通信的
- 管道
- 消息队列
- 共享内存
- 信号量
管道
所谓管道,是指用于连接一个读进程一个写进程以实现它们之间通信的一个共享文件
匿名管道
创建匿名管道函数
参数:pipifd文件描述符数组 pipefd[0] 读端 pipefd[1]写端
成功返回0 失败返回错误
利用管道实现父子进程间通信
父进程向子进程发送hello
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void Test()
{
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork:");
exit(2);
}
else if(pid == 0)
{
close(fd[1]); //关闭写端
char buf[100] = {0};
read(fd[0], buf, 100);
printf("%s\n", buf);
printf("pid = %d\n", pid);
exit(0);
}
else
{
close(fd[0]); //关闭读端
write(fd[1], "hello\n", 5);
printf("father pid = %d\n", pid);
}
}
int main()
{
Test();
return 0;
}
由于子进程是由父进程创建出来,即子进程也就能看到pipe创建出来的文件,利用read和write函数就能够实现通信。
匿名管道通信的特点:
- 只能具有亲缘关系的进程才能够通信
- 单向通信
- 基于字节流
- 管道的生命周期随进程
- 管道自带同步机制
命名管道
创建命名管道函数
参数1:文件名 参数2: 文件的权限
利用命名管道实现两进程间通信
//makefile
.PHONY:all
all:server client
server:serverPipe.c
gcc -o $@ $^
client:clientPipe.c
gcc -o $@ $^
.PHONY: clean
clean:
rm f server client
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int pf = mkfifo("p", 0644);
if(pf < 0)
{
perror("mkfifo");
return 1;
}
int fp = open("p",O_RDONLY);
if(fp < 0)
{
perror("open");
return 2;
}
char buf[1024] = {0};
while(1)
{
memset(buf, 0x0, sizeof(buf));
ssize_t r = read(fp, buf, sizeof(buf)-1);
printf("wait...:\n");
if(r > 0)
{
printf("client: %s\n", buf);
}
else if(r == 0)
{
printf("client exit\n");
return 3;
}
else
{
perror("read:");
return 2;
}
}
close(fp);
return 0;
}
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fp = open("p", O_WRONLY);
if(fp < 0)
{
perror("open");
return 1;
}
char buf[1024] = {0};
while(1)
{
memset(buf, 0x0, sizeof(buf));
printf("please Enter:\n");
int rd = read(0, buf, sizeof(buf)-1);
if(rd > 0)
{ ssize_t r = write(fp, buf, sizeof(buf)-1);
if(r < 0)
{
perror("write");
return 3;
}
}
else
{
perror("read:");
return 4;
}
}
close(fp);
return 0;
}
命名管道就能实现任意两个间进程通信
消息队列
消息队列是指操作系统将进程用于通信的信息储存的一个容器。
这个容器由操作系统维护,需要通信的进程通过操作系统看到这个队列。
进程可以在这个队列中读信息和发信息。
消息队列提供了一个进程向别一个进程发送具有类型的数据块的方法。(具有类型是由于需要知道这个数据块那个进程发送的)
每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上(MSGMNI)
消息队列函数
ftok函数
功能:得到一个id
key_t ftok( char * fname, int id )
参数: 可以任意取(来得到一个key值)
msgget函数
功能:用来创建和访问一个消息队列
原型
int msgget(key_t key, int msgflg);
参数
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1
msgctl函数
功能:消息队列的控制函数
原型
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数
msqid: 由msgget函数返回的消息队列标识码
cmd:是将要采取的动作
返回值:成功返回0,失败返回-1
msgsnd函数
功能:把一条消息添加到消息队列中
原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息,
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。
返回值:成功返回0;失败返回-1
说明
1.消息结构在两方面受到制约:
首先,它必须小于系统规定的上限值;
其次,它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型
2.消息结构参考形式如下:
struct msgbuf {
long mtype;
char mtext[1];
}
msgrcv函数
功能:是从一个消息队列接收消息
原型
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1
利用消息队列实现进程间通信
//Makefile
.PHONY:all clean
all: server client
server: server.c comm.c
gcc -o $@ $^
client: client.c comm.c
gcc -o $@ $^
clean:
rm -f server client
//comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PHAH "."
#define FTOK_ID 0x777
#define CLIENT_TYPE 1
#define SERVER_TYPE 2
struct Buf_msg
{
long type;
char text[100];
};
int createmsgQueue();
int getmsgQueue();
int destroymsgQueue(int msgid);
int sentMsg(int msgid, char* buf, int _type);
int recvMsg(int msgid, char* buf,int _type);
#endif // _COMM_H_
//comm.c
#include "comm.h"
static int comm(int flag)
{
key_t key = ftok(PHAH,FTOK_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, flag);
if( msgid < 0)
{
perror("msgget");
return -2;
}
return msgid;
}
int createmsgQueue()
{
return comm(IPC_CREAT|IPC_EXCL|0666);
}
int getmsgQueue()
{
return comm(IPC_CREAT);
}
int destroymsgQueue(int msgid)
{
if( msgctl(msgid, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sentMsg(int msgid, char* buf, int _type)
{
struct Buf_msg buff;
buff.type = _type;
strcpy(buff.text, buf);
if(msgsnd(msgid,(void*)&buff, sizeof(buff.text), 0) < 0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid, char* buf, int _type)
{
struct Buf_msg buff;
ssize_t size = 0;
size = msgrcv(msgid,(void*)&buff, sizeof(buff.text), _type, 0);
if(size < 0)
{
perror("msgrcv");
return -1;
}
strncpy(buf, buff.text, size);
buf[size] = '\0';
return 0;
}
//server.c
#include "comm.h"
int main()
{
int msgid = createmsgQueue();
char buf[100] = {0};
while(1)
{
buf[0] = 0;
printf("wait recv:\n");
recvMsg(msgid, buf, CLIENT_TYPE);
printf("client : %s\n", buf);
fflush(stdout);
if(strcasecmp("quit", buf) == 0)
{
printf("cilent exit now!\n");
break;
}
printf("please Entry:");
fflush(stdout);
int size = read(0, buf, sizeof(buf)-1);
if(size < 0)
{
perror("read");
return 1;
}
else
{
buf[size-1] = '\0';
sentMsg(msgid, buf, SERVER_TYPE);
}
}
destroymsgQueue(msgid);
return 0;
}
//client.c
#include "comm.h"
int main()
{
int msgid = getmsgQueue();
char buf[100] = {0};
while(1)
{
buf[0] = 0;
printf("please say:");
fflush(stdout);
int size = read(0, buf, sizeof(buf)-1);
if(size < 0)
{
perror("read");
return 1;
}
else
{
buf[size-1] = '\0';
sentMsg(msgid, buf, CLIENT_TYPE);
if(strcasecmp("quit", buf) == 0)
{
printf("client quit now!\n");
break;
}
printf("wait recv:\n");
}
recvMsg(msgid, buf, SERVER_TYPE);
printf("server : %s\n", buf);
}
return 0;
}
使用ipcs -q 可以看到消息队列
使用ipcsrm -q id 可以删除信息队列
消息队列的生命周期是随内核,与管道不同。
进程间通信(2)