美文网首页
asio C++ library核心理念和功能(二)

asio C++ library核心理念和功能(二)

作者: 山羊歌的演唱会 | 来源:发表于2018-05-31 14:09 被阅读0次

    1.2.3 线程和Asio

    线程安全
    一般来说,并发使用不同的对象是安全的,使用单个对象是不安全的。 但是,像io_service这样的类型可以更有力地保证可以同时使用单个对象。

    线程池
    多线程可能会调用io_service :: run()来设置线程池,从中可以调用完成处理程序。 这种方法也可以与io_service :: post()一起使用,以便在线程池中执行任何计算任务。
    请注意,所有已加入io_service池的线程都被认为是等同的,并且io_service可以以任意方式在他们之间分配工作。

    内部线程
    针对特定平台的这个库的实现可以利用一个或多个内部线程来模拟异步性。尽可能地,这些线程必须对库用户不可见。 特别是,线程:

    • 不能直接调用用户的代码;
    • 必须阻止所有信号
      这种做法有以下保证作为补充:
    • 异步完成处理程序只能从当前调用io_service :: run()的线程中调用。
      因此,库用户有责任创建和管理通知将传送到的所有线程。
      这种方法的原因包括:
    • 通过仅从一个线程调用io_service :: run(),用户的代码可以避免与同步相关的开发复杂性。 例如,库用户可以实现单线程的可伸缩服务器(从用户的角度来看)。
    • 库用户可能需要在线程启动后,执行任何其他应用程序代码之前不久,在线程中执行初始化。 例如,Microsoft的COM用户必须先调用CoInitializeEx,然后才能从该线程调用任何其他COM操作。
    • 库接口与用于线程创建和管理的接口分离,并允许在线程不可用的平台上实现。

    1.2.4 链:使用线程而不显式锁定

    链被定义为事件处理程序的严格顺序调用(即无并发调用)。 链的使用允许执行代码在多线程程序中,而不需要显式锁定(例如使用互斥锁)。
    链可以是隐含的也可以是明确的,如以下替代方法所示:

    • 仅从一个线程调用io_service :: run()意味着所有事件处理程序都在隐式链中执行,这是由于io_service保证处理程序只能从run()内部调用
    • 在存在与连接相关联的单个异步操作链的情况下(例如,在诸如HTTP的半双工协议实现中),不存在并发执行处理程序的可能性。 这是一条隐含的链条。
    • 显式链是io_service :: strand的一个实例。 所有事件处理函数对象都需要使用io_service :: strand :: wrap()进行包装,否则通过io_service :: strand对象进行发布/分派。
      在组成异步操作的情况下,如async_read()或async_read_until(),如果完成处理程序经过一个链,则所有中间处理程序也应该经过同一链。 这需要确保线程安全地访问调用者和组合操作之间共享的对象(在async_read()的情况下,它是套接字,调用者可以关闭()以取消操作)。 这是通过为所有中间处理程序提供钩子函数来完成的,该函数将调用转发给与最终处理程序关联的可定制钩子:
    struct my_handler
    {
    void operator()() { ... }
    };
    template<class F>
    void asio_handler_invoke(F f, my_handler*)
    {
    // Do custom invocation here.
    // Default implementation calls f();
    }
    

    io_service :: strand :: wrap()函数创建了一个新的完成处理程序,它定义了asio_handler_invoke,以便函数对象通过该线程执行。

    1.2.5 缓冲区

    从根本上讲,I / O涉及将数据传入和传出连续的内存区域,称为缓冲区。 这些缓冲区可以简单地表示为由指针和字节大小组成的元组。 但是,为了开发高效的网络应用程序,Asio支持分散 - 集中操作。 这些操作涉及一个或多个缓冲区:

    • 从多个的缓冲区中分散读
    • 向多个缓冲区集中写入
      因此,我们需要一个抽象来表示一组缓冲区。 Asio使用的方法是定义一个类型(实际上是两种类型)来表示单个缓冲区。 这些可以存储在一个容器中,可以传递给分散 - 集中操作。
      除了将缓冲区指定为指针和字节大小之外,Asio还在可修改内存(称为可变内存)和不可修改内存(其中后者是从用于const限定变量的存储创建的)之间进行了区分。 因此这两种类型可以定义如下:
    typedef std::pair<void*, std::size_t> mutable_buffer;
    typedef std::pair<const void*, std::size_t> const_buffer;
    

    这里,一个mutable_buffer可以转换为一个const_buffer,但是逆向的转换是无效的。
    但是,Asio并没有按原样使用上述定义,而是定义了两个类:mutable_buffer和const_buffer。
    这些目标是提供对连续内存的不透明表示,其中:

    • 类型在转换中表现为std :: pair。 也就是说,mutable_buffer可以转换为const_buffer,但是相反的转换是不允许的。
    • 有防止缓冲区溢出的保护。 给定一个缓冲区实例,用户只能创建另一个代表相同范围的内存或其子范围的缓冲区。 为了进一步提高安全性,库还包括自动机制,从数组中确定缓冲区的大小,POD元素的boost :: array或std :: vector或std :: string
    • 必须使用buffer_cast函数明确请求类型安全违规。 通常,应用程序永远不需要这样做,但是库实现需要将原始内存传递到底层操作系统函数。
      最后,通过将缓冲区对象放入容器中,可以将多个缓冲区传递给分散 - 收集操作(例如read()或write())。 MutableBufferSequence和ConstBufferSequence概念已被定义,因此可以使用诸如std :: vector,std :: list,std :: vector或boost :: array之类的容器。

    流缓冲用于集成IOSTREAMS
    类asio :: basic_streambuf源自std :: basic_streambuf,用于将输入序列和输出序列与某个字符数组类型的一个或多个对象相关联,这些对象的元素存储任意值。 这些字符数组对象是streambuf对象的内部对象,但提供对数组元素的直接访问以允许它们与I / O操作一起使用,例如套接字的发送或接收操作:

    • streambuf的输入序列可通过data()成员函数访问。 这个函数的返回类型符合ConstBufferSequence的要求。
    • streambuf的输出顺序可以通过prepare()成员函数来访问。 该函数的返回类型符合
      MutableBufferSequence要求。
    • 通过调用commit()成员函数将数据从输出序列的前面传输到输入序列的后面
    • 通过调用consume()成员函数将数据从输入序列的前面移除。
      streambuf构造函数接受一个size_t参数,用于指定输入序列和输出序列大小总和的最大值。 如果成功,任何将超出此限制的内部数据增加的操作都会抛出一个std :: length_error异常。

    缓冲区序列的顺序遍历
    buffers_iterator <>类模板允许缓冲区序列(即满足MutableBufferSequence或ConstBufferSequence要求的类型)遍历,就像它们是连续的字节序列一样。 还提供了名为buffers_begin()和buffers_end()的辅助函数,其中会自动推导出buffers_iterator <>模板参数。
    作为一个例子,为了从一个套接字读入一个单行并写入一个std :: string,你可以这样写:

    asio::streambuf sb;
    ...
    std::size_t n = asio::read_until(sock, sb, ’\n’);
    asio::streambuf::const_buffers_type bufs = sb.data();
    std::string line(
    asio::buffers_begin(bufs),
    asio::buffers_begin(bufs) + n);
    

    缓冲区调试
    某些标准库实现(如Microsoft Visual C ++ 8.0及更高版本附带的标准库实现)提供了称为迭代器调试的功能。 这意味着在运行时检查迭代器的有效性。 如果程序试图使用已经失效的迭代器,则会触发断言。 例如:

    std::vector<int> v(1)
    std::vector<int>::iterator i = v.begin();
    v.clear(); // invalidates iterators
    *i = 0; // assertion!
    

    Asio利用此功能来添加缓冲区调试。 考虑下面的代码:

    void dont_do_this()
    {
    std::string msg = "Hello, world!";
    asio::async_write(sock, asio::buffer(msg), my_handler);
    }
    

    在调用异步读或写时,需要确保操作的缓冲区有效,直到调用完成处理程序。在上面的例子中,缓冲区是std :: string变量msg。这个变量在堆栈中,所以就这样了
    在异步操作完成之前超出范围。如果你很幸运,那么应用程序会崩溃,但随机失败更可能。
    启用缓冲区调试时,Asio会将迭代器存储到字符串中,直到异步操作完成,然后将其解引用以检查其有效性。在上面的例子中,你会在Asio尝试调用完成处理程序之前观察断言失败。
    当定义_GLIBCXX_DEBUG时,此功能会自动提供给Microsoft Visual Studio 8.0或更高版本以及GCC。这种检查有一个性能成本,所以缓冲区调试只能在调试版本中启用。对于其他编译器,可以通过定义ASIO_ENABLE_BUFFER_DEBUGGING来启用。它也可以通过定义ASIO_DISABLE_BUFFER_DEBUGGING来显式禁用。

    相关文章

      网友评论

          本文标题:asio C++ library核心理念和功能(二)

          本文链接:https://www.haomeiwen.com/subject/irrxsftx.html