一点一滴成长

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

boost--asio

 1、概述

  Boost.Asio使用的是Proactor模式,在Windows下通过重叠IO和完成端口实现,在Linux下通过对Reactor模式(如select、epoll、kqueue)的封装来实现了Proactor模式。

  Asio中一次IO请求的基本流程:调用IO对象来执行读写等操作,IO对象会将请求操作转发给IO执行上下文,由IO执行上下文来调用系统接口服务,并将调用结果(error_code类型)转发回IO对象。

  IO执行上下文是Asio的核心类,它是boost::asio::io_context或boost::asio::io_service,后者实际上是前者的typedef。

  IO执行上下文的run()用来开启事件处理循环,它会阻塞等待异步操作完成并分派事件:当异步操作完成后其结果会被放到一个队列中,I/O执行上下文将操作结果从队列中取出,将其转换为error_code后传递给完成处理回调。

  也就是说,程序发起的IO操作都交给io_service,Iio_service再把操作转交给操作系统,如果是同步模式的话,IO操作会阻塞等待操作完成,如果是异步模式的话(需要定义一个用于回调的完成处理函数),调用io_service的run()方法来等待异步操作完成。

  使用Asio需要包含头文件"boost\asio.hpp",在VC下还要添加_WIN32_WINNT的定义以避免编译警告,如下为简单示例:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"

boost::asio::io_context io_context; //io_context是IO执行上下文,用来与操作系统的IO服务相联通,其它的IO执行上下文还有boost::asio::thread_pool、boost::asio::system_context
boost::asio::ip::tcp::socket socket(io_context); //IO对象,这里是TCP socket类型的对象,IO对象会将IO请求转发给IO执行上下文,所以需要传入一个io_context

socket.connect(server_endpoint); //连接服务器,出错的话会抛出异常boost::system::system_error

boost::system::error_code ec;
socket.connect(server_endpoint, ec); //连接服务器,传入了error_code类型参数,这样连接结果会设置到该参数,不再抛出异常

socket.async_connect(server_endpoint, completion_handler); //异步连接服务器,completion_handler为完成结果回调(函数或函数对象,参数类型是const boost::system::error_code&)
io_context.run(); //必须调用执行上下文的run()或类似的成员函数才能获得异步操作的结果,该方法会一直阻塞直到操作完成
View Code
复制代码

  除了function外,还可以通过future来获得异步操作的结果,如下所示,通过异步操作的最后一个参数(完成标记,又称完成令牌completion token)来指示使用的异步通知,比如指定其为use_awaitable来使用协程。

复制代码
    std::future<std::size_t> myFuture = socket.async_read_some(boost::asio::buffer(_recv_buf), boost::asio::use_future); //返回一个future,用来获得异步操作的结果

    awaitable<void> foo() //返回可唤醒的协程
    {
        size_t n = co_await socket.async_read_some(buffer, boost::asio::use_awaitable); //使用协程,该协程不会直接启动,当协程为co_await-ed状态时,它会被启动
        // ...
    }
View Code
复制代码

  如下我们通过定时器演示了asio程序的基本流程:首先定义一个io_service对象,然后执行一个IO操作(这里是定时器),并把它挂载到io_service上,再然后就可以执行后续的同步或异步操作。

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
#include "boost\date_time\posix_time\posix_time.hpp"
#include "boost\bind.hpp"
    using namespace std;
    using namespace boost::asio;

    void print(const boost::system::error_code & e)
    {
        cout << "timeout" << endl;
    }

    void myPrint(const boost::system::error_code & e, int n)
    {
        cout << "timeout, " << n << endl;
    }

    int main(int argc, char** argv)
    {
        try {
            /*同步定时器*/
            io_service ios;
            deadline_timer dt(ios, boost::posix_time::seconds(10)); //开启10秒的定时,也可以传入一个posix_time绝对时间,或者不传参数(后期通过expires_from_now()或expires_at()设置终止时间)
            cout << dt.expires_at() << endl; //获得定时器终止的绝对时间
            dt.wait(); //等待定时器终止
            cout << "timeout" << endl;


            /*异步定时器*/
            boost::asio::io_service ios;
            boost::asio::deadline_timer dt(ios, boost::posix_time::seconds(10));
            dt.async_wait(print); //通知io_service异步的执行I/O操作,并注册回调函数后立即返回
            ios.run();


            /*异步定时器——回调方法中增加参数*/
            boost::asio::io_service ios;
            boost::asio::deadline_timer dt(ios, boost::posix_time::seconds(10));
            int num = 1000;
            dt.async_wait(bind(myPrint, _1, num)); //通知io_service异步的执行I/O操作,并注册回调函数后立即返回
            ios.run();
        }
        catch (exception& e) {
            cout << e.what() << endl;
        }
    }
View Code
复制代码

2、工具类

  address地址类、endpoint端点类、resolver解析器类:

复制代码
#include "boost\asio.hpp"
using namespace std;
using namespace boost::asio;

