15章 进程间通信之消息传递(管道、FIFO、消息队列)

管道

管道一般是半双工(数据只能在一个方向上流动),管道只能在具有公共祖先(亲缘关系)的两个进程之间使用。一个管道由一个进程创建,在进程fork之后,管道就可以在父子进程之间通信了。

特点:

  • 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  • 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
  • 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
  • 实现单向,需要创建一个管道,然后fork,父子关掉对应不需要的fd。
  • 实现双向,需要创建两个管道,然后fork,父子关掉对应不需要的fd。
    这里写图片描述
/*
返回两个文件描述符:
fd[0]:用来读
fd[1]:用来写
成功返回0,失败返回-1.
*/
int pipe (int __pipedes[2]);
int main(void)
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];
    if(pipe(fd) < 0)//先创建管道,然后fork,然后设置流向
        err_sys("pipe error");

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid == 0){//子进程
        close(fd[1]);//关闭子进程的写管道
        n = read(fd[0] , line , MAXLINE);//仅仅用读,等待父进程写
        write(STDOUT_FILENO , line , n);
        printf("%d\n" , getpid());
    }else {//父进程
        close(fd[0]);//关闭父进程的读管道
        write(fd[1] , "hello my child \n" , 16);
        printf("%d\n" , getpid());
    }
    exit(0);
}

在shell利用 | 产生管道功能,就实现类似上面的程序,一个程序输出,用过fd[1],写到后面程序的一个fd[0]进行处理。

通过管道,将父进程读取得到的文件,传递给子进程,并让子进程通过more程序显示出来。重点有二:dup2如何使用?mystrrchr如何使用?

//每次一页地显示已产生的输出
#define DEF_PAGE "/bin/more"
char *mystrrchr(const char *str, char c){
    const char ch = c;
    const char *sc;//和传入参数类型一样const否则报错. const为了警示在程序处理过程中不会
                    //改变传入参数
    for(sc = NULL; ; ++str){//这里利用for,循环,更加灵活,通过条件返回,和while一样.
        if(*str == ch)//sc记录s最后ch的位置
            sc = str;
        if(*str == '\0')//字符串结尾,那么返回
            return ((char *)sc);//必须和返回类型一样,否则报错.
    }
}
int main(int argc , char *argv[])
{
    int n;
    int fd[2];
    char line[MAXLINE];
    char *argv0 , *page;
    pid_t pid;
    FILE *fp;
    if((fp = fopen(argv[1] , "r")) == NULL)//打开文件
        err_sys("open err %s\n" , argv[1]);
    if(pipe(fd) < 0 )//创建管道
        err_sys("pipe error\n");

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid == 0){//子进程
        close(fd[1]);//关闭子进程的写管道
        if(fd[0] != STDIN_FILENO){
            //将标准输入成为管道的读端,执行分页程序时候,标准输入是管道的读.那么就可以通过more读取.
            if(dup2(fd[0] , STDIN_FILENO) != STDIN_FILENO)//复制副本
                err_sys("dup2  error to stdin");
            close(fd[0]);//复制了以后就不需要fd[0]
        }
        //get execl();
        page = DEF_PAGE;
        if((argv0 = mystrrchr(page,'\0')) != NULL){
            argv0++;//argv0此时执行m,然后可以充当execl第二参数.
        }
        if(execl(page , argv0 , (char *)0) < 0)
            err_sys("execl error for %s" , page);
    }else {//父进程
        close(fd[0]);//关闭父进程的读管道
        while(fgets(line , MAXLINE , fp) != NULL){
            n = strlen(line);
            if(write(fd[1] , line, n) != n)
                err_sys("write error to pipe\n");
        }
        if(ferror(fp))
            err_sys("fgets error");
        close(fd[1]);
        if(waitpid(pid , NULL , 0) < 0)//等待子进程结束
            err_sys("wait pid error");
    }
    exit(0);
}

利用管道在两个输出父子进程之间通信,和信号的区别在于,根本不需要传递进程pid进去,直接通过管道告诉另外一个进程,否则给我阻塞,read默认阻塞读取。注意因为用了两组管道,所以每一个管道都有一个额外的读取进程。

