Unix网络编程-进程间通信---6、System IPC---消息队列

1、使用消息队列标识符表示(和普通文件fd相似)
2、打开一个消息队列标识符时,内核对应维护一个信息结构体

内核给每一个system V IPC维护一个信息结构体:
struct ipc_perm{
        uid_t    uid;
        gid_t    gid;
        uid_t    cuid;
        gid_t    ugid;
        mode_t   mode;//读写权限
        ulong_t  seq;
        ket_t    key;//IPC_key
}
 

struct msqid_ds{
        struct ipc_perm  msg_perm;//主要存放key和权限
        struct msg      *msg_first;//指向第一个消息指针
        struct msg      *msg_last;//指向最后一个
        msglen_t         msg_cbytes;//队列有多少字节
        msgqnum_t        msg_qnum;//队列中有多少个消息
        msglen_t         msg_qbytes;//最大消息字节
        pid_t            msg_lspid;//最后一个发送消息的pid
        pid_t            msg_lrpid;//最后一个接收消息的pid
        time_t           msg_stime;//时间,最后一个发送
        time_t           msg_rtime;//时间,最后一个接收
        time_t           msg_ctime;//时间,最后一个fctrl
}

 


3、msgget

int msgget(ket_t key, int oflag);
key:可以是ftok返回的值也可以是IPC_PRIVATE
oflag:IPC_CREATE IPC_EXECL组合
注意:创建一个消息消息队列时,会初始化一个新的msgid_ds;

4、msgsnd

int msgsnd(int msgid, const void *ptr, size_t length, int flag)
msgid:由msgget获取的消息队列标识符
ptr:消息结构体指针,并且结构体中前8个字节放消息类型, 后面就当作消息内容处理
struct msgbuf{
    long mtype;//注意消息内容必须大于0,因为后面获取消息队列内容的时候会有负值消息类型参数
    char mtext[1];
}
flag:可以是0,也可以是IPC_NOWAIT


5、msgrcv

ssize_t msgrcv(int msqid, void* ptr, size_t length, long type, int flag)
注意:
(1)length接收的消息长度不包括long类型字段,表示ptr能存放的最大长度
(2)type == 0 返回第一个消息msg_first
     type > 0  返回对应type类型的第一个消息
 type < 0  返回绝对值中最小类型的第一个消息

 

6、msgctl

int msgctl(int msgid, int cmd, struct msgid_ds *buff);
cmd: IPC_RMID  选择这个参数的时候,第三个参数会被忽略
     IPC_SET   给所指定的消息队列设置其msqid_ds结构中的四个成员:msg_perm.uid,msg_per.gid, msg_perm.mode,msg_perm_qbytes 第二、三个参数应该填什么?
     IPC_STAT 给调用者返回所指定消息队列对应当前msgid_ds结构信息

 

7、测试结构体中的参数:使用:不知为何,读出来就是不对

#define SVMSG_MODE  (MSG_R | MSG_W | MSG_R >> 3 | MSG_R >> 6)
#include <unistd.h> 
#include<stdio.h>
#include<sys/msg.h>

struct msgbuf {
    long int mtype;
    char mtext[1];
    
};

//通过IPC_STAT命令返回消息队列对应的当前的msqid_ds结构体中的内容:

int main()
{
    int                  msgid;//消息队列表示符
    struct msqid_ds      info;//内核维护的结构提
    struct msgbuf        buf;//消息体结构
    
    msgid = msgget(IPC_PRIVATE, IPC_CREAT);
    
    buf.mtype = 1;
    buf.mtext[1] = 1;
    msgsnd(msgid, &buf, 1, 0);
    
    msgctl(msgid, IPC_STAT, &info);
    
    printf("权限 = %03o                  队列中有多少字节 = %d     队列中消息数=%d        最大消息字节数=%d\n",
            info.msg_perm.mode &0777,    (int)info.msg_cbytes,     (int)info.msg_qnum,    (int)info.msg_qbytes);//反正结果不对
    
    system("ipcs -q");
    msgctl(msgid, IPC_RMID, NULL);
    
    return 0;
}

 

//msgcreate程序---getopt这个函数第一次使用,通过命令行获取msgid
int main(int argc, char **argv)// ----> ./a.out  111 2222  33333   --->argc = 4,对应字符串分别argv[0] = /a.out; argv[1]=111; argv[2]=2222; argv[3]=33333
{
    int c, oflag, mqid;
    oflag = IPC_CREAT;
    while((c = getopt(argc, argv, "e")) != -1)  //getopt函数的使用挺复杂的,
    {
        switch(c)
        {
            case 'e':
                oflag |= IPC_EXCL;
                break;
        }
    }

    
    if(optind != argc - 1)
    {
        printf("usage:\n");
    }
    msqid = msgget(ftok(argv[optind], 0), oflag);
    
    return 0;
}

 

