Linux进程间通信——管道和消息队列

进程间通信

多个进程需要互相配合完成一项任务,称为进程同步,例如公交车问题。

多个进程需要访问共享资源(临界资源),而资源同时只能被一个进程访问,各个进程相互竞争使用资源,称为互斥。例如买票问题。

为什么需要进程间通信:

  1. 数据传递
  2. 资源共享,多个进程使用一个资源
  3. 通知事件,告诉其他进程事件发生

通信方式分类:

  1. 管道
  2. System V IPC,消息队列,共享内存,信号量
  3. POSIX 比IPC多了互斥量,条件变量,读写锁
  4. socket

管道

管道将一个进程的输出流连接到另一个进程的输入流。

其原理是使用内核里的一块缓存区保存数据流。

管道是有大小限制的,内核总缓存大小65536字节,一次写入大小不超过4096B.

int pipe(int fd[2]);

利用fork共享管道时,由于子进程继承父进程文件描述符,所以管道的文件描述符fd0,fd1的引用计数为2。需要先调用close将不需要用的描述符计数-1。

//模拟shell管道
if(0 == fork()){
    //子进程执行ls
    //标准输出重定向到管道写端
    dup2(fds[1], 1);
    close(fds[0]);
    execlp("ls", "ls", "-l", NULL);
    perror("execlp");
    exit(1);
}
else{
    //父进程从管道接受输入流
    dup2(fds[0], 0);
    close(fds[1]);
    execlp("wc", "wc", "-l", NULL);
    perror("execlp");
    exit(1);
}

视频解码器就类似于管道,拿走数据和接受数据以一定速度同时进行。

命名管道

匿名管道只能让子进程继承父进程文件描述符,从而得到管道描述符和内核的缓存。

命名管道是一种特殊文件,创建打开后,使两个无亲缘关系进程拿到同一块内核空间,与管道使用方法相同。

创建命名管道

int mkfifo (const char *filename, mode_t mode);

命名管道实现C/S通信:

/*server.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
    do{\
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0);

int main()
{
    umask(0);
    if(mkfifo("mypipe", 0644) < 0){
        ERR_EXIT("mkfifo");
    }

    int rfd = open("mypipe", O_RDONLY);
    if(rfd < 0){
        ERR_EXIT("open");
    }
    char buf[1024];
    while(1){
        buf[0] = 0;
        printf("please wait\n");
        ssize_t s = read(rfd, buf, sizeof(buf)-1);
        if(s > 0){
            buf[s-1] = 0;
            printf("client say# %s\n", buf);
        }
        else if(s == 0){
            printf("client quit, exit now!\n");
            exit(EXIT_SUCCESS);
        }
        else{
            ERR_EXIT("read");
        }
    }
    close(rfd);
    return 0;
}
/*client.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define ERR_EXIT(m) \
    do{\
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0);

int main()
{
    int wfd = open("mypipe", O_WRONLY);
    if(wfd < 0){
        ERR_EXIT("open");
    }

    char buf[1024];
    while(1){
        buf[0] = 0;
        printf("please Enter #");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf)-1);
        if(s > 0){
            buf[s] = 0;
            write(wfd, buf, strlen(buf));
        }
        else{
            ERR_EXIT("read");
        }
    }
    close(wfd);
    return 0;
}

消息队列

消息队列提供一个进程向另外一个进程发送数据的方法。

消息队列结构体存储了关于消息指针,消息个数,消息最大值,消息队列现在的大小等信息…

创建消息队列

int msgget(key_t key, int msgflag);

key:消息队列的名字

msgflag:创建 IPC_CREAT | 权限

返回值:消息队列的标识符,类似于文件描述符。

发送消息

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflag);

参数:消息队列标识符,消息类型指针,消息类型的长度,状态控制。

msgflag:IPC_NOWAIT

自定义的消息类型

struct msg{
    long type;  //消息类型,区分队列中不同的消息通道
    char text[100];
};

接受消息

ssize_t msgrcv(int qid, void *msgp, size_t msgsz, long msgtyp, int msgflag);

//send
void send(){
    struct msg m;
    int qid = msgget(123, IPC_CREAT);
    long type;
    scanf("%ld", &type);
    m.type = type;
    scanf("%s", m.text);
    int ret = msgsnd(qid, &m, strlen(m.text),  0);
    if(ret == -1)
        perror("msgsnd");
}
//recv
void recv(){
    struct msg m;
    int qid = msgget(123, 0);
    long type;
    scanf("%ld", &type);
    int ret = msgrcv(qid, &m, 100, type,  0);
}

查看/删除消息队列

$ipcs -q

$ipcrm -Q key

猜你喜欢

转载自blog.csdn.net/hanzheng6602/article/details/80528746
今日推荐