int main(int argc, char** argv)
{
    try {
        /*address地址类*/
        ip::address addr;
        addr = addr.from_string("127.0.0.1");
        addr.is_v4(); //是否是IPV4地址(Windows下默认不支持IPV6,可以在命令行里运行ipv6 install以增加ipv6支持)
        std::string strAddr = addr.to_string(); //"127.0.0.1"

        /*endpoint网络端点类*/
        ip::tcp::endpoint ep(addr, 8080);
        bool b = ep.address() == addr; //true

        /*resolver解析器类,可以通过域名获得IP地址*/
        ip::tcp::resolver::query qry("www.baidu.com", "80");
        ip::tcp::resolver::iterator iter = ip::tcp::resolver(io_service()).resolve(qry);
        ip::tcp::resolver::iterator end;
        for (; iter != end; ++iter) {
            const boost::asio::ip::tcp::endpoint& endPoint = *iter;
            cout << "ip: " << endPoint.address().to_string() << ", port: " << std::to_string(endPoint.port()) << endl;
        }

        //resolver也支持使用IP地址和协议名进行初始化
        boost::asio::ip::tcp::resolver::query qry2("127.0.0.1", "http");
    }
    catch (exception& e) {
        cout << e.what() << endl;
    }
}
View Code
复制代码

  asio中通常不能直接使用数组来作为收发数据的缓冲区,而是使用mutable_buffer(可读写)或const_buffer(只读)类型来作为收发缓冲区。实际上,为了提高效率,asio中的方法使用的是MutableBufferSequence或ConstBufferSequence类型,它们相当于是mutable_buffer或const_buffer类型的vector。我们一般通过boost::asio::buffer()来获得MutableBufferSequence或ConstBufferSequence,如下所示,它们返回的类型分别是mutable_buffers_1(MutableBufferSequence接口实现类)、asio::const_buffers_1(ConstBufferSequence接口实现类)。

复制代码
 std::vector<char> vc(1024, 0); //需要设置好原始buffer的大小以接收数据
    auto buf = boost::asio::buffer(vc); //mutable_buffers_1
    
    char buffer[1024];
    auto buf1 = boost::asio::buffer(buffer);
    char* pBuf = boost::asio::buffer_cast<char*>(buf1); //获得mutable_buffer中包装的原始内存指针

    const std::string ss = "hello world";
    auto buf2 = boost::asio::buffer(ss); //const_buffers_1
    size_t size = boost::asio::buffer_size(buf2); //获得buffer大小: 11

    auto buf3 = boost::asio::buffer(ss, 5);
    size = boost::asio::buffer_size(buf3); //5
View Code
复制代码

3、客户端-服务端通信

  同步客户端:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
using namespace std;
using namespace boost::asio;

int main(int argc, char** argv)
{
    try {
        io_service ios;
        ip::tcp::socket sock(ios); //socket对象
        ip::tcp::endpoint endpoint(ip::address::from_string("127.0.0.1"), 6688);

        boost::system::error_code errCode = error::host_not_found;
        sock.connect(endpoint, errCode); //连接到端点
        if (errCode) {
            std::cout << "connect error" << std::endl;
            std::string strErrMsg = errCode.message();
            throw boost::system::system_error(errCode);
        }

        vector<char> vc(1024, 0); //需要提前设置好缓冲区大小
        sock.read_some(boost::asio::buffer(vc)); //接收数据,使用buffer()包装接收缓冲区
        cout << "recive from " << sock.remote_endpoint().address().to_string() << ": " << &vc[0] << endl; //输出对方地址及发送内容

        sock.close();  //socket析构的时候也会自动调用close()
    }
    catch (exception& ex) {
        std::cout << ex.what() << std::endl;
    }

    getchar();
}
View Code
复制代码

  同步服务端:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
using namespace std;
using namespace boost::asio;

int main(int argc, char** argv)
{
    try
    {
        io_service ios;
        ip::tcp::acceptor acceptor(ios, ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6688)); //创建acceptor对象,绑定本机IP及6688端口
        cout << acceptor.local_endpoint().address() << endl; //输出绑定的地址

        ip::tcp::socket sock(ios); //创建socket对象
        acceptor.accept(sock); //等待客户连接
        cout << sock.remote_endpoint().address() << ": " << sock.remote_endpoint().port() << " now connect." << endl; //输出对方信息
        sock.write_some(boost::asio::buffer("hello!")); //发送数据,使用buffer()包装发送缓冲区

        sock.close();

        getchar();
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }

    getchar();
}
View Code
复制代码

  异步客户端:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
#include "boost\asio\placeholders.hpp"
using namespace std;
using namespace boost::asio;

#include <functional>
using namespace std::placeholders;