static int pfd1[2] , pfd2[2];
void tell_wait(void)
{
    if(pipe(pfd1) < 0 || pipe(pfd2) < 0)//创建两个管道
        err_sys("pipe error");
}
void tell_parent(void)
{
    if(write(pfd2[1] , "c" , 1) != 1)//c告诉p
        err_sys("write error");//"c"这才是一个指针
}
void wait_child(void)
{
    char c;
    if(read(pfd2[0] , &c , 1) != 1)//p等待c
        err_sys("read error");
    if(c != 'c')//收到c发给p的c
        err_quit("error");
}
void tell_child(void)
{
    if(write(pfd1[1] , "p" , 1) != 1)//p告诉c
        err_sys("write error");
}
void wait_parent(void)
{
    char c;
    if(read(pfd1[0] , &c , 1) != 1)//p告诉c
        err_sys("write error");
    if(c != 'p')
        err_quit("error");
}

static void print(char *str)
{
    char *ptr;
    char c;
    setbuf(stdout , NULL);
    for(ptr = str ;(c = *ptr++) != 0 ;){//这句for循环,先初始化ptr,然后利用c等于一个值,++的情况处理,既可以遍历,也可以输出.
        putc(c , stdout);
        sleep(1);//睡觉一秒
    }
}
int main(void)
{
    pid_t pid;
    tell_wait();
    if((pid = fork()) < 0)
        err_sys("fork error\n");
    else if(pid == 0){
        wait_parent();//等待父亲告诉我,我在继续做事
        print("child ouput\n");
    }else{
        print("parent ouput\n");
        tell_child();//告诉孩子我已经输出了
        if(waitpid(pid , NULL , 0) < 0)
            err_sys("wait error");
    }
    exit(0);
}

parent ouput
child ouput

简单的客户-服务器的例子

创建两个管道,然后fork,通过两个管道设置成客户和服务器双向通信。

这里写图片描述
这里写图片描述

pid_t
Waitpid(pid_t pid, int *iptr, int options)
{
    pid_t   retpid;

    if ( (retpid = waitpid(pid, iptr, options)) == -1)
        err_sys("waitpid error");
    return(retpid);
}
pid_t
Fork(void)
{
    pid_t   pid;

    if ( (pid = fork()) == -1)
        err_sys("fork error");
    return(pid);
}
char *
Fgets(char *ptr, int n, FILE *stream)
{
    char    *rptr;

    if ( (rptr = fgets(ptr, n, stream)) == NULL && ferror(stream))
        err_sys("fgets error");

    return (rptr);
}
void
Write(int fd, void *ptr, size_t nbytes)
{
    if (write(fd, ptr, nbytes) != nbytes)
        err_sys("write error");
}
ssize_t//包裹函数
Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t     n;

    if ( (n = read(fd, ptr, nbytes)) == -1)
        err_sys("read error");
    return(n);
}
void
Pipe(int *fds)
{
    if (pipe(fds) < 0)
        err_sys("pipe error");
}
void
Close(int fd)
{
    if (close(fd) == -1)
        err_sys("close error");
}

void server(int readfd , int writefd)
{
    int fd;
    ssize_t n;
    char buff[MAXLINE +1];

    n = Read(readfd , buff , MAXLINE);//从管道读取客户机发送的路径名
    if(n == 0)//从管道读取信息
        err_quit("end-of-file while reading pathname");
    buff[n] = '\0';//结束符
    if((fd = open(buff , O_RDONLY)) < 0){//打开路径文件
        snprintf(buff+n , sizeof(buff)-n , ": can't open,%s\n",strerror(errno));//如果出错,通过管道返回错误信息给客户机
        n = strlen(buff);
        Write(writefd , buff , n);
    }else {
        while((n = Read(fd , buff , MAXLINE)) > 0)//通过打开的文件描述符读取文件里面内容
            Write(writefd , buff , n);//全部发送出去.
        Close(fd);//读取完毕,然后关闭文件描述符
    }
}
void client(int readfd , int writefd)
{
    size_t len;
    ssize_t n;
    char buff[MAXLINE];
    Fgets(buff , MAXLINE , stdin);//读取
    len = strlen(buff);
    if(buff[len - 1] == '\n')
       len--;//删除换行符
    Write(writefd , buff , len);//写管道
    while((n  = Read(readfd , buff , MAXLINE)) > 0)//循环从管道读取服务器发送的内容
        Write(STDOUT_FILENO , buff ,n);//读服务器管道信息
}
int main(int argc , char *argv[])
{
    int pipe1[2] , pipe2[2];
    pid_t childpid;
    Pipe(pipe1);//初始化管道
    Pipe(pipe2);//包裹函数里面直接进行了错误判断,返回正确即可
    if((childpid = Fork()) == 0){//child
        Close(pipe1[1]);//关闭1,写
        Close(pipe2[0]);//关闭0,读
        server(pipe1[0] , pipe2[1]);//服务器通过客户机发送路径,读取文件内容,传回客户机
        exit(0);
    }
    Close(pipe1[0]);//关闭1,读
    Close(pipe2[1]);//关闭0,写
    client(pipe2[0] , pipe1[1]);//客户通过标准输入读入一个文件路径,发送给服务器
    Waitpid(childpid , NULL , 0);//等待子进程终止
    exit(0);
}

