반응형

C++ ACE와 Boost Asio의 Proactor 패턴 비교

C++에서 비동기 TCP 서버를 구현할 때 많이 사용되는 ACEBoost Asio는 각기 다른 패턴을 지원하는데, 특히 Proactor 패턴 구현에서 차이가 두드러집니다. 아래에서는 두 라이브러리의 Proactor 패턴 차이점과 간단한 예제 코드를 비교하여 살펴보겠습니다.


주요 차이점

  1. 기본 구조 및 설계 철학

    • ACE Proactor: ACE는 Proactor 패턴을 완벽하게 구현합니다. 주로 비동기 작업이 완료 이벤트를 발생시킬 때 핸들러를 실행하며, 운영체제의 비동기 API를 활용하여 작업 완료 후 이벤트가 트리거되는 구조입니다.
    • Boost Asio: Boost Asio는 Reactor 패턴을 기본으로 동작하며, 운영체제의 비동기 I/O API와 결합하여 Proactor 패턴과 유사하게 비동기 처리를 지원합니다. Boost Asio는 작업이 등록되면 핸들러를 바로 호출하는 방식으로 동작하는 경우가 많아 ACE의 Proactor와 차이가 있습니다.
  2. OS 종속성

    • ACE는 Windows에서 IOCP, Unix에서 aio_* 함수 등 특정 OS의 비동기 API에 의존하여 Proactor 패턴을 구현합니다.
    • Boost Asio는 플랫폼 독립적으로 설계되어 있어 운영체제에 맞는 비동기 API를 추상화하여 제공됩니다.

ACE Proactor 예제 코드

아래 예제에서는 ACE의 Proactor를 사용하여 비동기 TCP 서버를 구현하는 방식입니다.

cpp

#include <ace/Proactor.h>
#include <ace/Asynch_IO.h>
#include <ace/SOCK_Acceptor.h>
#include <ace/INET_Addr.h>

class MyHandler : public ACE_Service_Handler {
public:
    void open(ACE_HANDLE new_handle, ACE_Message_Block &message_block) override {
        ACE_SOCK_Stream peer(new_handle);
        ACE_Asynch_Read_Stream asynch_reader_;
        asynch_reader_.open(*this, new_handle);
        
        ACE_Message_Block *mb;
        ACE_NEW_NORETURN(mb, ACE_Message_Block(4096));
        asynch_reader_.read(*mb, mb->size());
    }

    void handle_read_stream(const ACE_Asynch_Read_Stream::Result &result) override {
        if (!result.success() || result.bytes_transferred() == 0) {
            delete this;
            return;
        }
        result.message_block().rd_ptr()[result.bytes_transferred()] = '\0';
        ACE_DEBUG((LM_DEBUG, ACE_TEXT("Data received: %s\n"), result.message_block().rd_ptr()));
    }
};

int main() {
    ACE_INET_Addr listen_addr(8080);
    ACE_Asynch_Acceptor<MyHandler> acceptor;
    acceptor.open(listen_addr);
    ACE_Proactor::instance()->proactor_run_event_loop();
    return 0;
}

Boost Asio에서의 Proactor 방식 구현 예제

Boost Asio는 Proactor 패턴을 직접 지원하지 않지만, 비동기 작업 등록 및 콜백을 통해 비슷한 방식으로 구현할 수 있습니다.

cpp

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

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

class Session : public std::enable_shared_from_this<Session> {
public:
    explicit Session(tcp::socket socket) : socket_(std::move(socket)) {}

    void start() { read(); }

private:
    void read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    std::cout << "Data received: " << data_ << std::endl;
                    write(length);
                }
            });
    }

    void write(std::size_t length) {
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    read();
                }
            });
    }

    tcp::socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};

class Server {
public:
    Server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        accept();
    }

private:
    void accept() {
        acceptor_.async_accept([this](boost::system::error_code ec, tcp::socket socket) {
            if (!ec) {
                std::make_shared<Session>(std::move(socket))->start();
            }
            accept();
        });
    }

    tcp::acceptor acceptor_;
};

int main() {
    try {
        boost::asio::io_context io_context;
        Server server(io_context, 8080);
        io_context.run();
    }
    catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
    return 0;
}

요약

  • ACE는 Proactor 패턴을 완전하게 구현하여 OS의 비동기 I/O API에 따라 동작합니다. Proactor 패턴을 완전하게 구현하고자 할 때는 ACE가 더 적합할 수 있습니다.
  • Boost Asio는 플랫폼 독립적이며 Reactor 패턴을 사용하지만, 운영체제의 비동기 API와 결합하여 Proactor와 유사하게 동작하게 할 수 있습니다.
728x90
반응형

+ Recent posts