class AsyncClient
{
public:
    AsyncClient(io_service& io, const std::string& address, int port) : ios(io), ep(ip::address::from_string(address), port) {
        connect();
    }
private:
    void connect() {
        shared_ptr<ip::tcp::socket> spSock(new ip::tcp::socket(ios));
        spSock->async_connect(ep, [this, spSock](const boost::system::error_code& ec) {
            bConnected = true;
            if (ec) {
                if (ec.value() == 10061) //服务端未开启等
                    ;
                else if (ec.value() == 995) //关闭了socket(比如超时回调中)等
                    ;

                std::cout << "connect error " << ec.value() << ":" << ec.message() << std::endl;
                return;
            }
            std::cout << "connect sucess: " << spSock->remote_endpoint().address() << " " << spSock->remote_endpoint().port() << std::endl;

            shared_ptr<vector<char>> vcBuf(new vector<char>(100, 0)); //需要提前设置好缓冲区大小
            spSock->async_read_some(buffer(*vcBuf), std::bind(&AsyncClient::readHandle, this, _1, _2, vcBuf, spSock));
            });

        //开启3秒的定时器,超时后没有完成连接的话则关闭socket
        deadline_timer t(ios, boost::posix_time::seconds(3));
        t.async_wait([this, spSock](const boost::system::error_code& ec) {
            if (!bConnected) {
                spSock->close();
            }
            });
    }

    void readHandle(const boost::system::error_code& ec, std::size_t size, shared_ptr<vector<char>> vcBuf, shared_ptr<ip::tcp::socket> spSock) {
        if (ec) { //出错或对方关闭连接
            if (ec.value() == 2/*对方正常关闭socket*/ || ec.value() == 10054/*对方强制关闭程序*/)
                std::cout << "peer close socket" << std::endl;
            else
                std::cout << "read error " << ec.value() << ":" << ec.message() << std::endl;
            return;
        }

        std::cout << "recive:" << &(*vcBuf)[0] << std::endl;
        //再次发起一个读取操作,否则io_service上无异步请求的话, 事件处理循环结束, run()直接返回
        spSock->async_read_some(buffer(*vcBuf), std::bind(&AsyncClient::readHandle, this, _1, _2, vcBuf, spSock));
    }

    io_service& ios;
    ip::tcp::endpoint ep;
    bool bConnected = false;
};

int main(int argc, char** argv)
{
    try {
        io_service ios;
        AsyncClient client(ios, "127.0.0.1", 8088);
        ios.run();
    }
    catch (std::exception& e) {
        cout << e.what() << std::endl;
    }
    std::cout << "client end" << std::endl;
}
View Code
复制代码

  异步服务端:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
#include "boost\asio\placeholders.hpp"
using namespace std;
using namespace boost::asio;

#include <functional>
using namespace std::placeholders;

class CServer
{
public:
    CServer(io_service& io, ip::tcp::endpoint& ep)
        : ios(io)
        , acceptor(ios, ep)
    {
        std::cout << ep.address() << std::endl;
        accept_async();
    }
    virtual ~CServer() {}
private:
    void accept_async()
    {
        shared_ptr<ip::tcp::socket> spSock(new ip::tcp::socket(ios));
        acceptor.async_accept(*spSock, std::bind(&CServer::accept_handler, this, _1, spSock));
    }
    void accept_handler(const boost::system::error_code& ec, shared_ptr<ip::tcp::socket> spSock)
    {
        if (ec)
            return;

        cout << "a client connect, from ";
        cout << spSock->remote_endpoint().address() << ": " << spSock->remote_endpoint().port() << endl;

        spSock->async_write_some(boost::asio::buffer("hello"), std::bind(&CServer::send_handler, this, _1, spSock)); //发送数据

        accept_async(); //再次发起一个异步接受连接
    }
    void send_handler(const boost::system::error_code& ec, shared_ptr<ip::tcp::socket> spSock)
    {
        cout << "send msg complete" << endl;
        //spSock->close(); //本函数结束后spSock被释放,会自动调用close()
    }

    io_service& ios;
    ip::tcp::acceptor acceptor;
};

int main(int argc, char** argv)
{
    try {
        io_service ios;
        CServer srv(ios, ip::tcp::endpoint(ip::tcp::v4(), 8088)/*绑定本机IP及6688端口*/);
        ios.run();
    }
    catch (std::exception& e) {
        cout << e.what() << std::endl;
    }
    std::cout << "server end" << std::endl;
}
View Code
复制代码

  对于TCP连接,可以使用asio::ip::tcp::iostream来代替socket,使用它可以像std中的标准流一样来操作socket(它是std::basic_iostream的子类),它内部还集成了connect和resolver域名解析功能:

复制代码
//客户端
        boost::asio::ip::tcp::iostream tcp_stream("127.0.0.1", "6688"); //连接到本机6688端口
        string strBuf;
        std::getline(tcp_stream, strBuf);


//服务端
        ip::tcp::endpoint ep2(ip::tcp::v4(), 8088);
        boost::asio::ip::tcp::acceptor acceptor(ios, ep2);
        boost::asio::ip::tcp::iostream streamSocket;
        acceptor.accept(*streamSocket.rdbuf());
        streamSocket << "hello\r\n" << endl;
