【网络编程】asio

asio 定时器的基本使用

asio 的定时器可以提供同步或异步定时事件,我们通常用定时器来处理客户端连接超时的问题,比如服务器就设置一个超时时间,客户端连接成功之后,若没有发送消息给服务器的时间大于超时时间,则认为客户端连接已经断开了,可以关闭这个连接。

asio 定时器的用法比较简单,在 code4 目录下新建一个 code1.cpp 文件:

#include <iostream>
#include <boost/asio.hpp>

int g_count = 0;

void reset_timer(boost::asio::deadline_timer& timer){
    timer.expires_from_now(boost::posix_time::seconds(1));// 重新设置超时时间为 1s
    timer.async_wait([&timer](const boost::system::error_code& ec){ // async_wait() 不会阻塞程序。超时后会调用 lambda 表达式。
        if(ec){
            std::cout<<ec.message()<<std::endl;
            return;
        }

        if(g_count==10){
            return;
        }

        g_count++;

        std::cout<<"timer event"<<std::endl;
        reset_timer(timer);
    });
}

int main()
{
    try{
        boost::asio::io_service io_service;

        boost::asio::deadline_timer timer(io_service);// 初始化 timer,让 timer 作用于上条语句中定义的 io_service。
        timer.expires_from_now(boost::posix_time::seconds(5)); // 超过 5s 后退出,从执行完这句语句开始计时。
        std::cout << "Starting synchronous wait\n";
        timer.wait(); // wait() 函数的作用为阻塞等待。程序会一直卡在本条语句,直到定时器超时。
        std::cout << "Finished synchronous wait\n";

        reset_timer(timer);// 调用上面定义的 reset_timer() 函数。
        io_service.run();// run 函数的作用是阻塞等待 io_service 上所有的时间执行完毕后退出。
                        //在程序执行到这一句的时候,io_service 上只剩下 timer.async_wait() 没有执行完毕,所以此处 run() 的作用为阻塞等待超时。超时之后执行作为 async_wait() 参数的 lambda 表达式。


        std::cout << "Finished asynchronous wait\n";
    }
    catch (std::exception& e){
        std::cout << "Exception: " << e.what() << "\n";
    }

    return 0;
}

编译和运行代码:在 build 目录下执行

g++ ../code1.cpp -std=c++11 -o code1  -lboost_system && ./code1

输出结果:

Starting synchronous wait
Finished synchronous wait
timer event
timer event
timer event
timer event
timer event
timer event
timer event
timer event
timer event
timer event
Finished asynchronous wait

asio::deadline_timer 需要由一个 io_service 对象构造, io_service 可以认为是一个网络事件的反应器,任何网络事件都由它驱动和回调。

timer.expires_from_now(boost::posix_time::seconds(5)) 设置定时器超时时间为 5 秒, timer.wait 则是同步方式等待定时器事件,等待 5 秒后就会打印 Finished synchronous wait 。

asio::deadline_timer 的异步事件则需要调用异步接口 timer.async_wait 。调用 timer.async_wait(func) 并不会导致程序阻塞,也不会立刻执行函数 func ,需要等到运行完 io_service.run(); 语句之后, 回调函数 func 才会被执行。若调用 io_service.run(); 和调用 timer.async_wait(func) 的时间差小于 timer.expires_from_now() 设置的时间,则io_service.run(); 会阻塞等到超时才会调用 func 。

asio 服务端编程

服务器端通过监听特定端口等待客户端连接,asio 的 acceptor 对象提供了同步和异步监听的接口。

在 code4 目录下新建一个 code2.cpp 文件:

#include <iostream>
#include <memory>
#include <vector>
#include <thread>
#include <string>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class server{
public:
    server(boost::asio::io_service& ios, short port) : ios_(ios),
    acceptor_(ios, tcp::endpoint(tcp::v4(), port)) {
        do_accept();
    }

    void do_accept()
    {
        std::cout<<"begin to listen and accept"<<std::endl;
        auto socket = std::make_shared<tcp::socket>(ios_); // 使用智能指针保证安全回调。
        acceptor_.async_accept(*socket, [this, socket](boost::system::error_code ec)
                {
                    if (ec){ // 如果发送错误,则打印错误信息,并关闭套接字。
                        std::cout<<ec.message()<<std::endl;
                        socket->close();
                    }else{ // 没有错误,将套接字存入套接字队列中。
                        conns_.push_back(socket);
                        std::cout<<"new connection coming"<<std::endl;
                    }

                   do_accept(); // 无论是否发送错误,都要自己调用自己,继续监听端口。
                });
    }

private:
    boost::asio::io_service& ios_;
    tcp::acceptor acceptor_;
    std::vector<std::shared_ptr<tcp::socket>> conns_;
};

