ACE_Message_Block实现浅析
1. 概述
ACE_Message_Block是ACE中很重要的一个类,和ACE框架中的重要模式的实现 如ACE_Reactor, ACE_Proactor, ACE_Stream, ACE_Task都有紧密的联系.

换个角度看,ACE_Message_Block实际上已经是这些实现中的重要组成部分.抛开和框架的配合不谈, ACE_Message_Block本身也相当有用,功能强大,用途广泛.ACE_Message_Block的实现中使用了很多技巧和模式,代表性的有GOF的composite模式.这让它在处理数据特别是网络数据时会很方便.而且, 在多线程开发中,它也可以充当线程之间传递的消息.

2. ACE_Message_Block的特点
ACE_Message_Block有几个重要特性:

1. ACE_Message_Block内部采用ACE_Data_Block来间接管理实际数据, 减轻了其它功能与实际内存管理之间的耦合.

2. ACE_Message_Block采用了引用计数, 可以灵活高效的共享数据, 并降低了内存拷贝带来的额外开销.

实际上, ACE_Message_Block本身并没有reference count, 而是间接的由ACE_Data_Block来提供.

3. 允许多条消息连接起来,形成一个单向链表, 从而支持复合消息Composite模式).

由此,ACE_Message_Block提供了cont()方法.

4. 允许将多条消息连接起来,形成一个双链表. 为ACE_Message_Queue的实现提供了支持.

由此,ACE_Message_Block提供next()和prev()方法.

5. 集成将同步策略和内存管理策略, 使得无需修改底层代码就能改变ACE_Message_Block的运行特征.

下面是自己总结的, 一开始总是混淆和迷惑的地方, 需要注意:

6. 3)4)特性实际上是正交的, 不存在交叉和冲突. 单链表实现复合消息, 双链表实现消息队列; 前者重内部, 后者重外部. 换个角度来说, 就是消息队列中的消息可以是复合消息.

7. ACE_Message_Block对内存空间的管理采用“谁申请谁释放”的策略.

在控制权转移时, 需要特别注意这一点.(空间的所有权可能会随size()方法的调用而转移)在使用外部的缓冲区构造ACE_Message_Block或者初始化时,需要特别注意.

3. ACE_Message_Block的数据管理
前面已经说明, 处于减少耦合的原因,ACE_Message_Block并不直接管理实际数据,而是委托ACE_Data_Block进行管理.因此, 对ACE_Data_Block的很多函数都有包裹调用:如base(), end(), mark()等等.

虽然ACE_Data_Block的管理很高效,但是,总体来说ACE_Data_Block并不关注”数据的有效性”.ACE_Data_Block只是简单的对空间进行管理, 提供起始地址.就像一个普通buffer.

ACE_Message_Block弥补了ACE_Data_Block的不足, 提供了读指针和写指针(内部实现为游标). 这样,用户可以方便的进行连续读写,代表性的例子是ACE_Message_Block的copy()方法.也为多个ACE_Message_Block复用相同的ACE_Data_Block提供了良好的支持.

ACE_Data_Block有两种方式获取空间:

1) 自行申请空间

2) 使用用户提供的空间

如果没有设定DONT_DELETE标志, ACE_Data_Block还能自动替用户来释放所管理的空间.

当使用栈上的缓冲区初始ACE_Data_Block时,要特别注意保证缓冲区的有效性,防止ACE_Data_Block使用无效指针.

4. ACE_Message_Block的构造和初始化
ACE_Message_Block提供了7个构造函数和3个初始化函数, 在实现中, 这些函数都不过是内部函数

init_i()函数的一个包裹.

4.1 init_i()的参数
init_i的参数有11个,不过大部分构造函数中调用时都设定了默认值.

int init_i (size_t size,
ACE_Message_Type type,
ACE_Message_Block *cont,
const char *data,
ACE_Allocator *allocator_strategy,
ACE_Lock *locking_strategy,
Message_Flags flags,
unsigned long priority,
const ACE_Time_Value &execution_time,
const ACE_Time_Value &deadline_time,
ACE_Data_Block *db,
ACE_Allocator *data_block_allocator,
ACE_Allocator *message_block_allocator);

