読者です 読者をやめる 読者になる 読者になる

Boost.Asio ゲームループで非同期操作を行う

C++

ゲームループと書いていますが、要はタイマーによる定期実行プログラムです。


io_service::run()の使いどころで嵌りました。

async系関数を次々記述していき、最後にio_service::run()を使用することで、非同期処理が終わるまで待機することになるので、ゲームループでフレームごとに通信を行う場合には、フレームの最初でio_service::reset()を呼んで非同期処理をリセットし、フレームの最後でio_service::run()を呼び、非同期処理が終わるまで待機することになります。

void update()
{
    io_service.reset();

    async_proc();

    io_service.run();
}

以下、サンプルコード:

クライアントから"request\n"を送り、サーバーがそれを受け、"response\n"を返してクライアントがそれを受け取る、を繰り返すプログラムです。


クライアント:

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

namespace asio = boost::asio;
namespace ip = asio::ip;

class Game {
    asio::io_service& io_service_;
    ip::tcp::socket socket_;

    asio::streambuf send_buffer_;
    asio::streambuf receive_buffer_;

public:
    Game(asio::io_service& io_service)
        : io_service_(io_service),
          socket_(io_service)
    {
        socket_.connect(ip::tcp::endpoint(ip::address::from_string("127.0.0.1"), 31400));
    }

    void update()
    {
        io_service_.reset();

        async_send();
        async_receive();

        io_service_.run();
    }

private:
    void async_send()
    {
        const std::string request = "request\n";

        std::ostream os(&send_buffer_);
        os << request;

        asio::async_write(socket_, send_buffer_.data(), boost::bind(&Game::send_end, this, _1));
    }

    void send_end(const boost::system::error_code& error)
    {
        std::cout << "request : " << asio::buffer_cast<const char*>(send_buffer_.data()) << std::endl;
        send_buffer_.consume(send_buffer_.size());
    }

    void async_receive()
    {
        asio::async_read_until(socket_, receive_buffer_, '\n', boost::bind(&Game::receive_end, this, _1));
    }

    void receive_end(const boost::system::error_code& error)
    {
        std::cout << "response : " << asio::buffer_cast<const char*>(receive_buffer_.data()) << std::endl;
        receive_buffer_.consume(receive_buffer_.size());
    }
};

int main()
{
    asio::io_service io_service;

    Game game(io_service);

    for (;;) { // てきとーなタイマー
        game.update();

        boost::this_thread::sleep(boost::posix_time::seconds(1));
    }
}


サーバー:

#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

namespace asio = boost::asio;
namespace ip = asio::ip;

class server {
    asio::io_service& io_service_;
    ip::tcp::socket socket_;

    asio::streambuf request_buffer_;
    asio::streambuf response_buffer_;

public:
    server(asio::io_service& io_service)
        : io_service_(io_service),
        socket_(io_service) {}

    ip::tcp::socket& get_socket()
    {
        return socket_;
    }

    void start()
    {
        receive_start();
    }

    void receive_start()
    {
        asio::async_read_until(socket_, request_buffer_, '\n', boost::bind(&server::receive_end, this, _1));
    }

    void receive_end(const boost::system::error_code& error)
    {
        if (error) {
            std::cout << "receive error : "  << error.message() << std::endl;
        }
        else {
            std::cout << "request : " << asio::buffer_cast<const char*>(request_buffer_.data()) << std::endl;

            request_buffer_.consume(request_buffer_.size());

            const std::string response = "response\n";

            std::ostream os(&response_buffer_);
            os << response;

            asio::async_write(socket_, response_buffer_.data(), boost::bind(&server::send_end, this, _1));
        }
    }

    void send_end(const boost::system::error_code& error)
    {
        if (error) {
            std::cout << "send error : " << error.message() << std::endl;
        }
        else {
            std::cout << "response : " << asio::buffer_cast<const char*>(response_buffer_.data()) << std::endl;
        }

        response_buffer_.consume(response_buffer_.size());

        receive_start();
    }
};

int main()
{
    asio::io_service io_service;
    server connection(io_service);

    ip::tcp::acceptor acc(io_service, ip::tcp::endpoint(ip::tcp::v4(), 31400));
    acc.accept(connection.get_socket());

    connection.start();
    io_service.run();

    for (;;) {}
}