C++ client and server
- Build a server with the RAII classes of
pdcomm_opcua.hppand expose three variables. - Build a client with the same header and write and read values.
- See how errors arrive as exceptions.
What you need
Section titled “What you need”- A lab folder that completed the lab setup: the
nativepackage, the environment variables, certificates in$PKIand built examples. - A C++17 compiler and two terminals.
The C++ wrapper
Section titled “The C++ wrapper”pdcomm_opcua.hpp is a header-only wrapper around the C ABI. It has no library of its own and uses the same pdcomm_opcua_ffi library.
| Class or function | Role |
|---|---|
options, server_options |
Check the ABI version and return an options structure filled with the defaults. |
client |
Owns a client handle; provides connect, activate_anonymous, begin, poll, take and close. |
server |
Owns a server handle; provides create, add_variable, set_value, read_value, local_address, poll and close. |
response, buffer |
Own response and buffer handles; copy() returns the bytes and info() the response metadata. |
error |
The exception thrown when a C ABI call fails; status() is the C ABI status and what() the error text. |
Every handle is released automatically when its object goes away. The objects can be moved but not copied.
1. Read the server code
Section titled “1. Read the server code”// PDComm OPC UA C++ server example using the header-only RAII wrapper.// Hosts ns=2;i=1001 Value (public, writable), ns=2;i=1002 Limit (public,// read-only) and ns=2;i=1003 Protected (authenticated only) until Ctrl+C,// SIGTERM or the optional --seconds limit.#include <pdcomm/opcua/pdcomm_opcua.hpp>#include <algorithm>#include <chrono>#include <csignal>#include <fstream>#include <iostream>#include <iterator>#include <string>#include <thread>#include <vector>#include "example_io.h"#include "ua_binary.h"
namespace ua = pdcomm::opcua;static volatile std::sig_atomic_t stop_requested = 0;extern "C" void on_signal(int) { stop_requested = 1; }
static std::vector<uint8_t> load(const std::string& path) { std::ifstream file(path, std::ios::binary); std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()}; if (data.empty() || data.size() > EXAMPLE_MAX_FILE) throw std::runtime_error("Invalid file " + path); return data;}static std::vector<uint8_t> node_id(uint32_t id) { uint8_t out[7]; return {out, out + ua_encode_node_id(out, 2, id)};}static std::vector<uint8_t> int32_variant(int32_t value) { uint8_t out[5]; return {out, out + ua_encode_int32_variant(out, value)};}
int main(int argc, char** argv) { unsigned long seconds = 0; if (argc != 6 && !(argc == 8 && std::string(argv[6]) == "--seconds")) { std::cerr << "Usage: opcua_server_cpp BIND_ADDRESS ENDPOINT_URL SERVER_CERT_DER SERVER_KEY_PEM " "TRUSTED_CLIENT_DER [--seconds N]\n"; return 2; } try { if (argc == 8) seconds = std::stoul(argv[7]); } catch (const std::exception&) { seconds = 0; } if (argc == 8 && (seconds == 0 || seconds > 86400)) { std::cerr << "Error: --seconds must be between 1 and 86400\n"; return 2; } try { const std::string endpoint = argv[2]; const auto certificate = load(argv[3]); auto key = load(argv[4]); const auto trust = load(argv[5]); auto config = ua::server_options(); config.endpoint = ua::bytes(endpoint); config.certificate_der = ua::bytes(certificate); config.private_key_pem = ua::bytes(key); config.trusted_client_der = ua::bytes(trust); auto server = ua::server::create(argv[1], config); std::fill(key.begin(), key.end(), static_cast<unsigned char>(0)); // The SDK copied the identity. server.add_variable(node_id(1001), "Value", int32_variant(42), PDCOMM_OPCUA_VARIABLE_WRITABLE | PDCOMM_OPCUA_VARIABLE_PUBLIC); server.add_variable(node_id(1002), "Limit", int32_variant(100), PDCOMM_OPCUA_VARIABLE_PUBLIC); server.add_variable(node_id(1003), "Protected", int32_variant(7), PDCOMM_OPCUA_VARIABLE_WRITABLE); const auto address = server.local_address().copy(); // Install the stop handlers before announcing readiness. std::signal(SIGINT, on_signal); std::signal(SIGTERM, on_signal);#ifdef SIGBREAK std::signal(SIGBREAK, on_signal); // Windows Ctrl+Break, e.g. from a service manager#endif std::cout << "Listening on " << std::string(address.begin(), address.end()) << std::endl; const auto started = std::chrono::steady_clock::now(); while (!stop_requested && (!seconds || std::chrono::steady_clock::now() - started < std::chrono::seconds(seconds))) { server.poll(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } const auto snapshot = server.read_value(node_id(1001)).copy(); ua_value value; if (ua_decode_data_value(snapshot.data(), snapshot.size(), &value) || !value.has_int32) throw std::runtime_error("Unexpected DataValue"); server.close(); std::cout << "Final Value=" << value.int32 << "\nServer stopped" << std::endl; return 0; } catch (const ua::error& e) { std::cerr << "Error: SDK status " << e.status() << ": " << e.what() << std::endl; return 3; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 3; }}ua::server_options()creates the options; the example sets the endpoint URL, the server certificate and private key, and the client certificate to trust. The flags stay 0, so only secure endpoints are advertised.ua::server::createbinds the address. The private key is copied at that point, so the example wipes its copy right away.- The NodeId and value passed to
add_variableare UA Binary bytes:node_id(1001)isns=2;i=1001andint32_variant(42)is an Int32 Variant holding 42. Variables withoutPDCOMM_OPCUA_VARIABLE_PUBLICare for authenticated users only. - The server serves clients only while you call
poll; the example calls it every millisecond. - On exit, the example reads the current DataValue with
read_valueand callsclose.
2. Read the client code
Section titled “2. Read the client code”// PDComm OPC UA C++ client example using the header-only RAII wrapper.// Opens a secure anonymous session, optionally writes an Int32 and reads it.// Exit codes: 0 success, 1 a Bad OPC UA status, 2 invalid arguments, 3 failure.#include <pdcomm/opcua/pdcomm_opcua.hpp>#include <algorithm>#include <chrono>#include <cstdio>#include <fstream>#include <iostream>#include <iterator>#include <map>#include <string>#include <thread>#include <vector>#include "example_io.h"#include "ua_binary.h"
namespace ua = pdcomm::opcua;
static std::vector<uint8_t> load(const std::string& path) { std::ifstream file(path, std::ios::binary); std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()}; if (!file.good() && !file.eof()) throw std::runtime_error("Cannot read " + path); if (data.empty() || data.size() > EXAMPLE_MAX_FILE) throw std::runtime_error("Invalid file " + path); return data;}
static std::vector<uint8_t> exchange(ua::client& client, const uint8_t* request, size_t size, std::chrono::milliseconds timeout) { const auto id = client.begin(std::vector<uint8_t>(request, request + size)); const auto deadline = std::chrono::steady_clock::now() + timeout; while (std::chrono::steady_clock::now() < deadline) { client.poll(); if (auto response = client.take(id)) return response->copy(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } throw ua::error(PDCOMM_OPCUA_RUNTIME_ERROR, "Response timed out");}
static std::string hex(uint32_t value) { char text[11]; std::snprintf(text, sizeof(text), "0x%08x", static_cast<unsigned>(value)); return text;}
int main(int argc, char** argv) { if (argc < 5 || (argc - 5) % 2) { std::cerr << "Usage: opcua_client_cpp ENDPOINT CLIENT_CERT_DER CLIENT_KEY_PEM " "TRUSTED_SERVER_DER [--policy aes256sha256rsapss|aes128sha256rsaoaep|" "basic256sha256] [--mode signandencrypt|sign] [--node ID] [--write VALUE]\n"; return 2; } const std::map<std::string, uint32_t> policies{{"aes256sha256rsapss", PDCOMM_OPCUA_AES256SHA256RSAPSS}, {"aes128sha256rsaoaep", PDCOMM_OPCUA_AES128SHA256RSAOAEP}, {"basic256sha256", PDCOMM_OPCUA_BASIC256SHA256}}; const std::map<std::string, uint32_t> modes{{"signandencrypt", PDCOMM_OPCUA_SIGN_AND_ENCRYPT}, {"sign", PDCOMM_OPCUA_SIGN}}; uint32_t policy = PDCOMM_OPCUA_AES256SHA256RSAPSS, mode = PDCOMM_OPCUA_SIGN_AND_ENCRYPT, node = 1001; bool write = false; int32_t write_value = 0; try { for (int i = 5; i < argc; i += 2) { const std::string name = argv[i], text = argv[i + 1]; if (name == "--policy" && policies.count(text)) policy = policies.at(text); else if (name == "--mode" && modes.count(text)) mode = modes.at(text); else if (name == "--node") node = static_cast<uint32_t>(std::stoul(text)); else if (name == "--write") { write = true; write_value = static_cast<int32_t>(std::stol(text)); } else throw std::invalid_argument(name); } } catch (const std::exception&) { std::cerr << "Error: invalid option\n"; return 2; } try { auto config = ua::options(); const auto certificate = load(argv[2]); auto key = load(argv[3]); const auto trust = load(argv[4]); config.policy = policy; config.mode = mode; config.timeout_ms = 5000; config.certificate_der = ua::bytes(certificate); config.private_key_pem = ua::bytes(key); config.trusted_server_der = ua::bytes(trust); auto client = ua::client::connect(argv[1], config); std::fill(key.begin(), key.end(), static_cast<unsigned char>(0)); // The SDK copied the identity. client.activate_anonymous(); std::cout << "Session activated (anonymous)" << std::endl; int result = 0; uint8_t request[256]; uint32_t service = 0, status = 0; if (write) { const auto size = ua_encode_write_int32(request, sizeof(request), 2, node, write_value); const auto body = exchange(client, request, size, std::chrono::seconds(5)); if (ua_decode_write(body.data(), body.size(), &service, &status)) throw std::runtime_error("Malformed WriteResponse"); std::cout << "Write ns=2;i=" << node << " value=" << write_value << " service=" << hex(service) << " status=" << hex(status) << std::endl; if ((service | status) & 0x80000000u) result = 1; } const auto size = ua_encode_read(request, sizeof(request), 2, node); const auto body = exchange(client, request, size, std::chrono::seconds(5)); ua_value value; if (ua_decode_read(body.data(), body.size(), &service, &value)) throw std::runtime_error("Malformed ReadResponse"); std::cout << "Read ns=2;i=" << node << " value=" << (value.has_int32 ? std::to_string(value.int32) : std::string("none")) << " service=" << hex(service) << " status=" << hex(value.status) << std::endl; if ((service | value.status) & 0x80000000u) result = 1; client.close(); std::cout << "Session closed" << std::endl; return result; } catch (const ua::error& e) { std::cerr << "Error: SDK status " << e.status() << ": " << e.what() << std::endl; return 3; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 3; }}ua::client::connectopens the secure channel and session; the returned object owns the client.exchangequeues a request withbegin, then repeatspollandtake.takereturns an emptystd::optionaluntil the response arrives.- Every C ABI failure arrives as
ua::error. The example prints it asError: SDK status ...and exits with code 3.
3. Start the server
Section titled “3. Start the server”In the first terminal, run the C++ server:
cd "$OPCUA_SDK"build/opcua_server_cpp 127.0.0.1:4840 opc.tcp://localhost:4840/pdcomm "$PKI/server.der" "$PKI/server.key.pem" "$PKI/client.der"Listening on 127.0.0.1:4840To stop after a fixed time, append --seconds N. With port 0, the operating system picks a free port and the Listening on line shows the actual address.
4. Write and read values
Section titled “4. Write and read values”In the second terminal, write 11 with the C++ client:
cd "$OPCUA_SDK"build/opcua_client_cpp opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --write 11Session activated (anonymous)Write ns=2;i=1001 value=11 service=0x00000000 status=0x00000000Read ns=2;i=1001 value=11 service=0x00000000 status=0x00000000Session closedRead again with the Aes128_Sha256_RsaOaep policy and Sign mode:
build/opcua_client_cpp opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --policy aes128sha256rsaoaep --mode signSession activated (anonymous)Read ns=2;i=1001 value=11 service=0x00000000 status=0x00000000Session closedWriting a read-only variable gives the item result BadNotWritable and exit code 1:
build/opcua_client_cpp opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" --node 1002 --write 5Session activated (anonymous)Write ns=2;i=1002 value=5 service=0x00000000 status=0x803b0000Read ns=2;i=1002 value=100 service=0x00000000 status=0x00000000Session closed5. See an exception
Section titled “5. See an exception”Pass the wrong certificate (client.der) as the server certificate to trust:
build/opcua_client_cpp opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/client.der"Error: SDK status 5: OPC UA security CertificateUntrustedua::client::connect threw ua::error with status() 5 (PDCOMM_OPCUA_RUNTIME_ERROR). Use the error text for logs and people only; branch on status() in your program.
The C++ example accepts only aes256sha256rsapss, aes128sha256rsaoaep and basic256sha256 for --policy. With none, it prints Error: invalid option and exits with code 2.
6. Clean up
Section titled “6. Clean up”Press Ctrl+C in the server’s terminal. The server stops on SIGINT, SIGTERM and, on Windows, Ctrl+Break.
Final Value=11Server stoppedNext steps
Section titled “Next steps”- Qt client worker: run the same client on its own thread in a UI application.
- C++ API reference