int main() {
    boost::asio::io_service ios;

    server s(ios, 9000); //实例化一个服务器类,监听端口 9000

    boost::asio::deadline_timer timer(ios);
    timer.expires_from_now(boost::posix_time::seconds(5)); // 设置超时时间为 5s。

    timer.async_wait([&timer,&ios](const boost::system::error_code& ec){ // 异步等待超时
    if(ec){
        std::cout<<ec.message()<<std::endl;
        return;
    }

    ios.stop(); // 到 5s 之后手动停止 io_service。因为 server.do_accept() 一直调用自己,所以 io_service 上一直会有事件发生,不会自动停止。
    std::cout<<"server stoped"<<std::endl;
    });

    ios.run();


    return 0;
}

上面的代码中我们监听了 9000 端口,acceptor_.async_accept 接受 socket 和一个回调函数作为参数,这个回调函数就是异步事件,当有客户端连接过来的时候就会进入这个回调函数。

连接成功之后就把这个连接 socket 放到 vector 中,打印一条消息,然后继续监听和等待新的连接过来。

代码中还使用了 timer.expires_from_now(boost::posix_time::seconds(5)); 定时,使得服务器在 5 秒之后自动退出。

接下来我们写一个客户端来连接这个服务器。

asio 客户端编程

客户端通过 connect 函数接入服务器。

在 code4 目录下新建一个 code3.cpp 文件:

#include <iostream>
#include <chrono>
#include <thread>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main() {
    try{
        boost::asio::io_service io_service;

        tcp::socket s(io_service);
        tcp::resolver resolver(io_service); // 存储服务器的地址信息(IP,端口)。

        boost::asio::connect(s, resolver.resolve({"127.0.0.1", "9000"})); // 连接服务器,执行 TCP 三次握手的过程。
        std::this_thread::sleep_for(std::chrono::seconds(2)); // 本线程睡眠两秒

    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

调用 asio::connect 方法,传入 IP 和端口就可以连接服务器了,如果连接出错这个接口就会抛异常,因此我们需要通过 try-catch 来捕捉异常。

接下来我们测试一下服务器和客户端是否可以进行通信。

编译客户端和服务端

在 build 目录下执行

g++ ../code2.cpp -std=c++11 -o code2  -lboost_system -lpthread && g++ ../code3.cpp -std=c++11 -o code3  -lboost_system -lpthread

运行服务端和客户端代码

& 表示让 ./code2 在后台运行。

./code2 & ./code3

输出结果如下图所示。

asio通信过程

 

错误处理

网络连接或者收发消息过程中经常会出现网络错误,如何处理这些网络错误呢? asio 提供了几种错误处理的方法,一种是在回调函数中传入错误码,如果错误码不为空则说明有网络错误,这时候我们可以根据需要记录错误码和错误原因等信息,然后关闭 socket ;另外一种方法就是抛异常,我们在调用接口时捕获异常,当有异常发生时我们就可以做错误处理了。

以下是服务端代码关于异常处理的部分:

void do_accept()
{
    std::cout<<"begin to listen and accept"<<std::endl;
    auto socket = std::make_shared<tcp::socket>(ios_); // 使用智能指针保证安全回调。
    acceptor_.async_accept(*socket, [this, socket](boost::system::error_code ec)
            {
                if (ec){ // 如果发送错误,则打印错误信息,并关闭套接字。
                    std::cout<<ec.message()<<std::endl;
                    socket->close();
                }else{ // 没有错误,将套接字存入套接字队列中。
                    conns_.push_back(socket);
                    std::cout<<"new connection coming"<<std::endl;
                }

                do_accept(); // 无论是否发送错误,都要自己调用自己,继续监听端口。
            });
}

注意看这里的代码:

if (ec){
    std::cout<<ec.message()<<std::endl;
    socket->close();
}

这里对 error code 做了判断,如果不为空就说明存在网络错误。

以下是客户端代码关于异常处理的部分:

try{
    boost::asio::io_service io_service;

    tcp::socket s(io_service);
    tcp::resolver resolver(io_service); // 存储服务器的地址信息(IP,端口)。

    boost::asio::connect(s, resolver.resolve({"127.0.0.1", "9000"})); // 连接服务器,执行 TCP 三次握手的过程。
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 本线程睡眠两秒

}
catch (std::exception& e)
{
    std::cerr << "Exception: " << e.what() << "\n";
}

如果服务器关闭,则这时候 connect 就会失败,然后就会抛异常,我们在外面就能记录错误原因了。

猜你喜欢

转载自blog.csdn.net/bandaoyu/article/details/107403827