Linux进程间通信

发布于:2024-04-10 ⋅ 阅读:(37) ⋅ 点赞:(0)

Linux进程间通信(Inter-Process Communication,IPC)是指在不同进程之间传送数据或信号的一些方法。Linux提供了多种IPC机制,主要包括以下几种:

  1. 无名管道(Pipe)和有名管道(FIFO)

    • 无名管道是用于连接两个进程之间的一个单向数据通道,只能用于具有亲缘关系的进程之间,如父子进程。
    • 有名管道(也称为FIFO)克服了管道没有名字的限制,它可以通过文件系统中的特殊文件来实现不相关进程间的通信。
  2. 信号(Signal)

    • 信号是一种较为简单的通信方式,用于通知接收进程某个事件已经发生。
  3. 消息队列(Message Queue)

    • 消息队列允许一个或多个进程向队列中写入消息,其他进程则可以读取队列中的消息。它独立于发送和接收进程,因此,进程间的通信不需要关心对方的存在。
  4. 共享内存(Shared Memory)

    • 允许多个进程共享一段内存区域,是最快的IPC方式,因为数据不需要在客户和服务进程间复制。
  5. 信号量(Semaphore)

    • 主要用于进程间的同步操作,可以保证多个进程访问共享资源而不发生冲突。
  6. 套接字(Socket)

    • 提供了建立于不同机器上的进程之间进行数据交换的方法,广泛用于客户端与服务器端通信。

每种机制都有其适用的场景和优缺点。在设计多进程应用时,需要根据具体需求选择合适的IPC机制。 废话不多说了,以下我分享了管道,共享内存,消息队列,这三种通信方式,其他几种后续再分享。

一. 管道 

1. 无名管道

函数: int pipe(int fd[2])
功能:  创建无名管道(只能父进程创建,子进程继承) ,一旦执行了pipe函数,fd[0] 和 fd[1]就被填入值 ,fd[0]是读句柄,fd[1]是写句柄。
特点:
47c017c9680148c99b7e131ab4d54f61.png
示例:子进程使用无名管道给父进程发送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 内核提供了一块缓冲区,然后两个或多个进程可以共享访问, 不像管道,要再定义缓冲区进行存储,共享内存对缓冲区直接访问, 所以,在多进程通信机制中,共享内存 效率最高

44c5945a545a490d8f928a741d5562f6.png

共享内存容易出的问题: 互斥问题: 可以和互斥锁组合使用
 
查看共享内存: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 不同的消息类型的消息不必满足先入先出,相同的消息类型的消息间才需要满足先入先出
和管道的区别( 消息带类型 )
2833bc2474c74b9d9c62ca87e93df01c.png

 

查看共享内存: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的终端就会收到消息。
 

四.结语

到这里,进程间通信代码就分享结束了,如果代码哪里不对,请在评论区留言。

最后的最后,还请大家点点赞,点点关注,谢谢大家了!