一、ftok函数
关于ftok
函数,先不去了解它的作用来先说说为什么要用它,共享内存,消息队列,信号量它们三个都是找一个中间介质,来进行通信的,这种介质多的是。就是怎么区分出来,就像唯一一个身份证来区分人一样。你随便来一个就行,就是因为这。只要唯一就行,就想起来了文件的设备编号和节点,它是唯一的,但是直接用它来作识别好像不太好,不过可以用它来产生一个号。ftok()就出场了。ftok函数具体形式如下:
key_t ftok(const char *pathname, int proj_id);
其中参数fname
是指定的文件名,这个文件必须是存在的而且可以访问的。id
是子序号,它是一个8bit
的整数。即范围是0~255
。当函数执行成功,则会返回key_t键值,否则返回-1。在一般的UNIX
中,通常是将文件的索引节点取出,然后在前面加上子序号就得到key_t的值。
有关该函数的三个常见问题:
1.pathname是目录还是文件的具体路径,是否可以随便设置
2.pathname指定的目录或文件的权限是否有要求
3.proj_id是否可以随便设定,有什么限制条件
解答:
1、ftok根据路径名,提取文件信息,再根据这些文件信息及project ID合成key,该路径可以随便设置。
2、该路径是必须存在的,ftok只是根据文件inode在系统内的唯一性来取一个数值,和文件的权限无关。
3、proj_id是可以根据自己的约定,随意设置。这个数字,有的称之为project ID; 在UNIX系统上,它的取值是1到255;
关于ftok()函数的一个陷阱
在使用ftok()
函数时,里面有两个参数,即fname
和id
,fname
为指定的文件名,而id
为子序列号,这个函数的返回值就是key
,它与指定的文件的索引节点号和子序列号id
有关,这样就会给我们一个误解,即只要文件的路径,名称和子序列号不变,那么得到的key值永远就不会变。
事实上,这种认识是错误的,想想一下,假如存在这样一种情况:在访问同一共享内存的多个进程先后调用ftok()
时间段中,如果fname
指向的文件或者目录被删除而且又重新创建,那么文件系统会赋予这个同名文件新的i节点信息,于是这些进程调用的ftok()
都能正常返回,但键值key
却不一定相同了。由此可能造成的后果是,原本这些进程意图访问一个相同的共享内存对象,然而由于它们各自得到的键值不同,实际上进程指向的共享内存不再一致;如果这些共享内存都得到创建,则在整个应用运行的过程中表面上不会报出任何错误,然而通过一个共享内存对象进行数据传输的目 的将无法实现。
这是一个很重要的问题,希望能谨记!!!
所以要确保key
值不变,要么确保ftok()的文件不被删除,要么不用ftok()
,指定一个固定的key
值。
二、消息队列 msgget() msgsnd() msgrcv() 及代码实现
1. msgget函数原型
msgget(得到消息队列标识符或创建一个消息队列对象) | ||
所需头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> | |
函数说明 | 得到消息队列标识符或创建一个消息队列对象并返回消息队列标识符 | |
函数原型 | int msgget(key_t key, int msgflg) | |
函数传入值 | key | 0(IPC_PRIVATE):会建立新的消息队列 |
大于0的32位整数:视参数msgflg来确定操作。通常要求此值来源于ftok返回的IPC键值 | ||
msgflg | 0:取消息队列标识符,若不存在则函数会报错 | |
IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符 | ||
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错 | ||
函数返回值 | 成功:返回消息队列的标识符 | |
出错:-1,错误原因存于error中 | ||
附加说明 | 上述msgflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定消息队列的存取权限 | |
错误代码 | EACCES:指定的消息队列已存在,但调用进程没有权限访问它 EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志 ENOENT:key指定的消息队列不存在同时msgflg中没有指定IPC_CREAT标志 ENOMEM:需要建立消息队列,但内存不足 ENOSPC:需要建立消息队列,但已达到系统的限制 |
2. msgctl函数原型
msgctl (获取和设置消息队列的属性) | ||
所需头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> | |
函数说明 | 获取和设置消息队列的属性 | |
函数原型 | int msgctl(int msqid, int cmd, struct msqid_ds *buf) | |
函数传入值 | msqid | 消息队列标识符 |
cmd
| IPC_STAT:获得msgid的消息队列头数据到buf中 | |
IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes | ||
buf:消息队列管理结构体,请参见消息队列内核结构说明部分 | ||
函数返回值 | 成功:0 | |
出错:-1,错误原因存于error中 | ||
错误代码 | EACCESS:参数cmd为IPC_STAT,确无权限读取该消息队列 EFAULT:参数buf指向无效的内存地址 EIDRM:标识符为msqid的消息队列已被删除 EINVAL:无效的参数cmd或msqid EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行 |
3. msgsnd函数原型
msgsnd (将消息写入到消息队列) | ||
所需头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> | |
函数说明 | 将msgp消息写入到标识符为msqid的消息队列 | |
函数原型 | int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) | |
函数传入值 | msqid | 消息队列标识符 |
msgp | 发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下: struct s_msg{ /*msgp定义的参照格式*/ | |
msgsz | 要发送消息的大小,不含消息类型占用的4个字节,即mtext的长度 | |
msgflg | 0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列 | |
IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回 | ||
IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。 | ||
函数返回值 | 成功:0 | |
出错:-1,错误原因存于error中 | ||
错误代码 | EAGAIN:参数msgflg设为IPC_NOWAIT,而消息队列已满 EIDRM:标识符为msqid的消息队列已被删除 EACCESS:无权限写入消息队列 EFAULT:参数msgp指向无效的内存地址 EINTR:队列已满而处于等待情况下被信号中断 EINVAL:无效的参数msqid、msgsz或参数消息类型type小于0 |
4. msgrcv函数原型
msgrcv (从消息队列读取消息) | ||
所需头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> | |
函数说明 | 从标识符为msqid的消息队列读取消息并存于msgp中,读取后把此消息从消息队列中删除 | |
函数原型 | ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); | |
函数传入值 | msqid | 消息队列标识符 |
msgp | 存放消息的结构体,结构体类型要与msgsnd函数发送的类型相同 | |
msgsz | 要接收消息的大小,不含消息类型占用的4个字节 | |
msgtyp | 0:接收第一个消息 | |
>0:接收类型等于msgtyp的第一个消息 | ||
<0:接收类型等于或者小于msgtyp绝对值的第一个消息 | ||
msgflg | 0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待 | |
IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG | ||
IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息 | ||
IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃 | ||
函数返回值 | 成功:实际读取到的消息数据长度 | |
出错:-1,错误原因存于error中 | ||
错误代码 | E2BIG:消息数据长度大于msgsz而msgflag没有设置IPC_NOERROR EIDRM:标识符为msqid的消息队列已被删除 EACCESS:无权限读取该消息队列 EFAULT:参数msgp指向无效的内存地址 ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读 EINTR:等待读取队列内的消息情况下被信号中断 |
三、测试代码实现
1、read.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <string.h>
#include <sys/msg.h>
typedef struct {
long mtype;
char data[1024];
}msgbuf;
int main(void)
{
int ret = -1;
key_t key_id;
char* file_path = "/home/zhaoky/Desktop/test";
int shmid;
msgbuf data_buf;
key_id = ftok(file_path,1);
if (key_id < 0) {
printf("file is not exited!\n");
return ret;
}
if ((shmid = msgget(key_id, IPC_CREAT | 0660))) {
perror("msgget");
return ret;
}
memset(&data_buf, 0, sizeof(data_buf));
ret = msgrcv(shmid, &data_buf, sizeof(data_buf.data), 1, IPC_NOWAIT);
if (ret < 0) {
printf("err!\n");
}
printf("message:%s\n", data_buf.data);
}
2、write.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <string.h>
#include <sys/msg.h>
typedef struct {
long mtype;
char data[1024];
}msgbuf;
int main(void)
{
int ret = -1;
int i = 0;
key_t key_id;
char* file_path = "/home/zhaoky/Desktop/test";
int shmid;
msgbuf data_buf;
key_id = ftok(file_path,1);
if (key_id < 0) {
printf("file is not exited!\n");
return ret;
}
if ((shmid = msgget(key_id, IPC_CREAT | 0660))) {
perror("msgget");
return ret;
}
memset(&data_buf, 0, sizeof(data_buf));
data_buf.mtype = 1;
memcpy(data_buf.data, "hello", sizeof("hello"));
msgsnd(shmid, &data_buf, sizeof(data_buf.data), 0);
}