반응형


cmake_minimum_required(VERSION 3.14)
project(timer_example CXX)
set(CMAKE_CXX_STANDARD 20)

find_package(sdbus-c++ REQUIRED)

find_package(PkgConfig REQUIRED)
pkg_check_modules(SYSTEMD REQUIRED libsystemd)

add_executable(server server.cpp)
target_include_directories(server PRIVATE ${SYSTEMD_INCLUDE_DIRS})
target_link_libraries(server
    SDBusCpp::sdbus-c++
    ${SYSTEMD_LIBRARIES}
)



#include <sdbus-c++/sdbus-c++.h>
#include <systemd/sd-event.h>
#include <iostream>
#include <csignal>

//------------------------------------------------------------
// 전역 sd_event (signal 핸들러에서 루프 종료용)
//------------------------------------------------------------
static sd_event* g_event = nullptr;

static void onSignal(int)
{
    if (g_event)
        sd_event_exit(g_event, 0);
}

//------------------------------------------------------------
// TimerService: connection을 주입받아 타이머 관리
//------------------------------------------------------------
class TimerService
{
public:
    explicit TimerService(sdbus::IConnection& conn)
        : mConn_(conn)
    {}

    ~TimerService()
    {
        stopTimer();
    }

    void startTimer()
    {
        // 기존 타이머 제거
        stopTimer();

        // ✅ getSdEventLoop()로 sd_event 핸들 가져오기
        sd_event* event = mConn_.getSdEventLoop();
        if (!event) {
            std::cerr << "[Timer] sd_event loop not available\n";
            return;
        }

        uint64_t now{};
        sd_event_now(event, CLOCK_MONOTONIC, &now);

        int r = sd_event_add_time(
            event,
            &timerSource_,
            CLOCK_MONOTONIC,
            now + 5 * 1'000'000,  // 5초 후 (microsecond)
            0,
            onTimer,
            this
        );

        if (r < 0) {
            std::cerr << "[Timer] 등록 실패: " << strerror(-r) << "\n";
            return;
        }

        std::cout << "[Timer] 시작 (5초 후 onTimerFired 호출)\n";
    }

    void stopTimer()
    {
        if (!timerSource_) return;

        sd_event_source_unref(timerSource_);
        timerSource_ = nullptr;
        std::cout << "[Timer] 해제\n";
    }

private:
    void onTimerFired()
    {
        std::cout << "[Timer] 5초 경과 → onTimerFired 실행!\n";

        // timerSource_ 정리 (단발성)
        stopTimer();

        // 원하는 작업 수행
        std::cout << "[Timer] 작업 완료\n";
    }

    static int onTimer(sd_event_source* source,
                       uint64_t         usec,
                       void*            userdata)
    {
        auto* self = static_cast<TimerService*>(userdata);
        self->onTimerFired();
        return 0;
    }

    sdbus::IConnection& mConn_;
    sd_event_source*    timerSource_{nullptr};
};

//------------------------------------------------------------
// main
//------------------------------------------------------------
int main()
{
    // 1. sd_event 생성
    sd_event* event{};
    int r = sd_event_default(&event);
    if (r < 0) {
        std::cerr << "sd_event_default failed: " << strerror(-r) << "\n";
        return 1;
    }
    g_event = event;

    // 2. SIGINT/SIGTERM 처리
    std::signal(SIGINT,  onSignal);
    std::signal(SIGTERM, onSignal);

    // 3. D-Bus connection 생성
    auto conn = sdbus::createSessionBusConnection(
        sdbus::ServiceName{"org.example.Calculator"}
    );

    // 4. ✅ sdbus connection을 sd_event에 attach
    conn->attachSdEventLoop(event);

    // 5. TimerService 생성 및 타이머 시작
    //    attachSdEventLoop() 이후에 getSdEventLoop()가 유효한 포인터 반환
    TimerService timerService{*conn};
    timerService.startTimer();

    std::cout << "[Server] Running... (Ctrl+C to stop)\n";

    // 6. ✅ sd_event 루프 실행 (sdbus + 타이머 통합)
    r = sd_event_loop(event);
    if (r < 0) {
        std::cerr << "sd_event_loop failed: " << strerror(-r) << "\n";
    }

    // 7. 정리
    conn->detachSdEventLoop();
    sd_event_unref(event);
    g_event = nullptr;

    std::cout << "[Server] Stopped\n";
    return 0;
}






# 빌드
mkdir build && cd build
cmake ..
make -j$(nproc)

# 실행
./server