//msgsnd使用
struct msgbuf {
    long int mtype;
    char mtext[1];    
};
int main(int argc, char **argv)//msgsnd <pathname> <#bytes> <typed>
{
  int mqid;
  size_t len;
  long type;

  len = atoi(argv[2]);
  type = atoi(argv[3]);

  mqid = mssget(ftok(argv[1], 0), MSG_W);
  ptr = calloc(sizeof(long) + len, sieof(char));
  ptr->mtype = type;
  msgsend(mqid, ptr, len, 0);

  return 0;
}

 

//msgrcv
#define MAXMSG 8192+sizeof(long)
int main(int argc, char **argv)
{
  struct msgbuf *buff;
  type = flag =0;
  while((c = getopt(argc, argv, "nt:")) != -1)
  {
      switch(c)
      {
          case 'n':
              flag |= IPC_NOWAIT;
              break;
          case 't':
              type = atol(optarg);
              break;
      }
  }

  mqid = msgget(ftok(argv[optind], 0), MSG_R);
  buff = malloc(MAXMSG);
  n=msgrcv(mqid, buff, MAXMSG, type, flag);
  printf("%s", buff->mtext);

  return 0;
}

 

8、通过消息队列进程通信方式实现客户端-服务器

(1)消息队列----main函数服务器

#define MQ_KEY1 1234L
#define MQ_KEY2 2345L
void server(readid, writeid);
int main(int argc, char **argv)
{
int readid, writeid;
readid = msgget(MQ_KEY1, IPC_CREAT);
writeid = msgget(MQ_KEY2, IPC_CREAT);

server(readid, writeid);
return 0;
}

(2)消息队列----客户端

void client(int, int)
int main(int argc, char **argv)
{
   int readid, writeid;
   /*假设服务器已经创建了消息队列,这里可以把oflag设置为0?*/
   writeid = msgget(MQ_KEY1, 0);
   readid = msgget(MQ_KEY2, 0);

   client(readid, writeid);

   /*使用完之后可以删除*/
   msgctl(readid, IPC_RMID, NULL);
   msgctl(writeid, IPC_RMID, NULL);

   return 0;
}

通过一个统一分server和client接口,可以实现ipc通信的全部公用;会从管道处开始使用

(3)server(int, int)

struct mymesg{
    long mesg_type;
    char mesg_data[1024];
    long mesg_len;
    ...
};


ssize_t mesg_recv(int id, struct mymesg *mptr)//使用消息队列实现接收消息
{
    ssize_t n;
    n = msgrcv(id, &(mptr->mesg_type), 1024, mptr->mesg_type, 0);//有问题吧,&(mptr->mesg_type)---》mptr
    mptr->mesg_len = n;

    return (n);
}


