目录

一、handler

二、io_service

1.strand

2.work

3.mutable_buffer、const_buffer

4.mutable_buffer_1、const_buffer_1

5.buffer()

三、定时器

同步定时器、异步定时器

四、网络通信

1.address

2.endpoint

3.socket

4.accepter

5.resolver

五、thread库和 asio 库区别


        Asio的全称为Asynchronous input and output(异步输入输出)的缩写。asio主要关注于网络通信方面,使用大量的类和函数封装了socketAPI,提供了一个现代 C ++ 风格的网络编程接口,支持 TCP 、 UDP 、 ICMP 等网络通信协议。但它不仅仅是一个网络库,asio 的异步操作并不局限于网络编程,它还支持 UNIX 信号、定时器、串口读写、 SSL等功能,而且 asio 是一个很好的、富有弹性的框架,可以将它扩展到其他有异步操作需要的领域。

        asio库基于前摄器模式封装了操作系统的 select 、poll、 epoll 、 kqueue 等机制,实现了异步 IO 模型。它的核心类是 io_service ,相当于前摄器模式中的Proactor角色, asio 的任何操作都需要有 io_service的参与。

        在同步模式下,程序发起一个 IO 操作,向 io_service提交请求, io_service 把操作转交给操作系统,同步等待。当 IO 操作完成时,操作系统通知 io_service ,然后 io_service 再把结果返回给程序,完成整个同步流程。这个处理流程与多线程的 join ()等待方式类似。

        在异步模式下,程序除了要发起 IO 操作,还要定义一个用于“回调”的完成处理函数 complete handler。 io_service 同样把 IO 操作转交给操作系统执行,但它不同步等待,而是立即返回。调用io_service 的 run ()成员函数可以等待异步操作完成,当异步操作完成时, io_service 从操作系统获取结果,再调用 handler 执行后续逻辑(回调)。

一、handler

        handler是 asio 库里的重要概念,它是符合某种函数签名的回调函数。handler 必须是可拷贝的,io_service 会存储 handler 的拷贝,当某种异步事件发生时,io_service 就会调用事件对应的handler。handler并不一定是函数或函数指针,函数对象、function对象、 bind / lambda 表达式等可调用物都可以用于 io_service 调用。但要小心,由于operator ( ) 是异步发生的,时机不确定,必须保证它们引用的外部变量可用,否则就会发生未定义行为 。

        在 asio 库里 , handler 主要有以下三种 :

  • 只有一个 error _ code 参数 , 标志某个异步事件完成了 , 是最基本的 handler。
  • 有 error _ code 和 signal _ number 两个参数 , 标志发生了一个 UNIX 信号事件。
  • 有 error _ code 和 bytes _ transferred 两个参数 , 标志某个读写操作完成了 , 可读写的数据字节数是bytes _ transferred , 通常用于 socket 回调。

二、io_service

        io_service 类代表了系统里的异步处理机制(如epoll),必须在 asio 库里的其他对象之前初始化,其他对象则向io_service提交异步操作的 handler 。我们最常用的成员函数是 run (),它启动事件循环,阻塞等待所有注册到io_service的事件完成。

1.strand

        asio库基于操作系统的异步 IO 模型,不直接使用系统线程,而是定义了一个自己的线程概念:strand, strand 可以序列化异步操作,保证异步代码在多线程的环境中可以正确执行,无须使用互斥量。

2.work

        io_service里的内部类可以防止io_service里注册的所有事件完成时退出事件循环。

3.mutable_buffer、const_buffer

        I/O操作会经常使用到数据缓冲区 , 相当于一片指定的内存区域 , asio 库专门用两个类
mutable_buffer、const_buffer来表示这个概念。 这两个类保存了一个void*的内存地址和数据长度。

4.mutable_buffer_1、const_buffer_1

        mutable_buffer_1、const_buffer_1包装mutable_buffer、const_buffer了这两个类,为其提供了begin()、end()两个操作,将其适配为容器的概念。

