【OrangePi Zero2 智能家居】阿里云人脸识别方案

一、接入阿里云
二、C语言调用阿里云人脸识别接口
[三、System V消息队列和POSIX 消息队列](#三、System V消息队列和POSIX 消息队列)

一、接入阿里云

在之前树莓派的人脸识别方案采用了翔云平台的方案去1V1上传比对两张人脸比对,这种方案是可行,可

以继续采用。但为了接触更多了云平台方案,在Orange Pi Zero2里, 讲采用人脸搜索1:N方案,通过提

前在阿里云人脸数据库里存储人脸照片后,输入单张已授权人脸图像,与人脸库中人脸图片进行对比,

最终获取比对结果。

官网地址如下:https://vision.aliyun.com/

点击"人脸搜索1:N"

点击"立即开通":

使用阿里云APP/支付宝/钉钉扫码登录:

购买"人脸搜索1:N"能力,第一次购买,可以有5000次的免费使用:

开通完后, 在"工作台->开发能力->人脸人体->人脸数据库管理 " 添加人脸照片样本 :

上传数据库后,安装阿里云人脸识别SDK:

bash 复制代码
pip install alibabacloud_facebody20191230

导入ALIBABA_CLOUD_ACCESS_KEY_IDALIBABA_CLOUD_ACCESS_KEY_SECRET环境变量:

bash 复制代码
vi ~/.bashrc #最后的结尾添加, 在垃圾分类的项目里如果已经添加过就不需要添加了
export ALIBABA_CLOUD_ACCESS_KEY_ID="你的KEY_ID"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="你的KEY_SECRECT"

可以拿同一人的照片和不同人的照片用官方python代码进行对比:

python 复制代码
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230
# face.py

import os
import io
from urllib.request import urlopen
from alibabacloud_facebody20191230.client import Client
from alibabacloud_facebody20191230.models import SearchFaceAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions

config = Config(
	# 创建AccessKey ID和AccessKey Secret,请参考
	https://help.aliyun.com/document_detail/175144.html。
	# 如果您用的是RAM用户的AccessKey,还需要为RAM用户授予权限AliyunVIAPIFullAccess,请参考
	https://help.aliyun.com/document_detail/145025.html。
	# 从环境变量读取配置的AccessKey ID和AccessKey Secret。运行代码示例前必须先配置环境变量。
	access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
	access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
	# 访问的域名
	endpoint='facebody.cn-shanghai.aliyuncs.com',
	# 访问的域名对应的region
	region_id='cn-shanghai'
)

search_face_request = SearchFaceAdvanceRequest()
#场景一:文件在本地
stream0 = open(r'/tmp/SearchFace.jpg', 'rb')
search_face_request.image_url_object = stream0

#场景二:使用任意可访问的url
#url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/facebody/SearchFace1.png'
#img = urlopen(url).read()
#search_face_request.image_url_object = io.BytesIO(img)
search_face_request.db_name = 'default'
search_face_request.limit = 5

runtime_option = RuntimeOptions()

try:
	# 初始化Client
	client = Client(config)
	response = client.search_face_advance(search_face_request, runtime_option)
	# 获取整体结果
	print(response.body)
	
except Exception as error:
	# 获取整体报错信息
	print(error)
	# 获取单个字段
	print(error.code)
	# tips: 可通过error.__dict__查看属性名称
	
#关闭流
#stream0.close()

一般比对成功的Python字典数据里的score会有大于0.6的值,而比对失败score普遍低于0.1。

例如下面是比对成功的数据:

python 复制代码
{'Data': {'MatchList': [{'FaceItems': [{'Confidence': 80.54945, 'DbName':
'default', 'EntityId': 'sfms', 'FaceId': '88665949', 'Score':
0.7572572231292725}, {'Confidence': 77.51004, 'DbName': 'default', 'EntityId':
'sfms', 'FaceId': '88665951', 'Score': 0.7193253040313721}, {'Confidence':
74.420425, 'DbName': 'default', 'EntityId': 'sfms', 'FaceId': '88665946',
'Score': 0.6665557622909546}, {'Confidence': 11.461451, 'DbName': 'default',
'EntityId': 'lyf', 'FaceId': '88657431', 'Score': 0.0663260966539383},
{'Confidence': 5.28706, 'DbName': 'default', 'EntityId': 'lyf', 'FaceId':
'88657429', 'Score': 0.030595608055591583}], 'Location': {'Height': 527, 'Width':
405, 'X': 136, 'Y': 123}, 'QualitieScore': 99.3521}]}, 'RequestId': '6DE302BB-
130A-5D3C-B83D-0937D5A257FD'}

比对失败的数据则如下所示:

python 复制代码
{'Data': {'MatchList': [{'FaceItems': [{'Confidence': 6.137868, 'DbName':
'default', 'EntityId': 'lyf', 'FaceId': '88657429', 'Score':
0.03551913797855377}, {'Confidence': 2.9869182, 'DbName': 'default', 'EntityId':
'lyf', 'FaceId': '88657433', 'Score': 0.017284952104091644}, {'Confidence':
2.0808065, 'DbName': 'default', 'EntityId': 'lyf', 'FaceId': '88657431', 'Score':
0.01204138807952404}, {'Confidence': 0.71279377, 'DbName': 'default', 'EntityId':
'lyf', 'FaceId': '88657430', 'Score': 0.004124855622649193}, {'Confidence': 0.0,
'DbName': 'default', 'EntityId': 'sfms', 'FaceId': '88665951', 'Score':
-0.09112970530986786}], 'Location': {'Height': 257, 'Width': 173, 'X': 156, 'Y':
42}, 'QualitieScore': 99.673065}]}, 'RequestId': '62C20100-CCAC-5FE2-9BA6-
AE583F0056EF'}

因此,就可以利用获取的最大score的值判断是否大于0.6来判断是否比对成功。

返回数据的说明:

复制代码
Data:这是一个对象,其中包含了匹配列表的信息。
MatchList:这是一个数组,其中包含了匹配的结果。每个元素都是一个对象,代表一个匹配项。
FaceItems:这是一个数组,其中包含了匹配项中所有人脸的信息。每个元素都是一个对象,包含了一些关于
该人脸的信息,如自信度(Confidence)、数据库名(DbName)、实体ID(EntityId)、面部ID
(FaceId)和分数(Score)。
Location:这是一个对象,包含了人脸在原始图像中的位置信息,包括宽度(Width)、高度(Height)、
左上角的x坐标(X)和y坐标(Y)。
QualitieScore:这是一个浮点数,表示了整个匹配过程的质量得分。

二、C语言调用阿里云人脸识别接口

修改上一小节的face.py代码,将其中的代码封装成函数,并获取其中字典里score的最大值,以备C语言

调用:

python 复制代码
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230

import os
import io
from urllib.request import urlopen
from alibabacloud_facebody20191230.client import Client
from alibabacloud_facebody20191230.models import SearchFaceAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions

config = Config(
	# 创建AccessKey ID和AccessKey Secret,请参考
	https://help.aliyun.com/document_detail/175144.html。
	# 如果您用的是RAM用户的AccessKey,还需要为RAM用户授予权限AliyunVIAPIFullAccess,请参考
	https://help.aliyun.com/document_detail/145025.html。
	# 从环境变量读取配置的AccessKey ID和AccessKey Secret。运行代码示例前必须先配置环境变量。
	access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
	access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
	# 访问的域名
	endpoint='facebody.cn-shanghai.aliyuncs.com',
	# 访问的域名对应的region
	region_id='cn-shanghai'
)

def alibaba_face():
	search_face_request = SearchFaceAdvanceRequest()
	#场景一:文件在本地
	stream0 = open(r'/tmp/SearchFace.jpg', 'rb')
	search_face_request.image_url_object = stream0
	
	#场景二:使用任意可访问的url
	#url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/facebody/SearchFace1.png'
	#img = urlopen(url).read()
	#search_face_request.image_url_object = io.BytesIO(img)
	search_face_request.db_name = 'default'
	search_face_request.limit = 5
	
	runtime_option = RuntimeOptions()
	try:
		# 初始化Client
		client = Client(config)
		response = client.search_face_advance(search_face_request, runtime_option)
		# 获取整体结果
		match_list = response.body.to_map()['Data']['MatchList']
		scores = [item['Score'] for item in match_list[0]['FaceItems']]
		highest_score = max(scores)
		value = round(highest_score, 2)
		return value
	except Exception as error:
		# 获取整体报错信息
		print(error)
		# 获取单个字段
		print(error.code)
		return 0.0
		# tips: 可通过error.__dict__查看属性名称
	#关闭流
	stream0.close()
	
if __name__ == "__main__":
	alibaba_face()
python 复制代码
这里面对scores = [item['Score'] for item in match_list[0]['FaceItems']] 的解释:
match_list[0]['FaceItems']]输入内容为:
[{'Confidence': 12.260886, 'DbName': 'default', 'EntityId': 'sfms', 'FaceId':
'88665949', 'Score': 0.07095234096050262}, {'Confidence': 9.446312, 'DbName':
'default', 'EntityId': 'sfms', 'FaceId': '88665946', 'Score':
0.054664719849824905}, {'Confidence': 1.2030103, 'DbName': 'default', 'EntityId':
'sfms', 'FaceId': '88665951', 'Score': 0.006961682811379433}, {'Confidence': 0.0,
'DbName': 'default', 'EntityId': 'lyf', 'FaceId': '88657431', 'Score':
-0.03559441864490509}, {'Confidence': 0.0, 'DbName': 'default', 'EntityId':
'lyf', 'FaceId': '88657429', 'Score': -0.04274216294288635}]
那么[item['Score'] for item in match_list[0]['FaceItems']是一个 Python 列表推导式),
用于从嵌套的字典中提取特定的值。
具体来说,match_list 是一个包含字典的列表。每个字典里都有很多键值对,其中一个键是
'FaceItems'。'FaceItems' 对应的值是一个字典列表,每个字典都代表一个面部信息,并且都有一个
'Score' 键。
这个列表推导式的目的是从 data 的第一个元素(即第一个字典)中的 'FaceItems' 键对应的字典列表中
提取所有 'Score' 键的值,并将这些值存储在一个新的列表 scores 中。
分解一下这个列表推导式:
for item in data[0]['FaceItems']:这部分代码遍历 match_list 的第一个元素中的
'FaceItems' 键对应的字典列表。在每次循环中,item 被赋予列表中的下一个字典。
item['Score']:这部分代码获取当前 item(即一个包含面部信息的字典)中 'Score' 键对应的值。
[item['Score'] for item in data[0]['FaceItems']]:整体而言,这个列表推导式创建一个新的列
表 scores,该列表包含 data 中第一个元素的 'FaceItems' 键对应的所有字典的 'Score' 键的值。
最终,scores 将是一个包含所有 'Score' 值的列表,你可以对这个列表进行进一步的操作和分析,比如找
出最大值。

三、System V消息队列和POSIX 消息队列

在后面的项目中会用POSIX消息队列, 和System V消息队列(msgget、msgsnd, msgrcv)

类似,都是用以队列的形式传递消息。接口主要有以下几个:

System V消息队列 POSIX 消息队列
主要函数 1. 创建或获取消息队列: msgget(key_t key, int oflag) 2. 往消息队列中放入消息: msgsnd(int msqid, const void *ptr, size_t length, int flag) 3. 从消息队列中读取消息: msgrcv(int msqid, void *ptr, size_t length, long type, int flag) 4. 控制消息队列: msgctl(int msqid, int cmd, struct msqid_ds *buf) 1. 创建或打开消息队列: mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr attr ); 2. 发送消息到消息队列: int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio) 3. 从消息队列接收消息: ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio) 4. 关闭一个消息队列: int mq_close(mqd_t mqdes) 5. 删除一个消息队列: int mq_unlink(const char *name) 6. 注册消息队列的异步通知: int mq_notify(mqd_t mqdes,const struct sigevent *notification); 7. 获取消息队列的属性: int mq_getattr(mqd_t mqdes, struct mq_attr *attr); 8. 设置消息队列的属性: int mq_setattr(mqd_t mqdes, struct mq_attr *attr, struct mq_attr *oattr);
基本用法 1.创建或获取消息队列: 使用msgget()函数来创建或获取一个消息队列。该函数接受一个键(key)和一个标志(flag)作为参数。如果键的值为IPC_PRIVATE或当前没有消息队列与给定键相关联,将会创建一个新的消息队列。标志位可以用来指定权限组合。 2. 往消息队列中放入消息: 使用msgsnd()函数来往一个消息队列中放入一个消息。该函数接受四个参数,分别为消息队列标识符、指向消息的指针、消息的大小以及标志位。成功放入消息后,该函数返回0,否则返回-1并设置errno来表示错误原因。 3. 从消息队列中读取消息: 使用msgrcv()函数来从一个消息队列中读取一个消息。该函数接受五个参数,分别为消息队列标识符、指向消息的指针、消息的最大大小、消息的类型以及标志位。成功读取消息后,该函数返回读取到的消息的大小,否则返回-1并设置errno来表示错误原因。 4. 控制消息队列: 使用msgctl()函数来对一个消息队列进行控制操作,如删除、设置权限等。该函数接受三个参数,分别为消息队列标识符、操作命令以及一个可选的参数。 1.创建或打开消息队列: 使用mqd_t mq_open(const char *name,int oflag,mode_t mode,struct mq_attr *attr);函数来创建或打开一个消息队列。该函数接受队列名称、打开标志以及可选的权限和属性作为参数。如果队列不存在且指定了创建标志,将会创建一个新的消息队列。成功创建或打开后,函数返回一个消息队列描述符(mqd_t)。 2. 发送消息: 使用int mq_send(mqd_t mqdes,const char *msg_ptr,size_t msg_len,unsigned int msg_prio);函数来发送一个消息到指定的消息队列。该函数接受消息队列描述符、指向消息的指针以及消息的大小作为参数。发送消息时,可以指定消息的优先级,较高的优先级数值表示较高的优先级。成功发送后,函数返回0,否则返回-1并设置errno来表示错误原因。 3. 接收消息: 使用ssize_t mq_receive(mqd_t mqdes, char *mdg_ptr,size_t msg_len,unsigned int *msg_prio);函数来从指定的消息队列中接收一个消息。该函数接受消息队列描述符、指向接收缓冲区的指针以及缓冲区的最大大小作为参数。接收消息时,可以选择按优先级接收,也可以选择非阻塞接收。成功接收后,函数返回接收到的消息的大小,否则返回-1并设置errno来表示错误原因。 4. 关闭消息队列: 使用int mq_close(mqd_t mqdes);函数来关闭一个已打开的消息队列。该函数接受消息队列描述符作为参数。关闭消息队列后,相关的资源将被释放。 5. 删除消息队列: 使用int mq_unlink(const char *name);函数来删除一个已存在的消息队列。该函数接受队列名称作为参数。删除一个消息队列将会移除与之关联的所有消息和状态。 2、3步可以改成下面的6、7、8步,支持异步通知: 6. 设置异步通知: 使用int mq_notify(mqd_t mqdes,const struct sigevent *notification);函数来注册一个进程以接收异步通知。该函数接受消息队列描述符、一个指向sigevent结构的指针以及一个通知标志作为参数。在sigevent结构中,可以设置当消息到达时要发送的信号或者要调用的回调函数。通过设置用int mq_notify(mqd_t mqdes,const struct sigevent *notification);,当消息队列从空变为非空时,已注册的进程将收到一个信号或触发一个回调函数,以异步地通知该进程。 7. 发送消息: 使用int mq_send(mqd_t mqdes,const char *msg_ptr,size_t msg_len,unsigned int msg_prio);函数来发送一个消息到指定的消息队列。该函数接受消息队列描述符、指向消息的指针以及消息的大小作为参数。发送消息时,可以指定消息的优先级,较高的优先级数值表示较高的优先级。成功发送后,函数返回0,否则返回-1并设置errno来表示错误原因。 8.处理异步通知: 当有新消息到达时,已注册的进程将收到一个信号或触发一个回调函数。在信号处理函数或回调函数中,可以执行相关的操作来处理新到达的消息。例如,可以调用ssize_t mq_receive(mqd_t mqdes, char *mdg_ptr,size_t msg_len,unsigned int *msg_prio);来接收并处理消息。

