APUE——IPC_管道,消息队列

18 篇文章 0 订阅

1. 匿名管道

1.1 匿名管道原理

pipe()创建管道,可以使用的单向数据通道 用于进程间通信。数组pipefd用于返回引用管道末端的两个文件描述符。 pipefd [0] 是指管道的读取端。 pipefd [1]是指写管道的末端。写入管道写入端的数据是由内核缓冲,直到从读取端读取管道。
pipe函数

       #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);

在这里插入图片描述
fork的时候,两个PCB有两个fd_array,复制一份后,指向同一个file,然后指向同一块内存,pipe应该原理类似,指向同一个内核缓存
在这里插入图片描述

1.2 管道指令使用原理

管道指令的使用(过滤)

ls | grep y

在这里插入图片描述
执行顺序

  1. int pipe(int fildes[2]); 创建匿名管道,fork
  2. int dup2(int fildes, int fildes2); 将write文件描述符指针复制到STDOUT_FILENO
  3. char *getcwd(char *buf, size_t size);获取当前路径, 搭配glob或者readdir函数,获取当前目录所有文件名
  4. 第三步将数据写入STDOUT_FILENO,实际写入管道所指向FILE
  5. 子进程从管道中读取所有数据,可用glob函数,提取出过滤完的数据
  6. 将数据打印到STDOUT_FILENO

1.3 匿名管道读写规则

  1. 当没有数据可读时
    a. O_NONBLOCK disable时:read阻塞,等到数据到来
    b. O_NONBLOCK enable时:read返回-1,errno值为EAGAIN
  2. 如果管道write端对应fd被关闭,则read返回0(读到文件尾)
  3. 如果管道read端对应fd被关闭,则write产生SIGPIPE
  4. 如果写入数据量不大于PIPE_BUF(4k),保证原子性,否则不行
  5. 管道内核缓存buf大小为65535

1.4 例子分析

1.4.1 模拟管道ls -l | wc -c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()

{

    int rp,pid;
    int pipefd[2];

    char recfbuf[20] = "helloworld";
    struct sigaction siga,oldsiga;
    char writebuf[1024];

    siga.sa_handler = SIG_IGN;
    siga.sa_flags = SA_NOCLDWAIT;
    sigemptyset(&siga.sa_mask);
    sigaction(SIGCHLD,&siga,&oldsiga);
    void fun(int i)
    {
        puts("no read\n");
    }
    signal(SIGPIPE,fun);

    rp = pipe(pipefd);
    pid = fork();
    if(pid<0)
    {
        perror("fork");
        exit(1);
    }
    if(pid == 0)
    {
        close(pipefd[0]);
        dup2(pipefd[1],STDOUT_FILENO);
        execlp("ls","ls","-l",NULL);        
        close(pipefd[1]);
        exit(0);
    }
    // close(pipefd[0]);
    close(pipefd[1]);
    dup2(pipefd[0],STDIN_FILENO);
    execlp("wc","wc","-c",NULL);
   // open("890.c",O_WRONLY|O_CREAT|O_TRUNC,0644);      
    close(pipefd[0]);
    puts("end");
    exit(0);
}
xili271948@er04180p:~/tcp$ ./789
2452
xili271948@er04180p:~/tcp$ ls -l | wc -c
2452
1.4.2 非阻塞read,通过管道发送文件
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()

{

    int rp,pid,fd,num,numr,frd,rflags;
    int pipefd[2];

    struct sigaction siga,oldsiga;
    char writebuf[1024];
    char readbuf[1024];
    siga.sa_handler = SIG_IGN;
    siga.sa_flags = SA_NOCLDWAIT;
    sigemptyset(&siga.sa_mask);
    sigaction(SIGCHLD,&siga,&oldsiga);

    rp = pipe(pipefd);

    rflags = fcntl(pipefd[0],F_GETFL);
    fcntl(pipefd[0],F_SETFL,rflags|O_NONBLOCK);
    pid = fork();
    if(pid<0)
    {
        perror("fork");
        exit(1);
    }
    if(pid == 0)
    {
        //sleep(2);
        close(pipefd[0]);
        fd = open("123.c",O_RDONLY);

        while(1)
        {
            num = read(fd,writebuf,1024);
            if(num <0)
            {
                perror("read");
                exit(1);
            }
            else if(num > 0)
                write(pipefd[1],writebuf,num);
            else
                break;
        }
        puts("end of send");
        close(pipefd[1]);
        close(fd);
        exit(0);
    }
    else
    {
        //sleep(1);
        frd = open("456.c",O_WRONLY|O_CREAT|O_TRUNC,0644);
        close(pipefd[1]);
        numr = 1;
        while(numr)
        {
            numr = read(pipefd[0],readbuf,num);
            if(numr<0)
            {
                puts(strerror(errno));
            }
            write(frd,readbuf,numr);
            
        }
        puts("end of recv");
        close(pipefd[0]);
        close(frd);
    }

    exit(0);
}
xili271948@er04180p:~/tcp$ ./noblockpipe
Resource temporarily unavailable
Resource temporarily unavailable
Resource temporarily unavailable
Resource temporarily unavailable
Resource temporarily unavailable
end of send
Resource temporarily unavailable
end of recv
1.4.3 测试read已关闭,write发送SIGPIPE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()