1、客户机从终端读入文件路径通过管道发送给服务器。
2、服务器收到路径之后,读取路径里面的内容,通过管道回传给客户机。
3、客户机将收到的内容显示在终端。

全双工管道

这里写图片描述
部分系统提供全双工管道,全双工管道由两个半双工管道实现,存在两个缓冲区。
在ubuntu上面测试,发现fd[0]并不支持写,所以在ubuntu上面并没有实现全双工管道的功能。

int main(void)
{
    int fd[2] , n;
    char c;
    pid_t childpid;
    Pipe(fd);//创建两个全双管道
    if((childpid = Fork()) == 0){//child
        sleep(3);//休眠3s
//        if((n = Read(fd[1] , &c , 1)) != 1)//管道0读
//            err_quit("read error");
//        printf("child read %c\n" , c);
        Write(fd[1] , "c" , 1);//管道0写
        exit(0);
    }
    //Write(fd[0] , "p" , 1);//父进程利用管道1写
    if((n = Read(fd[0] , &c , 1)) != 1)//管道1读,加入没有内容,阻塞读
        err_quit("error");
    printf("parent read %c\n" , c);
    exit(0);
}

FIFO

FIFO,也称为命名管道,它是一种文件类型。管道没有名字,仅仅适用于由共同祖先的各个进程。而FIFO因为和路径名关联,所以允许无亲属关系的进程访问FIFO,FIFO也称为有名管道。FIFO是半双工管道。
FIFO通常有两种用途:shell命令使用FIFO将数据从一条管道传送到另一条,无需创建临时文件(类似与管道,但是FIFO可用于非线性连接);客户进程-服务器进程应用程序中,FIFO用作汇聚点,在客户进程和服务器进程二者之间传递数据。

特点:

  • FIFO可以在无关的进程之间交换数据,与无名管道不同。
  • FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
/*
path:管道的名字,通过路径标识。
mode:管道创建各个组的读写属性。
mkfifo隐含指定O_CREAT|O_EXCL,要么创建新的FIFO,要么返回EEXIST错误。
由于FIFO是单向操作,所以必须打开为读或者写权限,不能同时打开为读写权限。
write总是往管道末尾添加数据;read总是从开头返回数据。
*/
int mkfifo (const char *__path, __mode_t __mode);

其中的mode参数与open函数中的mode相同。一旦创建了一个FIFO,就可以用一般的文件I/O函数操作它。
当open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写open要阻塞到某个其他进程为读而打开它。
  • 若指定了O_NONBLOCK,则只读open立即返回。而只写open将出错返回 -1,如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

FIFO简单的客户-服务器的例子

这里写图片描述
服务器端:

#define FIFO1   "/tmp/fifo1"
#define FIFO2   "/tmp/fifo2"