void server(int readfd, int writefd)
{
    FILE *fp;
    ssize_t n;
    struct mymesg mesg;

    mesg.mesg_type = 1;
    n=mesg_recv(readfd, &mesg);/*读取IPC的路径在哪里*/
    mesg.mesg_data[n]='\0';

    if((fp=fopen(mesg.mesg_data, "r") == NULL)
    {
        /*打开错误告诉客户端*/
        snprintf(mesg.mesg_data + n, sizeof(mesg.mesg_data)-n, ":can't open %s", strerror(errno));
      // int snprintf(char *str, size_t size, const char *format, ...);
        mesg.mesg_len = strlen(mesg.mesg_data);
        mesg_send(writefd, &mesg);
    }
    else
    {
        while(fgets(mesg.mesg_data, 1024, fp) != NULL)
        {
            mesg.mesg_len = strlen(mesg.mesg_data);
            mesg_send(writefd, &mesg);
        }
        close(fp);
    
   }

}

 

(4)client

ssize_t mesg_send(int id, struct mymesg *mptr)

{

     return msgsnd(id, &(ptr->mesg_type), mptr->mesg_len, 0);//感觉也有问题,

}

void client (int readfd, int writefd)
{
    size_t len;
    ssize_t n;
    struct mymesg, mesg;
    
    fget(mesg_data, 128, stdin);//客户端读取输入的文件路径名
    len = strlen(mesg_data);
    
    if( mesg.mesg_data[n-1] == '\n')
        len--;
    mesg.mesg_len = len;
    mesg.mesg_type = 1;

    mesg_send(writefd, &mesg);

    while((n = mesg_recv(readfd, &mesg)) > 0)
        write(STDOUT_FIFONO, mesg_data, n);  

}

 

9、单服务器对多客户端的通信(1)

思路:客户的进程用作消息类型,每个客户把自己的进程id指定为msgrcv的type参数;

int main(int argc, char **argv)
{

    int msqid;
    msgqid = msgget(MQ_KEY1, IPV_CREATE | IPC_EXECL | 0777);
    
    server(msgqid, msgqid);

    exit(0);
}

 

void server(int readfd, int writefd)
{

    FIFL *fp;
    char *ptr;
    pid_t pid;
    ssize_t n;

    struct mymesg mesg;

    for(; ; )//服务器一直运行着
    {
        mesg.mesg_type = 1;
        if( (n = mesg_recv(readfd, &mesg) == 0)//等待接收消息类型位1的
            continue;

        mesg.mesg_dat[n-1]='\0';//防止接收的数据没有结束符
        if( (*ptr = strchr(mesg.mesg_data, ' ') == NULL)
            continue;
        
        *ptr++ =0;//ptr指向文件路径
        pid = atol(mesg.mesg_data);
        mesg.mesg_type = pid;//重新封装消息内容发送给客户端
        
        if((fp = fopen(ptr, "r")) == NULL){;;;}
        else
        {
            while(fgets(mesg.mesg_data, 1024, fp) != NULL)
            {
                mesg.mesg_len = strlen(mesg.mesg_data);
                mesg_send(writefd, &mesg);
            } 
            close(fd); 
        }
        //文件发送完毕后,发送0字节消息表示结束
        mesg.mesg_len = 0;
        mesg_send(writefd, &mesg);      
    }

}

客户端---

int main(int argc, char **argv)
{
    int msqid;
    
    msqid = msgget(MQ_KEY1, 0);//服务器创建的

    client(msqid, msqid);    

    return 0;
}
void client(int readfd, int writefd)
{
    size_t len;
    ssize_t n;
    char *ptr;
    struct mymesg mesg;

    snprintf(mesg.mesg_data, 128, "%ld ", (long)getpid());
    len =strlen(mesg.mesg_data);
    ptr = mesg.mesg_data + len;
    
    fgets(ptr, 128 -len, stdin);
    len = strlen(mesg.mesg_data);
    if((mesg.mesg_data[len -1]) =='\n'
        len --;

    mesg.mesg_len = len;
    mesg.mesg_type = 1

    mesg_send(writefd, &mesg); //发送请求之后,接下来就是接收数据了

    mesg.mesg_type = getpid();
    while((n = mesg_recv(readfd,&mesg)) > 0)
        write(STDOUT_FILENO, mesg.mesg_data);

}

9、单服务器对多客户端的通信(2)

思路:每个客户使用一个队列接收去往各个客户的服务器应答,服务器的队列有个公开的键,各个客户以IPC_PRIVATE键创建字的队列;这里就不需要发送字节的pid了,而是由每个客户把字自己的私有队列的标识符传递给服务器,服务器把自己的应答发送给客户指定的队列中。同时还要以并发服务器的形式;

客户端代码:

int main(int argc, char **argv)
{
    int readid, writeid;   
    writeid = msgget(MQ_KEY1, 0);
    readid = msgget(IPC_PRIVATE, IPC_CREATE | IPC_EXECL | 0777);
    
    client(reafid, writeid);

    msgctl(readid, IPC_RMID, NULL);
}

void client(int readid, int writeid)
{
    size_t len;
    ssize_t n;
    char *ptr;
    struct mymesg  mesg;

    snprintf(mesg.mesg_data, 128, "%d", readid);
    len = strlen(mesg.mesg_data);
    ptr = mesg.mesg_data + len;
    
    fgets(ptr, 128 -len, stdin);
    
    len = strlen(mesg.mesg_data);
    if(mesg.mesg_data[len -1] == '\n')
        len--;

    mesg.mesg_data=len;
    mesg.mesg_type = 1;
    
    mesg_send(writeid, &mesg);
    
    while((n == mesg_recv(readid, &mesg)) > 0)
        write(STDOUT_FILENO, mesg.mesg_data, n);       
}

服务器代码:

void sig_chld(int signo)
{
    pid_t pid;
    int stat;

    while((pid = waitpid(-1, &stat, WNOHANG)) > 0);
    return;
}

void server(int readid, int writeid)
{
    FIFE *fp;
    char *ptr;
    ssize_t n;
    struct mymesg mesg;

    signal(SIGCHLD, sig_chld);

    for(; ;)
    {
        mesg.mesg_data_type = 1;
        if((n = mesg_recv(readid, &mesg)) == 0)
            continue;
        mesg .mesg_data[n]='\0';

        if( (ptr = strchr(mesg.mesg_data, ' ') == NULL)
            continue;
        *ptr++ = 0;
        writeid =atoi(mesg.mesg_data);
    
        if(fork() == 0) //子进程
        {
            fp = open(ptr, "r");
            while(fgets(mesg.mesg_data, 1024, fp) != NULL)
            {
                mesg.mesg_len = strlen(mesg.mesg_data);
                mesg_send(writeid, &mesg);
            }
            fclose(fp);
        
            //发送0字节代表结束
            mesg.mesg_len = 0;
            mesg_send(writeid, &mesg);
            exit(0);
        }
        else //父进程什么也不做
        {}
    }
}

10、如何在消息队列中使用select或者poll

注意:管道由于他们是描述符标识的,所以可以直接使用select

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LuckyDog0623

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

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

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

打赏作者

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

抵扣说明:

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

余额充值