Linux编程-进程间通讯(1)

一、进程间的通讯方式

网上有的分七种,有的五种,其实都是一样的.

  1. 管道(又可以分为有名管道和无名管道)
  2. 消息队列
  3. 共享内存
  4. 信号(可以分为信号和信号量两种)
  5. socket套接字

其实大多数就是这五种,分细一点就可以认为是七种。
有的还有命令流管道s_pipe,和内存映射,这两种可能用的比较少,也很少看到,所以就没有写。

二、管道

2.1 无名管道

无名管道:管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
函数原型:

SYNOPSIS
       #include <unistd.h>

       int pipe(int pipefd[2]);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

       int pipe2(int pipefd[2], int flags);

2.2 读写规则

  1. 管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道 读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件 的I/O函数都可以用于管道,如close、read、write等等。
  2. 如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0; 当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数。如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。
  3. 向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。(只有在管道的读端存在时,向管道中写入数据才有意义)

2.3 单进程实现

#include <unistd.h>
#include <stdio.h>

int main()
{
    int fd[2];
    pipe(fd);
    char buf[128];
    write(fd[1], "Hello world", 12);
    sleep(2);
    read(fd[0], buf, 128);

    printf("read = %s\n",buf);
    close(fd[0]);
    close(fd[1]);

    return 0;
}
$ gcc noNamePipe.c 
$ ./a.out noNamePipe.c 

read = Hello world

2.4 父子进程实现

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    int fd[2];
    pipe(fd);
    char buf[128];

    if(fork() != 0)
    {
        write(fd[1],"Hello world\n",12);
        printf("father pid input\n");
    }
    else
    {
        read(fd[0],buf,128);
        printf("child pid read = %s\n",buf);
    }
    close(fd[0]);
    close(fd[1]);

    return 0;
}

$ gcc noNamePipe_1.c 
$ ./a.out noNamePipe_1.c 

father pid input
child pid read = Hello world

2.6 有名管道

因为无名管道只有亲缘关系才能通信,所以创建了有名管道来解决这个问题
实现一个有名管道实际上就是实现一个FIFO文件,有名管道一旦建立,之后它的读,以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但仅是一个节点而已,文件的数据还是存在内核缓冲页面上,和普通管道相同。有名管道的文件仅仅是作为传输数据的通道,它并不存放传输的数据。

在这里插入图片描述

2.7 有名管道读写规则

  1. 若该管道是阻塞打开,且当前FIFO内没有数据,则对读进程而言将一直阻塞到有数据写入。
  2. 若该管道是非阻塞打开,则不论FIFO内是否有数据,读进程都会立即执行读操作。即如果FIFO内没有数据,则读函数将立刻返回0。
  3. 若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入。
  4. 若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败

2.8 代码实现

先写读端

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define MYFIFO "myfifo"

int main()
{
    int fd;
    char r_buf[128];
    /*判断管道是否存在*/
        if(mkfifo(MYFIFO,0666) < 0) {
            printf("error mkfifo\n");
            return -1;
        }
        printf("make myfifo_r success\n");
        /*只读权限打开*/
        fd = open(MYFIFO, O_RDONLY);
    if (fd == -1) {
        printf("open myfif0 error\n");
        return -2;
    }
    while(1) {
    /*memset初始化这个数组*/
        memset(r_buf,0,sizeof(r_buf));
        if(read(fd, r_buf, sizeof(r_buf))) {
            printf("buf read success\nr_buf: %s\n",r_buf);
        }
    }
    close(fd);

    return 0;
}

$ gcc namePipe_read.c -o read 
$ ./read 

make myfifo_r success
buf read success
r_buf: 456789
buf read success
r_buf: lalala

再写写端

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define MYFIFO "myfifo"

int main(int argc, char *argv[])
{
    int fd;
    char w_buf[128]={0};
    if(argc < 1) {
        printf("error argc");
        return -1;
    }

    fd = open(MYFIFO, O_WRONLY);
    if (fd == -1) {
        printf("open myfifo error\n");
        return -2;
    }

    printf("Write:>");
    sscanf(argv[1],"%s",w_buf);

    if(write(fd, w_buf, sizeof(w_buf))) {
        if(strcmp(buf, "end") == 0){
            close(fd);
            exit -1;
        }
        printf("w_buf write success\nw_buf: %s\n",w_buf);
 }
    close(fd);
    return 0;
}

$ ./write 456789

Write:>w_buf write success
w_buf: 456789

$ ./write lalala

Write:>w_buf write success
w_buf: lalala

三、消息队列

消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应唯一的键值,这个键的数据类型为key_t,通常在头文件<sys/type.h>中被定义为长整型,键值由内核转换为标识。内核中的消息队列是通过IPC的标识符来区别的,不同的消队列之间是相互独立的。要获得一个消息队列的描述符,只需要提供该消息队列的键值即可,该键值可以在公共头文件定义,也可以用ftok函数生成键值。

3.1 读取特性

  1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据

3.2 应用举例

消息队列主要是用于:异步处理,应用解耦,流量削锋和消息通讯这四个模块
异步处理:
当我们注册app时,手机发送一条短信,很快会返回一条验证码,提高处理效率。
应用解耦:
当我们用淘宝下单的时候,可能商家已经休息不可能会及时处理,就会把我们下的订单的信息存储在消息队列里面,然后商家工作就会刷新拉取信息,此时商家就会看到对应的订单消息。
流量削锋:
其实消息队列我们也经常遇到,比如说在手机首发的时候,会提示只有一千个名额,就需要我们去抢,服务器不可能一下子处理那么多事情,所以就按照前后顺序存放到这个消息队列里面,当存满了过后,剩下的人继续去抢,就会返回错误页面,或则其余的页面。
消息通讯:
就类似我们的qq和微信,发送了消息对方可以选择看与不看,更是在的就是企业微信和钉钉会提示对方看了没有。

这些都是上层很明显的事列,我们也可以直观的看到。

3.3 底层实现

函数原型:

1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

代码实现:
read.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
int main()
{
        struct msgbuf sendbuf={999,"888 message already received"};
        struct msgbuf readbuf;
        int msgid = 0;
        key_t key;
        key = ftok(".",'z');//获取键值
        printf("key=%x\n",key);
        msgid=msgget(key,IPC_CREAT|0777);//在内核中打开或建立键值为key的,权限为0777的消息队列
        if(msgid == -1){
                printf("create msgq failure\n");
        }
        msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);//从队列中获取888类型的数据,如果队列中未出现888类型的数据,则程序>阻塞在这里
        printf("read from que:%s\n",readbuf.mtext);
        msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);//往队列id为msgid的队列写入sendbuf(类型为999)数据
        msgctl(msgid,IPC_RMID,NULL);//将队列从系统内核中删除
        return 0;
}

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
int main()
{
        struct msgbuf sendbuf={888,"this is message from que"};
        struct msgbuf readbuf;
        int msgid= 0;
        key_t key;
        key = ftok(".",'z');//获取键值
        printf("key=%x\n",key);
        msgid=msgget(key,IPC_CREAT|0777);//在内核中打开或建立键值为key的,权限为0777的消息队列
        if(msgid == -1){
                printf("create msgq failure\n");
        }
        msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);//往队列id为msgid的队列写入sendbuf(类型为888)数据
        msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),999,0);//从队列中获取999类型的数据,如果队列中未出现999类型的数据,则程序>阻塞在这里
        printf("%s\n",readbuf.mtext);
        msgctl(msgid,IPC_RMID,NULL);//将队列从系统内核中删除
        return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永不秃头的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值