VLink 2.0.0
A high-performance communication middleware
Loading...
Searching...
No Matches
client.h
Go to the documentation of this file.
1/*
2 * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 * Author: Thun Lu <thun.lu@zohomail.cn>
4 * Repo: https://github.com/thun-res/vlink
5 * _ __ __ _ __
6 * | | / / / / (_) ____ / /__
7 * | | / / / / / / / __ \ / //_/
8 * | |/ / / /___ / / / / / / / ,<
9 * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 */
23
24/**
25 * @file client.h
26 * @brief Type-safe method-model client (caller side) for VLink RPC.
27 *
28 * @details
29 * @c Client<ReqT, RespT, SecT> is the caller side of the VLink method model.
30 * It serialises a request, delivers it to a matching @c Server, and returns
31 * the deserialised response.
32 *
33 * @par Method Model Overview
34 * @code
35 * Client<Req,Resp> Server<Req,Resp>
36 * | Transport Back-end |
37 * |-- invoke(req) -------> | |
38 * | serialize(req) |-- request delivery -----------> |
39 * | [wait] | |--> callback(req,resp)
40 * | |<-- response delivery ---------- |
41 * | deserialize(resp) | |
42 * |<-- returns resp -------| |
43 * @endcode
44 *
45 * @par Five Invocation Modes
46 * | Method | Signature | Block | Notes |
47 * | -------------------- | ----------------------------------------- | ----- | ------------------------------ |
48 * | @c invoke (ref) | @c invoke(req, resp&, timeout) | Yes | Returns @c true/false. |
49 * | @c invoke (optional) | @c invoke(req, timeout) -> optional<Resp> | Yes | Returns @c nullopt on timeout. |
50 * | @c invoke (callback) | @c invoke(req, RespCallback) | No | Callback on response. |
51 * | @c async_invoke | @c async_invoke(req) -> future<Resp> | No | @c std::future based. |
52 * | @c send | @c send(req) | No | Fire-and-forget (no resp). |
53 *
54 * @par Synchronous Invocation
55 * @code
56 * Client<Req, Resp> client("dds://my_service");
57 * client.wait_for_connected();
58 *
59 * Resp resp;
60 * if (client.invoke(Req{...}, resp)) {
61 * std::cout << "result: " << resp.value << std::endl;
62 * }
63 * // or using optional:
64 * if (auto r = client.invoke(Req{...})) {
65 * std::cout << r->value << std::endl;
66 * }
67 * @endcode
68 *
69 * @par Asynchronous Invocation
70 * @code
71 * // callback-based
72 * client.invoke(Req{...}, [](const Resp& resp) {
73 * std::cout << "async result: " << resp.value << std::endl;
74 * });
75 *
76 * // future-based
77 * auto future = client.async_invoke(Req{...});
78 * auto resp = future.get();
79 * @endcode
80 *
81 * @par Server Connection Detection
82 * @code
83 * Client<Req, Resp> client("dds://my_service");
84 * client.detect_connected([](bool connected) { ... }); // async
85 * client.wait_for_connected(); // blocking
86 * if (client.is_connected()) { ... } // non-blocking
87 * @endcode
88 *
89 * @note A timeout of @c 0 is treated as infinite (a warning is logged).
90 * @c send() is only valid when @c RespT == @c EmptyType.
91 * @c invoke() / @c async_invoke() are only valid when @c kHasResp is @c true.
92 *
93 * @tparam ReqT Request message type. Must satisfy @c Serializer::is_supported().
94 * @tparam RespT Response type. Defaults to @c Traits::EmptyType (fire-and-forget).
95 * @tparam SecT Security mode; defaults to @c SecurityType::kWithoutSecurity.
96 */
97
98#pragma once
99
100#include <chrono>
101#include <functional>
102#include <future>
103#include <memory>
104#include <mutex>
105#include <optional>
106#include <string>
107#include <unordered_map>
108
109#include "./impl/client_impl.h"
110#include "./node.h"
111
112namespace vlink {
113
114/**
115 * @class Client
116 * @brief Type-safe client for the VLink method (RPC) communication model.
117 *
118 * @tparam ReqT Request type.
119 * @tparam RespT Response type (defaults to @c Traits::EmptyType -- fire-and-forget).
120 * @tparam SecT Security mode.
121 */
122template <typename ReqT, typename RespT = Traits::EmptyType, SecurityType SecT = SecurityType::kWithoutSecurity>
123class Client : public Node<ClientImpl, SecT> {
124 public:
125 /** @brief Unique-pointer alias. */
126 using UniquePtr = std::unique_ptr<Client<ReqT, RespT, SecT>>;
127
128 /** @brief Shared-pointer alias. */
129 using SharedPtr = std::shared_ptr<Client<ReqT, RespT, SecT>>;
130
131 /** @brief Callback type fired when server connection state changes. */
133
134 /** @brief Callback type for async response delivery. */
135 using RespCallback = std::function<void(const RespT&)>;
136
137 /** @brief Node role identifier (@c kClient). */
138 static constexpr ImplType kImplType = kClient;
139
140 /** @brief @c true when @c RespT is not @c EmptyType (client expects a response). */
141 static constexpr bool kHasResp = !std::is_same_v<RespT, Traits::EmptyType>;
142
143 /** @brief Serializer type for @c ReqT. */
145
146 /** @brief Serializer type for @c RespT. */
148
149 static_assert(Serializer::is_supported(kReqType), "<ReqT> is not a supported Serializer type.");
150 static_assert(!kHasResp || Serializer::is_supported(kRespType), "<RespT> is not a supported Serializer type.");
151
152 /**
153 * @brief Creates a @c Client on the heap wrapped in a @c unique_ptr.
154 *
155 * @param url_str Service URL string.
156 * @param type @c kWithInit to call @c init() immediately (default).
157 * @return @c UniquePtr owning the new client.
158 */
159 [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, InitType type = InitType::kWithInit);
160
161 /**
162 * @brief Creates a @c Client on the heap wrapped in a @c shared_ptr.
163 *
164 * @param url_str Service URL string.
165 * @param type @c kWithInit to call @c init() immediately (default).
166 * @return @c SharedPtr owning the new client.
167 */
168 [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, InitType type = InitType::kWithInit);
169
170 /**
171 * @brief Constructs a client from a typed transport configuration object.
172 *
173 * @details
174 * Accepts any @c Conf-derived configuration. A compile-time @c static_assert
175 * verifies the configuration supports the client role.
176 *
177 * @tparam ConfT @c Conf-derived configuration type.
178 * @param conf Populated configuration object.
179 * @param type @c kWithInit to call @c init() immediately (default).
180 */
181 // NOLINTNEXTLINE(modernize-use-constraints)
182 template <typename ConfT, typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
183 explicit Client(const ConfT& conf, InitType type = InitType::kWithInit);
184
185 /**
186 * @brief Constructs a client from a URL string.
187 *
188 * @param url_str Service URL (e.g. @c "someip://30490/0x1/my_method").
189 * @param type @c kWithInit to call @c init() immediately (default).
190 */
191 explicit Client(const std::string& url_str, InitType type = InitType::kWithInit);
192
193 ~Client() override;
194
195 /**
196 * @brief Registers a callback invoked when the server connection state changes.
197 *
198 * @details
199 * Fires immediately (synchronously) if the server is already connected.
200 * Otherwise fires asynchronously when the server first becomes available.
201 *
202 * @param callback @c void(bool) -- @c true when connected to a server.
203 */
204 void detect_connected(ConnectCallback&& callback);
205
206 /**
207 * @brief Blocks until a server is available or the timeout expires.
208 *
209 * @details
210 * A @p timeout of @c 0 is treated as infinite (a warning is logged).
211 * A negative timeout also waits indefinitely. Can be interrupted by
212 * @c interrupt(), which causes this method to return @c false.
213 *
214 * @param timeout Maximum wait duration. Default: @c Timeout::kDefaultInterval.
215 * @return @c true if a server appeared; @c false on timeout or interrupt.
216 */
217 bool wait_for_connected(std::chrono::milliseconds timeout = Timeout::kDefaultInterval);
218
219 /**
220 * @brief Returns @c true if a server is currently available.
221 *
222 * @details
223 * Non-blocking poll; reflects the transport's last known server state.
224 *
225 * @return @c true when connected to a server.
226 */
227 [[nodiscard]] bool is_connected() const;
228
229 /**
230 * @brief Sends a request and blocks until the response is received.
231 *
232 * @details
233 * Only valid when @c kHasResp is @c true (enforced by @c static_assert).
234 * Serialises @p req, sends it to the server, blocks for up to @p timeout,
235 * and deserialises the response into @p resp. A @p timeout of @c 0 is
236 * treated as infinite.
237 *
238 * @param req Request value to send.
239 * @param resp Output parameter filled with the deserialised response.
240 * @param timeout Maximum wait for the response.
241 * @return @c true if the response was received in time; @c false otherwise.
242 */
243 [[nodiscard]] bool invoke(const ReqT& req, RespT& resp,
244 std::chrono::milliseconds timeout = Timeout::kDefaultInterval);
245
246 /**
247 * @brief Sends a request and returns the response as @c std::optional.
248 *
249 * @details
250 * Convenience overload that returns @c std::nullopt on timeout or error
251 * instead of requiring an output parameter. Only valid when @c kHasResp is
252 * @c true. A @p timeout of @c 0 is treated as infinite.
253 *
254 * @param req Request value to send.
255 * @param timeout Maximum wait for the response.
256 * @return @c std::optional<RespT> -- @c nullopt on timeout/error.
257 */
258 [[nodiscard]] std::optional<RespT> invoke(const ReqT& req,
259 std::chrono::milliseconds timeout = Timeout::kDefaultInterval);
260
261 /**
262 * @brief Sends a request and invokes @p callback asynchronously on the response.
263 *
264 * @details
265 * Only valid when @c kHasResp is @c true. The call returns immediately; the
266 * @p callback is invoked on the transport thread (or on the attached
267 * @c MessageLoop thread) when the response arrives.
268 *
269 * @param req Request value to send.
270 * @param callback @c void(const RespT&) invoked with the response.
271 * @return @c true if the request was accepted by the transport; @c false on error.
272 */
273 bool invoke(const ReqT& req, RespCallback&& callback);
274
275 /**
276 * @brief Sends a request and returns a @c std::future for the response.
277 *
278 * @details
279 * Only valid when @c kHasResp is @c true (enforced by @c static_assert).
280 * The future is set when the response arrives. If the call fails (serialisation
281 * error, transport error, or deserialisation failure) the future's exception is
282 * set with an @c Exception::RuntimeError.
283 *
284 * @param req Request value to send.
285 * @return @c std::future<RespT> resolved when the response arrives.
286 */
287 [[nodiscard]] std::future<RespT> async_invoke(const ReqT& req);
288
289 /**
290 * @brief Sends a fire-and-forget request with no response.
291 *
292 * @details
293 * Only valid when @c RespT == @c EmptyType (enforced by @c static_assert).
294 * The call returns immediately after the transport has accepted the request;
295 * no response is expected or awaited.
296 *
297 * @param req Request value to send.
298 * @return @c true if the transport accepted the request; @c false on error.
299 */
300 bool send(const ReqT& req);
301
302 private:
303 bool call_bytes(const Bytes& req_data, NodeImpl::MsgCallback&& callback = nullptr,
304 std::chrono::milliseconds timeout = std::chrono::milliseconds(0));
305
306 uint64_t future_seq_{0};
307 std::mutex future_mtx_;
308 std::unordered_map<std::uint64_t, std::shared_ptr<std::promise<RespT>>> future_map_;
309};
310
311/**
312 * @class SecurityClient
313 * @brief Convenience alias for @c Client with message security enabled.
314 *
315 * @details
316 * Equivalent to @c Client<ReqT, RespT, SecurityType::kWithSecurity>.
317 * Each outgoing request is encrypted and each incoming response is decrypted
318 * using the configured security key or callbacks.
319 *
320 * @tparam ReqT Request type.
321 * @tparam RespT Response type (defaults to @c Traits::EmptyType).
322 */
323template <typename ReqT, typename RespT = Traits::EmptyType>
324class SecurityClient : public Client<ReqT, RespT, SecurityType::kWithSecurity> {
325 public:
327};
328
329} // namespace vlink
330
Abstract base class for all transport-specific client (RPC caller) implementations.
Base CRTP template for all VLink communication nodes.