View Code
复制代码

  UDP同步客户端:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
using namespace boost::asio;

int main(int argc, char** argv)
{
    try {
        io_service ios;

        //初始化socket
        ip::udp::socket sock(ios);
        sock.open(ip::udp::v4());

        //发送数据
        ip::udp::endpoint ep(ip::address::from_string("127.0.0.1"), 6699);
        sock.send_to(boost::asio::buffer("123"), ep);

        sock.close();
    }
    catch (std::exception& ex) {
        std::cout << ex.what() << std::endl;
    }
}
View Code
复制代码

  UDP同步服务端:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
using namespace boost::asio;

int main(int argc, char** argv)
{
    try {
        io_service ios;

        //初始化socket
        ip::udp::socket sock(ios, ip::udp::endpoint(ip::udp::v4(), 6699));

        //接收数据
        for (;;)
        {
            char buf[4] = { 0 };
            ip::udp::endpoint ep;
            boost::system::error_code ec;
            sock.receive_from(boost::asio::buffer(buf), ep, 0, ec);
            if (ec && ec != boost::asio::error::message_size)
            {
                //UDP从套接字读取数据时,即使提供的缓冲区的大小小于消息,它也会一直读取到消息边界,
                //当消息大小大于提供的缓冲区大小时,错误码为boost::asio::error::message_size
                throw boost::system::system_error(ec);
            }

            std::cout << "recv from " << ep.address() << ": " << buf << std::endl;
        }
    }
    catch (std::exception& ex) {
        std::cout << ex.what() << std::endl;
    }
}
View Code
复制代码

4、读写高级功能

  还可以使用streambuf来支持对收发数据的流式操作,streambuf内部是自己动态分配内存的: 

复制代码
        //读取数据
        boost::asio::streambuf sb1;
        boost::asio::streambuf::mutable_buffers_type bufs = sb1.prepare(512); //获取大小为512字节的buffer用来读取数据
        size_t n = sock.receive(bufs);
        sb1.commit(n); //数据从输出队列中被移除(从write操作)然后加入到输入队列中(为read操作准备)
        std::istream is(&sb1);
        std::string s;
        is >> s;

        //遍历streambuf
        std::string srBuffer(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + n);

        //发送数据
        boost::asio::streambuf sb2;
        std::ostream os(&sb2);
        os << "Hello, World!\n";

        auto buf = sb2.data(); //获得buffer用来写入数据
        size_t nSent = sock.send(buf);
        sb2.consume(nSent); //数据从输入队列中被移除(从read操作)
View Code
复制代码

  asio名字空间下也提供了通用的读写方法,通过它们可以实现更加高级的读写功能,比如boost::asio::read()、boost::asio::async_read()可以指定读取到指定大小数据才返回或进行回调通知(socket.read_some()和socket.async_read_som()会读取到数据就返回或回调通知,比如接收buffer大小为100,对方只发送了10字节就会读取这10字节后返回或进行回调通知)。:

复制代码
        std::array<char, 128> buf;
        boost::system::error_code errCode;

        size_t bytes_transfered = boost::asio::read(sock, boost::asio::buffer(buf), boost::asio::transfer_all()/*一直读满buffer才返回*/, errCode);
        if (errCode)
        {
            //error
        }
        else
        {
            // n == 128
        }

        bytes_transfered = boost::asio::read(sock, boost::asio::buffer(buf), boost::asio::transfer_exactly(64)/*读取到64个字节就返回*/, errCode);
        if (errCode)
        {
            //error
        }
        else
        {
            // n == 64
        }

        bytes_transfered = boost::asio::read(sock, boost::asio::buffer(buf), boost::asio::transfer_at_least(64)/*读取到64或大于64个字节就返回*/, errCode);
        if (errCode)
        {
            //error
        }
        else
        {
            // n >= 64 && n <= 128
        }
View Code
复制代码

  boost::asio::read_until()可以一直读取到指定字符或字符串或满足函数对象指示的条件或匹配正则表达式才返回,因为不清楚读到多少数据才符合条件,所以read_until()需要一个streambuf或dynamicbuffer。dynamicbuffer是一个可以动态调整大小的缓冲区,可以通过boost::asio::dynamic_buffer()来获得一个dynamic_string_buffer(传入类型为string)或dynamic_vector_buffer(传入类型为vector<char>)。 

