linux C 四、进程间通信(有名管道、共享内存、消息队列)

一、有名管道
对应管道文件,可用于任意进程之间进行通信,打开管道时可指定读写方式,通过文件IO操作,内容存放在内存中。

1.1 API
#include <unistd.h>
#include <fcntl.h>
int mkfifo(const char *path, mode_t mode);

成功时返回0,失败时返回EOF
path 创建的管道文件路径
mode 管道文件的权限,如0666

1.2写操作demo
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
int main(){
int re;
int fd;
char buf[32];
unlink("/myfifo"); //删除myfifo
re = mkfifo("/myfifo",0666);
if(re==-1){
perror(“mkfifo”);
return -1;
}
fd = open("/myfifo",O_WRONLY);
if(fd<0){
perror(“open”);
return -1;
}
strcpy(buf,“fifo write test”);
while(1){
write(fd,buf,strlen(buf));
sleep(1);
}
}

1.3 读操作
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
int main(){
int re;
int fd;
char buf[32];
fd = open("/myfifo",O_RDONLY);
if(fd<0){
perror(“open”);
return -1;
}
while(1){
memset(buf,0,32);
read(fd,buf,32);
printf("%s\n",buf);
sleep(1);
}
}

注释 :
1、管道面试:
(1)、有名管道和无名管道的区别?
有名管道可以在任意两个进程间通信,无名管道只能在父子进程间通信;

(2)、写入管道文件的数据在内存上还是磁盘上?
无论是有名管道还是无名管道,管道文件的数据在内存存放;
p 管道文件 存放在内存中 不可能在磁盘中存储数据 ;临时存放;断电重启关闭程序会丢失数据;
衍生的一个问题:为什么不能通过普通文件实现进程间的通信?
普通文件存放在磁盘上,读写速度慢;磁盘(永久存储)<<内存(临时存储)<<寄存器(速度);磁盘>>内存>>寄存器(容量);

(3)、管道通信方式是单工、半双工还是全双工?
管道通信方式是半双工;
单工:数据传输只支持数据在一个方向上传输;在同一时间只有一方能接受或发送信息,不能实现双向通信,举例:电视,广播。
半双工:数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;在同一时间只可以有一方接受或发送信息,可以实现双向通信。举例:对讲机。
全双工:数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力;在同一时间可以同时接受和发送信息,实现双向通信,举例:电话通信。

(4)、管道实现
创建一个管道文件,系统为之分配一块内存空间,管道文件有头指针和尾指针,头指针写入数据,尾指针读取数据;当尾指针赶上头指针时,管道数据为空或管道数据全部读完了;当尾指针超过头指针时,管道数据写满了,循环到第一个写入的管道数据将其覆盖;头尾指针的距离就是当前管道的数据;

(5)、进程间通信的方式
进程间通信:管道、(信号量、共享内存、消息队列)常考、套接字;

2、管道情况(不会考)
open阻塞:只有一个程序运行时会发生阻塞;
read阻塞:写端没关闭读取不到数据时即管道为空read读端会发生阻塞,write写端关闭read返回0,不再阻塞;
write阻塞:管道写满,write阻塞;
关闭read读端,write写端写入数据会发生异常,收到信号默认终端中断;程序异常退出,多半是程序收到了信号;

3、有名管道
mkfifo命令:创建一个管道文件;
mkfifo库函数:创建管道;

4、无名管道
pipe系统调用函数:创建无名管道;
头文件:#include <unistd.h>
int pipe(int pipefd[2]);
pipe函数的参数是一个由文件描述符组成的数组,fd[0]默认为读,fd[1]默认为写;

5、dup2系统调用函数:实现重定向;
头文件:#include <unistd.h>
int dup2(int oldfd, int newfd);
第一个参数是旧的文件描述符;
第二个参数是新的文件描述符;
旧的文件描述符会复制给新的文件描述符将其覆盖,从而改变输出位置,实现重定向;

二、共享内存 (这里看下后来写的demo去操作这个内存地址,正常存数据)
1.1 API
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *path, int proj_id);
成功时返回合法的key值,失败时返回EOF
path 存在且可访问的文件的路径
proj_id 用于生成key的数字,范围1-255。

使用步骤5步
1)创建/打开共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg);

成功时返回共享内存的id,失败时返回EOF
key 和共享内存关联的key,IPC_PRIVATE 或 ftok生成
shmflg 共享内存标志位 IPC_CREAT|0666

2)映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

成功时返回映射后的地址,失败时返回(void *)-1
shmid 要映射的共享内存id
shmaddr 映射后的地址, NULL表示由系统自动映射
shmflg 标志位 0表示可读写;SHM_RDONLY表示只读

3)读写共享内存
4)撤销共享内存映射
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *shmaddr);

成功时返回0,失败时返回EOF
不使用共享内存时应撤销映射
进程结束时自动撤销

5)删除共享内存对象
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

成功时返回0,失败时返回EOF
shmid 要操作的共享内存的id
cmd 要执行的操作 IPC_STAT IPC_SET IPC_RMID
buf 保存或设置共享内存属性的地址