参数

类型

用途说明

size

size_t

数据空间大小

type

ACE_Message_Type

消息类型

cont

ACE_Message_Block *

挂接其它MB以成为复合消息

data

const char *

data!=0 表示使用外部提供的空间
data==0 表示由ACE_Message_Block来分配所需空间

allocator_strategy

ACE_Allocator *

申请空间时, 使用的内存分配策略.

data==0时使用.

默认值ACE_Allocator::instance()

locking_strategy

ACE_Lock *

多线程下的安全策略

Message_Flags

flags

MB属性,用于判断是否要释放内部的ACE_Data_Block

当data!=0时,默认设置为DONT_DELETE

priority

unsigned long

优先级,默认为0

excute_time

const ACE_Time_Value&

暂不使用

deadline_time

const ACE_Time_Value&

暂不使用

db

ACE_Data_Block *

使用外部提供的

data_block_allocator

ACE_Allocator *

ACE_Data_Block的分配策略. db==0时使用

默认值ACE_Allocator::instance()

message_block_allocator

ACE_Allocator *

ACE_Message_Block的分配策略.

默认值ACE_Allocator::instance()

默认的ACE_Allocator::instance()返回ACE_New_Allocator类型的策略.

4.2 init_i()的实现
实现动作很简单, 主要是旧data_block的释放和再申请,至于用户要求的size大小的空间,则交由ACE_Data_Block去具体负责.

{
this->rd_ptr_ = 0;
this->wr_ptr_ = 0;
this->priority_ = priority;
this->cont_ = msg_cont;
this->next_ = 0;
this->prev_ = 0;
this->message_block_allocator_ = message_block_allocator;

if (this->data_block_ != 0)
{
this->data_block_->release ();
this->data_block_ = 0;
}

if (db == 0)
{
if (data_block_allocator == 0)
ACE_ALLOCATOR_RETURN (data_block_allocator, , ACE_Allocator::instance (), -1);

ACE_TIMEPROBE (ACE_MESSAGE_BLOCK_INIT_I_DB_ALLOC);

ACE_NEW_MALLOC_RETURN (db,
static_cast<ACE_Data_Block *> ( data_block_allocator->malloc (sizeof (ACE_Data_Block))),
ACE_Data_Block (size,
msg_type,
msg_data,
allocator_strategy,
locking_strategy,
flags,
data_block_allocator),
-1);

ACE_TIMEPROBE (ACE_MESSAGE_BLOCK_INIT_I_DB_CTOR);

if (db != 0 && db->size () < size)
{
db->ACE_Data_Block::~ACE_Data_Block();
data_block_allocator->free (db);
errno = ENOMEM;
return -1;
}
}

this->data_block (db);
return 0;
}

5. ACE_Message_Block的析构和释放
ACE_Message_Block的在析构之外还单独具备了一个release()函数,各有用途,不能相互替代.

1) ACE_Message_Block的析构函数不关心单向链(复合消息)的处理,只是把自己本身清理干净.

2) release()是个递归函数, 它会通过cont()访问所有链接的ACE_Message_Block, 依次对其进行清理,然后最后清理自己本身.清理的方式是ACE_DES_FREE,类似于delete this,但不完全一样.

3) 还有一个静态的releas(ACE_Message_Block*)函数,功能是一样的

由此也可以看出,在栈上生成的ACE_Message_Block,千万不能调用release(),否则会发生所谓的”fall off the stack”.

ACE的注释:

* release() is designed to release the continuation chain; the
* destructor is not. If we make the destructor release the
* continuation chain by calling release() or delete on the message
* blocks in the continuation chain, the following code will not
* work since the message block in the continuation chain is not off
* the heap:
*
* ACE_Message_Block mb1 (1024);
* ACE_Message_Block mb2 (1024);
*
* mb1.cont (&mb2);
*
* And hence, call release() on a dynamically allocated message
* block. This will release all the message blocks in the
* continuation chain. If you call delete or let the message block
* fall off the stack, cleanup of the message blocks in the
* continuation chain becomes the responsibility of the user.

