-------根据网络视频整理
1、进程间通信分为:
1.1同一台主机上的进程间通信;
1)管道:
- 匿名管道
- 命名管道
2)XSI ---->SysV
- Message Queues//消息队列
- Semaphore array//信号量数组
- Shared Memory //共享内存
1.2不同主机上的进程间通信;
- 网络套接字socket
2、各种进程间通信方式
2.1管道
是内核提供的,单工(一端为读端,一端为写端),具有自同步机制(迁就慢的一方,读或写要去等慢的那一方有写的内容或者读的那一方)。
1)匿名管道
磁盘上看不到匿名管道名(即ls 是看不到的),若两个进程没有血缘关系,是找不到同一个匿名管道来通信的,即只有有血缘关系的两个进程才能用匿名管道通信;
pipe()函数
#include <unistd.h>
int pipe(int pipefd[2]);
参数:会回填两个文件描述符
pipefd[0] 作为读端
pipefd[1] 作为写端
成功返回0,失败返回-1,设置errno.
因为是应用在有血缘关系的两个进程间通信;所以可以父写子读,或者父读子写。
下例:父写子读,故父的读fd关上;子的写fd关上。
先pipe,后fork,因为fork之后便创建了子进程,拿到了父进程中的pipe中的两个文件描述符。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE 1024
int main()
{
int pd[2];
pid_t pid;
int len;
char buf[BUFSIZE];
if(pipe(pd)<))
{
perror("pipe()");
exit(1);
};
pid = fork();
if(pid<0)
{
perror("fork()");
exit(1);
}
if(pid == 0)//子进程
{
close(pd[1]);//子进程的写端不用,先关闭
len = read(pd[0],buf,BUFSIZE);
write(1,buf,len);
close(pd[0]);//子进程的读端读完后关闭
exit(0);
}
else{//父进程
close(pd[0]);//父进程的读端不用,先关闭
write(pd[1],"hello!",6);
close(pd[1]);//父进程的写端写完后关闭
wait(NULL);//收尸
exit(0);
}
//exit(0);
}
实质:是将文件打开,给了一个fd或者FILE*,
2)命名管道
文件名为p的文件---通过ls能看到的(是命名管道,匿名管道是看不到的)
不具有血缘关系的两个进程也能通信,管道其实就是当前存在的一个文件。
mkfifo()
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
参数:
pathname:创建的命令管道的名称;
mode:管道权限
返回值:
成功返回0;
失败返回-1,设置errno.
2.2XSI--->SysV (system 5)
三种SysV的实现都以如下步骤:
- ftok //有血缘关系的进程,此函数可以不调用,只要获取匿名的IPC即可。形如 shmid = shmget(IPC_PRIVATE,MEMSIZE,0600);
- xxget
- xxop //例如:直接man msgop 就直接能找到 msgrcv 和msgsend
- xxctl
通过ipcs(ipc show)命令查看有以下类型:
既可以用在有血缘关系也可用在没血缘关系的进程间通信,有血缘关系的好办,fork之后就拿到了。
没有血缘关系的进程间是通过上图中的key值。
key: 使用ftok();函数 (f to key)产生同一个key ,然后以下三种机制都对通过key值产生id号,msqid,semid,shmid。
即:同一个key值创建实例,产生同一个id,通信双方就可以通过该id通信。
key_t ftok(const char *pathname,int proj_id);
以下三中机制用到的函数都是形如:
XXXget -----创建
XXXop ------操作,如读写
XXXctl -------控制,如,销毁
将XXX替换成msg sem shm 可以通过man手册查看。
2.2.1Message Queues//消息队列
消息队列为双工的。
-----msgget():
key为使用ftok获得的值。
返回值成功为ID号。失败为-1,并设置errno.
使用man msgop可以查看如下megop分为如下两个操作。
------megrcv
int msgrcv(int msgid, void *msgp, size_t msg_sz, long msgtyp, int msgflg);
参数: msgid创建的id
msgp,收到的内容存放的首地址,
msgsz,收到的内容的大小
msgtyp:是否要挑消息来收,即包的编号。
msgflg:特殊要求,比如nowiat,没有消息立即返回。
msgp指向的是如上的msgbuf大概的结构体格式,即必须得有long mtype(>0);
char mtext[1];则是说是变参的,并不是说数组中只有一个数据。,并不是说只能是一个数组,可以是任意类型的数据,例如,可以如下方式,
也就是,结构体名可以变,结构体中除了mtype必须有,其他的都可以任意更改。
返回值:成功:返回接收到的真正的字节个数
失败,返回-1,设置errno.
------megsnd
int msgsend(int msgid, const void *msgp, size_t msgsz, int msgflg);
参数:
msgid :id
msgp:待发送的数据
msgsz:数据长度
msgflg:特殊要求
-------msgctl
cmd有很多,根据cmd的不同,buf是否需要传参
//协议proto.h
#ifndef PROTO_H__
#defifin PROTO_H__
#define KEYPATH "/etc/services" //任意找的一个文件
#define KEYPROJ 'g' //保证一定是个整型,因为是ascii码值
#define NAMESIZE 32
struct msg_st
{
long mtype;//这是格式要求的类型。
char name[NAMESIZE];
int math;
int chinese;
};
#endif
//发包的一方一定是主动端
//发送进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h?
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include "proto.h"
int main()
{
key_t key;
struct msg_st sbuf;
int msgid;
key = ftok(KEYPATH,KEYPROJ);
if(key<0)
{
perror("ftok()");
exit(0);
}
msgid = msgget(key,0);//后运行一方不需要再CREAT了。
if(msgid<0)
{
perror("msgget");
exit(0);
}
sbuf.mtype = 1;
strcpy(sbuf.name,"Alan");
sbuf.math = rand()%100;
sbuf.chinese = rand()%100;
if(msgsnd(msgid ,&sbuf,sizeof(sbuf)-sizeof(long),0)<0)
{
perror("msgsnd()");
exit(1);
}
//msgctl(); //此处不需要,因为并没有创建,所以并不用销毁。
puts("ok");
exit(0);
}
//被动端一定是先收包的一方,故被动端一定是先运行的一方。故,接收方一定要有IPC_CREAT权限。
send方可以有IPC_CREAT也可以没有。
注意:msgrcv(msgid,&rbuf,sizeof(rbuf)-sizeof(long),0,0);的写法,大小是实际所传数据的大小,故要减去一个long的大小。
//接收方进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "proto.h"
int main()
{
key_t key;
msg_t msgid;
struct msg_st rbuf;
key = ftok(KEYPATH,KEYPROJ);
if(key<0)
{
perror("ftok()");
exit(0);
}
msgid = msgget(key,IPC_CREAT|0600);//只要有IPC_CREAT,就一定有后面的权限,权限可以根据幻定义
if(msgid<0)
{
perror("msgget");
exit(0);
}
while(1)
{
if( msgrcv(msgid,&rbuf,sizeof(rbuf)-sizeof(long),0,0)<0)
{
perror("msgrcv");
exit(0);
}
printf("NAME= %s\n",rbuf.name);
printf("MATH=%s\n",rbuf.math);
printf("CHINESE=%s\n",rbuf.chinese);
}
msgctl(msgid,IPC_RMID,NULL);//销毁实例,故三参为NULL
exit(0);
}
运行该接收进程后,
在终端中查看命令 ipcs发现:会看到key值,和id,以及权限perms 600
使用ipcrm -q 0 //可以kill掉上面的消息队列。
2.2.2Semaphore array//信号量数组
//创建
int semid;
semid = semget(IPC_PRIVATE,1,0600);//父子进程间通信,可直接使用IPC_PRIVATE,使用IPC_PRIVATE表示创建,必须有权限,如0600
if(semid<0)
{
perror("semget()");
exit(1);
}
//初始化
if(semctl(semid,0,SETVAL,1)<0)
{
perror("semctl()");
exit(1);
}
//销毁
semctl(semid,0,IPC_RMID);
2.2.3Shared Memory//共享内存 ,这个与 mmap函数实现的功能类似
key 为同一个key值,返回值int 即为id
shmget()
shmop();
shmctl();
shmat---->把共享内存映射过来。
shmid:id
shmaddr:映射到当前的哪块地址上,一般置空,因为当我们不确定哪块内存地址可用的时候,值为NULL,就会自动给我们找到当前进程空间可以使用的内存地址。
shmflg:操作权限。无特殊要求的话,一般设置为0
返回值:
成功:真正映射过来的地址位置。
失败:返回 (void*)-1,设置errno
shmdt----->把共享内存解除映射。delet
//实现子进程读,父进程写的功能
注意:只有在父进程中销毁实例shmctl(),子进程中没有.因为是谁创建谁销毁,谁打开,谁释放。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define MEMSIZE 1024
int main()
{
//ftok();//有血缘关系的进程,我们不关心key值,可以不用,使用匿名IPC。即shmget中使用IPC_PRIVATE
char *ptr = NULL;
shmid = shmget(IPC_PRIVATE,MEMSIZE,0600);
if(shmid<0)
{
perror("shmget()");
exit(1);
}
pid = fork();
if(pid<0)
{
perror("fork()");
exit(1);
}
if(pid == 0) //子进程
{
ptr = shmat(shmid,NULL0);
if(ptr = (void*)-1)
{
perror("shmat()");
exit(1);
}
strcpy(ptr,"hello");
shmdt(ptr);//子进程解除映射
exit(0);
}
else //父进程
{
wait(NULL);//收尸,因为没有用到阻塞的系统调用,放在这能确保子进程写完。
ptr = shmat(shmid,NULL,0);
if(ptr== (void*)-1)
{
perror("shmat");
exit(1);
}
puts(ptr);
shmdt(ptr);
shmctl(shmid,IPC_RMID,NULL);//因为是销毁,故第3个参数为NULL
exit(0);
}