复制代码
        /*****************************一直读取到字符'\n'*********************************/
        boost::asio::streambuf sb;
        size_t n = boost::asio::read_until(sock, sb, '\n');
        

        /*****************************一直读取到字符串"\r\n"*********************************/
        std::string data;;
        auto n2 = boost::asio::read_until(sock, 
            boost::asio::dynamic_buffer(data)/*dynamic_string_buffer*/, boost::regex("\r\n"));


        /*****************************一直读取到空白字符*********************************/
        typedef boost::asio::buffers_iterator<boost::asio::const_buffers_1> iterator;
        std::pair<iterator, bool> match_whitespace(iterator begin, iterator end)
        {
            iterator i = begin;
            while (i != end)
                if (std::isspace(*i++))
                    return std::make_pair(i, true);
            return std::make_pair(i, false);
        }

        std::string str1;
        boost::asio::read_until(sock, boost::asio::dynamic_buffer(str1), match_whitespace);


        /*****************************一直读取到字符'a'*********************************/
        class match_char
        {
        public:
            explicit match_char(char c) : c_(c) {}

            template <typename Iterator>
            std::pair<Iterator, bool> operator()(Iterator begin, Iterator end) const
            {
                Iterator i = begin;
                while (i != end) {
                    if (c_ == *i++)
                        return std::make_pair(i, true);
                }
                return std::make_pair(i, false);
            }
        private:
            char c_;
        };
        namespace boost {
            namespace asio {
                template <> struct is_match_condition<match_char> : public boost::true_type {};
            }
        }

        std::string str2;
        boost::asio::read_until(sock, boost::asio::dynamic_buffer(str2), match_char('a'));