所以如果一定要在栈上生成ACE_Message_Block,那么只能自己手动的清理内部的单向链了.
5.1 析构函数

析构函数很简单,主要是内部data_block的清理,注意是调用data_block()->release()

,因为ACE_Data_Block使用了引用计数.

ACE_Message_Block::~ACE_Message_Block (void)
{
ACE_TRACE ("ACE_Message_Block::~ACE_Message_Block");
if (ACE_BIT_DISABLED (this->flags_, ACE_Message_Block::DONT_DELETE) && this->data_block ())
this->data_block ()->release ();

this->prev_ = 0;
this->next_ = 0;
}

5.2 release()函数

ACE_Message_Block::release (void)
{
destroy_dblock = this->release_i (0);
if (destroy_dblock != 0)
{
ACE_Allocator *allocator = tmp->data_block_allocator ();
ACE_DES_FREE (tmp,
allocator->free,
ACE_Data_Block);
}

return 0;
}

这里省略了大部分线程策略处理,只保留了关键代码,可以看出,核心是release_i()函数.

int ACE_Message_Block::release_i (ACE_Lock *lock)
{
// Free up all the continuation messages.
if (this->cont_)
{
ACE_Message_Block *mb = this->cont_;
ACE_Message_Block *tmp = 0;

do
{
tmp = mb;
mb = mb->cont_;
tmp->cont_ = 0;

ACE_Data_Block *db = tmp->data_block ();
if (tmp->release_i (lock) != 0)
{
ACE_Allocator *allocator = db->data_block_allocator ();
ACE_DES_FREE (db,
allocator->free,
ACE_Data_Block);
}
}
while (mb);

this->cont_ = 0;
}

int result = 0;

if (ACE_BIT_DISABLED (this->flags_,
ACE_Message_Block::DONT_DELETE) &&
this->data_block ())
{
if (this->data_block ()->release_no_delete (lock) == 0)
result = 1;
this->data_block_ = 0;
}

if (this->message_block_allocator_ == 0)
delete this;
else
{
ACE_Allocator *allocator = this->message_block_allocator_;
ACE_DES_FREE (this,
allocator->free,
ACE_Message_Block);
}

return result;
}

处理也很清晰, 基本递归处理完所有的单链MB
6. ACE_Message_Block中容易混淆的几个函数
ACE_Message_Block中有多个获取大小或者长度的函数,容易混淆.
下图是根据ACE_Message_Block(实际是ACE_Data_Block)空间的处理状况所绘,能比较清晰的反应出它们的异同.
需要注意,为了表现出多样性,下图是wr_ptr(),rd_ptr(),size()都调用过之后的情景.

红色表示是ACE_Message_Block独有的函数, 其余则ACE_Message_Block和ACE_Data_Block均有.
矩形纸上函数的返回值均为指针类型,之下的返回值均为size_t类型.

函数

说明

length()

有效数据的长度

== wr_ptr() – rd_ptr()

size()

全部可用空间的长度,如果没有size()而变小,则等同capacity()

== mark() – base()

space()

剩余可用空间的长度

<= size() - length(),因为不含rd_ptr()移动过的空间

== mark() – wr_ptr()

capacity()

最大空间的长度(ACE_Message_Block构造或初始化时所用参数值)

== end() – base()

total_length()

复合消息(ACE_Message_Block内单向链 cont())的总长度

total_size()

复合消息(ACE_Message_Block内单向链 cont())的总大小

total_capacity()

复合消息(ACE_Message_Block内单向链 cont())的总空间大小

