消息队列:
- 消息队列提供了一种从一个进程向另外一个进程发送“一块”数据的方法。
- 每块数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型。
- 消息队列的每块消息有最大长度的上限(MSGMAX),每个消息队列的总字节数也是有上限的(MSGMNB),系统上消息队列的总数也是有上限的(MSGMNI)。
内核为每个IPC对象维护一个结构体(struct ipc_perm),该结构存储了IPC对象的权限和所有者。在/usr/include/linux/ipc.h中。
struct ipc_perm
{
__kernel_key_t key;//调用xxxget()函数时的key,通常由ftok()函数产生
__kernel_uid_t uid;//所有者有效用户ID。
__kernel_gid_t gid;//所有者所属组的有效组ID。
__kernel_uid_t cuid;//创建者有效用户ID。
__kernel_gid_t cgid;//创建者所属组的有效组ID。
__kernel_mode_t mode; //IPC对象的权限,类似于文件的权限。
unsigned short seq;//序列号。
};
消息队列结构(struct msgid_ds),在/usr/include/linux/msg.h中。
struct msqid_ds {
struct ipc_perm msg_perm; /*上一个结构体,存储IPC对象的权限和所有者*/
struct msg *msg_first; /* 队列中第一个未使用的消息*/
struct msg *msg_last; /* 队列中最后一个未使用的消息*/
__kernel_time_t msg_stime; /* 最后发送消息时间 */
__kernel_time_t msg_rtime; /* 最后接收消息时间 */
__kernel_time_t msg_ctime; /* 消息队列最后修改时间 */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* 消息队列上当前的字节数 */
unsigned short msg_qnum; /* 消息队列上的消息数 */
unsigned short msg_qbytes; /* 消息队列上最大字节数 */
__kernel_ipc_pid_t msg_lspid; /* 最后发送消息的进程pid */
__kernel_ipc_pid_t msg_lrpid; /* 最后接收消息的进程pid */
};
内核中消息队列模型:
下面介绍几个关于操作消息队列的系统调用:
创建/打开消息队列:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
- 参数:
- key:消息队列的关键字,通常由ftok()产生。
- msgflg:类似于文件操作的mode参数。
- 返回值:失败返回-1,并设置errno,成功返回消息队列标识符。
向消息队列中添加消息:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- 参数:
- msqid:由msgget()返回的消息队列标识符。
- msgp:指向要发送的消息,消息一般为struct msgbuf{}结构体,如下:
truct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data 数组大小依据要发送或接收的数据大小可变*/
};
- msgsz:要发送消息的大小(char mtext[]大小,不包括mtype)。
- msgflg:控制当前消息队列满时将要发生的动作,msgflg=IPC_NOWAIT表示队列满时不等待,返回EAGAIN错误。
- 返回值:
成功返回0,失败返回-1,并设置errno。
从消息队列中取走消息:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
- 参数:
- msqid:由msgget()返回的消息队列标识符。
- msgp:指向存放接收到的消息的缓冲区。
- msgsz:要接收消息的大小(char mtext[]大小,不包括mtype)
- msgtyp:它可以实现简单的接收消息优先级:
msgtyp == 0 返回队列第一条消息。
msgtyp > 0 返回队列中第一条类型等于msgtyp的消息。
msgtyp < 0 返回队列中满足类型小于等于msgtyp绝对值的所有消息中最小的一个消息。 - msgflg:控制着当前消息队列中没有满足接收要求的消息时的动作,
- 返回值:
失败返回-1,并设置errno,成功返回接收到的字符个数。
控制/删除消息队列:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 参数:
- msqid:由msgget()返回的消息队列标识符。
- cmd:将要进行的命令,有三种取值如下:
IPC_STAT:将buf指向的struct msqid_ds结构体中的字段设置为消息队列的当前关联值。即取出消息队列的当前关联值。
IPC_SET:在进程有足够的权限的条件下,将消息队列当前的关联值设置为buf指向的struct msqid_ds结构体中的各字段。
IPC_RMID:删除消息队列。 - buf:指向struct msqid_ds结构体的指针。
- 返回值:
成功返回0,失败返回-1,并设置errno。
代码实例:
comm.h
#ifndef __COMM_H_
#define __COMM_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
struct msgbuf{
long mtype;
char mtext[1024];
};
int CreateMsgQueue();
int GetMsgQueue();
int DestroyMsgQueue(int msgid);
int SendMsg(int msgid,long who,char *msg);
ssize_t RecvMsg(int msgid,long recvType,char out[]);
#endif //__COMM_H_
comm.c
#include"comm.h"
//依据flag的不同,创建或者打开消息队列
static int CommMsgQueue(int flags){
//获取关键字
key_t _key = ftok(PATHNAME,PROJ_ID);
if(_key == -1){
perror("ftok");
return -1;
}
//产生或者打开消息队列
int msgid = msgget(_key,flags);
if(msgid == -1){
perror("msgget");
return -2;
}
return msgid;
}
//创建消息队列
int CreateMsgQueue(){
return CommMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
//打开消息队列
int GetMsgQueue(){
return CommMsgQueue(IPC_CREAT);
}
//销毁消息队列
int DestroyMsgQueue(int msgid){
int ret = msgctl(msgid,IPC_RMID,NULL);
if(ret == -1){
perror("msgctl");
}
return ret;
}
//发送数据
int SendMsg(int msgid,long who,char *msg){
struct msgbuf buf;
buf.mtype = who;
strcpy(buf.mtext,msg);
int ret = msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0);
if(ret == -1){
perror("msgsnd");
}
return ret;
}
//接收数据
ssize_t RecvMsg(int msgid,long recvType,char out[]){
struct msgbuf buf;
ssize_t ret = msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0);
if(ret == -1){
perror("msgrcv");
return -1;
}
strcpy(out,buf.mtext);
return 0;
}
client.c
#include"comm.h"
int main(){
//获得消息队列
int msgid = GetMsgQueue();
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';
//发送消息队列
SendMsg(msgid,CLIENT_TYPE,buf);
printf("send done...\n");
}else{
perror("read");
break;
}
//读消息队列
RecvMsg(msgid,SERVER_TYPE,buf);
printf("server #%s",buf);
}
return 0;
}
server.c
#include"comm.h"
int main(){
//创建消息队列
int msgid = CreateMsgQueue();
char buf[1024];
while(1){
buf[0] = 0;
//读取消息队列
RecvMsg(msgid,CLIENT_TYPE,buf);
printf("client# %s",buf);
printf("please Enter# ");
fflush(stdout);
//读标准输入
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s > 0){
buf[s] = '\0';
//发送消息队列
SendMsg(msgid,SERVER_TYPE,buf);
printf("send done...\n");
}else{
break;
}
}
//销毁消息队列
DestroyMsgQueue(msgid);
return 0;
}
Makefile
.PHONY:all
all:client server
client:client.c comm.c
gcc $^ -o $@
server:server.c comm.c
gcc $^ -o $@
.PHONY:cl
cl:
rm -f client server
结果演示:
注:以Ctrl+c
关闭程序时,第二次运行前需要ipcrm
命令删除上一次创建的消息队列。
ipcs&ipcrm
ipcs:
- 说明:ipcs命令往标准输出写入一些关于活动进程间通讯设施的信息,如没有指定任何标志,则输出当前活动消息队列、共享内存段、信号量数组。
- 选项:
- q:输出消息队列。
- m:输出共享内存段。
- s:信号量。
- 示例:
[DELL@localhost lesson9]$ ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x6602268c 98304 DELL 666 0 0
[DELL@localhost lesson9]$
ipcrm
- 说明:ipcrm用于删除进程间通讯设施。用法与ipcs类似,不过要在ipcrm命令后面指定要删除通讯设施的标识符(xxxid)。
- 示例:
[DELL@localhost lesson9]$ ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x6602268c 98304 DELL 666 0 0
[DELL@localhost lesson9]$ ipcrm -q 98304
[DELL@localhost lesson9]$ ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
[DELL@localhost lesson9]$
共享内存:
共享内存是最快的IPC通讯形式,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再通过内核,即进程不需要再调用系统调用进入内核来传递信息,但共享内存没有内置同步与互斥操作。
共享内存模型:
共享内存结构(shmid_ds{}),在/usr/include/linux/shm.h中:
struct shmid_ds {
struct ipc_perm shm_perm; /* 保存权限和所有者 */
int shm_segsz; /* 共享内存大小 */
__kernel_time_t shm_atime; /* 最后一个进程映射此共享内存时间 */
__kernel_time_t shm_dtime; /* 最后一个进程分离该共享内存的时间 */
__kernel_time_t shm_ctime; /* 最后改变该共享内存的时间 */
__kernel_ipc_pid_t shm_cpid; /* 创建该共享内存进程pid */
__kernel_ipc_pid_t shm_lpid; /* 最后操作该共享内存的pid */
unsigned short shm_nattch; /* 映射该共享内存的进程总数 */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
下面介绍关于共享内存的几个系统调用:
创建共享内存:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- 参数:
- key:关键字,通常由ftok()函数产生。
- size:共享内存大小。
- shmflg:类似于文件操作的mode
- 返回值:
失败返回-1,并设置errno,成功返回共享内存标识符。
映射共享内存:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数:
- shmid:由shmget()返回的标识符。
- shmaddr:指定的映射的地址。
1、为NULL时,由内核决定映射地址。
2、不为NULL且shmflg不为SHM_RND时,则以shmaddr为映射地址。
3、不为NULL且shmflg为SHM_RND时,则shmaddr向下取最近的SHMLBA整数倍处,即shmaddr = shmaddr - (shmaddr%SHMLBA)。 - shmflg:两个可能取值。
1、SHM_RND:配合shmaddr一块使用。
2、SHM_RDONLY:映射的共享内存以只读方式操作。
- 返回值:
成功返回映射后的首地址,失败返回-1,并设置errno。
分离共享内存:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
- 参数:
- shmaddr:由shmat()函数返回的指针。
- 返回值:
成功返回0,失败返回-1,并设置errno。
控制/删除共享内存:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数:
- shmid:由shmget()返回的标识符。
- cmd:将要进行的命令,有三种取值如下:
IPC_STAT:将buf指向的struct shmid_ds结构体中的字段设置为共享内存的当前关联值。即取出共享内存的当前关联值。
IPC_SET:在进程有足够的权限的条件下,将共享内存当前的关联值设置为buf指向的struct shmid_ds结构体中的各字段。
IPC_RMID:删除共享内存。 - buf:指向struct shmid_ds结构体的指针。
- 返回值:
成功返回0,失败返回-1,并设置errno。
实例代码:
comm.h
#ifndef __COMM_H_
#define __COMM_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x666
//创建共享内存
int CreateShm(size_t size);
//销毁共享内存
int DestroyShm(int shmid);
//获取共享内存
int GetShm(size_t size);
#endif //__COMM_H_
comm.c
#include"comm.h"
//依据flags的不同,创建或者打开共享内存。
static int CommShm(size_t size,int flags){
key_t _key = ftok(PATHNAME,PROJ_ID);
if(_key == -1){
perror("ftok");
return -1;
}
int shmid = shmget(_key,size,flags);
if(shmid == -1){
perror("shmget");
return -2;
}
return shmid;
}
int CreateShm(size_t size){
return CommShm(size,IPC_CREAT|IPC_EXCL|0666);
}
int DestroyShm(int shmid){
int ret = shmctl(shmid,IPC_RMID,NULL);
if(ret == -1){
perror("shmctl");
}
return ret;
}
int GetShm(size_t size){
return CommShm(size,IPC_CREAT);
}
client.c
#include"comm.h"
int main(){
//获得共享内存
int shmid = GetShm(4096);
sleep(1);
//将共享内存映射到进程的虚拟地址空间中。
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i < 26){
addr[i] = 'A'+i;
i++;
addr[i] = 0;
sleep(1);
}
//分离共享内存
shmdt(addr);
sleep(2);
return 0;
}
server.c
#include"comm.h"
int main(){
//创建共享内存
int shmid = CreateShm(4096);
//将共享内存映射到进程的虚拟地址空间。
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i++ < 26){
printf("client# %s\n",addr);
sleep(1);
}
//分离共享内存
shmdt(addr);
sleep(2);
//删除共享内存。
DestroyShm(shmid);
return 0;
}
Makefile
.PHONY:all
all:client server
client:client.c comm.c
gcc $^ -o $@
server:server.c comm.c
gcc $^ -o $@
.PHONY:cl
cl:
rm -f server client
结果演示:
注:以Ctrl+c
关闭程序时,第二次运行前需要ipcrm
命令删除上一次创建的共享内存。