其他说明

  1. mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr) 中oflag和
    mode 参数说明
    参数oflag:同int open(const char *pathname, int flags, mode_t mode);函数的的oflag类似有
    O_RDONLY、O_RDWR, O_WRONLY,除此之外还有 O_CREAT、O_EXCL(如果 O_CREAT 指定,但
    name 不存在,就返回错误),O_NONBLOCK(以非阻塞方式打开消息队列,在正常情况下
    mq_receive和mq_send 函数会阻塞的地方,使用该标志打开的消息队列会返回 EAGAIN 错误)。
    参数mode:同int open(const char *pathname, int flags, mode_t mode);函数的mode参数,用于指定
    权限位, 比如0644权限
  2. 关于 struct mq_attr属性结构提:
c 复制代码
struct mq_attr
{
	long mq_flags;//阻塞标志, 0(阻塞)或O_NONBLOCK
	long mq_maxmsg;//最大消息数
	long mq_msgsize;//每个消息最大大小
	long mq_curmsgs;//当前消息数
};
  1. mq_notiy函数的使用注意事项:
    a. 注册撤销:当通知被发送给它的注册进程时,其注册会被撤销。这意味着,如果希望继续接收通知,
    进程必须再次调用 mq_notify 以重新注册。
    b. 空队列与数据到来:消息机制触发条件是,在消息队列为空的情况下有数据到来才会触发。当消息队
    列不为空时,即使有新的数据到来也不会触发通知。
    c. 阻塞与通知:只有当没有任何线程阻塞在该队列的 mq_receive 调用的前提下,通知才会发出。这意
    味着,如果有线程正在等待接收消息,通知可能不会被发送。
  2. struct sigevent和sigval_t sigev_val 的定义如下:
c 复制代码
union sigval { /* Data passed with notification */
	int sival_int; /* Integer value */
	void *sival_ptr; /* Pointer value */
};

struct sigevent {
	int sigev_notify; /* Notification method */
	int sigev_signo; /* Notification signal */
	union sigval sigev_value;
					/* Data passed with notification */
	void (*sigev_notify_function) (union sigval);
					/* Function used for thread
	notification (SIGEV_THREAD) */
	void *sigev_notify_attributes;
					/* Attributes for notification thread
					(SIGEV_THREAD) */
	pid_t sigev_notify_thread_id;
					/* ID of thread to signal
					(SIGEV_THREAD_ID); Linux-specific */
};

a. sigev_notify取值:

SIGEV_NONE:这个值表示不需要任何通知。当sigev_notify被设置为这个值时,即使事件发生了,也不会有任何通知发送到进程。

SIGEV_SIGNAL:事件发生时,将sigev_signo指定的信号发送给指定的进程;

SIGEV_THREAD:事件发生时,内核会(在此进程内)以sigev_notify_attributes为线程属性创建一个线程,并让其执行sigev_notify_function,并以sigev_value为其参数

b. sigev_signo: 在sigev_notify=SIGEV_SIGNAL时使用,指定信号类别, 例如SIGUSR1、SIGUSR2 等

