热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一、asio使用教程–基本技能

本教程使用了简单的异步计时器演示了asio的基本使用。同步使用定时器如何实现阻塞等待定时器。首先引入头文件#include#include

本教程使用了简单的异步计时器演示了asio的基本使用。


同步使用定时器

如何实现阻塞等待定时器。首先引入头文件

#include
#include

"asio.hpp"可以简单地帮我们将所需的头文件引入。

使用asio的所有程序都需要至少一个I/O execution context,像io_context或者thread_pool对象。通过I/O execution context我们来访问I/O功能。

在主函数中声明一个io_context的对象

int main() {
boost::asio::io_context io;
}

接下来再声明一个boost::asio::steady_timer类型的对象。asio中提供I/O功能的核心类(例如本例中的定时器)总是将io_context作为第一个参数。在本例的定时器中,将过期时间设置为第二个参数。

boost::asio::steady_timer t(io,boost::asio::chrono::seconds(5));

在这个简单的例子中,我们对定时器执行阻塞等待。也就是说,对 steady_timer::wait() 的调用将不会返回,直到定时器到期,即创建后 5 秒。

定时器始终处于以下两种状态之一:“过期”或“未过期”。 如果对过期的定时器调用了 steady_timer::wait() 函数,它将立即返回。

t.wait();

最后,在定时器到期后,我们习惯性地打印“Hello,World!” 。

完整代码如下:

#include
#include
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.wait();
std::cout <<"Hello, world!" < return 0;
}

异步使用定时器

实现一个异步等待的定时器。

使用 asio 的异步功能意味着拥有一个回调函数,该函数将在异步操作完成时被调用。 在这个程序中,我们定义了一个名为 print 的函数,在异步等待完成时调用它。

void print(const boost::system::error_code& /*e*/)
{
std::cout <<"Hello, world!" <}
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));

接下来与同步方式不同的是,我们让定时器异步等待,并将上面定义的回调函数传递过去

t.async_wait(&print);

最后,我们必须要调用io_context::run()成员函数。

因为asio保证异步回调函数只会在调用io_context::run()函数的线程中执行。所以,不调用该函数,就永远无法取得异步函数执行结果并进行回调。

只要还有异步操作没有完成,那么io_context::run()函数就不会返回。

完整代码如下:

#include
#include
void print(const boost::system::error_code& /*e*/)
{
std::cout <<"Hello, world!" <}
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.async_wait(&print);
io.run();
return 0;
}

为Handler绑定参数

本节演示如何为Handler附加参数。

我们要实现每秒定时打印一次信息,最多打印5次。

引入头文件

#include
#include
#include

要实现每秒打印,那么我们就要在回调函数中更改定时器的到期时间,然后开始新的异步等待。这就意味着,在回调函数中我们要能够访问定时器对象和当前的次数,所以需要添加两个参数:



  • 指向定时器对象的指针

  • 当前计数值

void print(const boost::system::error_code& /*e*/,
boost::asio::steady_timer* t, int* count)
{
if (*count <5)
{
std::cout <<*count < ++(*count);

接下来,我们将定时器的到期时间从前一个到期时间向前移动一秒。 通过旧的时间计算新的到期时间,我们可以确保计时器不会由于处理处理程序的任何延迟而偏离整秒标记。

t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));

steady_timer::async_wait() 函数需要一个签名为 void(const boost::system::error_code&) 的处理函数(或函数对象),我们使用boost::bind()函数来讲print函数转为与要求函数签名一致的函数对象。

在此示例中,boost::bind()boost::asio::placeholders::error 参数是传递给处理程序的错误对象的命名占位符。 启动异步操作时,如果使用 boost::bind(),则必须仅指定与处理程序的参数列表匹配的参数。

t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}

完整代码如下:

#include
#include
#include
void print(const boost::system::error_code& /*e*/,
boost::asio::steady_timer* t, int* count)
{
if (*count <5)
{
std::cout <<*count < ++(*count);
//修改过期时间
t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
//启动异步等待
t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}
int main()
{
boost::asio::io_context io;
int count = 0;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
t.async_wait(boost::bind(print,
boost::asio::placeholders::error, &t, &count));
io.run();
std::cout <<"Final count is " < return 0;
}

使用成员函数作为Handler

本节中,我们使用类的一个成员函数作为回调函数,实现与上一节相同的功能。

不同于上一节使用print函数作为回调函数,这里我们使用print类作为回调处理器。

我们将定时器与计数都封装在内部,在构造函数中就启动异步操作

class Printer
{
public:
Printer(boost::asio::io_context* ioc) : timer_(ioc)
{
timer_.async_wait(boost::bind(&Printer::print,this));
}
~Printer()
{
std::cout <<"Final count is " < }
void print()
{
if(count_ <5)
{
std::cout < ++count_;
timer_.expires_at(timer_.expiry() + boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&Printer::print,this));
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};

完整代码如下:

#include
#include
#include
class printer
{
public:
printer(boost::asio::io_context& io)
: timer_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
timer_.async_wait(boost::bind(&printer::print, this));
}
~printer()
{
std::cout <<"Final count is " < }
void print()
{
if (count_ <5)
{
std::cout < ++count_;
timer_.expires_at(timer_.expiry() + boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&printer::print, this));
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
io.run();
return 0;
}

多线程程序中的同步处理程序

我们知道调用了io_context::run()的函数会执行回调函数。前面几节中,我们都只在一个线程中调用了该函数,因此不会并发执行。

在使用 asio 开发应用程序时,单线程方法通常是最好的方法。 缺点是它对程序(尤其是服务器)的限制,包括:



  • 当处理程序可能需要很长时间才能完成时,响应能力差。

  • 无法在多处理器系统上扩展。

如果您发现自己遇到了这些限制,另一种方法是让线程池调用 io_context::run()。然而,由于这允许处理程序并发执行,当处理程序可能访问共享的、线程不安全的资源时,我们需要一种同步方法。

在上一节的基础上,我们使用两个定时器,总共输出10条信息后结束。

我们同样定义一个Printer类,将其扩展为并行运行两个定时器。

除了初始化一对 boost::asio::steady_timer 成员之外,构造函数还初始化了 strand_ 成员,它是 boost::asio::strand 类型的对象。

strand类模板是一个执行器适配器,它保证通过它分派的处理程序能够在启动下一个处理程序之前完成一个正在执行的处理程序。无论调用io context::run()的线程数量如何,这都是可以保证的。当然,处理程序仍然可以与其他处理程序并发执行,这些处理程序不是通过一个链分派的,或者是通过一个不同的链对象分派的。

class printer
{
printer(boost::asio::io_context& io)
: strand_(boost::asio::make_strand(io)),
timer1_(io, boost::asio::chrono::seconds(1)),
timer2_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
};

启动异步操作时,每个回调处理程序都“绑定”到一个 boost::asio::strand 对象。 boost::asio::bind_executor() 函数返回一个新的处理程序,该处理程序通过strand对象自动分派其包含的处理程序。 通过将处理程序绑定到同一条strand,我们确保它们不能同时执行

timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
~printer()
{
std::cout <<"Final count is " < }

在多线程程序中,如果异步操作的处理程序访问共享资源,则它们应该同步。 在本教程中,处理程序(print1 和 print2)使用的共享资源是 std::coutcount_数据成员。

void print1()
{
if (count_ <10)
{
std::cout <<"Timer 1: " < ++count_;
timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
}
}
void print2()
{
if (count_ <10)
{
std::cout <<"Timer 2: " < ++count_;
timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
}
private:
boost::asio::strand strand_;
boost::asio::steady_timer timer1_;
boost::asio::steady_timer timer2_;
int count_;
};

现在,主函数会导致从两个线程调用 io_context::run():主线程和一个附加线程。 这是使用boost::thread对象完成的。

int main()
{
boost::asio::io_context io;
printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run();
t.join();
return 0;
}

完整代码如下所示:

#include
#include
#include
#include
class printer
{
public:
printer(boost::asio::io_context& io)
: strand_(boost::asio::make_strand(io)),
timer1_(io, boost::asio::chrono::seconds(1)),
timer2_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
~printer()
{
std::cout <<"Final count is " < }
void print1()
{
if (count_ <10)
{
std::cout <<"Timer 1: " < ++count_;
timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
}
}
void print2()
{
if (count_ <10)
{
std::cout <<"Timer 2: " < ++count_;
timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
}
private:
boost::asio::strand strand_;
boost::asio::steady_timer timer1_;
boost::asio::steady_timer timer2_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run();
t.join();
return 0;
}


推荐阅读
  • 使用GDI的一些AIP函数我们可以轻易的绘制出简 ... [详细]
  • 文件描述符、文件句柄与打开文件之间的关联解析
    本文详细探讨了文件描述符、文件句柄和打开文件之间的关系,通过具体示例解释了它们在操作系统中的作用及其相互影响。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 本文详细解析了Python中的os和sys模块,介绍了它们的功能、常用方法及其在实际编程中的应用。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 机器学习中的相似度度量与模型优化
    本文探讨了机器学习中常见的相似度度量方法,包括余弦相似度、欧氏距离和马氏距离,并详细介绍了如何通过选择合适的模型复杂度和正则化来提高模型的泛化能力。此外,文章还涵盖了模型评估的各种方法和指标,以及不同分类器的工作原理和应用场景。 ... [详细]
  • 本文详细介绍了macOS系统的核心组件,包括如何管理其安全特性——系统完整性保护(SIP),并探讨了不同版本的更新亮点。对于使用macOS系统的用户来说,了解这些信息有助于更好地管理和优化系统性能。 ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • 本教程涵盖OpenGL基础操作及直线光栅化技术,包括点的绘制、简单图形绘制、直线绘制以及DDA和中点画线算法。通过逐步实践,帮助读者掌握OpenGL的基本使用方法。 ... [详细]
  • 本文详细介绍了如何解决MyBatis中常见的BindingException错误,提供了多种排查和修复方法,确保Mapper接口与XML文件的正确配置。 ... [详细]
  • 本文探讨了《魔兽世界》中红蓝两方阵营在备战阶段的策略与实现方法,通过代码展示了双方如何根据资源和兵种特性进行战士生产。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • 本题涉及一棵由N个节点组成的树(共有N-1条边),初始时所有节点均为白色。题目要求处理两种操作:一是改变某个节点的颜色(从白变黑或从黑变白);二是查询从根节点到指定节点路径上的第一个黑色节点,若无则输出-1。 ... [详细]
author-avatar
手机用户2502881937
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有