{

    int rp,pid;
    int pipefd[2];

    char recfbuf[20] = "helloworld";
    struct sigaction siga,oldsiga;
    char writebuf[1024];

    siga.sa_handler = SIG_IGN;
    siga.sa_flags = SA_NOCLDWAIT;
    sigemptyset(&siga.sa_mask);
    sigaction(SIGCHLD,&siga,&oldsiga);
    void fun(int i)
    {
        puts("no read\n");
    }
    signal(SIGPIPE,fun);

    rp = pipe(pipefd);
    pid = fork();
    if(pid<0)
    {
        perror("fork");
        exit(1);
    }
    if(pid == 0)
    {
        close(pipefd[0]);
        write(pipefd[1],recfbuf,sizeof(recfbuf));
        puts(recfbuf);
        close(pipefd[1]);
        exit(0);
    }
    close(pipefd[0]);
    close(pipefd[1]);
    puts("end");
    exit(0);
}
xili271948@er04180p:~/tcp$ ./pip_sigpipe 
end
no read

helloworld

2. 命名管道FIFO

2.1 FIFO原理

       #include <sys/types.h>
       #include <sys/stat.h>

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

       #include <fcntl.h>           /* Definition of AT_* constants */
       #include <sys/stat.h>

       int mkfifoat(int dirfd, const char *pathname, mode_t mode);
filname是指文件名,而mode是指定文件的读写权限。mknod是比较老的函数,而使用mkfifo函数更加简单和规范,所以建议用mkfifo。
  1. FIFO其适用于进程间通信,其可以用于父子进程通信
  2. FIFO由mkfifo函数创建,并需要使用open函数打开,除了创建方式不同于匿名管道的pipe()之外,其他基本一致
  3. FIFO实际创建了pipe类型的文件,有inode,但是没有block,所以没有所谓的临时文件,匿名管道没有pipe类型文件
    在这里插入图片描述
open(const char *path, O_RDONLY);//1
open(const char *path, O_RDONLY | O_NONBLOCK);//2
open(const char *path, O_WRONLY);//3
open(const char *path, O_WRONLY | O_NONBLOCK);//4

需要搭配O_RDONLY或者O_WRONLY使用,因为管道为单向
同时,mkfifo()创建的为pipe文件,通过ls -l或者stat查看,其如下,不能使用vim cat等查看
在这里插入图片描述

2.2 FIFO读写规则

  1. 如果是为读而open(与pipe不同,pipe考虑的是read write)
    a. O_NONBLOCK disable时:open阻塞,知道有进程是为写open该FIFO
    b. O_NONBLOCK enable时:open立即返回
  2. 如果是为写而open
    a. O_NONBLOCK disable时:open阻塞,知道有进程是为读open该FIFO
    b. O_NONBLOCK enable时:open立即返回,错误码为ENXIO,即打开fd失败
  3. 如果管道write端对应fd被关闭,则read返回0(读到文件尾)
  4. 如果管道read端对应fd被关闭,则write产生SIGPIPE

2.3 FIFO代码

2.3.1 普通fifo读写

fifo读

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

#define BUFFSIZE 50

int main()
{
    char rdbuf[BUFFSIZE];
    int mk,fo;
    fo = open("mkdfifo",O_RDONLY);
    int nw;
    nw = read(fo,rdbuf,BUFFSIZE);

    if(nw < 0)
    {
        perror("read\n");
        exit(1);
    }
    puts("read all");

    puts(rdbuf);


    exit(0);
}

fifo写

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

#define BUFFSIZE 50

int main()
{

    int mk,fo;
    mk = mkfifo("./mkdfifo",0600);
    fo = open("mkdfifo",O_WRONLY);
    char *wrbuf = "helloworld";

    int nw;
    nw = write(fo,wrbuf,BUFFSIZE);
    puts("write all\n");

    if(nw < 0)
    {
        perror("write\n");
        exit(1);
    }
    puts("send all");


    exit(0);
}
xili271948@er04180p:~/tcp$ ./wfifo 
write all
send all