c.sigev_value: sigev_notify=SIGEV_SIGEV_THREAD时使用,作为sigev_notify_function的参数, 当发送信号时,这个值会传递给信号处理函数。

示例1:使用阻塞方式读写

c 复制代码
// 包含所需的头文件
#include <pthread.h> // POSIX线程库
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库
#include <unistd.h> // UNIX标准库
#include <mqueue.h> // POSIX消息队列库
#include <string.h> // 字符串处理库

// 定义消息队列的名称和要发送的消息
#define QUEUE_NAME "/test_queue" // 消息队列的名称
#define MESSAGE "Hello, World!" // 要发送的消息

// 全局的消息队列描述符
mqd_t mq;

// sender_thread函数:发送线程的主体
void *sender_thread(void *arg) {
	char message[] = MESSAGE; // 创建要发送的消息的副本
	printf("Sender thread started.\n"); // 打印发送线程开始的消息
	mq_send(mq, message, strlen(message) + 1, 0); // 发送消息到消息队列
	printf("Message sent.\n"); // 打印消息已发送的消息
	return NULL; // 返回NULL,表示线程正常结束
}

// receiver_thread函数:接收线程的主体
void *receiver_thread(void *arg) {
	char buffer[256]; // 创建用于接收消息的缓冲区
	printf("Receiver thread started.\n"); // 打印接收线程开始的消息
	mq_receive(mq, buffer, sizeof(buffer), NULL); // 从消息队列接收消息
	printf("Received message: %s\n", buffer); // 打印已接收的消息
	return NULL; // 返回NULL,表示线程正常结束
}

