首页 >> 大全

boost.asio

2023-11-13 大全 31 作者:考证青年

boost.asio为异步IO提供了一份标准的C++的跨平台实现,特别针对网络IO提供了良好的支持,使之成为C++网络编程利器。关于如何使用asio,boost文档中已经有了详尽说明,而且附带的例子也很直观,我们不必再造轮子;本文则结合asio的基本应用,侧重于源代码的分析,特别是针对平台上的实现进行分析。

纵观asio源码,在统一的接口层之下,asio提供了大量的类来支持不同的平台(、Unix...)、不同的IO类型(同步、异步)及IO模型(IOCP、、Poll)及网络协议(TCP,UDP,ICMP)。归纳起来,这一大堆类可以分为三层,分别是:

本系列文章从类型入手,开启代码分析行程,采用自底向上的方法,逐层推导,最后得到asio的体系结构;如果想先了解asio的整体结构,可以先跳到asio体系结构部分。

(理论上来说,行文伊始还是该先举个简单的例子说一下asio如何使用,不过这样的例子少说也要六七十行代码,为节约篇幅就不贴了;真想先看一个完整例子的,烦请移步下面链接瞧下那只麻雀:)

·类

从第一个boost.asio的教程开始,boost文档就一直在告诉我们:使用boost.asio第一步就是要创建一个对象。那么是个什么东西呢?

boost.asio文档说,为下面的这些异步IO对象提供最核心的IO功能:

接着,文档就会说,像下面这样,就可以简简单单声明一个对象了:

int main()

boost::asio:: io;

上面的一行代码,表明无限地风光与潇洒,看起来简简单单一行,就几乎有了异步IO、网络操作的框架,那么他到底代表了什么,在后面,又有什么事情发生呢?又是如何支撑起这些功能的呢?带着这些问题,我们开始分析吧……

从C++的角度看,上面的这一行代码,无非就是定义了一个的实例,而C++的底层机制所隐藏起来的东西,无非就是初始化该对象的所有数据成员。再来看的声明,我们知道,该类除了一堆成员函数之外,事实上只有三个成员(暗想:这几个成员肯定很是神奇无匹了):

#if () || ()

:: init_;

#elif (__sun) || () || () || (_AIX) \

|| ()

:: init_;

#endif

// The .

boost::asio::::* ;

// The .

& impl_;

暂时抛开那几个成员,再来看一下比较重要的一个函数:run() 的实现,发现该函数也就是将真正的功能委托给成员impl_去做事儿了:

std:: ::run()

boost:::: ec;

std:: s = impl_.run(ec);

boost::asio::::(ec);

s;

种种迹象表明,impl_是个巨牛的东西了。统揽的实现,我们不难发现,该类的所有功能,几乎都是委托给了impl_成员去干了——典型的“有事秘书干”。不过想想也挺容易理解的,提供了一个上层的接口,充当抛头露面的BOSS,真正的工作则委托给下一层的员工去实现——哪家公司不是这样呢?

所以,要想了解的玄机,就需要弄清楚这三个数据成员到底是什么来历,特别是impl_的来历才行。

的初始化

成员init_ 在的各个函数中,并没有显式用到。其存在的价值,就在于该类的构造函数里面,调用了初始化相关的代码,具体到平台,就是的初始化,也就是调用 ::() 这一编程所必须调用的第一个函数;而其析构函数则进行清理工作,同样交由函数 ::() 完成。

也就是说,通过init_数据成员的创建与销毁,自动完成了相关的初始化及清理工作。

的实现委托

前面说过,impl_ 带着无限的神秘默默地完成了::run()的功能。至于他到底是什么style,现在来揭开盖头吧。

从直接声明来看,impl_ 具有 & 类型。用不难发现该类型只不过是一个类型别名:

:: ;

一波未平一波又起,这儿又冒出个。继续刨根问底,找到:

#if ()

{ ; }

#else