xili271948@er04180p:~/tcp$ ./rfifo 
read all
helloworld
2.3.2 非阻塞fifo读写

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#define BUFFSIZE 50

int main()
{
    char rdbuf[BUFFSIZE];
    int mk,fo;
    fo = open("mkdfifo",O_RDONLY|O_NONBLOCK);
    int nw;
    nw = read(fo,rdbuf,BUFFSIZE);

    if(nw < 0)
    {
        perror("read\n");
        exit(1);
    }
    puts("read all");

    puts(rdbuf);


    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#define BUFFSIZE 50

void fun(int argc)
{
    puts("SIGPIPE");
}

int main()
{

    int mk,fo;

    signal(SIGPIPE,fun);
    //mk = mkfifo("./mkdfifo",0600);
    fo = open("mkdfifo",O_WRONLY|O_NONBLOCK);
    if(fo < 0)
    {
        perror("open");
        exit(1);
    }
    char *wrbuf = "helloworld\n";
    sleep(10);

    int nw;
    nw = write(fo,wrbuf,BUFFSIZE);
    puts("write all");

    if(nw < 0)
    {
        perror("write\n");
        exit(1);
    }
    puts("send all");


    exit(0);
}

父子进程之间pipe

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

int main()
{
    char rdbuf[BUFFSIZE];
    char* wrbuf = "helloworld";
    int mk,pid;
    mk = mkfifo("./testmkfifo",0600);
    int nw;

    pid = fork();
    if(pid == 0)
    {
        int fr;
        fr = open("testmkfifo",O_WRONLY);

        write(fr,wrbuf,strlen(wrbuf)+1);
        exit(0);

    }
    else
    {
        int fw;
        fw = open("testmkfifo",O_RDONLY);
        nw = read(fw,rdbuf,BUFFSIZE);
        if(nw < 0)
        {
            perror("read\n");
            exit(1);
        }
    }
    puts("read all");

    puts(rdbuf);


    exit(0);
}
xili271948@er04180p:~/tcp$ ./fifotest 
read all
helloworld

3. 消息队列

消息列队基础知识,参考https://www.jianshu.com/p/7e3045cf1ab8
消息队列本质上是位于内核空间的链表,链表的每个节点都是一条消息。每一条消息都有自己的消息类型,消息类型用整数来表示,而且必须大于 0。每种类型的消息都被对应的链表所维护:
下表中,第一列为消息类型mtype,每个type对应一个链表
在这里插入图片描述

3.1 相关函数

ftok

key_t ftok(const char *pathname, int proj_id);
pathname为文件名,proj_id为非零,最少8位的数,类似于salt,使用文件名对应独一无二的inode号,用于生成key。
返回值为key

msgget

int msgget(key_t key, int msgflg);
获取消息队列的id,msgflg是一个位图,IPC_CREAT用于创建id,如果msgflg是IPC_PRIVATE,则key与消息队列id没有关系。

msgop

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

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
               int msgflg);

返回值:msgsnd成功返回0,msgrcv成功返回接收成功的mtext大小

msgp指向msgbuf,msgsz为mtext的size,msgtyp与mtype有关
msgtyp = 0:获取消息队列中的第一条消息
msgtyp > 0:获取类型为 msgtyp 的第一条消息,除非指定了 msgflg 为MSG_EXCEPT,这表示获取除了 msgtyp 类型以外的第一条消息。
msgtyp < 0:获取类型 ≤|msgtyp||msgtyp| 的第一条消息。
参数 msgflg:可选项。
如果为 0 表示没有消息就阻塞。
IPC_NOWAIT:如果指定类型的消息不存在就立即返回,同时设置 errno 为 ENOMSG
MSG_EXCEPT:仅用于 msgtyp > 0 的情况。表示获取类型不为 msgtyp 的消息
MSG_NOERROR:如果消息数据正文内容大于 msgsz,就将消息数据截断为 msgsz

struct msgbuf 
{
  long mtype;       /* message type, must be > 0 */
  char mtext[1];    /* message data */
};

msgctl

 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
成功返回0

 struct msqid_ds {
     struct ipc_perm msg_perm;     /* Ownership and permissions */
     time_t          msg_stime;    /* Time of last msgsnd(2) */
     time_t          msg_rtime;    /* Time of last msgrcv(2) */
     time_t          msg_ctime;    /* Time of last change */
     unsigned long   __msg_cbytes; /* Current number of bytes in
                                      queue (nonstandard) */
     msgqnum_t       msg_qnum;     /* Current number of messages
                                      in queue */
     msglen_t        msg_qbytes;   /* Maximum number of bytes
                                      allowed in queue */
     pid_t           msg_lspid;    /* PID of last msgsnd(2) */
     pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
 };
 
