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

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

一、接入阿里云

在之前树莓派的人脸识别方案采用了翔云平台的方案去1V1上传比对两张人脸比对,这种方案是可行,可
以继续采用。但为了接触更多了云平台方案,在Orange Pi Zero2里, 讲采用人脸搜索1:N方案,通过提
前在阿里云人脸数据库里存储人脸照片后,输入单张已授权人脸图像,与人脸库中人脸图片进行对比,
最终获取比对结果。

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

点击“人脸搜索1:N”
在这里插入图片描述
点击"立即开通":
在这里插入图片描述
使用阿里云APP/支付宝/钉钉扫码登录:
在这里插入图片描述
在这里插入图片描述
购买“人脸搜索1:N”能力,第一次购买,可以有5000次的免费使用:
在这里插入图片描述
在这里插入图片描述
开通完后, 在”工作台->开发能力->人脸人体->人脸数据库管理 " 添加人脸照片样本 :
在这里插入图片描述
在这里插入图片描述
上传数据库后,安装阿里云人脸识别SDK:

pip install alibabacloud_facebody20191230

导入ALIBABA_CLOUD_ACCESS_KEY_IDALIBABA_CLOUD_ACCESS_KEY_SECRET环境变量:

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

可以拿同一人的照片和不同人的照片用官方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。
例如下面是比对成功的数据:

{'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'}

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

