서비스
OPC UA 클라이언트는 서비스(service)를 호출해 서버와 통신합니다. 서비스는 요청 메시지와 응답 메시지의 쌍입니다. Part 4가 서비스를 정의하고, Part 6이 메시지를 바이트로 인코딩하는 방법과 opc.tcp 전송을 정의합니다.
연결 순서
섹션 제목: “연결 순서”opc.tcp 연결은 세 층으로 이루어집니다.
- 연결 프로토콜(UA Connection Protocol, UACP): TCP 연결 뒤 Hello(
HEL)와 Acknowledge(ACK)로 버퍼 크기를 협상합니다. Hello에는 클라이언트가 쓰는 endpoint URL이 들어갑니다(Part 6, 7.1절). - 보안 채널(secure channel): OpenSecureChannel로 보안 정책과 보안 모드를 정하고 대칭 키를 만듭니다. 이후 모든 메시지는 이 키로 서명되거나 서명·암호화됩니다(Part 4, 5.5절 SecureChannel Service Set, Part 6 보안 대화).
- 세션(session): CreateSession으로 세션을 만들고 ActivateSession으로 사용자를 인증합니다(Part 4, 5.6절 Session Service Set). 세션이 활성화되면 다른 서비스를 호출할 수 있습니다.
Client Server | HEL / ACK | | OpenSecureChannel (SecurityPolicy None) ------> | discovery | GetEndpoints ----------------------------------> | | <--------------------- EndpointDescriptions ---- | | CloseSecureChannel ----------------------------> | | | | HEL / ACK | | OpenSecureChannel (policy, mode, certificate) -> | secure channel | <------------------- channel token, nonce ------ | | CreateSession ---------------------------------> | session | <----------- session id, nonce, certificate ---- | | ActivateSession (user identity token) ---------> | | Read / Browse / Call / Publish ... ------------> | services | <------------------------------------- responses | | CloseSession ----------------------------------> | | CloseSecureChannel ----------------------------> |첫 번째 연결은 엔드포인트(endpoint) 목록을 얻는 탐색(discovery) 단계입니다. 엔드포인트 하나는 URL, 보안 정책, 보안 모드, 서버 인증서, 허용하는 사용자 토큰 종류의 조합입니다. 클라이언트는 목록에서 원하는 조합을 골라 두 번째 연결을 만듭니다.
서비스 집합
섹션 제목: “서비스 집합”| 서비스 집합 | 주요 서비스 | Part 4 |
|---|---|---|
| Discovery | FindServers, GetEndpoints | 5.4절 |
| SecureChannel | OpenSecureChannel, CloseSecureChannel | 5.5절 |
| Session | CreateSession, ActivateSession, CloseSession, Cancel | 5.6절 |
| NodeManagement | AddNodes, AddReferences, DeleteNodes, DeleteReferences | 5.7절 |
| View | Browse, BrowseNext, TranslateBrowsePathsToNodeIds, RegisterNodes | 5.8절 |
| Attribute | Read, Write, HistoryRead, HistoryUpdate | 5.10절 |
| Method | Call | 5.11절 |
| MonitoredItem | CreateMonitoredItems, ModifyMonitoredItems, SetMonitoringMode, SetTriggering, DeleteMonitoredItems | 5.12절 |
| Subscription | CreateSubscription, ModifySubscription, SetPublishingMode, Publish, Republish, TransferSubscriptions, DeleteSubscriptions | 5.13절 |
자주 쓰는 서비스는 다음과 같습니다.
- Browse: 노드에서 나가는 참조를 나열합니다. 방향, 참조 타입, NodeClass로 거를 수 있습니다. 결과가 많으면 연속 지점(continuation point)을 돌려주고, BrowseNext로 나머지를 받습니다.
- Read, Write: 노드의 속성을 읽고 씁니다. 요청 하나에 여러 노드를 담을 수 있고, 결과는 항목마다 따로 옵니다.
- Call: 객체(object)의 메서드를 입력 인자와 함께 호출합니다. 결과에는 메서드의 StatusCode, 인자별 결과, 출력 인자가 있습니다.
- CreateSubscription, CreateMonitoredItems, Publish: 값과 이벤트의 변화를 알림으로 받습니다. 구독과 게시 주기에서 설명합니다.
- HistoryRead: 저장된 이력을 읽습니다. 이력 접근에서 설명합니다.
UA Binary 메시지
섹션 제목: “UA Binary 메시지”opc.tcp는 UA Binary 인코딩을 씁니다(Part 6, 5.2절). 서비스 메시지는 해당 타입의 인코딩 NodeId로 시작하고, 요청 헤더(RequestHeader) 또는 응답 헤더(ResponseHeader)와 본문이 뒤따릅니다.
| 메시지 | 인코딩 NodeId |
|---|---|
| ReadRequest / ReadResponse | i=631 / i=634 |
| WriteRequest / WriteResponse | i=673 / i=676 |
| BrowseRequest / BrowseResponse | i=527 / i=530 |
| CallRequest / CallResponse | i=712 / i=715 |
| HistoryReadRequest | i=664 |
| CreateSubscriptionRequest | i=787 |
| CreateMonitoredItemsRequest | i=751 |
| PublishRequest | i=826 |
| GetEndpointsResponse | i=431 |
| ServiceFault | i=397 |
요청 헤더에는 세션 인증 토큰(authenticationToken), 시각, 요청 핸들(requestHandle), 진단 요청 비트, 제한 시간 힌트(timeoutHint)가 있습니다. 응답 헤더에는 같은 요청 핸들과 서비스 결과(serviceResult)가 있습니다. 서버가 요청 자체를 처리하지 못하면 ServiceFault를 보냅니다.
PDComm C ABI의 대응
섹션 제목: “PDComm C ABI의 대응”PDComm SDK 클라이언트는 연결과 보안을 맡고, 서비스 메시지는 응용 프로그램이 UA Binary로 만듭니다. 이 방식은 서비스를 타입별 API로 감싸지 않으므로 모든 서비스를 같은 함수로 보냅니다.
| 단계 | C 함수 | 하는 일 |
|---|---|---|
| 탐색 | pdcomm_opcua_discover |
None 보안 채널로 GetEndpoints를 보내고 GetEndpointsResponse 전체를 돌려줍니다. 인증서를 검증하지 않습니다. |
| 연결 | pdcomm_opcua_client_connect |
엔드포인트를 조회하고, 정책과 모드가 정확히 같은 엔드포인트를 고릅니다. 서버 인증서를 검증한 뒤 OpenSecureChannel과 CreateSession을 보냅니다. 대체 정책은 고르지 않습니다. |
| 사용자 인증 | pdcomm_opcua_client_activate_anonymous, pdcomm_opcua_client_activate_user_name |
ActivateSession을 보냅니다. |
| 요청 | pdcomm_opcua_client_begin |
완성된 요청 메시지를 대기열에 넣습니다. 인증 토큰, 시각, 요청 핸들은 SDK가 채웁니다. timeoutHint가 0이면 연결 옵션의 제한 시간을 씁니다. |
| 입출력 | pdcomm_opcua_client_poll |
소켓에서 바이트를 보내고 받습니다. 블로킹하지 않습니다. |
| 응답 | pdcomm_opcua_client_take_response |
검증한 응답 버퍼를 돌려줍니다. 아직 없으면 PDCOMM_OPCUA_EMPTY입니다. |
| 응답 정보 | pdcomm_opcua_response_get_info |
응답의 인코딩 ID(binary_id)와 서비스 결과(service_status)를 읽습니다. |
| 종료 | pdcomm_opcua_client_close |
CloseSession(구독 삭제 포함)과 CloseSecureChannel을 보냅니다. |
pdcomm_opcua_client_begin이 받는 서비스는 FindServers, GetEndpoints, Cancel, Browse, BrowseNext, TranslateBrowsePathsToNodeIds, RegisterNodes, UnregisterNodes, Read, Write, Call, HistoryRead, HistoryUpdate, Subscription 서비스, MonitoredItem 서비스, AddNodes, AddReferences, DeleteNodes, DeleteReferences입니다. 보안 채널과 세션 요청은 전용 함수로만 보냅니다.
C ABI 함수의 반환값은 호출 자체의 상태입니다. OPC UA StatusCode는 응답 본문 안에 있습니다. 예를 들어 서버가 ServiceFault를 보내도 pdcomm_opcua_client_take_response는 PDCOMM_OPCUA_OK를 반환하고, service_status가 Bad입니다.
SDK는 스레드를 만들지 않고 콜백도 부르지 않습니다. 요청이 대기 중이면 응용 프로그램이 pdcomm_opcua_client_poll을 주기적으로 호출합니다. 한 클라이언트에는 동시에 64개까지 요청을 대기시킬 수 있습니다.
C++ 헤더 pdcomm_opcua.hpp, Qt 작업자, .NET 어셈블리는 같은 C ABI를 감쌉니다. C 클라이언트 튜토리얼에서 이 흐름을 직접 실행합니다.
PDComm 메모리 서버
섹션 제목: “PDComm 메모리 서버”pdcomm_opcua_server_create는 표준 주소 공간(Server 객체, 네임스페이스 배열, 표준 타입)을 가진 서버를 만듭니다. 응용 프로그램은 pdcomm_opcua_server_add_variable로 변수를 추가하고 pdcomm_opcua_server_poll을 주기적으로 호출합니다. 이 서버는 Discovery, Session, View, Attribute, Method, Subscription, MonitoredItem 서비스를 메모리에서 처리합니다. 외부 데이터 제공자, 이벤트, 이력, PubSub은 없습니다. 사용자 토큰은 익명만 받습니다.
검색(discovery)과 연결에는 opcua-client 모듈 라이선스가, 서버 생성에는 opcua-server 모듈 라이선스가 필요합니다. 라이선스가 없으면 네트워크 I/O 전에 PDCOMM_OPCUA_LICENSE_REQUIRED로 거부하며, 이미 있는 클라이언트와 서버는 계속 동작합니다. 라이선스 설치와 확인을 참고합니다.
시뮬레이터
섹션 제목: “시뮬레이터”PDComm OPC UA 시뮬레이터는 서비스를 동작(action) 단위의 JSON 명령으로 감쌉니다. 예를 들어 {"action":"browse","node":"ns=2;s=Simulator"}는 BrowseRequest를 만들어 보내고 결과를 JSON으로 돌려줍니다. 이벤트, 이력, PubSub처럼 SDK 서버에 없는 기능도 시뮬레이터에서 실습합니다. 헤드리스 시뮬레이터에서 동작 목록을 확인합니다.
다음 단계
섹션 제목: “다음 단계”보안에서 보안 채널과 세션을 지키는 요소를 살펴봅니다.