// main函数:程序的入口点
int main() {
	pthread_t sender, receiver; // 创建发送和接收线程的标识符
	struct mq_attr attr; // 创建消息队列属性结构体变量
	
	// 设置消息队列的属性值
	attr.mq_flags = 0; // 消息队列的标志位设置为0
	attr.mq_maxmsg = 10; // 消息队列的最大消息数设置为10
	attr.mq_msgsize = 256; // 消息队列的每个消息的最大大小设置为256字节
	attr.mq_curmsgs = 0; // 消息队列的当前消息数设置为0
	
	// 打开或创建名为QUEUE_NAME的消息队列,并设置其属性为attr指定的值
	mq = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0666, &attr);
	if (mq == (mqd_t)-1) { // 如果打开或创建失败,则打印错误信息并返回1
		perror("mq_open");
		return 1;
	}
	
	// 创建发送线程,如果创建失败,则打印错误信息并返回1
	if (pthread_create(&sender, NULL, sender_thread, NULL) != 0) {
		perror("pthread_create (sender)");
		return 1;
	}
	
	// 创建接收线程,如果创建失败,则打印错误信息并返回1
	if (pthread_create(&receiver, NULL, receiver_thread, NULL) != 0) {
		perror("pthread_create (receiver)");
		return 1;
	}
	
	// 等待发送线程结束,如果发送线程已经结束,则立即返回,否则阻塞等待其结束
	pthread_join(sender, NULL);
	// 等待接收线程结束,如果接收线程已经结束,则立即返回,否则阻塞等待其结束
	pthread_join(receiver, NULL);
	
	// 关闭已打开的消息队列描述符mq所引用的消息队列,并释放由该描述符占用的所有资源
	mq_close(mq);
	// 删除名为QUEUE_NAME的消息队列,并将其从系统中删除,如果成功则返回0,否则返回-1并设置errno以指示错误。
	mq_unlink(QUEUE_NAME); // 删除消息队列
	return 0; // 程序正常退出,返回0
}