{'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语言
调用:

# -*- 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()
这里面对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属性结构提:
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 的定义如下:
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:使用阻塞方式读写

// 包含所需的头文件
#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异步通知的方式实现

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/381839.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

常用工具类-Collections

常用工具类-Collections 排序操作查找操作填充操作判断集合是否有交集不可变集合 java.util.Collections类是一个工具类&#xff0c;它包含了一些静态方法&#xff0c;用于操作集合&#xff08;如列表和映射&#xff09;。这个类主要用于创建不可修改的集合、填充集合、替换元素…

「优选算法刷题」:数青蛙

一、题目 给你一个字符串 croakOfFrogs&#xff0c;它表示不同青蛙发出的蛙鸣声&#xff08;字符串 "croak" &#xff09;的组合。由于同一时间可以有多只青蛙呱呱作响&#xff0c;所以 croakOfFrogs 中会混合多个 “croak” 。 请你返回模拟字符串中所有蛙鸣所需不…

如何启动若依框架

Mysql安装 一、下载 链接&#xff1a;https://pan.baidu.com/s/1s8-Y1ooaRtwP9KnmP3rxlQ?pwd1234 提取码&#xff1a;1234 二、安装(解压) 下载完成后我们得到的是一个压缩包&#xff0c;将其解压&#xff0c;我们就可以得到MySQL 5.7.24的软件本体了(就是一个文件夹)&…

网络原理(一)

&#x1f495;"Echo"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;网络原理(一) 一. 应用层 应用层是和程序员联系最密切的一层,对于应用层来说,程序员可以自定义应用层协议,应用层的协议一般要约定好以下两部分内容: 根据需求,明确要传输哪些信…

MySQL进阶45讲【19】幻读是什么,幻读会产生什么问题?

1 前言 在MySQL进阶45讲【3】事务隔离的恩恩怨怨这篇文章中&#xff0c;我们有提到过幻读的概念&#xff0c;为了更好地介绍幻读&#xff0c;我们先创建一个表&#xff0c;并添加一些数据&#xff0c;建表和初始化语句如下&#xff1a; CREATE TABLE t ( id int(11) NOTNULL,…

「C++ 类和对象篇 10」初始化列表

目录 一、什么是初始化列表&#xff1f; 二、为什么需要初始化列表&#xff1f; 三、初始化列表怎么使用&#xff1f; 3.1 在构造函数中使用初始化列表 3.2 注意 3.3 结论 3.4 应用场景 四、初始化列表的初始化顺序 五、另一种初始化成员变量的方法 【总结】 一、什么是初始化…

【深度学习】:滴滴出行-交通场景目标检测

清华大学驭风计划课程链接 学堂在线 - 精品在线课程学习平台 (xuetangx.com) 代码和报告均为本人自己实现&#xff08;实验满分&#xff09;&#xff0c;只展示主要任务实验结果&#xff0c;如果需要详细的实验报告或者代码可以私聊博主&#xff0c;接实验技术指导1对1 有任…

单目深度估计任意未标记数据:释放大规模数据潜力 | 开源日报 No.166

LiheYoung/Depth-Anything Stars: 2.6k License: Apache-2.0 Depth-Anything 是一个释放大规模未标记数据力量的项目&#xff0c;可以对任意未标记数据进行单目深度估计。 该项目主要功能、关键特性和核心优势包括&#xff1a; 相对深度估计度量深度估计更好的深度条件控制网…

【Java八股面试系列】并发编程-并发关键字,线程池

目录 并发关键字 Synchronized synchronized最主要的三种使用方式&#xff1a; 具体使用&#xff1a;双重校验锁单例模式 synchronized 底层实现原理&#xff1f; synchronized锁的优化 偏向锁 轻量级锁 重量级锁 Mark Word 与 Monitor 之间的关系 总结 偏向锁、轻量…

阿里百秀移动端首页

技术选型 方案:采取响应式页面开发方案技术: bootstrap框架设计图∶设计图采用1280px设计尺寸 屏幕划分分析 屏幕缩放发现中屏幕和大屏幕布局是一致的。因此我们列定义为col-md-就可以了&#xff0c;md是大于等于970以上的屏幕缩放发现小屏幕布局发生变化&#xff0c;因此我…

ArcGIS学习(四)坐标系-1

ArcGIS学习(四)坐标系 大家平时在处理数据的时候肯定经常遇到坐标系相关的问题。最常见的就是同一个地区的两个数据,导入ArcGIS内却对不上;也肯定听到过坐标系相关的一些词语,比如地理坐标系投影坐标系、投影、WGS1984坐标、CGCS2000坐标系、火星坐标系、百度坐标系等。 …

架构(十二)动态Excel

一、引言 作者最近的平台项目需要生成excel&#xff0c;excel的导入导出是常用的功能&#xff0c;但是作者想做成动态的&#xff0c;不要固定模板&#xff0c;那就看看怎么实现。 二、后端 先捋一下原理&#xff0c;前后端的交互看起来是制定好的接口&#xff0c;其实根本上是…

算法学习——华为机考题库9(HJ56 - HJ63)

算法学习——华为机考题库9&#xff08;HJ56 - HJ63&#xff09; HJ56 完全数计算 描述 完全数&#xff08;Perfect number&#xff09;&#xff0c;又称完美数或完备数&#xff0c;是一些特殊的自然数。 它所有的真因子&#xff08;即除了自身以外的约数&#xff09;的和&…

基于idea解决springweb项目的Java文件无法执行问题

前言 上一篇文章的话介绍了spring以及创建spring项目&#xff0c;但是因为有宝子私聊我说创建的项目那个JAVA文件显示灰色还有一个红点&#xff0c;问我怎么解决下面我来简答的写一下怎么修改配置让他正常的运行 配置 原因好像是因为基于maven的JAVA项目构架&#xff0c;对应…

主干网络篇 | YOLOv5/v7 更换主干网络为 VGG13 / VGG16 / VGG19 | 对比实验必备

论文地址:https://arxiv.org/pdf/1409.1556.pdf 在这项工作中,我们研究了卷积网络深度对其在大规模图像识别环境中准确性的影响。我们的主要贡献是对使用非常小(33)卷积滤波器的架构的不断增加深度的网络进行了彻底评估,这表明通过将深度推进到16-19个权重层,可以在先前…

【漏洞复现】狮子鱼CMS某SQL注入漏洞

Nx01 产品简介 狮子鱼CMS&#xff08;Content Management System&#xff09;是一种网站管理系统&#xff0c;它旨在帮助用户更轻松地创建和管理网站。该系统拥有用户友好的界面和丰富的功能&#xff0c;包括页面管理、博客、新闻、产品展示等。通过简单直观的管理界面&#xf…

怎么把视频音乐提取成mp3?分享详细工具和方法!

在数字媒体时代&#xff0c;音乐已经成为我们生活中不可或缺的一部分。有时候&#xff0c;我们会在社交媒体、视频分享网站或在线视频平台上看到一些非常喜欢的视频音乐&#xff0c;想要将其保存为MP3格式以便随时随地聆听。那么&#xff0c;如何从视频中提取音乐并转换为MP3格…

SpringBoot源码解读与原理分析(七)BeanFactory

文章目录 3 SpringBoot的IOC容器3.1 SpringFramework的IOC容器3.1.1 BeanFactory3.1.1.1 BeanFactory根接口3.1.1.2 HierarchicalBeanFactory3.1.1.3 ListableBeanFactory3.1.1.4 AutowireCapableBeanFactory3.1.1.5 ConfigurableBeanFactory3.1.1.6 AbstractBeanFactory3.1.1.…

ctfshow-命令执行(web118-web122)

web118 是一个窗口 查看源码 发现是system($code) 命令执行 经过测试禁用了很多东西 很多很多 $IFS可以 思路就是使用系统变量 构造我需要的poc 这些都是系统的环境变量 这是答案${PATH:~A}${PWD:~A}$IFS????.??? 解释一下 PATH变量输出结尾一般都是n 因为网站默认根目…