1.2 写
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main(){
key_t key;
int shmid;
char *addr;
key = ftok(".",23);
if(key==-1){
perror(“ftok”);
return -1;
}
shmid = shmget(key,1024,IPC_CREAT|0666);
if(shmid==-1){
perror(“shmget”);
return -1;
}
addr = shmat(shmid,NULL,0);
fgets(addr,32,stdin);
//strcpy(addr,“this my share memeory”);
shmdt(addr);
}

1.3 读
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main(){
key_t key;
int shmid;
char *addr;
key = ftok(".",23);
if(key==-1){
perror(“ftok”);
return -1;
}
shmid = shmget(key,1024,IPC_CREAT|0666);
if(shmid==-1){
perror(“shmget”);
return -1;
}
addr = shmat(shmid,NULL,0);
//strcpy(addr,“this my share memeory”);
printf(“get share mem=%s\n”,addr);
shmdt(addr);
shmctl(shmid,IPC_RMID,NULL); //共享内存正常跑不要删的时候不用shmctl,这样会清空数据
}

三、消息队列
1.步骤
打开/创建消息队列 msgget
向消息队列发送消息 msgsnd
从消息队列接收消息 msgrcv
控制消息队列 msgctl
2.API
2.1 打开/创建消息队列
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);

成功时返回消息队列的id,失败时返回EOF
key 和消息队列关联的key IPC_PRIVATE 或 ftok
msgflg 标志位 IPC_CREAT|0666

2.2 向消息队列发送消息
include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t size,int msgflg);

成功时返回0,失败时返回-1
msgid 消息队列id
msgp 消息缓冲区地址
size 消息正文长度
msgflg 标志位 0 或 IPC_NOWAIT

msgbuf消息格式
通信双方首先定义好统一的消息格式
用户根据应用需求定义结构体类型
首成员类型必须为long,代表消息类型(正整数)
其他成员都属于消息正文
消息长度不包括首类型 long

如:
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
int main() {
MSG buf;
……
buf.mtype = 100;
fgets(buf.mtext, 64, stdin);
msgsnd(msgid, &buf,LEN, 0);
……
return 0;
}

2.3 从消息队列接收消息
include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, void *msgp, size_t size, long msgtype, int msgflg);

成功时返回收到的消息长度,失败时返回-1
msgid 消息队列id
msgp 消息缓冲区地址
size 指定接收的消息长度
msgtype 指定接收的消息类型
msgflg 标志位 0 或 IPC_NOWAIT

如:
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
int main() {
MSG buf;
……
if (msgrcv(msgid, &buf,LEN, 200, 0) < 0) {
perror(“msgrcv”);
exit(-1);
}
……
}

2.4 控制消息队列
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf);

成功时返回0,失败时返回-1
msgid 消息队列id
cmd 要执行的操作 IPC_STAT / IPC_SET / IPC_RMID
buf 存放消息队列属性的地址

3 demo
3.1写消息队列
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

typedef struct{
long type;
char txt[64];
}MSG;

#define LEN sizeof(MSG)-sizeof(long)

int main(){
key_t ipkey;
int msgid;
MSG msg_t;
ipkey = ftok(".",23);
if(ipkey==-1){
perror(“ftok”);
return -1;
}
msgid = msgget(ipkey,IPC_CREAT|0666);
if(msgid ==-1){
perror(“msgget”);
return -1;
}
msg_t.type = 1;
strcpy(msg_t.txt,“msg type one”);
msgsnd(msgid,&msg_t,LEN,0);

msg_t.type = 2;
strcpy(msg_t.txt,"msg type two");
msgsnd(msgid,&msg_t,LEN,0);

msg_t.type = 3;
strcpy(msg_t.txt,"msg type three");
msgsnd(msgid,&msg_t,LEN,0);

msg_t.type = 4;
strcpy(msg_t.txt,"msg type four");
msgsnd(msgid,&msg_t,LEN,0);

msg_t.type = 5;
strcpy(msg_t.txt,"msg type five");
msgsnd(msgid,&msg_t,LEN,0);

}

3.2 读消息队列
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
//#include <sys/ipc.h>
//#include <sys/msg.h>
#include <linux/msg.h>
#include <string.h>
#include <stdlib.h>

typedef struct{
long type;
char txt[64];
}MSG;
int msgid;

#define LEN sizeof(MSG)-sizeof(long)
void rmmsg(int sig){
msgctl(msgid,IPC_RMID,NULL);
exit(0);
}

int main(){
key_t ipkey;
int re;
MSG msg_t;
ipkey = ftok(".",23);
if(ipkey==-1){
perror(“ftok”);
return -1;
}
msgid = msgget(ipkey,IPC_CREAT|0666);
if(msgid ==-1){
perror(“msgget”);
return -1;
}
signal(SIGINT,rmmsg);
while(1){
re = msgrcv(msgid,&msg_t,LEN,3,MSG_EXCEPT); //msgrcv(msgid,&msg_t,LEN,0,0); 这样就接收所有类型消息
printf(“receive msg:type=%d,txt=%s\n”,(int)msg_t.type,msg_t.txt);
if(re<=0){
break;
}
//删除消息队列
// msgctl(msgid,IPC_RMID,NULL);
}
}


版权声明:本文为sad1996原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。