View Code
复制代码

  boost文档中指出socket.write_some()和socket.async_write_some()可能不会发送所有数据就返回或进行回调通知,可以使用boost::asio::write()、boost::asio::async_write()来发送所有数据,比如 boost::asio::async_write(sock, boost::asio::buffer("data"), [](const boost::system::error_code& ec, std::size_t size) {}。

  boost::asio::async_write()内部其实是通过一个或多个async_write_some()实现的,比如一次async_write_some()没有发送完所有数据,那么就会再次调用async_write_some()来发送剩余数据。如果我们连续两次调用async_write(),因为第一次发送了很大的数据,所以其内部会两次调用async_write_some(),而我们第二次调用async_write()的时候,第一次async_write_some()还没完成(回调没通知),这样第二次调用async_write()发送的数据就会排到了第二次调用async_write_some()之前,造成了发送数据的乱序,所以在调用boost::asio::async_write()的时候,一定要确保上一次boost::asio::async_write()已经完成数据的发送。也可以发送数据直接使用同步发送方法来避免async_write()带来的发送乱序问题。

  boost::asio::async_read()内部也是通过调用一个或多个async_read_some()实现的,所以使用它的话,也必须在上一次读取完成之后进行。

  调用socket.async_write_some()或boost::asio::async_write()发送数据的时候,如果对方一直不读取数据,则可能导致发送缓冲区满,那么发送回调就不会被执行,一些预分配的内存如果得不到释放的话,最后就会导致内存耗尽。

5、串口通信

  aiso也支持串口通信,通过使用serial_port类。

6、post()

   io_service的post()可以投递一个方法,run()后投递的方法开始执行:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
using namespace boost::asio;

boost::asio::io_service service;

void func2(int i) {
    std::cout << "func2 called, i= " << i << std::endl;
}

void func1(int i) {
    std::cout << "func1 called, i= " << i << std::endl;
    service.post(std::bind(func2, 2)); //再次投递一个事件,因为还在事件处理循环中,所以此次投递有效
    std::cout << "func1 end" << std::endl;
}

int main(int argc, char** argv)
{
    service.post(std::bind(func1, 1));
    service.run();
    service.post(std::bind(func2, 2)); //此次投递无效,因为run()已返回,即事件处理循环已结束
    std::cout << "main end" << std::endl;

    getchar();
}
View Code
复制代码

  io_service的dispatch()方法也可以用来执行一个方法,与post()不同的是, dispatch()会立即执行方法,比如上面的post()如果换成dispatch()的话,以下为输出:

  post()、dispatch()的方法都是在本线程中执行的,如下所示我们可以向io_service的任务队列投递10个任务方法,然后在另一个线程里执行run()方法,这样所有的方法都会在工作线程中执行。从打印结果可以看到,任务的执行顺序是安装投递时的顺序执行的:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
using namespace boost::asio;

boost::asio::io_service service;
std::mutex mu;

void func(int i) {
    mu.lock();
    std::cout << "func called, i= " << i << std::endl;
    mu.unlock();
}

void worker_thread() {
    service.run();
    std::cout << "work end" << std::endl;
}

int main()
{
    for (int i = 0; i < 10; ++i)
        service.post(std::bind(func, i));

    std::thread first(worker_thread);
    first.detach();

    getchar();
}
View Code
复制代码

  工作线程还可以有多个,也就是说在多个线程里调用run()方法,那么每个工作线程都会去任务队列里取任务来执行,充分利用多核优势(除了多个线程共用一个io_service外,还可以创建多个io_service,每个io_service对应一个线程,即one loop per thread)。从打印结果来看的话,多线程执行任务的顺序是不固定的,我个人认为这两个工作线程取任务的时候还是按照投递的顺序一个一个的取的,但是因为线程调度原因,在执行的时候不保证按照任务队列里的顺序来执行(A线程执行完了第一个任务后B线程还在执行第二个任务,所以A会继续执行第三个任务,第三个任务的完成就有可能在第二个任务之前):

复制代码
boost::asio::io_service service;
    std::mutex mu;

    void func(int i) {
        mu.lock();
        std::cout << std::this_thread::get_id() << ": func called, i= " << i << std::endl;
        mu.unlock();
    }

    void worker_thread() {
        service.run();
    }

    int main()
    {
        for (int i = 0; i < 10; ++i)
            service.post(std::bind(func, i));

        boost::thread_group threads;
        for (int i = 0; i < 2; ++i)
            threads.create_thread(worker_thread);

        getchar();
    }
View Code
复制代码

7、strand

   boost::asio::strand能够保证同一个 strand 上的处理程序按提交顺序执行:多个异步操作被绑定到同一个 strand 上时,这些异步操作会按提交顺序一个一个的执行,即使在多线程环境下也如此,这就省去了多线程的同步手段。strand通过内部的队列管理待执行的回调函数,当一个回调函数正在执行时,其他绑定到同一个strand的回调函数会被放入队列中,等待前一个回调完成后再依次执行。如下我们修改前面的程序为使用strand,通过打印结果可以看到通过strand执行的任务虽然可以保证同一时刻只执行一个任务,但并不会保证只在一个线程里执行:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
#include "boost\thread.hpp"
using namespace boost::asio;

boost::asio::io_service service;
boost::asio::io_service::strand strand_(service); //通过执行器io_service来初始化一个strand

void func(int i) {
    std::cout << std::this_thread::get_id() << ": func called, i= " << i << std::endl;
}

void worker_thread() {
    service.run();
}

int main(int argc, char** argv)
{
    for (int i = 0; i < 10; ++i)
        strand_.post(std::bind(func, i));

    boost::thread_group threads;
    for (int i = 0; i < 2; ++i)
        threads.create_thread(worker_thread);

    getchar();
}
View Code
复制代码

  我们可以对前面的异步服务端进行改造——使用工作线程和在strand中执行IO回调,如下所示。这里我们使用的是strand<io_service::executor_type>类型,它与前面的io_service::strand类型等价:

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
using namespace std;
using namespace boost::asio;


class CSession : public std::enable_shared_from_this<CSession>
{
public:
    CSession(io_service& io, boost::asio::ip::tcp::socket& _socket) :
        strand_(boost::asio::make_strand(io)),
        socket_(std::move(_socket)) {}
    void read()
    {
        auto self = shared_from_this();
        socket_.async_read_some(boost::asio::buffer(recvBuf_),
            boost::asio::bind_executor(strand_, [this, self](const boost::system::error_code& ec, std::size_t length) {
                if (ec) {
                    if (ec.value() == 2 || ec.value() == 10054)
                        std::cout << "peer close socket" << std::endl;
                    else
                        std::cout << "read error " << ec.value() << ":" << ec.message() << std::endl;
                    return;
                }
                std::cout << "recive:" << recvBuf_ << std::endl;

                write();
                read();
                }));
    }
    void write()
    {
        auto self = shared_from_this();
        boost::asio::async_write(socket_, boost::asio::buffer("hello client!"),
            boost::asio::bind_executor(strand_,
                [this, self](const boost::system::error_code& ec, std::size_t length) {
                    if (ec) {
                        std::cout << "send error " << ec.value() << ":" << ec.message() << std::endl;
                        return;
                    }
                    cout << "send msg complete" << endl;
                }
            )
        );
    }
private:
    boost::asio::ip::tcp::socket socket_;
    boost::asio::strand<boost::asio::io_service::executor_type> strand_; //与CServer中的strand共享
    char recvBuf_[1024] = { 0 };
};

class CServer
{
public:
    CServer(io_service& io, ip::tcp::endpoint& ep) :
        ios_(io)
        strand_(boost::asio::make_strand(io))
        , acceptor_(io, ep)
    {
        accept_async();
    }
    virtual ~CServer() {}
    void run() {
        // 创建工作线程
        for (std::size_t i = 0; i < 2; ++i) {
            threads_.emplace_back([this]() {
                acceptor_.get_executor().context().run(); //相当于是io_service::run();
                });
        }
        // 等待工作线程退出
        for (auto& thread : threads_) {
            if (thread.joinable()) {
                thread.join();
            }
        }
    }
private:
    void accept_async()
    {
        acceptor_.async_accept(boost::asio::bind_executor(strand_, [this](const boost::system::error_code& ec, boost::asio::ip::tcp::socket socket) {
            if (ec) {
                std::cout << "accept error " << ec.value() << ":" << ec.message() << std::endl;
                return;
            }

            cout << "a client connect, from ";
            cout << socket.remote_endpoint().address() << ": " << socket.remote_endpoint().port() << endl;
            std::make_shared<CSession>(ios_, socket)->read();
            }));
    }

    ip::tcp::acceptor acceptor_;
    std::vector<std::thread> threads_;
    boost::asio::strand<boost::asio::io_context::executor_type> strand_;
    io_service& ios_;
};

int main(int argc, char** argv)
{
    try {
        io_service ios;
        CServer srv(ios, ip::tcp::endpoint(ip::tcp::v4(), 9088));
        srv.run();

    }
    catch (std::exception& e) {
        const char* pExceptInfo = e.what();
    }
}
View Code
复制代码

  如果短时间内会有大量客户端连接的话,可以投递多个accept,如下所示。客户端发送数据频率很高的话,也可以投递多个read,需要注意的地方是,因为投递了多个read,所以当客户端关闭的时候,readHandle会被多次回调。如下所示,添加了一个业务处理类和一个数据库操作类,业务处理类中操作可以在strand工作线程中进行,数据库中操作可以在线程池中处理。如下所示,除了boost::asio::bind_executor()外,也可以通过strand.wrap()来使用strand。

  还有三种乱序的请求需要注意:①、客户端发来两次请求,第一次查询比较耗时,所以就会出现先返回第二次的查询数据。如下所示,解决方法是我们准备一个队列listReadyWrite_来保存要返回的响应数据,数据根据请求时的顺序排列,即第一次请求要响应的数据放到队列头,第二次请求要响应的数据放到队列尾。②、如果投递了多个读,所以当客户端发来两次请求,由于线程切换的原因,所以就可能出现先返回第二次请求的数据。如下所示,解决方法就是读取的回调在strand中执行,这样第一次请求的回调执行完后才会执行第二次请求的回调。③、前面所说的boost::asio::async_write()导致的发送数据乱序,如下所示,解决方法是准备一个队列listToWrite_保存要写入的数据,只在写入回调或者队列为空的时候进行写操作,这样就能保证连续调用两次boost::asio::async_write()时,第二次永远在第一次写入完成后执行。

复制代码
#ifdef _MSC_VER
#define _WIN32_WINNT 0X0501
#endif

#include "boost\asio.hpp"
#include "boost\asio\placeholders.hpp"
#include "boost\array.hpp"
using namespace std;
using namespace boost::asio;

#include <functional>
using namespace std::placeholders;

class CDatabase //数据库操作类
{
public:
    CDatabase(boost::asio::thread_pool& threadPool) : threadPool_(threadPool) {}
    void query(const std::string strSQL, std::function<void(std::shared_ptr<std::string>)> funCallback)
    {
        boost::asio::post(threadPool_, [this, strSQL, funCallback] {
            //deal with strSQL

            auto spData = make_shared<std::string>("sql Response");
            if (funCallback)
                funCallback(spData);
            });
    }
private:
    boost::asio::thread_pool& threadPool_;
};

class CBusiness //业务处理类
{
public:
    CBusiness(boost::asio::io_service& io) : ios_(io) {}
    void getData(const std::string& strRequest, std::function<void(std::shared_ptr<std::string>)> funCallback)
    {
        ios_.post([strRequest, funCallback]() {
            //deal with strRequest

            auto spData = make_shared<std::string>("business Response");
            if (funCallback)
                funCallback(spData);
            });
    };
private:
    boost::asio::io_service& ios_;
};

class CSession : public std::enable_shared_from_this<CSession> //一个连接会话
{
public:
    CSession(boost::asio::io_service& io, shared_ptr<ip::tcp::socket> _socket, CBusiness& bn, CDatabase& db) :
        strand_(io),
        socket_(_socket),
        business_(bn),
        database_(db)
    {
        for (int i = 0; i < 2; ++i) {
            aryRecvBuf_.push_back(string());
        }
    }
    virtual ~CSession()
    {
        std::cout << socket_->remote_endpoint().address() << ": " << socket_->remote_endpoint().port() << " closed connecton" << std::endl;
    }
    void read()
    {
        for (int i = 0; i < 2; ++i) { //投递多个read
            auto spSelf = shared_from_this();
            std::string& strBuf = aryRecvBuf_[i];
            strBuf.resize(1024);
            socket_->async_read_some(boost::asio::buffer(strBuf), strand_.wrap(std::bind(&CSession::readHandle, this, _1, _2, &strBuf, spSelf)));
        }
    }
    void write(std::shared_ptr<std::string> spData)
    {
        bool bEmpty = listToWrite_.empty(); //队列为空的话进行写入操作,否则将数据缓存起来
        listToWrite_.emplace_back(spData);
        if (bEmpty)
            asyncWrite();
    }
private:
    void readHandle(const boost::system::error_code& ec, std::size_t bytes_transferred,
        std::string* recvBuf, std::shared_ptr<CSession> spSelf)
    {
        if (ec) {
            if (ec.value() == 2 || ec.value() == 10054)
                std::cout << "peer close socket" << std::endl;
            else
                std::cout << "read error " << ec.value() << ":" << ec.message() << std::endl;
            return;
        }
        std::cout << "recive:" << *recvBuf << std::endl;

        listReadyWrite_.emplace_back(std::shared_ptr<std::string>());
        auto ptReadyWrite = &(listReadyWrite_.back()); //本次请求的响应数据应该写入的位置, 将位置传给写入方法
        auto& strRecv = *recvBuf;
        if (0 == strRecv.find_first_of("sql")) {
            database_.query(strRecv, [ptReadyWrite, this, spSelf](std::shared_ptr<std::string> spData) {
                postWrite(ptReadyWrite, spData, spSelf);
                });
        }
        else {
            business_.getData(strRecv, [ptReadyWrite, this, spSelf](std::shared_ptr<std::string> spData) {
                postWrite(ptReadyWrite, spData, spSelf);
                });
        }

        socket_->async_read_some(boost::asio::buffer(strRecv), strand_.wrap(std::bind(&CSession::readHandle, this, _1, _2, recvBuf, spSelf)));
    }
    void writeHandle(const boost::system::error_code& ec, std::size_t bytes_transferred,
        std::shared_ptr<std::string> spData, std::shared_ptr<CSession> spSelf)
    {
        if (ec) {
            std::cout << "send error " << ec.value() << ":" << ec.message() << std::endl;
            listToWrite_.clear();
            return;
        }
        cout << "send msg complete" << endl;

        listToWrite_.pop_front();
        if (!listToWrite_.empty())
            asyncWrite();
    }
    void asyncWrite()
    {
        auto spData = listToWrite_.front();
        auto spSelf = shared_from_this();
        boost::asio::async_write(*socket_, boost::asio::buffer(*spData),
            strand_.wrap(std::bind(&CSession::writeHandle, this, _1, _2, spData, spSelf)));
    }
    void write()
    {
        while (!listReadyWrite_.empty() && listReadyWrite_.front()) {
            write(listReadyWrite_.front());
            listReadyWrite_.pop_front();
        }
    }
    void postWrite(std::shared_ptr<std::string>* ptReadyWrite, std::shared_ptr<std::string> spData, std::shared_ptr<CSession> spSelf)
    {
        strand_.post([ptReadyWrite, spData, spSelf, this]() {
            *ptReadyWrite = spData;
            write();
            });
    }

    shared_ptr<ip::tcp::socket> socket_;
    boost::asio::io_service::strand strand_;
    std::vector <std::string> aryRecvBuf_;
    CBusiness& business_;
    CDatabase& database_;
    std::list<std::shared_ptr<std::string>> listToWrite_, listReadyWrite_;
};

class CServer
{
public:
    CServer(io_service& io, ip::tcp::endpoint& ep) :
        ios_(io),
        strand_(io),
        acceptor_(io, ep),
        business_(io),
        database_(threadPool_)
    {
        for (std::size_t i = 0; i < 2; ++i) { //投递多个accept
            acceptAsync();
        }
    }
    virtual ~CServer() {}
    void run() {
        // 创建工作线程
        for (std::size_t i = 0; i < 2; ++i) {
            threads_.emplace_back([this]() {
                acceptor_.get_executor().context().run(); //相当于是io_service::run();
                });
        }
        // 等待工作线程退出
        for (auto& thread : threads_) {
            if (thread.joinable()) {
                thread.join();
            }
        }
        //等待线程池退出
        threadPool_.join();
    }
private:
    void acceptAsync()
    {
        shared_ptr<ip::tcp::socket> spSock(new ip::tcp::socket(ios_));
        acceptor_.async_accept(*spSock, strand_.wrap(std::bind(&CServer::acceptHandle, this, _1, spSock)));
    }
    void acceptHandle(const boost::system::error_code& ec, shared_ptr<ip::tcp::socket> spSock)
    {
        if (ec) {
            std::cout << "accept error " << ec.value() << ":" << ec.message() << std::endl;
            return;
        }

        cout << "a client connect, from ";
        cout << spSock->remote_endpoint().address() << ": " << spSock->remote_endpoint().port() << endl;
        std::make_shared<CSession>(ios_, spSock, business_, database_)->read();

        acceptAsync();
    }

    boost::asio::io_service& ios_;
    ip::tcp::acceptor acceptor_;
    std::vector<std::thread> threads_;
    boost::asio::thread_pool threadPool_{ 4 };
    boost::asio::io_service::strand strand_;
    CBusiness business_;
    CDatabase database_;
};

int main(int argc, char** argv)
{

    try {
        io_service ios;
        CServer srv(ios, ip::tcp::endpoint(ip::tcp::v4(), 9088));
        srv.run();

    }
    catch (std::exception& e) {
        const char* pExceptInfo = e.what();
    }
}
View Code
复制代码

 

  

 

posted on   整鬼专家  阅读(1467)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示