5.buffer()

      工厂函数buffer()包装C++容器类型,返回  mutable_buffer_1、const_buffer_1。asio库还提供几个自由函数可以操作 buffer:

  • buffer_size():获取缓冲区的长度。
  • buffer_cast<T*>():转换指针类型。
  • buffer_copy():拷贝缓冲区数据,类似 memcpy。

三、定时器

        在异步I/O里定时器是一个非常重要的功能,它可以在指定的某个时刻调用函数,实现异步操作。asio库提供四个定时器,分别是 deadline_timer 、 steady_timer 、 systern_timer和 high_resolution_timer 。

        这四个类的接口都是一样的,区别在于 deadline_timer 是asio早期版本提供的定时器,使用 boost.data_time 库提供时间支持,而后三个定时器则使用 std::chrono或者boost::chrono 里的时钟类提供时间支持。

同步定时器、异步定时器

        一旦定时器对象创建,它就会立即开始计时,可以使用成员函数 wait ( ) 来同步等待定时器终止,或者使用 async_wait ( ) 异步等待,当定时器终止时会调用 handler 函数。

四、网络通信

        asio库支持 TCP 、 UDP 和 IcMP 等通信协议,它在名字空间 boost::asio::ip 里提供了大量的网络通信方面的函数和类,很好地封装了原始的 Socket API ,展现给 asic 用户一个方便易用且健壮的网络通信库,下面的论述主要针对使用最广泛的 TCP 协议。

        类 ip::tcp 是 asio 网络通信( TCP )部分主要的类,表示 TCP 协议。但它本身并没有太多的功能,而是定义了数个用于 TCP 通信的 typedef 类型,用来协作完成网络通信。这些typedef包括端点类 endpoint、套接字类 socket 、流类 iostream ,以及接受器acceptor、解析器 resolver 等。从某种程度上来看, ip::tcp 类更像是一个名字空间。

        ip::tcp 的内部类型 endpoint 、 socket 、 acceptor 和 resolver 是 asio库 TCP通信中最核心的一组类,它们封装了 socket 的连接、断开、数据收发和地址解析等功能,使用它们可以很容易地编写出 socket 程序。

1.address

        IP地址独立于 TCP 、 UDP 等通信协议 , asio库使用类 ip : : address 来表示IP 地址,可以同时支持IPv4 和IPv6 两种地址。

        address类最重要的方法是静态成员函数 from_string ( ),它是一个工厂函数,可以从字符串产生IP 地址 , 地址的版本则可以用 is_v4 ( ) 和 is_v6 ( ) 来检测。相应地,address也有一个 to_string ( ) 函数 , 可以把IP 地址转换为字符串。

    #include <boost/asio.hpp>

    boost::asio::ip::address address;
    address = address.from_string("127.0.0.1");
    if (address.is_v4())
    {
        std::cout << "address is v4" << std::endl;
    }
    address = address.from_string("fe80::8525:1c3c:b557:55cd%6");
    if (address.is_v6())
    {
        std::cout << "address is v6" << std::endl;
    }

2.endpoint

        有了 IP 地址,再加上通信用的端口号就构成了一个 socket 端点,socket 端点在 asio库中用 ip::tcp::endpoint 来表示。

        endpoint的主要用法是通过构造函数创建一个可用于 socket 通信的端点对象,端点的
地址和端口号可以用 address ( ) 和 port ( ) 获得。

    boost::asio::ip::tcp::endpoint endpoint(address, 22);//创建端点对象
    std::cout << "ip 地址:" << endpoint.address() << std::endl;
    std::cout << "端口号:" << endpoint.port() << std::endl;