7. ACE_Message_Block常用函数简介
7.1 duplicate()
duplicate()浅拷贝函数,公用一个内部的ACE_Data_Block
ACE_Message_Block::duplicate() 与 ACE_Data_Block.duplicate()的实现是不同的.
ACE_Data_Block::duplicate()简单的只是将自身的reference加+1, 然后返回自身(this)
ACE_Message_Block:duplicate()则将自身copy了一份, 然后将自身的状态值赋给拷贝,注意它们公用同一个data_block.而且ACE_Message_Block::duplicate()支持复合消息,它会检查内部单向链,来依次调用其duplicate().

这里ACE_Data_Block::duplicate()的函数行为很怪异,以后就能看出它怪异行为的影响.说实话,这个地方如此设计我很不理解,因为ACE_Data_Block本身其实已经有reference了.

7.2 clone()
ACE_Message_Block::clone()深拷贝, 不但拷贝自身,内部的ACE_Data_Block也一并拷贝了,并且支持复合消息.

7.3 size()
ACE_Data_Block.size(size_t len)函数, 动态的变化ACE_Data_Block持有的空间.
ACE_Message_Block.size(size_t len)函数是ACE_Data_Block.size(size_t len)的简单包裹.
如果len比现有的尺寸小, 简单的cur_size_ = length;
如果len比现有的尺寸大, 会申请新的空间并拷贝原所有数据.

注意! 这里可能会发生空间控制权的转换! 即标志位DONT_DELETE的变化.若原ACE_Data_Block使用托管空间, 则此时会更替为自己申请的空间,从而拥有了控制权, 所以此时要注意原有空间的管理.
对ACE_Message_Block和ACE_Data_Block, 除非主动调用size(), 否则它们不会自动申请和扩大空间.

7.4 其它函数
ACE_Message_Block::crunch() 将现有数据移动到现有的缓冲的开始.

ACE_Message_Block::reset()将现有读写指针赋为初始值(ACE_Data_Block.base())

ACE_Message_Block::base()是对ACE_Data_Block.base()的简单包裹

8. 需要注意的地方
8.1 注意点1
1)ACE_Message_Block的构造函数中,如果data为NULL, 则ACE_Message_Block会为其自动分配空间. 但如data非NULL,则ACE_Message_Block会直接引用data指向的空间, 并不会进行新的空间分配和拷贝.

所以需要特别注意, 在ACE_Message_Block的实例没有销毁之前,不能释放data指向的空间.

2)虽然ACE_Message_Block会根据size的值来更改自己的size(),但wr_ptr不会根据data的长度进行设置, 造成length()的返回为0.

需要特别注意, 当构造一个ACE_Message_Block实例后, 随之需要追加数据时,必须设置wr_ptr的值,否则原有数据将会被覆写.

此时的含义是: ACE_Message_Block代管了data缓冲区,但不负责缓冲区的空间管理(因为也不是由它申请的).

8.2 注意点2
默认定义的flag: enum { DONT_DELETE = 01, USER_FLAGS = 0x1000 }

1) set_flags()、clr_flags()是对ACE_Message_Block中的数据指针(ACE_Data_Block*)进行设置.

2) set_self_flags(),clr_self_flags()是对ACE_Message_Block本身进行设置.

8.3 注意点3
ACE_Message_Block::copy(const char* buf) 函数将字符串copy到ACE_Message_Block, 如果内在空间不足, 将会返回-1.

需要特别注意, copy的数据将包括末尾的0, 也就是copy的数据长度为strlen(buf)+1.

而且, 会自动进行wr_ptr()的设置

9. ACE_Data_Block的思考
ACE_Data_Block的析构函数是释放持有空间base_的惟一路径(size()的情况不讨论).

ACE_Data_Block中通过duplicate()递增引用计数. ACE_Data_Block中通过release()递减引用计数, 当引用计数为0时,先调用ACE_Data_Block析构函数,然后释放ACE_Data_Block自身.
注意, ACE_Data_Block的构造和析构函数都不知道引用计数的存在. 在构造函数中, 只是设置了初始值1.