#include <sdbus-c++/sdbus-c++.h>
#include <systemd/sd-event.h>
#include <iostream>

class TimerService
{
public:
    // ✅ 생성자에서 connection 주입
    explicit TimerService(sdbus::IConnection& conn)
        : mConnection_(conn)
    {}

    ~TimerService()
    {
        stopTimer();
    }

    void startTimer()
    {
        stopTimer();

        // ✅ 주입받은 connection으로 getEventLoopHandle() 호출
        sd_event* event = static_cast<sd_event*>(
            mConnection_.getEventLoopHandle()
        );

        uint64_t now{};
        sd_event_now(event, CLOCK_MONOTONIC, &now);

        int r = sd_event_add_time(
            event,
            &timerSource_,
            CLOCK_MONOTONIC,
            now + 5 * 1'000'000,
            0,
            onTimer,
            this
        );

        if (r < 0) {
            std::cerr << "[Timer] 등록 실패: " << strerror(-r) << "\n";
            return;
        }

        std::cout << "[Timer] 시작 (5초 후 실행)\n";
    }

    void stopTimer()
    {
        if (!timerSource_) return;

        sd_event_source_unref(timerSource_);
        timerSource_ = nullptr;
        std::cout << "[Timer] 해제\n";
    }

private:
    void onTimerFired()
    {
        std::cout << "[Timer] 5초 경과 → onTimerFired 실행!\n";

        stopTimer();

        // 원하는 작업 수행
    }

    static int onTimer(sd_event_source* source,
                       uint64_t         usec,
                       void*            userdata)
    {
        auto* self = static_cast<TimerService*>(userdata);
        self->onTimerFired();
        return 0;
    }

    sdbus::IConnection& mConnection_;       // ✅ 주입받은 connection
    sd_event_source*    timerSource_{nullptr};
};



int main()
{
    auto conn = sdbus::createSessionBusConnection(
        sdbus::ServiceName{"org.example.Calculator"}
    );

    // ✅ 같은 connection 주입
    TimerService timerService{*conn};
    timerService.startTimer();

    conn->enterEventLoop();
    return 0;
}





타이머해제방법
// ✅ 방법 1: SD_EVENT_OFF로 비활성화 (source는 유지)
sd_event_source_set_enabled(timerSource_, SD_EVENT_OFF);

// ✅ 방법 2: unref로 완전 제거
sd_event_source_unref(timerSource_);
timerSource_ = nullptr;

class CalculatorImpl
    : public sdbus::AdaptorInterfaces<org::example::Calculator_adaptor>
{
public:
    CalculatorImpl(sdbus::IConnection& conn, sdbus::ObjectPath path)
        : AdaptorInterfaces(conn, std::move(path))
    {
        registerAdaptor();
        setupTimer();
    }

    ~CalculatorImpl()
    {
        stopTimer();
        unregisterAdaptor();
    }

protected:
    int32_t Add(const int32_t& a, const int32_t& b) override
    {
        // Add 호출 시 타이머 시작
        startTimer();
        return a + b;
    }

    int32_t Sub(const int32_t& a, const int32_t& b) override
    {
        // Sub 호출 시 타이머 해제
        stopTimer();
        return a - b;
    }

private:
    void setupTimer()
    {
        sd_event* event = static_cast<sd_event*>(
            getObject().getConnection().getEventLoopHandle()
        );

        uint64_t now{};
        sd_event_now(event, CLOCK_MONOTONIC, &now);

        sd_event_add_time(
            event,
            &timerSource_,
            CLOCK_MONOTONIC,
            now + 5 * 1'000'000,
            0,
            onTimer,
            this
        );

        // ✅ 처음엔 비활성화 상태로 등록
        sd_event_source_set_enabled(timerSource_, SD_EVENT_OFF);
        std::cout << "[Timer] Registered (inactive)\n";
    }

    // ✅ 타이머 시작
    void startTimer()
    {
        if (!timerSource_) return;

        sd_event* event = static_cast<sd_event*>(
            getObject().getConnection().getEventLoopHandle()
        );

        // 현재 시간 기준으로 재설정
        uint64_t now{};
        sd_event_now(event, CLOCK_MONOTONIC, &now);

        sd_event_source_set_time(timerSource_, now + 5 * 1'000'000);
        sd_event_source_set_enabled(timerSource_, SD_EVENT_ONESHOT);
        std::cout << "[Timer] Started\n";
    }

    // ✅ 타이머 해제
    void stopTimer()
    {
        if (!timerSource_) return;

        // 방법 1: 비활성화만 (재사용 가능)
        sd_event_source_set_enabled(timerSource_, SD_EVENT_OFF);
        std::cout << "[Timer] Stopped\n";

        // 방법 2: 완전 제거 (재사용 불가)
        // sd_event_source_unref(timerSource_);
        // timerSource_ = nullptr;
    }

    static int onTimer(sd_event_source* source,
                       uint64_t         usec,
                       void*            userdata)
    {
        auto* self = static_cast<CalculatorImpl*>(userdata);

        std::cout << "[Timer] Fired!\n";

        // 반복 타이머
        sd_event_source_set_time(source, usec + 5 * 1'000'000);
        sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);

        return 0;
    }

    sd_event_source* timerSource_{nullptr};
};