3.socket

        socket可以在构造时就指定使用的协议或者 endpoint ,或者稍后调用成员函数connect ( ) 。连接成功后可以用 local_endpoint ( ) 和 remote_endpoint ( ) 获得连接两端的端点信息,用 available ( ) 获取可读取的字节数,用 receive ( ) / read_some ( ) 和send ( ) / write_some ( ) 读写数据,当操作完成后使用 close ( ) 函数关闭 socket 。如果不关闭 socket ,那么在 socket 对象析构时也会自动调用 close ( ) 关闭。

        send ( ) / receive ( ) 函数和 write_some ( ) / read_some ( ) 函数功能完全相同,只是名字不同,内部都调用的是系统函数 sendmsg ( ) 和 recvmsg ( ) ,但 send ( ) / receive ( ) 函数要多出一种使用 socket_base::message_flags 参数的重载形式。

        socket读写函数的参数都是 buffer 类型,可以用 buffer ( ) 函数包装各种容器适配,区别在于 send ( ) / write_some ( ) 的参数要是一个可读 buffer ,而 receive ( ) / read_some ( ) 要求是可写 buffer 。

4.accepter

        acceptor类对应 Socket API 的 accept ( ) 函数功能,它用于服务器端,在指定的端口号接受连接,必须配合 socket 类才能完成通信。
        acceptor是 basic _ socket _ acceptor 的 TCP 协议特化。

//客户端
void client::TestClient()
{
    try{
        typedef boost::asio::ip::tcp::socket socket;
        typedef boost::asio::ip::tcp::endpoint endpoint;
        typedef boost::asio::ip::address address;

        boost::asio::io_service _io_service;

        socket _socket(_io_service);

        endpoint _endpoint(address::from_string("127.0.0.1"),6688);

        _socket.connect(_endpoint);

        qDebug()<<"socket byte length:"<<_socket.available();

        std::vector<char> _vec(_socket.available()+1);
        _socket.receive(boost::asio::buffer(_vec));
        qDebug()<<"receive data:"<<&_vec[0];
    }
    catch(std::exception e)
    {
        qDebug()<<e.what();
    }
}
//服务器端
void service::TestService()
{
    try{
        typedef boost::asio::ip::tcp::socket socket;
        typedef boost::asio::ip::tcp::endpoint endpoint;
        typedef boost::asio::ip::tcp::acceptor acceptor;

        boost::asio::io_service _io_service;

        endpoint _endpoint(boost::asio::ip::tcp::v4(),6688);

        acceptor _acceptor(_io_service,_endpoint);
        for(;;)
        {
            socket _socket(_io_service);

            _acceptor.accept(_socket);

            _socket.send(boost::asio::buffer("hello asio"));
            qDebug()<<"send data";
        }
    }catch(std::exception e)
    {
        qDebug()<<e.what();
    }

}

5.resolver

        resolver类对应 Socket API 的 getaddrinfo ( ) 系列函数,用于解析网址获得可用的IP 地址,解析得到的 IP 地址可以使用 socket 对象连接。

        之前关于 TCP 通信的所有论述我们都是使用直接的IP 地址,但在实际生活中大多数时候我们不可能知道 socket 连接另一端的地址,而只有一个域名,这时候我们就需要使用resolver类来通过域名获得可用的 IP,它可以实现与 IP 版本无关的网址解析。

        resolver使用内部类 query 和 iterator 共同完成查询IP 地址的工作:首先使用网址和服务名(通常是端口号)创建 query 对象,然后由 resolve ( ) 成员函数生成 iterator对象,它代表了查询到的IP 端点。之后就可以使用 socket 对象尝试连接,直到找到一个可用的为止。

    boost::asio::io_service _io_service;
    boost::asio::ip::tcp::resolver _resolver(_io_service);
    boost::asio::ip::tcp::resolver::query _query("baidu.com", "80");  
    auto iter = _resolver.resolve(_query);      //iter是个迭代器
    //尝试connect  *iter,++iter,直到连接成功...

五、thread库和 asio 库区别

        thread库和 asio 库都可以用于并发编程,但它们解决问题的途径不一样。 thread 使用的是进程内部的线程机制,很少需要操作系统内核干预,只要掌握了线程的同步方法,多线程程序的结构很容易理解,也很容易实现。而 asio 使用的是异步事件处理机制,与操作系统的内核密切相关,使用它需要对操作系统的底层机制有一定的了解,比多线程程序更难于编写,也难于调试,但由于 asio 把异步操作的管理工作交由操作系统处理,所以能够获得更高的运行性能。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