ACE_Data_Block一个很奇怪的地方就是ACE_Data_Block::duplicate()的实现, 并没有创建新的拷贝, 而仅仅是返回了自身(return this). 这中实现方式带来了很多奇怪的问题.如下面的2,3.
release()-> release_no_delete()->release_i()->~ACE_Data_Block()

如果在Stack上构造ACE_Data_Block,那么不能使用release()函数, 因为release()函数会试图删除this

如果在stack上构造ACE_Data_Block, 那么不能使用duplicate()函数, 因为duplicate()返回的是this指针, 栈中的ACE_Data_Block析构后会导致问题.

如果在heap上构造ACE_Data_Block,那么尽量使用release()来替代delete, 如果存在因为析构并不处理reference count, delete时不考虑其它会导致指针悬空.

ACE_Message_Block实现浅析的更多相关文章

  1. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  2. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  4. netty5 HTTP协议栈浅析与实践

      一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...

  5. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  6. 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler

    熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...

  7. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  8. 浅析匿名函数、lambda表达式、闭包(closure)区别与作用

    浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...

  9. word-break|overflow-wrap|word-wrap——CSS英文断句浅析

    ---恢复内容开始--- word-break|overflow-wrap|word-wrap--CSS英文断句浅析 一 问题引入 今天在再次学习 overflow 属性的时候,查看效果时,看到如下结 ...

随机推荐

  1. 【Python+postman接口自动化测试】(5)抓包工具Fiddler简介

    Fiddler简介 Fiddler 4.6 下载 http://www.downza.cn/soft/234727.html 为什么使用Fiddler? 可以抓到请求数据,查看Raw格式/表单格式/J ...

  2. shiro550反序列化分析

    拖了很久的shiro分析 漏洞概述 Apache Shiro <= 1.2.4 版本中,加密的用户信息序列化后存储在Cookie的rememberMe字段中,攻击者可以使用Shiro的AES加密 ...

  3. 菜鸟Markdown笔记,看这个就够了

    菜鸟markdown语法笔记 1.标题 写法:共六级标题,一个#是一级标题,两个#是二级标题,三个#是三级标题······以此类推 (#)+空格键,快捷方式是Ctrl+1/2/3/4/5/6 2.段落 ...

  4. (四)DQL查询数据(最重点)

    4.1   DQL Data Query Language 数据查询语言 1   所有的查询操作都用它  Select 2   简单的查询,复杂的查询它都能做 3   数据库中最核心的语言,最重要的语 ...

  5. git修改用户和邮箱

    GIT查看当前用户以及邮箱 $ git config user.name $ git config user.email GIT修改用户以及邮箱 $ git config --global user. ...

  6. 史上最简单的手写Promise,仅17行代码即可实现Promise链式调用

    Promise的使用相比大家已经孰能生巧了,我这里就不赘述了 先说说我写的Promise的问题吧,无法实现宏任务和微任务里的正确执行(也就是在Promise里面写setTimeout,setInter ...

  7. deepin系统安装与linux磁盘分区

    制作系统盘工具 链接:https://pan.baidu.com/s/1zcV0oulUErUdU0PAGxTDdw 提取码:1111 链接:https://pan.baidu.com/s/13zBd ...

  8. MYSQL免安装

    MYSQL免安装 本文使用mysql 8.0.22进行演示 一.MYSQL 下载 [x] 官网下载:地址:https://dev.mysql.com/downloads/mysql/ [x] 百度云盘 ...

  9. vue-cli的安装步骤

    1.安装Node.js 在Node.js官网 https://nodejs.org/zh-cn/下载安装包,修改安装路径到其它盘,如 G:\Program Files 2.设置 cnpm的下载路径和缓 ...

  10. CF1559D2 Mocha and Diana (Hard Version)

    考虑到加树边每次最多只导致一对联通块之间的状态. 所以我们以任意顺序加入当前的合法边. 我们考虑先加入所有可加的\((1,a)\) 然后统计只在\(A\)中与1连的点,\(B\)中与2连的点. 则他们 ...