네트워크 통신/Boost

Boost.asio I/O 비동기 연산 취소하기

마달랭 2024. 11. 14. 15:18
반응형

개요

비동기 연산을 시작하도록 한 후에 아직 완료되지 않았지만 종료하고 싶을 수가 있다.

더 이상 해당 작업이 필요 없거나, 도중에 다른 방향으로 연산을 하고 싶을 때 등등

클라이언트 입장에선 시작은 했지만 아직 끝나지 않은 연산을 종료할 수 있다면 좋은 기능이다.

네트워크 통신 연산은 예측하지 못할 정도로 긴 시간이 걸릴 수도 있으므로 취소하는 기능을 제공하는 것은 중요하다.

 

 

cancel()

일단 코드를 먼저 작성하고 리뷰를 진행한다.

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

using namespace boost;

int main()
{
    std::string raw_ip_address = "127.0.0.1";
    unsigned short port_num = 3333;

    try {
        asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num);

        // 최신 Boost에서는 io_service 대신 io_context 사용
        asio::io_context ios;

        std::shared_ptr<asio::ip::tcp::socket> sock(
            new asio::ip::tcp::socket(ios, ep.protocol()));

        // 비동기 연결 요청
        sock->async_connect(ep,
            [sock](const boost::system::error_code& ec) // 콜백 함수를 람다로 넣었음
            {
                if (ec.value() != 0) {
                    if (ec == asio::error::operation_aborted) {
                        std::cout << "Operation cancelled!" << std::endl;
                    }
                    else {
                        std::cout << "Error occured! "
                            << "Error code = " << ec.value()
                            << ". Message: " << ec.message() << std::endl;
                    }
                    return;
                }
                // 연결이 완료되었을 때, 소켓을 사용하여 원격 애플리케이션과 통신할 수 있음
            });

        // 비동기 작업이 완료되면 콜백을 실행할 worker_thread 생성
        std::thread worker_thread([&ios]() {
            try {
                ios.run();  // 비동기 작업 처리
            }
            catch (system::system_error& e) {
                std::cout << "Error occured! "
                    << "Error code = " << e.code()
                    << ". Message: " << e.what() << std::endl;
            }
            });

        // 임의로 지연을 주기 위해 2초 대기
        std::this_thread::sleep_for(std::chrono::seconds(2));

        // 비동기 작업 취소
        sock->cancel();

        // worker_thread가 종료될 때까지 대기
        worker_thread.join();
    }
    catch (system::system_error& e) {
        std::cout << "Error occured! Error code = " << e.code()
            << ". Message: " << e.what() << std::endl;

        return e.code().value();
    }

    return 0;
}

 

늘 그랬듯 서버의 IP 주소와 포트 번호를 안다고 가정하고 종료점 및 소켓을 생성하여 비동기 연결 요청을 한다.

async_connect 메서드를 통해 생성한 종료점 ep에 비동기 연결 요청을 하는 것이다.

이때 뒤에 연결 실패 시 ec를 출력하는 부분은 람다함수로 콜백 함수를 구현한 것이다.

만약 해당 메서드가 반복하여 사용될 것 같다면 별도의 함수로 정의해 주면 된다.

 

연결이 완료되었다면 이제 소켓을 사용해 서버와 통신할 수 있다.

여러가지 원하는 비동기 작업 후 콜백을 실행하기 위해 쓰레드를 사용해 준다.

스레드를 생성해 주고 ios.run()을 실행해 준다, 이때 에러가 발생해 준다면 catch문이 실행된다.

스레드를 만들고 난 후 중심 스레드는 2초간 대기 상태에 들어간다, 그 동안 연결 연산이 진행된다.

sleep을 해주지 않는다면 비동기 연산 작업이 시작되고 곧 바로 cancel메서드가 호출되면 연산 자체가 되지 않는다.

현재 예제로는 비동기 연산을 시작하고 2초 이상 걸리는 작업이 있다면 취소하는 것으로 볼 수 있다.

 

ios.run()을 스레드로 처리하는 이유는 ios.run()은 블로킹 함수이기 때문이다.

해당 함수가 호출되면 내부 이벤트가 모두 종료될 때 까지 다른 작업을 처리할 수 없다.

따라서 비동기 작업을 처리하며 다른 코드가 계속 실행되게 하려면 ios.run()을 별도의 쓰레드에서 실행시켜야 한다.

 

위 처럼 비동기 연결 중 cancel메서드를 사용하면 사용자가 원하는 시점에 비동기 작업을 종료할 수 있다.

728x90
반응형