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