void server(int readfd , int writefd)
{
    int fd;
    ssize_t n;
    char buff[MAXLINE +1];

    n = Read(readfd , buff , MAXLINE);//从管道读取客户机发送的路径名
    if(n == 0)//从管道读取信息
        err_quit("end-of-file while reading pathname");
    buff[n] = '\0';//结束符
    if((fd = open(buff , O_RDONLY)) < 0){//打开路径文件
        snprintf(buff+n , sizeof(buff)-n , ": can't open,%s\n",strerror(errno));
        n = strlen(buff);
        Write(writefd , buff , n);
    }else {
        while((n = Read(fd , buff , MAXLINE)) > 0)//通过打开的文件描述符读取文件里面内容,一行一行读取
            Write(writefd , buff , n);//全部发送出去.
        Close(fd);//读取完毕,然后关闭文件描述符
    }
}
int main(void)
{
    int readfd , writefd;

    if((mkfifo(FIFO1 , FILE_MODE) < 0 )&&(errno != EEXIST))//创建FIFO,如果出错且不是因为存在则返回
        err_sys("can't creat %s" , FIFO1);
    if((mkfifo(FIFO2 , FILE_MODE) < 0 )&&(errno != EEXIST)){//创建FIFO,如果出错且不是因为存在则返回
        Unlink(FIFO1);//因为2失败,那么解除FIFO1.
        err_sys("can't creat %s" , FIFO2);
    }
    readfd = Open(FIFO1 , O_RDONLY);//服务器读打开FIFO1
    writefd  = Open(FIFO2 , O_WRONLY);//服务器写打开FIFO2
    server(readfd , writefd);

    exit(0);
}

服务器端创建FIFO,然后类似于上图子进程的方式打开FIFO1和FIFO2。当服务器运行到Read的时候将阻塞。等待客户机写入FIFO数据。

客户端:

#define FIFO1   "/tmp/fifo1"
#define FIFO2   "/tmp/fifo2"
void client(int readfd , int writefd)
{
    size_t len;
    ssize_t n;
    char buff[MAXLINE];
    Fgets(buff , MAXLINE , stdin);//读取
    len = strlen(buff);
    if(buff[len - 1] == '\n')
       len--;//删除换行符
    Write(writefd , buff , len);//写管道
    while((n  = Read(readfd , buff , MAXLINE)) > 0)//循环从管道读取服务器发送的内容
        Write(STDOUT_FILENO , buff ,n);//读服务器管道信息
}
int main(void)
{
    int readfd , writefd;
    writefd  = Open(FIFO1 , O_WRONLY);//客户机通过读打开FIFO2
    readfd = Open(FIFO2 , O_RDONLY);//客户机通过写打开FIFO1


    client(readfd , writefd);
    Close(readfd);//关闭
    Close(writefd);
    Unlink(FIFO1);//删除FIFO
    Unlink(FIFO2);//删除FIFO
    exit(0);
}

客户端以上图父进程的方式打开FIFO,通过读入路径,发给服务器,然后服务器读取路径里面内容,返回给客户机。
注意假如出现两个进程对一个FIFO同时读或者同时写,那么两个进程都将出现死锁。
最后删除FIFO的是客户机。

消息队列

消息队列,是消息的链接表,存放在内核中(生命周期随内核持续性,直到关机,消息一直都在)。一个消息队列由一个标识符(即队列ID)来标识。一个进程可以往某个队列写入一些消息,然后终止,再让另外一个进程在以后某个时刻该出这些消息。区别于前面两种,当一个管道或FIFO的最后一次关闭发生时,仍在该管道或FIFO上的数据将被丢弃。

特点

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

这里写图片描述

#include <sys/msg.h>

//创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);

//添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);

//读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);

//控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

在以下两种情况下,msgget将创建一个新的消息队列:

  • 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
  • key参数为IPC_PRIVATE。

函数msgrcv在读取消息队列时,type参数有下面几种情况:

  • type == 0,返回队列中的第一个消息;
  • type > 0,返回队列中消息类型为 type 的第一个消息;
  • type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。

可以看出,type值非0时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。(其他的参数解释,请自行Google之)

下面写了一个简单的使用消息队列进行IPC的例子,服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来。

msg_server.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "/etc/passwd"

// 消息结构
struct msg_form {
    long mtype;
    char mtext[256];
};