示例2: 使用mq_notify sigev_notify = SIGEV_THREAD异步通知的方式实现

c 复制代码
#include <mqueue.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

#if 0
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr attr );
int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio); /tiemou
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *prio);
int mq_close(mqd_t mqdes);
int mq_unlink(const char *name)

struct mq_attr
{
	long mq_flags;//阻塞标志, 0(阻塞)或O_NONBLOCK
	long mq_maxmsg;//最大消息数
	long mq_msgsize;//每个消息最大大小
	long mq_curmsgs;//当前消息数
};

union sigval { /* Data passed with notification */
	int sival_int; /* Integer value */
	void *sival_ptr; /* Pointer value */
};

struct sigevent {
	int sigev_notify; /* Notification method */
	int sigev_signo; /* Notification signal */
	union sigval sigev_value;
					/* Data passed with notification */
	void (*sigev_notify_function) (union sigval);
					/* Function used for thread
	notification (SIGEV_THREAD) */
	void *sigev_notify_attributes;
					/* Attributes for notification thread
					(SIGEV_THREAD) */
	pid_t sigev_notify_thread_id;
					/* ID of thread to signal
					(SIGEV_THREAD_ID); Linux-specific */
};

#endif

#define QUEQUE_NAME "/test_queue"
#define MESSAGE "mqueque,test!"

void *sender_thread(void *arg)
{
	// 发送消息
	mqd_t mqd = *(mqd_t *)arg;
	int send_size = -1;
	char message[] = MESSAGE;
	printf("sender thread message=%s, mqd=%d\n", message, mqd);
	
	send_size = mq_send(mqd, message, strlen(message) + 1, 0);
	if (-1 == send_size)
	{
		if (errno == EAGAIN)
		{
			printf("message queque is full\n");
		}
		else
		{
			perror("mq_send");
		}
	}
	return NULL;
}