{ ; }

#endif

终于知道,在某些情况下,它是 ,在某些情况下,是task_ 。事实上,在Win NT 环境下,如果没有禁用IOCP,也就是没有声明OCP 这个预处理器指令,那么asio就采用 来做那些脏活累活;在剩下的其他平台,或者Win上禁用了IOCP,则使用 来做事儿了。

先稍微提一下, 是对环境下的IOCP(完成端口IO)模型的封装,该类作为boost.asio在下的核心,我们在后面详细分析其实现。

的服务管理

的另外一个数据成员,又具备什么样的身份和功能呢?我们来看看 的构造函数:

::()

:

new boost::asio::::(

*this,

(0),

(std::::max)()

),

impl_(->())

构造函数为了初始化,动态分配了一个boost::asio:::: 类对象。为了构造该对象,提供了三个参数:

再来观察的实现,发现其实际就是一个链表,管理所容纳的所有对象(就是一种)。每种都有一个id,链表以此id作为标志,在客户通过来请求一种服务时,例如调用 (&) 时,会查找链表,如果有对应类型的服务,就返回该类型服务实例的指针;否则就创建一个新的对象,并加入到链表末端,再返回此新创建的实例;通过这种形式,确保每种类型的服务都只有一个实例存在。

asio提供了这样几个函数,来进行的管理和使用——这也为扩展asio提供了可能,例如可以自己定义一种服务,使用加入进行管理。

& (& ios);

void (& ios, * svc);

bool (& ios);

::类型及跨平台策略

boost::asio::

+ run()

+ poll()

+ void ()

+ void stop()

- :: init_

- ::*

- & impl_

class boost::asio::::::id

class boost::asio::::work

enum boost::asio::::

class boost::asio::::

class boost::asio::::

- key key_

- boost::asio::& ;

- * next_;

内部定义了这样一些类型,来为其服务:

针对类型,asio从其派生出了数十个类分别完成不同的功能,例如在Win上充当的类,以及为各种IO 类型提供服务的类,如对应于TCP的e,对应于UDP的 。下图显示了asio常用到的服务类,及其针对不同平台的适配类之间的关系。

图中左边的类,会在我们的应用程序中直接用到(由于asio又对这些类提供了一层动态组装,所以代码中不会去直接声明这些类型的实例,但是剥掉动态组装的外衣,我们声明的仍然是这些类的实例,具体在下面一部分说明),作为应用层的类;而右边部分,则是针对不同平台所提供的不同实现,作为平台适配层。

应用层类在编译时,根据所在平台(其实是喂给编译器的各种预处理宏,如 ),选择对应的类进行编译。例如用于TCP的服务类e是这样进行选择的:

class e

:

// The type of the - .

#if ()

::ice

;

#else

::ice

;

#endif

// The - .

;

};

其他需要进行平台决策的类型,都是采用这种技术,来选择不同的实现的。

从::派生的完整的类列表如下:

还有几个非常重要的类,他们作为劳苦大众在金字塔底层默默提供功能,但却没有从::派生;他们是上述那些服务类在各个平台的具体实现,为金字塔中间层的服务类提供再服务的(不难想象,提供服务的方式,又是那种“有事儿秘书干”的方式):

·io

asio的文档,告诉我们在声明一个对象之后,就可以创建io对象去干活了,例如:

int main(int argc, char* argv[])

boost::asio:: ;

tcp:: ();

tcp::::query query("", "80");

tcp:::: = .(query);

上图中main()的第二行代码声明了一个tcp::对象,后续进行地址解析的逻辑,都是围绕此对象展开的。那么这个是一个什么样的类型呢?阅读源代码我们在boost::asio::ip::tcp类内部看到了这样的类型别名定义:

;

是否似曾相识呢?是的,这和我们STL中的, 等一样,使用的都是某个模板类的一个实例。这里的是在tcp模版参数下的实例,是在char模版参数下的实例——boost库和STL库统一风格的一个体现。

