进程间通信(一)
进程间通信的目的
1、数据传输
一个进程将数据发送给另一个进程
2、资源共享
多个进程之间共享相同的资源
3、通知事件
第一个进程需要向另一个进程发送消息,通知发生的事件
4、进程控制
有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够知道它的状态改变
进程间通信的本质就是两个互不相干的进程看到同一份资源,该资源一定是操作系统提供的。
进程间通信分类
管道
* 匿名管道
* 命名管道
System V IPC
* System V 消息队列
* System V 共享内存
* System V 信号量
POSIX IPC
* 消息队列
* 共享内存
* 信号量
* 互斥量
* 条件变量
* 读写锁
一、管道
本质上,管道也是一种文件,我们把一个进程连接到另一个进程的数据流称为管道
*匿名管道*
具有亲缘关系的进程进行数据间通信
我们创建一个子进程,通过管道实现子进程写的数据父进程可以读出来
1、父进程创建管道
2、父进程fork出子进程
3、父进程关闭写端、子进程关闭读端
代码实现:
匿名管道特点:
1、只能用于具有亲缘关系的进程
2、进程退出,管道释放,所以管道的生命周期随进程
3、内核会对管道操作进行同步与互斥(管道自带同步 )
4、单向传输,半双工的
5、管道是基于字节流的
四种情况:
* 写段关闭,读端一直在读,直到0值
* 写端不写也不关闭,读端一直在读
* 写端一直写,读端不读,管道满,等待读
* 写端一直写,读端关闭,写端被系统终止
命名管道
可以用于没有亲缘关系的进程
创建一个命名管道
方法一:
方法二:
匿名管道与命名管道的区别:
* 匿名管道由pipe函数创建并打开
* 命令管道由mkfifo函数创建,打开用open
* 匿名管道与匿名管道的唯一区别在于它们创建与打开方式不同
用命名管道实现客户端与服务器端之间的通信
server.c(服务器端)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
do{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
int main()
{
umask(0);
if(mkfifo("ppipe",0644) == -1)
{
ERR_EXIT("mkfifo");
}
int fd = open("ppipe",O_RDONLY);
if(fd == -1){
ERR_EXIT("open");
}
char buf[1024];
while(1){
buf[0] = 0;
ssize_t s = read(fd, buf, sizeof(buf)-1);
if(s > 0){
buf[s-1] = 0;
printf("client : %s\n",buf);
}else if(s == 0){
printf("client quit,exit now\n");
}else{
ERR_EXIT("read");
}
}
close(fd);
return 0;
}
client.c(客户端)
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
int main()
{
int fd = open("ppipe",O_WRONLY);
if(fd == -1)
{
ERR_EXIT("open");
} 22
char buf[1024];
while(1){
buf[0] = 0;
printf("Please Enter:");
fflush(stdout);
ssize_t s= read(0, buf, sizeof(buf)-1);
if(s > 0){
buf[s] = 0;
write(fd, buf, strlen(buf));
}else if(s <= 0){
ERR_EXIT("read");
}
}
close(fd);
return 0;
}
打开两个终端,客户端输入一条信息,服务器端接受到信息后在屏幕上显示。
二、消息队列
* 一个进程向另一个进程发送有类型数据块的方法
* 可以实现相互通信的功能
* 进程的生命周期随内核
* 消息队列是基于消息的
系统为每一个IPC对象保存一个ipc_perm结构体,该结构体说明了IPC对象的权限和所有者,每一个版本的内核有不同的ipc_perm结构成员,最重要的是key成员,表示队列的编号。
消息队列结构
从第一个成员,我们可以知道,消息队列中也含有队列的编号。
因为消息队列的生命周期随内核,所以我们需要手动删除一个消息队列
ipcs -q:显示IPC资源
ipcrm -q [消息队列的key值]:删除IPC资源
要想实现一个消息队列,首先认识一些函数
1、msgget:创建和访问一个消息队列
第二个参数:
IPC_CREAT|IPC_EXCL:如果没有该消息队列,则创建一个;如果有,则出错返回。如此创建的消息队列是全新的。
2、ftok函数
说明:
* ftok函数根据路径名,提取文件信息,再根据这些文件信息及proj_id合成key,该路径可以随便设置
* pathname必须存在,和文件的权限无关
* proj_id是随意设置的,在UNIX系统上,取值为1~255
3、msgsnd函数:把一条消息添加到消息队列中
msgrcv函数:从一个消息队列接受消息
代码实现:
makefile
.PHONY:all
all:client server
client:client.c comm.c
gcc -o [email protected] $^
server:server.c comm.c
gcc -o [email protected] $^
.PHONY:clean
clean:
rm -f client server
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 "."
#define PROJ_ID 0x664
#define SERVER_TYPE 0
#define CLIENT_TYPE 200
struct msgbuf{
long mtype;
char mtext[1024];
};
int createMsgQueue();
int getMsgQueue();
int destroyMsgQueue(int msgid);
int sendMsg(int msgid, int who, char *msg);
int recvMsg(int msgid, int recvType, char out[]);
#endif //_COMM_H_
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 msgid = msgget(_key,flags);
if(msgid < 0){
perror("msgget");
}
return msgid;
}
int createMsgQueue()
{
return commMsgQueue(IPC_CREAT|IPC_EXCL|0x664);
}
int getMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
int destoryMsgQueue(int msgid)
{
if(msgctl(msgid, IPC_RMID, NULL)<0){
perror("msgctl");
return -1;
}
return 0;
}
int sendMsg(int msgid, int who,char *msg)
{
struct msgbuf buf;
buf.mtype = who;
strcpy(buf.mtext,msg);
if(msgsnd(msgid, (void*)&buf,sizeof(buf.mtext),0)<0){
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid, int recvType, char out[])
{
struct msgbuf buf;
if(msgrcv(msgid,(void*)&buf, sizeof(buf.mtext),recvType, 0)<0){
perror("msgrcv");
return -1;
}
strcpy(out,buf.mtext);
return 0;
}
client.c
#include "comm.h"
int main()
{
int msgid = getMsgQueue();
char buf[1024];
while(1){
buf[0] = 0;
printf("Please Enter:>");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf));
if(s>0){
buf[s-1] = 0;
sendMsg(msgid, CLIENT_TYPE, buf);
printf("send done,wait...\n");
}
recvMsg(msgid, SERVER_TYPE, buf);
printf("server:>%s\n", buf);
}
return 0;
}
server.c
#include "comm.h"
int main()
{
int msgid = createMsgQueue();
char buf[1024];
while(1){
buf[0] = 0;
recvMsg(msgid, CLIENT_TYPE, buf);
printf("client:>%s\n",buf);
printf("Please Enter:>");
fflush(stdout);
ssize_t s = read(0, buf,sizeof(buf));
if(s > 0){
buf[s-1] = 0;
sendMsg(msgid, SERVER_TYPE, buf);
printf("send done, wait...\n");
}
}
destoryMsgQueue(msgid);
return 0;
}
运行后,可以看到实现了进程间消息的互相传输。
可以查看当前消息队列的信息,以及删除消息队列
共享内存和信号量会在接下来的博客中介绍到,谢谢关注哦!
上一篇: 经典-怀-孕-小笑话 看后差点笑破肚子了
下一篇: jQuery知识梳理20190818