SD_EVENT_OFF vs unref 비교
항목
SD_EVENT_OFF
unref
source 메모리
유지
해제
재시작 가능
✅ set_enabled(ONESHOT)
❌ 재등록 필요
적합한 상황
일시 중지 후 재사용
완전히 제거
일시적으로 멈췄다가 다시 쓸 거라면 SD_EVENT_OFF, 완전히 제거할 거라면 unref 를 사용하면 됩니다.








void setupTimer()
{
    sd_event* event = static_cast<sd_event*>(
        getObject().getConnection().getEventLoopHandle()
    );

    uint64_t now{};
    sd_event_now(event, CLOCK_MONOTONIC, &now);

    // ✅ 람다를 heap에 저장 후 userdata로 전달
    auto* callback = new std::function<void(sd_event_source*, uint64_t)>(
        [this](sd_event_source* source, uint64_t usec) {
            std::cout << "[Timer] Fired!\n";

            // 원하는 작업
            emitSubResult(42);

            // 반복 타이머
            sd_event_source_set_time(source, usec + 5 * 1'000'000);
            sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
        }
    );

    sd_event_add_time(
        event,
        &timerSource_,
        CLOCK_MONOTONIC,
        now + 5 * 1'000'000,
        0,
        // ✅ wrapper: userdata를 std::function으로 캐스팅해서 호출
        [](sd_event_source* source, uint64_t usec, void* userdata) -> int {
            auto* cb = static_cast<std::function<void(sd_event_source*, uint64_t)>*>(userdata);
            (*cb)(source, usec);
            return 0;
        },
        callback  // userdata
    );

    // ✅ source 소멸 시 람다 자동 정리
    sd_event_source_set_userdata(timerSource_, callback);
    sd_event_source_set_destroy_callback(timerSource_,
        [](void* userdata) {
            auto* cb = static_cast<std::function<void(sd_event_source*, uint64_t)>*>(userdata);
            delete cb;
        }
    );
}


더깔끔하게ㅜ핼퍼로

// TimerHelper.h
#pragma once
#include <systemd/sd-event.h>
#include <functional>
#include <stdexcept>

inline sd_event_source* addTimer(
    sd_event*  event,
    uint64_t   intervalUs,  // microsecond
    std::function<void(sd_event_source*, uint64_t)> callback)
{
    uint64_t now{};
    sd_event_now(event, CLOCK_MONOTONIC, &now);

    auto* cb = new std::function<void(sd_event_source*, uint64_t)>(
        std::move(callback)
    );

    sd_event_source* source{nullptr};
    int r = sd_event_add_time(
        event,
        &source,
        CLOCK_MONOTONIC,
        now + intervalUs,
        0,
        [](sd_event_source* s, uint64_t usec, void* userdata) -> int {
            auto* cb = static_cast<
                std::function<void(sd_event_source*, uint64_t)>*>(userdata);
            (*cb)(s, usec);
            return 0;
        },
        cb
    );

    if (r < 0) {
        delete cb;
        throw std::runtime_error("Failed to add timer");
    }

    sd_event_source_set_destroy_callback(source,
        [](void* userdata) {
            delete static_cast<
                std::function<void(sd_event_source*, uint64_t)>*>(userdata);
        }
    );

    return source;
}

헬퍼함수 사용예시

#include "TimerHelper.h"

void setupTimer()
{
    sd_event* event = static_cast<sd_event*>(
        getObject().getConnection().getEventLoopHandle()
    );

    // ✅ static 함수, this 전달 없이 깔끔하게 사용
    timerSource_ = addTimer(
        event,
        5 * 1'000'000,  // 5초
        [this](sd_event_source* source, uint64_t usec) {
            std::cout << "[Timer] Fired!\n";

            emitSubResult(42);

            // 반복
            sd_event_source_set_time(source, usec + 5 * 1'000'000);
            sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
        }
    );
}

반응형