void notify_thread (union sigval sval)
{
	// 获取消息队列描述符
	mqd_t mqd = -1;
	mqd = *((mqd_t *)sval.sival_ptr);
	
	// 定义一个缓冲区,用于存储接收到的消息
	char buffer[256];
	// 定义一个变量,用于存储接收到的消息的大小
	ssize_t bytes_read;
	// 定义一个结构体,用于重新注册消息队列的通知
	struct sigevent sev;
	// 打印提示信息
	printf("notify_thread started, mqd=%d\n", mqd);
	// 循环接收消息,直到队列为空
	while (1)
	{
		// 从消息队列中接收消息
		bytes_read = mq_receive(mqd, buffer, 256, NULL);
		
		// 如果接收失败,检查错误码
		if (bytes_read == -1)
		{
			// 如果错误码是EAGAIN,说明队列为空,跳出循环
			if (errno == EAGAIN)
			{
				printf("queue is empty\n");
				break;
			}
			// 否则,打印错误信息,退出程序
			else
			{
				perror("mq_receive");
				exit(1);
			}
		}
		
		// 如果接收成功,打印接收到的消息的大小和内容
		printf("read %ld bytes: %s\n", (long)bytes_read, buffer);
	}
	
	// 重新注册消息队列的通知,使用同样的回调函数和参数
	sev.sigev_notify = SIGEV_THREAD;
	sev.sigev_notify_function = notify_thread;
	sev.sigev_notify_attributes = NULL;
	sev.sigev_value.sival_ptr = &mqd;
	if (mq_notify(mqd, &sev) == -1)
	{
		perror("mq_notify");
		exit(1);
	}
}

#if 0
void *receiver_thread(void *arg)
{
	mqd_t mqd = *(mqd_t *)arg;
	ssize_t receiver_size = -1;
	//接收消息
	char buffer[256];
	printf("Receive trehad start\n");
	
	receiver_size = mq_receive(mqd, buffer, sizeof(buffer), NULL);
	printf("receiver thread message=%s, mqd=%d, receiver_size=%ld\n", buffer, mqd, receiver_size);
	return NULL;
}
#endif

int main(int argc, char *argv[])
{
	pthread_t sender, receiver;
	//创建消息队列
	mqd_t mqd = -1;
	
	struct mq_attr attr;
	attr.mq_flags = 0;
	attr.mq_maxmsg = 10;
	attr.mq_msgsize = 256;
	attr.mq_curmsgs = 0;
	mqd = mq_open(QUEQUE_NAME, O_CREAT | O_RDWR, 0666, &attr);
	if (mqd == (mqd_t)-1 )
	{
		perror("mq_open");
		return -1;
	}
	
	struct sigevent sev;
	// 注册消息队列的通知,使用线程模式,指定回调函数和参数
	sev.sigev_notify = SIGEV_THREAD;
	sev.sigev_notify_function = notify_thread;
	sev.sigev_notify_attributes = NULL;
	sev.sigev_value.sival_ptr = &mqd;
	
	if (mq_notify(mqd, &sev) == -1)
	{
		perror("mq_notify");
		exit(1);
	}
	
	if (pthread_create(&sender, NULL, sender_thread, (void *)&mqd) != 0)
	{
		perror("pthread_create sender");
		return -1;
	}
	
#if 0
	if (pthread_create(&receiver, NULL, receiver_thread, (void *)&mqd) != 0)
	{
		perror("pthread_create receiver");
		return -1;
	}
#endif

	pthread_join(sender, NULL);
	sleep(5);//等待触发并把消息读走
	//pthread_join(receiver, NULL);
	mq_close(mqd);
	
	mq_unlink(QUEQUE_NAME);
	//sleep(5);
	return 0;
}
相关推荐
jnrjian2 天前
ORA-01017 查找机器名 用户名 以及library cache lock 参数含义
数据库·oracle
TTc_2 天前
oracle中的union和union all有什么区别?
数据库·oracle
山峰哥2 天前
吃透 SQL 优化:告别慢查询,解锁数据库高性能
服务器·数据库·sql·oracle·性能优化·编辑器
小扎仙森3 天前
关于阿里云实时语音翻译-Gummy推送WebSocket
websocket·阿里云·云计算
南 阳3 天前
Python从入门到精通day37
数据库·python·oracle
晚秋大魔王3 天前
ubutnu 服务器配置openclaw 使用阿里云百炼模型
运维·服务器·阿里云
轩情吖3 天前
MySQL库的操作
android·数据库·mysql·oracle·字符集·数据库操作·编码集
脱发的老袁3 天前
【数据库】Oracle手动清理归档日志
数据库·oracle
jnrjian3 天前
Oracle 共享池 库缓存下的 Library Cache Lock
数据库·缓存·oracle
Shacoray3 天前
OpenClaw 接入阿里云百炼 Coding Plan 指南
阿里云·ai·云计算·qwen3·openclaw·coding plan