Linux进程间通信(Inter-Process Communication,IPC)是指在不同进程之间传送数据或信号的一些方法。Linux提供了多种IPC机制,主要包括以下几种:
无名管道(Pipe)和有名管道(FIFO):
- 无名管道是用于连接两个进程之间的一个单向数据通道,只能用于具有亲缘关系的进程之间,如父子进程。
- 有名管道(也称为FIFO)克服了管道没有名字的限制,它可以通过文件系统中的特殊文件来实现不相关进程间的通信。
信号(Signal):
- 信号是一种较为简单的通信方式,用于通知接收进程某个事件已经发生。
消息队列(Message Queue):
- 消息队列允许一个或多个进程向队列中写入消息,其他进程则可以读取队列中的消息。它独立于发送和接收进程,因此,进程间的通信不需要关心对方的存在。
共享内存(Shared Memory):
- 允许多个进程共享一段内存区域,是最快的IPC方式,因为数据不需要在客户和服务进程间复制。
信号量(Semaphore):
- 主要用于进程间的同步操作,可以保证多个进程访问共享资源而不发生冲突。
套接字(Socket):
- 提供了建立于不同机器上的进程之间进行数据交换的方法,广泛用于客户端与服务器端通信。
每种机制都有其适用的场景和优缺点。在设计多进程应用时,需要根据具体需求选择合适的IPC机制。 废话不多说了,以下我分享了管道,共享内存,消息队列,这三种通信方式,其他几种后续再分享。
一. 管道
1. 无名管道
函数: int pipe(int fd[2])
功能: 创建无名管道(只能父进程创建,子进程继承) ,一旦执行了pipe函数,fd[0] 和 fd[1]就被填入值 ,fd[0]是读句柄,fd[1]是写句柄。
特点:
示例:子进程使用无名管道给父进程发送hello,父进程读取hello并打印。
#include <stdio.h>
#include <unistd.h>
int main()
{
int fd[2];
pipe(fd); //父进程新建管道,子进程继承
pid_t pid = fork();
if(pid == 0)
{ //子进程,先关闭读端,然后写入hello
close(fd[0]);
write(fd[1],"hello", sizeof("hello"));
else if(pid > 0)
{ //父进程,先关闭写入,读出hello
char buf[100] = { 0 };
close(fd[1]);
wait(NULL); //等子进程写入
read(fd[0], buf, sizeof(buf));
printf("read is %s\n", buf);
}
}
2. 有名管道
函数: int mkfifo(const char *pathname, mode_t mode); //创建有名管道
mkfifo("myfifo", 0666); //有名管道以文件的方式管理,只不过这个文件创建在内存中,如果管道已
经存在,就不会重复创建。
打开管道
int fd = open("myfifo", O_RDWR);
读管道
read(fd, buf, sizeof(buf));
写管道
write(fd, buf, sizeof(buf));
关闭管道
close(fd);
通过Linux文件IO对管道文件进行打开读写关闭。
示例:用write.c打开写管道,read.c打开读端,写管道发送hello,读管道接受hello。
write.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char buf[100] = { "hello" };
mkfifo("myfifo", 0666);
int fd = open("myfifo", O_WRONLY);
write(fd, buf, sizeof(buf));
}
read.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char buf[100] = { 0 };
mkfifo("myfifo", 0666);
int fd = open("myfifo", O_RDONLY);
read(fd, buf, sizeof(buf));
printf("read is %s\n", buf);
}
在两个终端分别执行下面语句,
gcc read.c -o read 执行:./read
gcc write.c -o write 执行:./write
这样就可以使用有名管道通信了。
二. 共享内存(shm shared memory)
概念:linux 内核提供了一块缓冲区,然后两个或多个进程可以共享访问, 不像管道,要再定义缓冲区进行存储,共享内存对缓冲区直接访问, 所以,在多进程通信机制中,共享内存 效率最高。
共享内存容易出的问题: 互斥问题: 可以和互斥锁组合使用
查看共享内存:ipcs -m
共享内存使用步骤:
1 创建共享内存 shmget(key_t key, int size, int shmflg)
2 映射 char *shmat(int shmid, void *shmaddr, int shmflg)
3 访问共享内存
4 解除映射 shmdt(const void *shmaddr)
5 删除共享内存 shmctl(shmid, IPC_RMID, NULL)
1. shmget()
头文件:#include <sys/ipc.h>
#include <sys/shm.h>
功能: 如果共享内存存在,就打开, 不存在,就创建
函数: shmget(key_t key, int size, int shmflg)
参数:
key: 创建共享内存时指定的key, 如果值为IPC_PRIVATE 表示此共享内存是私有的,只能父子进程 间用 。非IPC_PRIVATE , 表示一个指定key值的共享内存 。
size: 新建的共享内存的大小,以字节为单位(类似malloc的参数)(如果共享内存已经存在,此值需 要与共享内存大小保持一致)
shmflg: 共享内存的权限(0666)(类似于open的第三个参数)如果创建公有的共享内存,需要加上IPC_CREAT(例:0666|IPC_CREAT)。
返回值:
成功:通过它来标识共享内存,只是共享内存的编号,是方便我们找到,哪块共享内存是我们
要使用的。
失败:-1。
私有共享内存:
int shmid = shmget(IPC_PRIVATE, 10 * sizeof(int), 0666);
公共共享内存:
int shmid = shmget(55555, 10 * sizeof(int), 0666|IPC_CREAT);
2.shmat ()
功能:映射共享内存(将共享内存的首地址给到进程)
函数: char *shmat(int shmid, void *shmaddr, int shmflg);
参数:
shmid: shmget的返回值
shmaddr: 进程指定的 映射后的地址,通常为NULL(表示地址由系统指定)
shmflg: 0 表示可读可写
返回值:
实际映射的地址(通过这个地址可以访问共享内存)
示例:
char *p = shmat(shmid, NULL, 0); //执行完,p就指向共享内存
3.对共享内存读写
*p = 100; //写入
printf("%d\n", *p); //读出
4.shmdt()
解除共享内存映射
函数:shmdt(const void *shmaddr); //参数 shmat的返回值
示例:shmdt(p); //解除共享内存映射
5.shmctl()
删除共享内存
函数: shmctl(shmid, IPC_RMID, NULL);
参数1:shmget的返回值
参数2:IPC_RMID
参数3:恒为null
示例:父子进程之间使用共享内存。
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int shmid = shmget(IPC_PRIVATE, 10 * sizeof(int), 0644);
//创建一块私有共享内存,保存10个int
printf("shmid %d\n", shmid);
int i;
int pid = fork(); //新建一个子进程
if(pid == 0)
{//写共享内存
int *p = (int *)shmat(shmid, NULL, 0); //映射
for(i = 0; i < 10; i++)
{
p[i] = i; //写入共享内存
}
shmdt(p);
exit(0);
}
else if(pid > 0)
{//读共享内存
wait(NULL); //等子进程写完
int *p = (int *)shmat(shmid, NULL, 0);//int *p = (int*)malloc(10);
for(i = 0; i < 10; i++)
{
printf("%d\n", p[i]); //读出并打印
}
shmdt(p);
shmctl(shmid, IPC_RMID, NULL);
}
}
三. 消息队列
消息队列特点:
消息队列以消息为单位管理(结构体) ,可以发送和接收多个消息,这些消息可以在消息队列中进行缓存(没取走,还在消息队列中保存)
1 消息先进先出
2 消息是一个整体(用结构体来表示)
3 消息有类型,接收方可以指定接收某种类型的消息
4 不同的消息类型的消息不必满足先入先出,相同的消息类型的消息间才需要满足先入先出
和管道的区别( 消息带类型 )
查看共享内存:ipcs -q
消息队列使用步骤 :
1) 创建消息队列 msgget(key_t key, int msgflg)
2) 发送消息 msgsnd(int msgid, void *msgp, size_t msgsz, int msgflg)
3) 接收消息 msgrcv(int msgid, void *msgp, size_t msgsz, long int msgtyp, int msgflg)
4) 删除消息队列 msgctl(int msgid, int cmd, struct msgid_ds *buf)
1.创建消息队列(获得消息队列)
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数: msgget(key_t key, int msgflg);
功能: 如果消息队列不存在,就新建,存在,就打开。
参数:
key : IPC_PRIVATE 私有消息队列 (父子进程之间用) ;非 IPC_PRIVATE ,多个进程之间使用
msgflg :访问权限(0666),如果创建一个公有的消息队列(0666|IPC_CREAT)
示例:
msgget(IPC_PRIVATE, 0666); //父子进程
msgget(44444, 0666 | IPC_CREAT); //不相关的进程
返回值:创建的消息队列ID,如果失败,返回值<0
2.发送消息
函数:msgsnd(int msgid, void *msgp, size_t msgsz, int msgflg);
功能: 发送消息
参数:
msgid: msgget 的返回值
msgp:将要发送的消息的首地址
struct mbuf
{
long mtype; //表示消息的类型,不能是0
char mtext[100];//消息的正文的缓存区(大小不固定)
};
整个结构体 :一条消息
msgsz :消息的正文的大小 (正文的大小:消息大小(sizeof(struct mbuf)) - 类型的大小(sizeof(long))。
msgflg :发送方式选项: 0 阻塞(如果消息队列满,会等) ;IPC_NOWAIT 不阻塞(如果消息队列满,不会等,会丢弃消息)
示例:
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>
struct msgbuf
{
long mtype;//消息类型
char mtext[100];//消息正文
};
int main()
{
struct msgbuf a = {2/*消息类型,也可以叫消息变量*/, "hello world"};
int key = ftok("/tmp", 1);
int msgid = msgget(key/*消息队列编号*/, 0666 | IPC_CREAT);
msgsnd(msgid, &a, sizeof(a) - sizeof(long), 0);
}
3.接收消息
函数:msgrcv(int msgid, void *msgp, size_t msgsz, long int msgtyp, int msgflg);
参数:
msgid :msgget 的返回值
msgp :将要接收的消息存放的位置(首地址)
struct mbuf
{
long mtype; //表示消息的类型
char mtext[100]; //消息的正文
};
msgsz :消息的正文的大小 (mtext: 整个消息的长度 - 类型的长度)
msgtyp :0 接收消息队列中第一条消息 ,>0 接收队列中类型为 msgtyp 的第一条消息
msgflg :接收方式选项 0 阻塞(如果消息队列空,会等) ,IPC_NOWAIT 不阻塞
返回值 :>0 接收的消息的长度 ,-1 出错
示例:
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>
struct msgbuf
{
long mtype;
char mtext[100];
};
int main()
{
struct msgbuf b; //没赋值
int key = ftok("/tmp", 1);
int msgid = msgget(key, 0666 | IPC_CREAT);
msgrcv(msgid, &b, sizeof(b) - sizeof(long), 2, 0);
//接收消息类型为2的消息
printf("msg type is %lu, buf is %s\n", b.mtype, b.mtext);
}
4.删除消息队列
函数:msgctl(int msgid, int cmd, struct msgid_ds *buf);
示例:msgctl(msgid, IPC_RMID, NULL);
示例:两个程序,一个不断从键盘输入数据,写入消息队列, 另一个循环读出。
在send.c写入消息队列
#include <sys/msg.h>
#include <stdio.h>
struct msgbuf
{
long mtype;
char mtext[100];
};
int main()
{
struct msgbuf a;
a.mtype = 1;
int msgid = msgget(11111, 0666 | IPC_CREAT);
while(1)
{
gets(a.mtext);//scanf("%s", a.mtext)
msgsnd(msgid, &a, sizeof(a) - sizeof(long), 0);
}
}
在recv.c中读消息队列
#include <sys/msg.h>
#include <stdio.h>
struct msgbuf
{
long mtype;
char mtext[100];
};
int main()
{
struct msgbuf b; //没赋值
int msgid = msgget(11111, 0666 | IPC_CREAT);
while(1)
{
msgrcv(msgid, &b, sizeof(b) - sizeof(long), 0, 0);
printf("msg type is %lu, buf is %s\n", b.mtype, b.mtext);
}
}
在两个终端分别执行下面语句,
gcc send.c -o send 执行:./send
gcc recv.c -o recv 执行:./recv
然后在执行./send的终端,键盘输入内容,在执行。在执行./recv的终端就会收到消息。
四.结语
到这里,进程间通信代码就分享结束了,如果代码哪里不对,请在评论区留言。
最后的最后,还请大家点点赞,点点关注,谢谢大家了!