The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

    struct ipc_perm {
        key_t          __key;       /* Key supplied to msgget(2) */
        uid_t          uid;         /* Effective UID of owner */
        gid_t          gid;         /* Effective GID of owner */
        uid_t          cuid;        /* Effective UID of creator */
        gid_t          cgid;        /* Effective GID of creator */
        unsigned short mode;        /* Permissions */
        unsigned short __seq;       /* Sequence number */
    };
IPC_STAT 获取消息队列数据结构msqid_ds的值,并存在buf中
IPC_SET 设置消息队列中数据结构msqid_ds里ipc_perm的值,取自buf
IPC_RMID 内核中移除消息队列

3.2 代码实例

1.发送端

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include "mq.h"
#include <string.h>


int main(int argc, char** argv)
{
    int msqid;
    int ifsend;
    struct msgbuf sendmsg1,sendmsg2,sendmsg3,sendmsg4;
    sendmsg1.mtype = 1l;
    strcpy(sendmsg1.name,"datengzi");    //注意,这里有三种不同的mtype,可以通过msgrcv,选择哪个type
    sendmsg1.chinese = 80;
    sendmsg1.math = 90;

    sendmsg2.mtype = 2l;
    strcpy(sendmsg2.name,"sunyue");
    sendmsg2.chinese = 80;
    sendmsg2.math = 80;

    sendmsg3.mtype = 1l;
    strcpy(sendmsg3.name,"meisen");
    sendmsg3.chinese = 70;
    sendmsg3.math = 80;

    sendmsg4.mtype = 3l;
    strcpy(sendmsg4.name,"wangzhu");
    sendmsg4.chinese = 70;
    sendmsg4.math = 80;

    struct msgbuf msgtest[4]={sendmsg1,sendmsg2,sendmsg3,sendmsg4};
    key_t key;

    key = ftok(KEYPATH,PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        exit(1);
    }

    if((msqid = msgget(key,0))<0) //server先运行,已经产生了key关联的消息队列id,client端只需要获取即可
    {
        perror("msgget");
        exit(1);
    }
    for(int i=0; i<4; i++)
    {
        ifsend = msgsnd(msqid,&msgtest[0]+i,sizeof(msgtest[i])-sizeof(long),MSG_NOERROR);
        if(ifsend<0)
        {
            perror("msgsnd");
            exit(1);
        }
    
    }


    puts("end");


    exit(0);
}

2.接收端

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include "mq.h"
#include <string.h>
#include <signal.h>
int msgid;
void sig_handler(int);

int main(int argc, char** argv)
{
    key_t key;
    int msgrecvnum;
    struct msgbuf recvmsg;
    struct sigaction act, oldact;
    act.sa_handler = sig_handler;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask,SIGQUIT);
    act.sa_flags = 0;
    sigaction(SIGINT,&act,&oldact);


    key = ftok(KEYPATH,PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        exit(1);
    }

    msgid = msgget(key,IPC_CREAT|0666);  //注意设置文件属性
    if(msgid < 0)
    {
        perror("msgget");
        exit(1);        
    }
    while(1)
    {
        msgrecvnum = msgrcv(msgid,&recvmsg,sizeof(recvmsg)-sizeof(long),atoi(argv[1]),0);
        if(msgrecvnum < 0)
        {
            perror("msgrcv");
            exit(1);        
        }
        fprintf(stderr,"the name of student is %s\n",recvmsg.name);
        fprintf(stderr,"the score of chinese is %d\n",recvmsg.chinese);
        fprintf(stderr,"the score of math is %d\n",recvmsg.math);

    }



    exit(0);
}


void sig_handler(int args)
{
    int remsgctl;
    puts("sig tested");
    remsgctl = msgctl(msgid,IPC_RMID,NULL);
    if(remsgctl < 0)
    {
        perror("msgctl");
        exit(1);
    }
}

3.结果
cmd: ipcs查看消息队列,上述例子中,使用signal函数,ctrl + c,产生信号SIGINT,触发去除消息队列函数,否则进程结束,消息队列不会删除。
在这里插入图片描述

xili271948@er04180p:~/tcp$ ./mq_recv 3
the name of student is wangzhu
the score of chinese is 70
the score of math is 80
^Csig tested
msgrcv: Interrupted system call
xili271948@er04180p:~/tcp$ ./mq_recv 0
the name of student is datengzi
the score of chinese is 80
the score of math is 90
the name of student is sunyue
the score of chinese is 80
the score of math is 80
the name of student is meisen
the score of chinese is 70
the score of math is 80
the name of student is wangzhu
the score of chinese is 70
the score of math is 80
^Csig tested
msgrcv: Interrupted system call
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值