콘텐츠로 이동
무료 평가 시작

pdcomm::opcua::MfcClientWorker

클래스

네이티브 OPC UA 클라이언트를 소유하는 일회용(one-shot) MFC 워커 스레드입니다.

class MfcClientWorker

CWinThread가 connect, activation, begin, poll, take, close, release를 순서대로 실행합니다. 연결하고, 세션을 활성화하고(익명 또는 사용자 이름으로), Ready를 보고한 뒤, 제출된 요청을 순서대로 보내고 응답마다 Completed를 보고합니다. RequestStop() 또는 치명적 오류 뒤에 종료되며, 항상 마지막에 Stopped를 보고합니다.

애플리케이션이 지켜야 할 규칙:

  • Wait()가 반환될 때까지 객체를 소유합니다. 워커 자신의 스레드에서 객체를 소멸하면 안 됩니다.
  • 알림 메시지는 수신 창의 스레드에서 처리하고 그 스레드에서 Drain()을 호출합니다. 어떤 창도 꺼내지 않은 이벤트는 워커에 남아 있다가 워커와 함께 소멸합니다.
  • Submit()과 RequestStop()은 스레드 안전(thread-safe)합니다. Start()와 Wait()는 소유자 스레드에서만 호출합니다.

실패는 네이티브 호출의 C ABI 상태를 따릅니다.

  • 요청 실패는 해당 요청만 끝내며, 워커는 다른 요청을 계속 처리합니다. client::begin()이 #PDCOMM_OPCUA_INVALID_INPUT 또는 #PDCOMM_OPCUA_LIMIT으로 거부하는 경우(phase begin)와, 서버가 응답을 중단했거나 응답이 잘못되어 client::take()가 #PDCOMM_OPCUA_RUNTIME_ERROR로 완료하는 경우(phase response)입니다. 이 요청은 completed로 집계됩니다.
  • 핸들 레지스트리가 가득 찬 것은 실패가 아닙니다. client::take()가 #PDCOMM_OPCUA_LIMIT을 보고하고 네이티브 클라이언트가 응답을 보관합니다. 그동안 다른 모든 클라이언트 호출은 #PDCOMM_OPCUA_BUSY를 반환하므로, 워커는 그 take만 재시도하고 애플리케이션이 응답을 해제하면 응답을 보고합니다. 레지스트리가 timeoutMs 동안 계속 가득 차 있으면 워커는 #PDCOMM_OPCUA_LIMIT으로 종료됩니다.
  • 그 밖의 모든 네이티브 오류는 워커를 종료합니다. connect, activation, poll(연결 끊김, 요청 타임아웃, 보안 실패), close, 그리고 잘못된 응답이 연결을 닫은 뒤의 #PDCOMM_OPCUA_CLOSED 같은 다른 begin 또는 take 상태가 해당합니다. 워커는 worker 실패를 보고하고, 남은 요청을 phase abort로 실패시킨 뒤 reason native-error로 멈춥니다.

MFC 소비자 예제(examples/opcua-mfc/main.cpp)에서 발췌:

// The UI thread polls the in-process server and pumps the receiver window until the
// worker reports its terminal event. Worker events are handled only on this thread.
template <typename Handler>
static void Pump(server &host, MfcClientWorker &worker, UINT message, Handler &&handle, bool &stopped) {
  const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(30);
  while (!stopped) {
    Require(std::chrono::steady_clock::now() < deadline, "MFC worker deadline");
    host.poll();
    MSG msg;
    while (::PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
      if (msg.message == message) {
        for (auto &event : worker.Drain()) handle(event);
      } else {
        ::TranslateMessage(&msg);
        ::DispatchMessageW(&msg);
      }
    }
    ::Sleep(1);
  }
}
// ...
HWND receiver = ::CreateWindowExW(0, L"STATIC", L"", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
const UINT message = ::RegisterWindowMessageW(L"PDComm.OpcUa.Mfc.Consumer");
MfcClientConfiguration configuration;
configuration.endpoint = endpoint;
configuration.anonymous = true;
configuration.policy = PDCOMM_OPCUA_POLICY_NONE;
configuration.mode = PDCOMM_OPCUA_MODE_NONE;
configuration.flags = PDCOMM_OPCUA_ALLOW_NONE;
auto worker = std::make_unique<MfcClientWorker>(configuration, receiver, message);
Require(worker->Submit(kRead, 1) == PDCOMM_OPCUA_OK, "Read admitted");
Require(worker->Start() && !worker->Start(), "One-shot worker start");
bool stopped = false;
Pump(host, *worker, message, [&](const MfcClientEvent &event) {
  switch (event.kind) {
  case MfcClientEventKind::Completed: kept = event.value; break;
  case MfcClientEventKind::Stopped: stopped = true; break;
  default: break;
  }
  if (kept) worker->RequestStop(MfcClientStop::Drain);
}, stopped);
Require(worker->Wait(10000), "Worker joined");

멤버

Drain

메서드

큐에 있는 모든 이벤트를 순서대로 꺼냅니다. 수신 스레드에서 호출합니다.

std::vector<MfcClientEvent> Drain()

알림 메시지가 도착하면 호출합니다. Drain() 뒤에는 다음 이벤트가 새 메시지를 게시합니다. 워커가 멈춘 뒤에 호출해도 안전합니다.

반환값

워커가 만든 순서대로 놓인 이벤트입니다. 대기 중인 이벤트가 없으면 비어 있습니다.

MaximumCommands

필드

큐에 있거나 전송 중인, 받아들인 요청의 최대 개수입니다.

static constexpr size_t MaximumCommands = 64

MaximumEvents

필드

메일박스에서 동시에 대기할 수 있는 이벤트 수의 상한입니다.

static constexpr size_t MaximumEvents = 4 * MaximumCommands + 2

꺼내지 않은 이벤트가 MaximumCommands개 이상 대기하는 동안 워커는 큐의 요청을 가져오지 않으므로, 메일박스는 이 상한 아래로 유지됩니다.

MaximumQueuedBytes

필드

요청 하나의 최대 크기이자, 큐에 있고 아직 보내지 않은 모든 요청을 합친 최대 크기입니다(4 MiB).

static constexpr size_t MaximumQueuedBytes = 4 * 1024 * 1024

MfcClientWorker

생성자

워커를 만듭니다. 연결은 Start()가 시작합니다.

MfcClientWorker(MfcClientConfiguration configuration, HWND receiver, UINT message)

매개변수

이름설명
configuration설정이며, 값으로 받습니다.
receiver알림 메시지를 받는 창입니다. 메시지 전용 창(HWND_MESSAGE)으로 충분합니다.
message메일박스에 이벤트가 들어오면 wParam과 lParam을 0으로 하여 receiver에 게시(post)하는 메시지입니다. 예를 들어 RegisterWindowMessage 값을 사용합니다.

참고

  • 예외 error: 엔드포인트, 애플리케이션 URI 또는 사용자 이름이 4096바이트를 넘거나, 인증서 또는 키가 64 KiB를 넘거나, 사용자 암호가 16 KiB를 넘으면 #PDCOMM_OPCUA_LIMIT으로 던집니다.

MfcClientWorker

생성자

복사할 수 없습니다.

MfcClientWorker(const MfcClientWorker &) = delete

RequestStop

메서드

워커에 중지를 요청합니다. 스레드 안전합니다.

void RequestStop(MfcClientStop mode = MfcClientStop::Drain)

이후의 Submit() 호출은 #PDCOMM_OPCUA_CLOSED를 반환합니다. MfcClientStop::Drain이면 워커는 받아들인 모든 요청을 완료한 뒤 CloseSession을 보내고 채널을 닫습니다. MfcClientStop::Abort이면 워커는 프로토콜 종료 절차 없이 자신의 스레드에서 클라이언트를 해제하고, 남은 요청을 phase abort로 실패시킵니다. 중단 요청은 drain으로 낮춰지지 않습니다.

워커는 네이티브 호출 사이에서 요청을 확인합니다. 블로킹 중인 connect, activation, close 또는 DNS 조회는 중단되지 않습니다.

매개변수

이름설명
modeMfcClientStop::Drain(기본값) 또는 MfcClientStop::Abort입니다.

Start

메서드

워커 스레드를 시작합니다. 한 번만 시작할 수 있습니다.

BOOL Start()

반환값

스레드가 시작되면 TRUE입니다. 워커가 이미 시작되었거나 스레드를 만들 수 없으면 FALSE입니다. 두 번째 경우 워커는 종료된 상태가 되며 Submit()은 #PDCOMM_OPCUA_CLOSED를 반환합니다.

Submit

메서드

UA Binary 서비스 요청 하나를 큐에 넣습니다. 스레드 안전(thread-safe)합니다.

int32_t Submit(const std::vector<uint8_t> &request, ULONGLONG correlation)

요청을 복사만 하며, 호출 스레드에서는 네이티브 호출이 실행되지 않습니다. Ready 전에 제출된 요청은 활성화 뒤에 제출 순서대로 보냅니다. 받아들인 요청은 워커가 실행되면 정확히 하나의 Completed 또는 Failed 이벤트를 받습니다. 상관 ID(correlation)는 그 이벤트가 나올 때까지 예약된 상태로 남습니다.

매개변수

이름설명
requestclient::begin()과 같은 형식의 완전한 인코딩된 요청입니다. 복사됩니다.
correlation0이 아닌 애플리케이션 ID이며, 아직 끝나지 않은 요청 사이에서 고유해야 합니다.

반환값

OPC UA StatusCode가 아니라 접수 상태입니다. #PDCOMM_OPCUA_OK, 요청이 비어 있거나 correlation이 0이면 #PDCOMM_OPCUA_INVALID_INPUT, 사용 중인 correlation이면 #PDCOMM_OPCUA_BUSY, 요청이 4 MiB를 넘거나 MaximumCommands 또는 MaximumQueuedBytes를 넘게 되면 #PDCOMM_OPCUA_LIMIT, RequestStop() 뒤나 워커가 끝난 뒤이면 #PDCOMM_OPCUA_CLOSED입니다.

Wait

메서드

워커 스레드가 종료될 때까지 기다립니다. 소유자 스레드에서만 호출합니다.

BOOL Wait(DWORD milliseconds = INFINITE)

매개변수

이름설명
milliseconds최대 대기 시간 또는 INFINITE(기본값)입니다.

반환값

워커가 종료되었거나 시작된 적이 없으면 TRUE입니다. 타임아웃이 지나거나 워커 스레드 자신에서 호출하면 FALSE입니다.

operator=

메서드

복사 대입할 수 없습니다.

MfcClientWorker &operator=(const MfcClientWorker &) = delete

~MfcClientWorker

메서드

중단(abort)을 요청하고, 스레드를 기다린 뒤 자격 증명을 지웁니다.

~MfcClientWorker()

대기는 협조적입니다. 블로킹 중인 connect, activation, close 또는 DNS 조회가 먼저 끝납니다. 워커 자신의 스레드에서 워커를 소멸하면 std::terminate()가 호출됩니다.