asio大量采用这种技术,所有和一样提供io功能的类,都是某个 模版类的实例化。下面我们来研究一下asio的io 逻辑。

io 类关系网

继续向上追溯,知道该类从派生,主要负责地址解析相关的操作,提供的主要接口有(), ()等。

查看整个asio的源代码,我们发现从派生的类不少,他们分别负责一些具体的事务,例如r可以作为一个服务器进行侦听,提供了诸如bind(), ()等接口;再如类是对 IO 操作的封装,提供了(), (), (), (), (), ()等接口。

整个asio中的io 关系网,我们用下图来显示:

这些io 的功能,简述如下:

另外一点,所有这些io 的构造函数,都要求有一个& 作为参数,使用这一参数,这些对象知道了自己的归属,之后自己所要派发出去的同步、异步操作请求,都将通过自己所在的这个对象来完成。这也就说明了,为什么创建对象是整个asio程序的第一步。

io 服务委托

上述的这些io 类,提供了应用开发中常用的各种“实际”功能,例如地址解析,以及读写等,那么这些io 类和第一部分中的类之间存在着什么样的关系呢?会不会是这些io 只是一个应用的接口,而具体的功能则委托给类来完成呢?如果是这样的,那么又是如何实现的呢?

带着这些问题,我们继续研究类的源代码,看看他所提供的功能,到底是如何实现的。

是如何委托的

我们知道,类是在boost::asio::ip::tcp类中的一个类型别名:

;

那么,类所提供的()接口,就来自于类。再来看看::()的实现——该函数有两个重载,我们以其中一个为例:

void (& q, () )

// If you get an error on the line it means that your does

// not meet the type for a .

(

, , ) ;

->.(this->, q,

()());

可见,()果然是委托给了某个类来做事的——再一次,典型的有事儿秘书干。那么,这儿的“this->”又是什么呢?

通过跟踪代码的执行,知道该其实是第一部分曾经提到过的boost::asio::ip::,而它又委托给了boost::asio::::——这家伙再无其他可以委托的对象了,只有苦逼的自己做事儿了——其基本思路就是先创建一个用以地址解析的,用这个op来代表本次异步操作,之后启动op去做事情。至于op又是什么东西,稍后在部分做介绍;先贴出这段苦主:

// boost::asio::::

(& impl,

& query, )

// and an to wrap the .

op;

::ptr p = { boost::(),

::(

(op), ), 0 };

p.p = new (p.v) op(impl, query, , );

((p.p, "", &impl, ""));

(p.p);

p.v = p.p = 0;

PS:

- 做过开发的对这种形式似曾相识,这多少和 异曲同工

- 那几行创建op的代码还不是很明白

OK,至此,整个()从上到下就拉通了。概括起来,就是io 将具体功能委托给服务类,服务类又委托给和平台实现相关的服务类来完成最后的功能。

的服务创建

通过的执行,我们知道了其层层委托关系,那么所委托的this->又是怎么来的呢?下面部分,我们来分析所做的服务管理工作。

首先来看该的具体类型。

要想知道是如何创建的,我们就要追根朔源找到的具体类型声明,看看到底是如何将拉上贼船的。好吧,我们再次从头开始(别嫌啰嗦):

;

针对这个模版实例,将展开:

= >

class :

à

= >

class : < >

boostasiotcp__boostasio教程

再对进行展开:

class

à

< >

class

在内部展开:

;

à

;

于是,可以确定::的类型为&;从而将io 和下层的关联起来。

再来看::的初始化。我们来看其构造函数:

(boost::asio::& )

: (boost::asio::())

.();