int main()
{
    int msqid;
    key_t key;
    struct msg_form msg;

    // 获取key值
    if((key = ftok(MSG_FILE,'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // 打印key值
    printf("Message Queue - Server key is: %d.\n", key);

    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // 打印消息队列ID及进程ID
    printf("My msqid is: %d.\n", msqid);
    printf("My pid is: %d.\n", getpid());

    // 循环读取消息
    for(;;) 
    {
        msgrcv(msqid, &msg, 256, 888, 0);//返回类型为888的第一个消息
        printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
        printf("Server: receive msg.mtype is: %d.\n", msg.mtype);

        msg.mtype = 999; //客户端接收的消息类型
        sprintf(msg.mtext, "hello, I'm server %d", getpid());
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
    }
    return 0;
}

msg_client.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "/etc/passwd"

// 消息结构
struct msg_form {
    long mtype;
    char mtext[256];
};

int main()
{
    int msqid;
    key_t key;
    struct msg_form msg;

    // 获取key值
    if ((key = ftok(MSG_FILE, 'z')) < 0) 
    {
        perror("ftok error");
        exit(1);
    }

    // 打印key值
    printf("Message Queue - Client key is: %d.\n", key);

    // 打开消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1) 
    {
        perror("msgget error");
        exit(1);
    }

    // 打印消息队列ID及进程ID
    printf("My msqid is: %d.\n", msqid);
    printf("My pid is: %d.\n", getpid());

    // 添加消息,类型为888
    msg.mtype = 888;
    sprintf(msg.mtext, "hello, I'm client %d", getpid());
    msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

    // 读取类型为777的消息
    msgrcv(msqid, &msg, 256, 999, 0);
    printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
    printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
    return 0;
}

消息队列

消息队列是消息的链表,存储于内核,由消息队列标识符标识。消息队列又叫队列,其标志符号简称队列ID。具有足够权限的任何进程都可以往一个给定消息队列中放置一个消息,具有足够权限的任何进程都可以从一个给定队列中读出一个消息。
这里写图片描述
- 系统函数解释:

struct ipc_perm
  {
    __key_t __key;          /* Key.  */
    __uid_t uid;            /* Owner's user ID.  */
    __gid_t gid;            /* Owner's group ID.  */
    __uid_t cuid;           /* Creator's user ID.  */
    __gid_t cgid;           /* Creator's group ID.  */
    unsigned short int mode;        /* Read/write permission.  */
    unsigned short int __pad1;
    unsigned short int __seq;       /* Sequence number.  */
    unsigned short int __pad2;
    __syscall_ulong_t __glibc_reserved1;
    __syscall_ulong_t __glibc_reserved2;
  };
  struct msqid_ds
{
  struct ipc_perm msg_perm; /* structure describing operation permission */
  __time_t msg_stime;       /* time of last msgsnd command */
#ifndef __x86_64__
  unsigned long int __glibc_reserved1;
#endif
  __time_t msg_rtime;       /* time of last msgrcv command */
#ifndef __x86_64__
  unsigned long int __glibc_reserved2;
#endif
  __time_t msg_ctime;       /* time of last change */
#ifndef __x86_64__
  unsigned long int __glibc_reserved3;
#endif
  __syscall_ulong_t __msg_cbytes; /* current number of bytes on queue */
  msgqnum_t msg_qnum;       /* number of messages currently on queue */
  msglen_t msg_qbytes;      /* max number of bytes allowed on queue */
  __pid_t msg_lspid;        /* pid of last msgsnd() */
  __pid_t msg_lrpid;        /* pid of last msgrcv() */
  __syscall_ulong_t __glibc_reserved4;
  __syscall_ulong_t __glibc_reserved5;
};

/*
调用这个函数可以打开一个现有队列或者创建一个新的队列。
IPC结构都和一个非负整数的标识符加以引用。就是键__key,这个键作为该对象的外部名,且有内核变成标识符。
每一个队列都有一个对应的msqid_ds。中间存放了消息队列的信息。
创建信队列时候,初始化msqid_ds部分成员。
 __msgflg设置权限位。
 key:可以是ftok返回值,也可以是IPC_PRIVATE
 flag:上图读写权限组合,还可以是IPC_CREAT|IPC_EXCL
*/
int msgget (key_t __key, int __msgflg);

/*
对消息队列执行各种操作。cmd指定msqid(由msgget返回)要执行的命令。
__msqid:消息队列标识符
__cmd:对消息队列操作命令。
__buf:cmd为IPC_SET,通过buf参数替换队列结构体成员;IPC_STAT返回队列结构体成员信息到buf;IPC_RMID从系统删除id指定的队列,第三个参数忽略。
返回:成功0,失败-1
*/
int msgctl (int __msqid, int __cmd, struct msqid_ds *__buf);


/*
将数据存放在消息队列。注意是void *类型指针。所以只要消息满足一定条件,都可以放进去。
消息3部分组成:长整形字段、一个数组、实际数据字节数。消息总是放在队尾。
__msqid:队列标识符
__msgp:通常是包含一个消息类型(一个long整形数而已)的结构体
__msgsz:待发消息的长度,长整型数消息类型之后用户自定义长度
__msgflg:设定调用阻塞或非阻塞。如果非阻塞则队列没有存放消息位置,函数立马返回。
返回:成功0,失败-1
*/
struct mymesg{//消息示例
    long mtype;//必须部分,用于标识消息类别
    int16_t mshort;//以下是消息部分
    char mchar[512];
}
int msgsnd (int __msqid, const void *__msgp, size_t __msgsz,
           int __msgflg);
/*
从队列中读出消息。
__msqid:消息队列标识符
__msgp:指定接收消息的存放位置(应该是和消息一样的结构体地址)
__msgsz:和msgsnd差不多,但是可以返回小于它的长度 
__msgtyp:指定从所给队列中取出哪种消息
__msgflg:指定请求消息类型不在应该如何处理
返回:成功则为读入缓冲区中数据字节数,出错则是-1.
*/
ssize_t msgrcv (int __msqid, void *__msgp, size_t __msgsz,
               long int __msgtyp, int __msgflg);

删除消息队列。注意因为没有引用计数,当队列删除后,其他进程仍在使用则会返回错误。
  • 具体例子:
#include "unpipc.h"
typedef unsigned long ulong_t;
int main(int argc , char *argv[])
{
    int msqid;//创建队列返回的标识符
    struct msqid_ds info;//队列维护的一个结构体信息
    struct msgbuf buf;//简单的消息模板
    msqid = msgget(IPC_PRIVATE , SVMSG_MODE | IPC_CREAT);//创建队列和权限返回标识符
    buf.mtype = 1;//消息类型,就一个数值定义
    buf.mtext[0] = 1;//具体发送消息
    msgsnd(msqid , &buf , 1, 0);//发送消息到队列
    msgctl(msqid , IPC_STAT , &info);//返回队列信息,存储在info结构里面
    printf("read-write:%03o,cbyte = %lu , qnum = %lu , qbytes = %lu\n",
           info.msg_perm.mode&0777,(ulong_t)info.__msg_cbytes,(ulong_t)info.msg_qnum,(ulong_t)info.msg_qbytes
           );
    //打印队列信息:权限,队列当前字节,消息数量,队列允许最大字节
    system("ipcs -q");
    msgctl(msqid , IPC_RMID , NULL);//删除标识符和队列,此时第三个参数可以为空
    exit(0);
}

read-write:644,cbyte = 1 , qnum = 1 , qbytes = 16384
—— Message Queues ——–
key msqid owner perms used-bytes messages
0x00000000 98304 wangjun 644 1 1

按 来关闭窗口…
0是共同键值。可以看出这里的输出和上面的讨论一样。

/*
该函数的argc和argv参数通常直接从main()的参数直接传递而来。shortopts是选项字母组成的字串。如果该字串里的任一字符后面有冒号,那么这个选项就要求有选项参数。
当给定getopt()命令参数的数量 (argc)、指向这些参数的数组 (argv) 和选项字串 (optstring) 后,getopt() 将返回第一个选项,并设置一些全局变量。使用相同的参数再次调用该函数时,它将返回下一个选项,并设置相应的全局变量。如果不再有可识别的选项,将返回 -1,此任务就完成了。所以可以使用while循环分析选项。

*/
extern char *optarg;//当前选项参数对应字符串
extern int optind;//argv的当前索引值。当getopt()在while循环中使用时,循环结束后,剩下的字串视为操作数,在argv[optind]至argv[argc-1]中可以找到
extern int opterr;//这个变量非零时,getopt()函数为“无效选项”和“缺少参数选项,并输出其错误信息。
extern int optopt;//当发现无效选项字符之时,getopt()函数或返回'?'字符,或返回':'字符,并且optopt包含了所发现的无效选项字符。
int getopt (int ___argc, char *const *___argv, const char *__shortopts);

另外一个例子:
创建一个队列

#include "unpipc.h"
#include "myerror.h"
int main(int argc , char *argv[])//ftok 根据目录创建队列标识符
{
    int c,oflag , mqid;
    oflag = SVMSG_MODE | IPC_CREAT;//队列权限和创建
    while( (c = getopt(argc , argv , "e")) != -1){
        switch(c){
        case 'e':
            oflag |= IPC_EXCL;//创建失败退出功能
            break;
        }
    }
    if(optind != argc -1)
        err_quit("[-e] <pathname>");
    mqid = msgget(ftok(argv[optind] , 0) , oflag);//创建消息队列
}

往队列上发送一个消息:

#include "unpipc.h"
#include "myerror.h"
int main(int argc , char *argv[])
{
    int mqid;
    size_t len;
    int type;
    struct msgbuf *ptr;//通过动态内存初始化地址,分配真正的结构
    if (argc != 4)
        err_quit("<pathname> <#bytes> <type>\n");
    len = atoi(argv[2]);//将字符串转换成数字
    type = atoi(argv[3]);
    mqid = msgget(ftok(argv[1] , 0) , MSG_W);//通过写的方式获得标识符
    ptr = (struct msgbuf *)calloc(sizeof(long) + len , sizeof(char));//分配内存块每一个块是size,并且初始化为1
    ptr->mtype = type;
    msgsnd(mqid , ptr , len , 0);
    exit(0);
}

读取队列上消息:

#include "unpipc.h"
#include "myerror.h"
#define MAXMSG (8129+sizeof(long))
int main(int argc , char *argv[])
{
    int c,flag,mqid;
    struct msgbuf *buf;
    long type;
    size_t n;
    type = flag = 0;
    while( (c = getopt(argc , argv , "nt:")) != -1){
        switch (c){
        case 'n':
            flag |= IPC_NOWAIT;//非阻塞
            break;
        case 't':
            type = atol(optarg);//optarg代表参数
            break;
        }
    }
    if(optind != argc -1)
        err_quit("[-n][-t type]<pathname>");
    mqid = msgget(ftok(argv[optind] , 0) , MSG_R);//通过读的方式获得标识符
    buf = (struct msgbuf *)malloc(MAXMSG);//分配缓存空间,没有简单方法定义缓存大小,所以利用最大分配
    n = msgrcv(mqid , buf , MAXMSG , type , flag);//返回读入消息的字节数.
    printf("read %ld bytes , type = %ld \n" , n , buf->mtype);//打印对应的消息
    exit(0);
}

删除队列标识符:

#include "unpipc.h"
#include "myerror.h"
int main(int argc , char *argv[])
{
    int mqid;
    if(argc != 2)
        err_quit("<pathname>");
    mqid = msgget(ftok(argv[1] , 0) , 0);//不读不写,获取标识符
    msgctl(mqid , IPC_RMID , NULL);//删除消息队列
    exit(0);
}

因为利用了命令行,所以通过路径配合ftok产生对于的消息队列标识符。
./msgsend/msgsend /tmp/test1 1 100 发送1字节消息
./msgsend/msgsend /tmp/test1 2 200 发送2字节消息
./msgsend/msgsend /tmp/test1 3 400 发送3字节消息
key msqid owner perms used-bytes messages
0x0001bafa 98306 wangjun 644 6 3
可以看出消息队列占用了6字节,其中类型并不占用字节。
./megrecive/msgrecive -n -t 300 /tmp/test1
通过非阻塞读取完消息以后,消息队列消息会自动清除。
假如通过阻塞读取一个消息队列上一个不存在的类型,那么进程将阻塞,等待该类型消息放置到消息队列。
并且可以通过shell命令,ipcs -q 标识符 (删除对于的消息队列)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有时需要偏执狂

请我喝咖啡

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

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

打赏作者

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

抵扣说明:

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

余额充值