iceoryx源码阅读(五)——共享内存通信(三)
最近有几个网友希望我续写《iceoryx源码阅读》的剩余部分,所以还是继续写,供大家参考。
本文主要介绍正常的消息接收流程,从流程本身而言,这部分代码逻辑比较简单。为了内容充实,我们对代码做了更深入地解读,结合对前面几篇文章的内容,从代码角度进行分析。
1 正常的消息接收流程
正常的消息接收流程是指接收端调用接收函数,从共享消息队列中获取消息所在位置的描述信息,然后根据这一描述信息,从共享内存读取数据的过程。下面,我们iceoryx向应用层提供的接收消息的函数开始介绍,逐步深入。
1.1 SubscriberImpl::take
职责:
应用层通过SubscriberImpl::take函数接收数据。从逻辑上来说,这个函数通过更底层的方法获取到ChunkHeader指针(我们在iceoryx源码阅读(二)中已经介绍这一数据结构),进而获取到用户负载数据,据此得到用户描述消息所用的结构体。
模板参数:
- T: 消息体类型。
- H: 消息头类型。
- BaseSubscriberType: 基类类型,不明白为什么iceoryx常常把基类也定义为范型,范型用的太范了。
入参:
无
返回值:
返回值的类型为cxx::expected<Sample<const T, const H>, ChunkReceiveResult>,包含两部分:
正常情况的返回值类型,即:
Sample<const T, const H>,这个类型是对用户负载数据的封装类,以便于更方便地读取其中的数据。错误情况的返回值类型,即:ChunkReceiveResult,其类型定义如下:
enum class ChunkReceiveResult
{
TOO_MANY_CHUNKS_HELD_IN_PARALLEL,
NO_CHUNK_AVAILABLE
};
说明了错误原因。
以下是这个函数的代码清单:
template <typename T, typename H, typename BaseSubscriberType>
inline cxx::expected<Sample<const T, const H>, ChunkReceiveResult>
SubscriberImpl<T, H, BaseSubscriberType>::take() noexcept
{
auto result = BaseSubscriberType::takeChunk();
if (result.has_error())
{
return cxx::error<ChunkReceiveResult>(result.get_error());
}
auto userPayloadPtr = static_cast<const T*>(result.value()->userPayload());
auto samplePtr = iox::unique_ptr<const T>(userPayloadPtr, [this](const T* userPayload) {
auto* chunkHeader = iox::mepoo::ChunkHeader::fromUserPayload(userPayload);
this->port().releaseChunk(chunkHeader);
});
return cxx::success<Sample<const T, const H>>(std::move(samplePtr));
}
逐段代码分析:
LINE 05 ~ LINE 09: 调用基类BaseSubscriberType的takeChunk,即:BaseSubscriber的takeChunk方法,获取指向ChunkHeader的指针,关于ChunkHeader,请参考iceoryx源码阅读(二)第4节。
LINE 10 ~ LINE 15: 根据指向ChunkHeader的指针,获取指向用户数据的指针,并据此构造便于应用层使用的
Sample<const T, const H>实例并返回。
1.2 BaseSubscriber<port_t>::takeChunk
职责:
模板类BaseSubscriber<port_t>中,包含了类型为port_t的成员m_port,这就是端口数据结构,iceoryx的端口中封装了队列数据结构,具体见iceoryx源码阅读(四)第1节。对于订阅者类,port_t实际上就是SubscriberPortUser。
入参:
无
返回值: cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult>
正常情况下返回值类型为
const mepoo::ChunkHeader*,即指向ChunkHeader的指针。错误情况下返回值类型为
ChunkReceiveResult,用以说明获取失败的原因。
整体代码分析:
template <typename port_t>
inline cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult> BaseSubscriber<port_t>::takeChunk() noexcept
{
return m_port.tryGetChunk();
}
这个函数只是调用m_port(这里是枚举类型port_t,特化类型为SubscriberPortUser)的SubscriberPortUser::tryGetChunk方法。
1.3 SubscriberPortUser::tryGetChunk
这个函数的实现代码如下:
cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult> SubscriberPortUser::tryGetChunk() noexcept
{
return m_chunkReceiver.tryGet();
}
同样是直接调用了m_chunkReceiver,其类型为ChunkReceiver<SubscriberPortData::ChunkReceiverData_t>的tryGet方法,这里的范型参数SubscriberPortData::ChunkReceiverData_t通过追溯至SubscriberPortData,位于iceoryx_posh/include/iceoryx_posh/internal/popo/ports/subscriber_port_data.hpp:64,发现其为iox::popo::SubscriberChunkReceiverData_t类型:
using ChunkReceiverData_t = iox::popo::SubscriberChunkReceiverData_t;
而SubscriberChunkReceiverData_t又是一个类型别名,定义于iceoryx_posh/include/iceoryx_posh/internal/popo/ports/pub_sub_port_types.hpp::
using SubscriberChunkReceiverData_t =
ChunkReceiverData<MAX_CHUNKS_HELD_PER_SUBSCRIBER_SIMULTANEOUSLY, SubscriberChunkQueueData_t>;
ChunkReceiverData类也是一个范型类,它的基类是第二个范型参数,在上述代码中就是SubscriberChunkQueueData_t,为类型别名,实际类型如下:
using SubscriberChunkQueueData_t = ChunkQueueData<DefaultChunkQueueConfig, ThreadSafePolicy>;
这说明ChunkReceiverData范型类继承自ChunkQueueData范型类,这个类是通信队列数据机构,我们在《iceoryx源码阅读(四)——共享内存通信(二)》介绍过。
ChunkReceiverData类中(不包括基类)定义了唯一一个成员m_chunksInUse,具体如下:
static constexpr uint32_t MAX_CHUNKS_IN_USE = MaxChunksHeldSimultaneously + 1U;
UsedChunkList<MAX_CHUNKS_IN_USE> m_chunksInUse;
这一成员用于缓存接收到的SharedChunk,原因下一小节将会说明。需要指出的是,UsedChunkList虽然类型名为List,但其本质上是一个数组,具体可以参考iceoryx_posh/include/iceoryx_posh/internal/popo/used_chunk_list.hpp:79-82中的数据成员:
uint32_t m_usedListHead{INVALID_INDEX};
uint32_t m_freeListHead{0u};
uint32_t m_listIndices[Capacity];
DataElement_t m_listData[Capacity];
下面来简单分析一下ChunkReceiver的继承结构,ChunkReceiver<SubscriberPortData::ChunkReceiverData_t>继承自ChunkQueuePopper<typename ChunkReceiverDataType::ChunkQueueData_t>,顾名思义,这个类是从通信队列获取元素的,是对ChunkQueueData的封装。原因如下:这里范型类型ChunkReceiverData_t,即:ChunkReceiverData中定义了一个类型别名:
using ChunkQueueData_t = ChunkQueueDataType;
这里的ChunkQueueDataType其实就是上文中的ChunkQueueData,通过它可以访问消息队列m_queue。下面,我们介绍ChunkReceiver<ChunkReceiverDataType>::tryGet的具体实现,进一步加深对上述数据结构的理解。
1.4 ChunkReceiver::tryGet
ChunkReceiver<ChunkReceiverDataType>为范型类,范型参数
职责:
调用基类的成员方法,取出接收队列中的一个元素,函数返回类型为SharedChunk,我们在《iceoryx源码阅读(二)——共享内存管理》中已经介绍过了(现在看来介绍得有点粗糙 :-)。
入参:
无
返回值: cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult>
正常情况下返回值类型为
const mepoo::ChunkHeader*,即指向ChunkHeader的指针。错误情况下返回值类型为
ChunkReceiveResult,用以说明获取失败的原因。
逐段代码分析:
template <typename ChunkReceiverDataType>
inline cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult>
ChunkReceiver<ChunkReceiverDataType>::tryGet() noexcept
{
auto popRet = this->tryPop();
if (popRet.has_value())
{
auto sharedChunk = *popRet;
// if the application holds too many chunks, don't provide more
if (getMembers()->m_chunksInUse.insert(sharedChunk))
{
return cxx::success<const mepoo::ChunkHeader*>(
const_cast<const mepoo::ChunkHeader*>(sharedChunk.getChunkHeader()));
}
else
{
// release the chunk
sharedChunk = nullptr;
return cxx::error<ChunkReceiveResult>(ChunkReceiveResult::TOO_MANY_CHUNKS_HELD_IN_PARALLEL);
}
}
return cxx::error<ChunkReceiveResult>(ChunkReceiveResult::NO_CHUNK_AVAILABLE);
}
LINE 05 ~ LINE 05: 调用基类(即:上一小节介绍的ChunkQueuePopper<typename ChunkReceiverDataType::ChunkQueueData_t>)成员函数tryPop从ChunkQueueData维护的消息队列中弹出一个消息元素,返回值为SharedChunk,下一小节详细介绍这个函数的具体实现。
LINE 07 ~ LINE 23: 从消息队列中成功获取消息,将其存入缓存数组中,目的是:(1)后续根据ChunkHeader *指针获取对应的SharedChunk;(2)若不存起来,离开这个函数,SharedChunk实例被析沟,意味着共享内存被释放。有人会问为什么不直接返回SharedChunk呢?其实也是可以的,但还是需要有个地方缓存SharedChunk实例的,直到这块共享内存不再被需要。首次获得就缓存起来是一种更好的方式。
LINE 24 ~ LINE 24:若缓存数组已满,则释放SharedChunk,即:释放了共享内存,并返回错误ChunkReceiveResult::TOO_MANY_CHUNKS_HELD_IN_PARALLEL。
1.5 ChunkQueuePopper::tryPop
职责:
从消息队列m_queue(存放于共享内存中,见:iceoryx源码阅读(四)——共享内存通信(二))获取消息描述数据,并将其转化为SharedChunk实例返回。
入参:
无
返回值: cxx::optional<mepoo::SharedChunk>
这里不需要说明错误原因等额外信息,所以不像前一个函数那样,使用expect作为返回类型。
正常情况下返回值类型为
mepoo::SharedChunk。错误情况下返回特殊值
nullopt_t。
逐段代码分析:
template <typename ChunkQueueDataType>
inline cxx::optional<mepoo::SharedChunk> ChunkQueuePopper<ChunkQueueDataType>::tryPop() noexcept
{
auto retVal = getMembers()->m_queue.pop();
// check if queue had an element that was poped and return if so
if (retVal.has_value())
{
auto chunk = retVal.value().releaseToSharedChunk();
auto receivedChunkHeaderVersion = chunk.getChunkHeader()->chunkHeaderVersion();
if (receivedChunkHeaderVersion != mepoo::ChunkHeader::CHUNK_HEADER_VERSION)
{
LogError() << "Received chunk with CHUNK_HEADER_VERSION '" << receivedChunkHeaderVersion
<< "' but expected '" << mepoo::ChunkHeader::CHUNK_HEADER_VERSION << "'! Dropping chunk!";
errorHandler(PoshError::POPO__CHUNK_QUEUE_POPPER_CHUNK_WITH_INCOMPATIBLE_CHUNK_HEADER_VERSION,
ErrorLevel::SEVERE);
return cxx::nullopt_t();
}
return cxx::make_optional<mepoo::SharedChunk>(chunk);
}
else
{
return cxx::nullopt_t();
}
}
LINE 04 ~ LINE 04: 从共享内存的消息队列m_queue中获取ShmSafeUnmanagedChunk元素(请参考《iceoryx源码阅读(三)——共享内存通信(一)》)。
LINE 07 ~ LINE 21: 获取成功
通过ShmSafeUnmanagedChunk的成员方法releaseToSharedChunk找到对应的共享内存区域,据此创建SharedChunk实例,并返回。注意,LINE 11 ~ LINE 19做了版本检查,如果和接收端版本不一致,同样认为获取失败。版本号是一个整数,用于标识某种不兼容的修改,iceory有如下解释:
/// @brief From the 1.0 release onward, this must be incremented for each incompatible change, e.g.
/// - data width of members changes
/// - members are rearranged
/// - semantic meaning of a member changes
static constexpr uint8_t CHUNK_HEADER_VERSION{1U};
LINE 22 ~ LINE 25: 获取失败
直接返回特殊值cxx::nullopt_t(),这里会隐式调用optional的如下构造函数:
optional(const nullopt_t) noexcept;
2 回调函数接收机制
如果接收端先启动,此时共享内存队列中为空,此时会返回错误ChunkReceiveResult::NO_CHUNK_AVAILABLE,如果接收端一直轮询,则会浪费性能。是否可以在发送端通知的机制实现消息的异步监听和处理?答案是肯定的,我们将在《iceoryx源码阅读(九)——等待与通知机制》对消息发送端的通知逻辑和接收端的等待逻辑进行深入的介绍。
3 小结
本文主要介绍了正常的消息接收流程,对于异步回调的消息接收流程将在后续文章中进行介绍。
iceoryx源码阅读(五)——共享内存通信(三)的更多相关文章
- Spark数据传输及ShuffleClient(源码阅读五)
我们都知道Spark的每个task运行在不同的服务器节点上,map输出的结果直接存储到map任务所在服务器的存储体系中,reduce任务有可能不在同一台机器上运行,所以需要远程将多个map任务的中间结 ...
- CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇
在上一篇中我分析了CoreCLR中GC的内部处理, 在这一篇我将使用LLDB实际跟踪CoreCLR中GC,关于如何使用LLDB调试CoreCLR的介绍可以看: 微软官方的文档,地址 我在第3篇中的介绍 ...
- JDK源码阅读(五)java.io.Serializable接口
package java.io; public interface Serializable { } (1)实现Serializable接口的类,将会被提示提供一个 serialVersionUID ...
- nsq源码阅读笔记之nsqd(三)——diskQueue
diskQueue是backendQueue接口的一个实现.backendQueue的作用是在实现在内存go channel缓冲区满的情况下对消息的处理的对象. 除了diskQueue外还有dummy ...
- 源码阅读 - java.util.concurrent (三)ConcurrentHashMap
在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:ConcurrentMap.本篇文章主要关注ConcurrentMap接口以及它的Hash版本的实现Concu ...
- koa源码阅读[3]-koa-send与它的衍生(static)
koa源码阅读的第四篇,涉及到向接口请求方提供文件数据. 第一篇:koa源码阅读-0第二篇:koa源码阅读-1-koa与koa-compose第三篇:koa源码阅读-2-koa-router 处理静态 ...
- Struts2源码阅读(一)_Struts2框架流程概述
1. Struts2架构图 当外部的httpservletrequest到来时 ,初始到了servlet容器(所以虽然Servlet和Action是解耦合的,但是Action依旧能够通过httpse ...
- Redis源码阅读(五)集群-故障迁移(上)
Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...
- 【原】SDWebImage源码阅读(五)
[原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...
- 【原】AFNetworking源码阅读(五)
[原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...
随机推荐
- 高校校园网下电脑IP是不是公网IP
突然想到一个问题,那就是高校校园网中的IP地址是不是公网IP,如果不是公网IP那么就是使用net后的共享IP,还或者是部分人用公网IP然后另一部分人使用net后的共享IP??? =========== ...
- .gitignore文件的使用方法(学习总结版)—— .gitignore 文件的配合用法
本文紧接前文: .gitignore文件的使用方法(学习总结版) ============================================= 本文主要讨论前文中所说的一个操作,即: . ...
- ReentrantLock之Condition源码解读
1.背景 阅读该源码的前提是,已经阅读了reentrantLock的源码! 2.await源码解读 condition代码理解的核心,其实就是理解到: 线程节点如何从sync双向链表队列到指定的条件队 ...
- hibernate validation,spring validation自定义参数校验
1.背景 在实际开发中,我们除了会使用常用的参数判断,如字符串不为空,最大值,最小值等 我们还可以自定义参数校验规则 2.实际生产问题 实际生产中同步订单的时候, 假设我们要求订单状态值只能是 -1, ...
- 【简单菊花图】Codeforce 1583Problem - B.md
1583Problem - B - Codeforces 题目大意:n个点的无根树 给出m个限制条件 (a,c,b)在a到b路径上不能存在c点,求任意一种可能的树的所有边 注意数据范围:1<m& ...
- .Net Aspire次体验
上次用上了在微软MVP的带领下用上了Aspire,在开发阶段隐藏了细节,什么都不用做,点个调试按钮就跑起来了,可是部署时出现了难题, 因为发布时只能选择Azure环境,为此注册了Azure,开了科网. ...
- Ubuntu 安裝 RIME 輸入法
RIME (Rime Input Method Engine,中州韻,中州韵)是一款很火的輸入法,虽然我目前还不知道它为什么火,不过先用用再说. 首先要吐槽一下 RIME 的说明文档,我感觉有点乱,第 ...
- .net 环境使用 RabbitMQ ,由浅入深 【一】
最近因为先开发的项目需要用到消息队列,因此捣鼓了一下市面上开源的消息队列. 原本听闻Rocketmq ,一开始用的是 RocketMQ,各种集群搭建完毕,消息发送什么的测试后,,但是结果因为 Rock ...
- 使用Vue3.5的onWatcherCleanup封装自动cancel的fetch函数
前言 在欧阳的上一篇 这应该是全网最详细的Vue3.5版本解读文章中有不少同学对Vue3.5新增的onWatcherCleanup有点疑惑,这个新增的API好像和watch API回调的第三个参数on ...
- 关于高清显示屏下canvas绘制模糊问题探索处理
一般场景 我们看下,我们在高清显示屏下,实现这样一个内容,里面填充颜色及文字.第一种是用普通div元素的方式绘制,第二种就是用canvas的方式来绘制,示例效果如下: 从图上我们可以看出,普通div的 ...