很明显,在构造过程中,使用的返回值来初始化该成员;我们知道,会在所维护的链表中查找该类型,如果没有,就创建一个新的实例;在此,就可以确保的实例已经被创建出来了,该服务就可以工作了(当然,不要忘记,该事实上又委托了一层,而这个最底层的实例,在此的构建过程中,内部再次调用()创建出来了。过程如下:

除此之外,在构造函数体中,调用了的函数,做进一步的初始化。(PS:是否有中的二段构造的影子呢?)

asio 的io逻辑总结

前面部分以为例,分析了的功能和对应之间的关系,纵观所有的io ,都是采用了这种模式,总结起来有如下几点:

· asio提供了多个 模版类。

· 应用层使用对应的 模版类的的具体类,对外提供服务接口。

· io 内部,将操作委托给底层服务类。

· 底层服务类再次将操作委托给平台实现层,完成实际的工作。

·asio的体系结构

三层类关系图

根据前面的分析,我们知道asio有着这样的逻辑:

鉴于此,我们将asio体系划分为三层:io 层, 模版类层,服务层。

基本的体系结构关系如下图所示。注意:图中并非全部asio中的类。

动态组装

前面已经提过,和STL中的一样,都是使用了模板类的一个具体实例。这种在编译时动态地选择对应的组件进行编译,我们姑且称为动态组装。这种技术使得我们可以对asio的进行扩展。例如自己实现一个的类,然后告诉,你要使用自己的类,而非默认的那个……

不过话说回来,自己对boost或者STL进行扩展,需要点实力的

·asio的实现

还记得前面我们在分析的实现的时候,挖了一个关于的坑?为了不让自己陷进去,现在来填吧;接下来我们就来看看asio中的各种。

和前面提到过的的类似,这里的也分为两大系:IOCP 和系列。这里我们重点关注下图中红色部分表示的IOCP 系列。

基类

从上图可以看到,所有IOCP 的,其基类都是 结构,该结构是Win32进行交叠IO一个非常重要的结构,用以异步执行过程中的参数传递。所有的直接从该结构继承的结果,就是所有对象,可以直接作为结构在异步函数中进行传递。

例如在中,为了启动一个的异步操作, 函数就直接把传递进来的指针作为结构传递给::函数,从而发起一个异步服务请求。

void ::(

::type& impl,

* , std:: ,

:: flags, bool noop, * op)

(impl);

.();

if (noop)

.(op);

else if (!(impl))

.(op, boost::asio::error::);

else

DWORD = 0;

DWORD = flags;

int = ::(impl., , (),

&, &, op, 0);

DWORD = ::();

if ( == D)

= ;

else if ( == LE)

= ;

if ( != 0 && != )

.(op, , );

else

.(op);

执行流程

关于对象的创建、传递,以及完成的执行序列等,使用下图可以清晰的描述。

下表反映了环境下,部分的异步请求所对应的服务、win函数、等信息:

异步请求

服务

start op

Win32函数

对应

ip::tcp::::

ice

()

::

ip::tcp::::

()

::

_op

ip::tcp::::

()

::

_op

ip::tcp::::

()

::

_op

ip::tcp::::

()

::

_op

ip::tcp::::

()

::

pt_op

ip::tcp::::

()

::

静态的

不知你是否注意到,在的类图中,所有从继承的子类,都定义了一个()函数,然而该函数声明为,这又是为何呢?

我们以_op为例来进行说明。该类中的是这样声明的:

void (* owner,

* base,

const boost::::& ,

std:: )

该类的构造函数,又把此函数地址传递给父类去初始化父类成员,这两个类的构造函数分别如下,请注意加粗代码:

_op ::

_op(:: state,

::pe ,

const e& , & )

: (&_op::),

(state),

(),

(),

(()())

::( func)

: next_(0),

func_(func)

reset();

至此,我们明白,将声明为,可以方便获取函数指针,并在父类中进行回调。那么,不仅要问,既然两个类存在继承关系,那么为何不将声明为虚函数呢?

再回头看看这些类的最顶层基类,就会明白的。最顶层的基类,使得将对象作为对象在异步函数中进行传递成为可能;如果将声明为虚函数,则多数编译器会在对象起始位置放置vptr,这样就改变了内存布局,从而不能再把对象直接作为对象进行传递了。

当然,一定要用虚函数的话,也不是不可能,只是在传递对象的时候,就需要考虑到vptr的存在,这会有两个方面的问题:一是进行多态类型转换时,效率上的损失;二是各家编译器对vtpr的实现各不相同,跨平台的asio就需要进行多种适配,这无疑又过于烦躁了。于是作者就采取了最为简单有效的方式——用函数来进行回调——简单,就美。

的实现

在 NT环境下(IOCP ),代表着,是整个asio的运转核心。本节开始来分析该类的实现。

从类的命名也可以看出,IOCP是该实现的核心。IOCP(IO Port, IOCP)在上,可以说是效率最高的异步IO模型了,他使用有限的线程,处理尽可能多的并发IO请求。该模型虽说可以应用于各种IO处理,但目前应用较多的还是网络IO方面。

我们都知道,在是环境下使用IOCP,基本上需要这样几个步骤:

使用Win函数rt()创建一个完成端口对象; 创建一个IO对象,如用于的对象; 再次调用rt()函数,分别在参数中指定第二步创建的IO对象和第一步创建的完成端口对象。由于指定了这两个参数,这一次的调用,只是告诉系统,后续该IO对象上所有的完成事件,都投递到绑定的完成端口上。 创建一个线程或者线程池,用以服务完成端口事件;

所有这些线程调用tatus()函数等待一个完成端口事件的到来;

进行异步调用,例如()等操作。 在系统执行完异步操作并把事件投递到端口上,或者客户自己调用了()函数,使得在完成端口上等待的一个线程苏醒,执行后续的服务操作。

那么,这些步骤,是如何分散到asio中的呢?来吧,先从完成端口创建开始。

完成端口的创建

如上所述,完成端口的创建,需要调用rt()函数,在的构造函数中,就有这样的操作:

::(

boost::asio::& , )

: boost::asio::::(),

iocp_(),

(0),

(0),

(0),

(0),

(0)

;

iocp_. = ::rt(, 0, 0,

((std::min)(, DWORD(~0))));

if (!iocp_.)

DWORD = ::();

boost:::: ec(,

boost::asio::error::());

boost::asio::::(ec, "iocp");

的构造函数,负责创建一个完成端口,并把此完成端口对象的句柄交给一个进行管理——的唯一用途,就是在对象析构时,调用::()把句柄资源关闭,从而保证不会资源泄露。

我们在环境下,声明一个boost::asio::对象,其内部就创建了一个的实例;因此,一个对象就对应着一个完成端口对象——这也就可以解释,为什么所有的IO 都需要一个参数了——这样,大家就好公用外面定义好的完成端口对象。

除了对象会创建一个完成端口对象,事实上,在asio中,另外一个也会创建一个,这就是boost::asio::ip::。该类对应的实现boost::asio::::中,有一个数据成员是: ,这样就同样创建了一个完成端口对象:

boost {

asio {

{

class e

...

:

// used for host .

;

_boostasio教程_boostasiotcp

...

至于该完成端口的用途如何,我们在后续部分再来说明——搽,又开始挖坑了。

完成端口的绑定

在创建了io对象后,例如,就需要将此对象和完成端口对象绑定起来,以指示操作系统将该io对象上后续所有的完成事件发送到某个完成端口上,该操作同样是由rt()函数完成,只是所使用的参数不同。

在中,这个操作由下面的代码完成——请注意参数的差别:

boost:::: ::(

, boost::::& ec)

if (::rt(, iocp_., 0, 0) == 0)

DWORD = ::();

ec = boost::::(,

boost::asio::error::());

else

ec = boost::::();

ec;

通过代码搜索,我们发现函数::()内部调用了();该函数的作用是打开一个(其中调用了函数()去创建一个),也就是说,在打开一个后,就把该绑定到指定的完成端口上,这样,后续的事件就会发送到完成端口了。

此外还有另外的和相关的两个函数也调用了(),不再贴出其代码了。

线程函数

IOCP要求至少有一个线程进行服务,也可以有一堆线程;早就为这些线程准备好了服务例程,即::run()函数。

void ::run()

// a pool of to run all of the .

std::::> > ;

for (std:: i = 0; i < ; ++i)

boost::

(

new boost::(

boost::bind(&boost::asio::::run, &)

);

.();

// Wait for all in the pool to exit.

for (std:: i = 0; i < .size(); ++i)

[i]->join();

由于::run()又是委托::run()来实现的,我们来看看后者的实现:

::run(boost::::& ec)

if (::dd(&, 0) == 0)

stop();

ec = boost::::();

0;

;

:: ctx(this, );

n = 0;

while ((true, ec))

if (n != (std::::max)())

++n;

n;

run()首先检查是否有需要处理的操作,如果没有,函数退出;使用 来记录当前需要处理的任务数。如果该数值不为0,则委托函数继续处理——asio中,所有的脏活累活都在这里处理了。

::函数较长,我们只贴出核心代码

::(bool block, boost::::& ec)

for (;;)

// Try to for and ops.

if (::(&, 0, 1) == 1) ? #1

mutex:: lock();

// and .

ops;

ops.push();

.(ops);

tions(ops); ? #2

();

// Get the next from the queue.

DWORD = 0;

= 0;

= 0;

::(0);

BOOL ok = ::tatus(iocp_., &,

&, &, block ? : 0); ? #3

DWORD = ::();

if ()

* op = (); ? #4

boost:::: (,

boost::asio::error::());

// We may have been the and in the

// .

if ( == )

= boost::::((op->),

*(op->));

= op->;

// any has been saved into the

// .

else

op-> =

另外还有一些提供的操作,例如请求执行代为执行指定的操作:

所有这些需要自己投递完成端口数据包的操作,基本上都是这样一个投递流程:

OK,至此,基本分析完了的投递,总数填了一个前面挖下的坑。

自己的IOCP

前面说过,自己会创建一个IOCP,为什么会这样呢?由于Win32下面没有提供对应于地址解析的版本的函数,为了实现操作,作者自己实现了这样一个异步服务。在内部,有一个数据成员,该数据成员创建了一个IOCP;除此之外,该内部还启动一个工作线程来执行::run(),使用此线程来模拟异步服务。

使用进行的详细过程如下:

Main (IOCP#1)

(IOCP #2)

1. 构建主 对象, IOCP#1 被创建

2. 构建 对象, IOCP#2 被创建,同时该持有主的引用

3. 发起异步调用:.()

4. 被创建

5. 线程启动,主线程开始等待

6. 开始运行,激活等待事件,并在 IOCP#2上开始等待

7. 线程恢复执行;将op投递到 IOCP#2

8. 执行op->() 操作,地址解析完成后,将op再回投给IOCP#1

9. () 得到从线程投递回来的op,开始执行op->() 操作,此时回调所设置的

10. 结束

请注意step8 和 step9,执行同样一个op->()函数,为什么操作不一样呢?看其实现就知道,该函数内部,会判断执行此函数时的owner,如果owner是主对象,则说明是在主线程中执行,此时进行的回调;否则就说明在工作线程中,就去执行真正的地址解析工作;

任务的取消

针对上提交的异步请求,可以使用()函数来取消掉该上所有没执行的异步请求。

使用该函数,在 Vista(不含)以前的版本,有几点需要注意:

针对这些问题,另外的替代方案是:

在 vista及后续版本中,()函数在内部调用Win32函数 (),该函数可以取消来自任何线程的异步操作请求,不存在上述问题。

需要注意的是,即使异步请求被取消了,所指定的也会被执行,只是传递的error code 为:boost::asio::error::。

ice实现

该提供了下所有相关的功能,是asio在环境中一个非常重要的角色,他所提供的函数主要分下面两类:

不过关于该类的实现前面已经做了较多的涉及,不再单独详述了。

前摄器模式

现在我们已经把环境下所涉及到的关键部件都涉及到了,此刻我们再回过头来,从高层俯瞰一下asio的架构,看看是否会有不一样的感受呢?事实上,asio的文档用下面的图来说明asio的高层架构——前摄器模式,我们也从这个图开始:

呵呵,其实这张图,从一开始就是为了表达(前摄器)模式的,基本上它和asio没半毛钱关系,只不过asio既支持同步IO,又支持异步IO,在异步IO部分,是参照模式来实现的。下面我们来分别说说asio的前摄器模式中的各个组件:

仅仅从asio使用者的角度看,高层的e类就是一个这样的处理器,因为从tcp::发送的异步操作都是由其完成处理的。但是从真正实现的角度看,这样的异步操作在上,大部分由操作系统负责完成的,另外一部分由asio自己负责处理,如,因此操作系统和asio一起组成了异步操作处理器。

在平台上,类通过类的()函数把每个异步操作所设定的 调用起来。

在上,asio的完成事件队列由操作系统负责管理;只不过该队列中的数据有两个来源,一个是内部,另外一个是asio中自己()所提交的事件。

在上,这一功能也是由操作系统完成的,具体来说,我认为是由tatus完成的,而该函数时由()调用的,因此,从高层的角度来看,这个分离器,也是由负责的。

基于上述信息,我们重绘模式架构图如下:

·其他

VS. VS.

是一个全局函数;后面两个则于ip::tcp::的成员个函数;都可以用来异步读取操作,他们有什么样的差别呢。先来看和,他们的文档说明如下:

从文档来看,只有一个单词的差别,一个是read,一个是;反正都是从中获取数据,这两个词有什么差别呢?我是看不出他们的差别,那就看代码吧:

void (const e& ,

() )

// If you get an error on the line it means that your does

// not meet the type for a .

(, ) ;

this->().(this->(),

, 0, ()());

void (const e& ,

() )

// If you get an error on the line it means that your does

// not meet the type for a .

(, ) ;

this->().(this->(),

, 0, ()());

好了,有了源代码,就无从狡辩了吧。他们都是使用底层服务的来读取数据,因此他们的功能是一样的,都是从获取一定的数据,但是该函数并不保证能够从获得指定长度的数据——也即不保证填满缓冲区;

如果想要保证异步操作完成时获取到指定数量(缓冲区的长度)的数据,那么使用全局函数。该函数内部启动一个 ,该op内部,会多次调用.,直到缓冲区填满,或者读操作结束;

这既是这三个函数的异同之所在。

妖怪

前面一节提到过,全局函数 会启动并在该op内部反腐调用async ()来读取数据直到缓冲区慢,或者EOF出现;如果要来看一下的(),你多半会感觉看到妖怪了。乖,别怕,来看看吧:

void ()(const boost::::& ec,

std:: , int start = 0)

std:: n = 0;

(start)

case 1:

n = this->(ec, );

for (;;)

.(

boost::asio::( + , n),

()(*this));

; :

+= ;

if ((!ec && == 0)

|| (n = this->(ec, )) == 0

|| == boost::asio::())

break;

(ec, ());

不知道你是否会和我一样感觉毛骨悚然,惊叫-case的邪乎。是的,说他妖怪,就妖怪在的那两个case标签,第一个“case 1” 还算人性,第二个标签就完全是妖怪了,它放置在for循环的内部——是的,编译器是允许的,假设程序流程首先跳转到标签,在执行完分支后,他会像正常的for循环流程一样,继续for loop的,这就是这个妖怪。

自己写的一个简单的测试及输出:

void ( int state )

int i = 0;

( state )

case 1:

cout

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了