VLink  2.0.0
A high-performance communication middleware
client.h
浏览该文件的文档.
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 Caller-side primitive of the VLink method (RPC) communication model.
27  *
28  * @details
29  * @c Client<ReqT, RespT, SecT> issues remote calls to a matching @c Server.
30  * The request is serialised with the codec selected at compile time, sent
31  * via the active transport back-end, and (when @c RespT != @c EmptyType) the
32  * response payload is decoded back into a @c RespT instance and handed to
33  * the caller through one of several invocation styles.
34  *
35  * The class is a thin, header-only template wrapper around @c ClientImpl
36  * with additional bookkeeping for @c std::future-based async invocations.
37  *
38  * @par Request / Response Sequence
39  * @verbatim
40  * Client<Req,Resp> Transport Back-end Server<Req,Resp>
41  * ----------------- ------------------ -----------------
42  * | invoke(req) | |
43  * |----------------------------->| serialised request |
44  * | |--------------------------->|
45  * | | | user handler
46  * | | | fills resp
47  * | | |
48  * | | serialised response |
49  * | |<---------------------------|
50  * | deserialised response | |
51  * |<-----------------------------| |
52  * @endverbatim
53  *
54  * @par Five Invocation Modes
55  * | Method | Blocking | Result delivery | When to use |
56  * | ----------------------------------------------- | -------- | --------------------- | ------------------------- |
57  * | @c invoke(req, resp&, timeout) | Yes | Out-param + bool | Classic synchronous call |
58  * | @c invoke(req, timeout) | Yes | @c std::optional | Synchronous, no out-param |
59  * | @c invoke(req, RespCallback) | No | Callback | Async with handler |
60  * | @c async_invoke(req) | No | @c std::future | Future / promise model |
61  * | @c send(req) | No | None | Fire-and-forget request |
62  *
63  * @par Synchronous Invocation Example
64  * @code
65  * vlink::Client<Req, Resp> cli("dds://compute/sum");
66  * cli.wait_for_connected();
67  *
68  * Resp resp;
69  * if (cli.invoke(Req{1, 2}, resp)) { use(resp); }
70  *
71  * if (auto r = cli.invoke(Req{3, 4})) { use(*r); }
72  * @endcode
73  *
74  * @par Asynchronous Invocation Example
75  * @code
76  * cli.invoke(Req{7, 8}, [](const Resp& r) { use(r); });
77  *
78  * std::future<Resp> fut = cli.async_invoke(Req{9, 10});
79  * Resp value = fut.get();
80  * @endcode
81  *
82  * @par Connection Detection
83  * @code
84  * cli.detect_connected([](bool connected) { ... });
85  * cli.wait_for_connected(std::chrono::milliseconds(200));
86  * if (cli.is_connected()) { ... }
87  * @endcode
88  *
89  * @note A @p timeout of @c 0 is treated as infinite and logs a warning.
90  * @c send() is only available when @c RespT == @c EmptyType, and the
91  * response-bearing overloads are only available when @c kHasResp is
92  * @c true (enforced by @c static_assert in the response-bearing entry
93  * points; the @c std::optional -returning overload inherits the
94  * constraint by delegating to the out-parameter @c invoke).
95  *
96  * @see server.h, node.h, serializer.h, base/functional.h
97  */
98 
99 #pragma once
100 
101 #include <chrono>
102 #include <future>
103 #include <memory>
104 #include <mutex>
105 #include <optional>
106 #include <string>
107 #include <type_traits>
108 #include <unordered_map>
109 
110 #include "./base/functional.h"
111 #include "./impl/client_impl.h"
112 #include "./node.h"
113 
114 namespace vlink {
115 
116 /**
117  * @class Client
118  * @brief Type-safe RPC caller for the VLink method communication model.
119  *
120  * @details
121  * Inherits the full @c Node API and adds method-specific operations:
122  * connection detection, blocking and non-blocking invocation, future-based
123  * async invocation, and fire-and-forget @c send(). The transport
124  * implementation (@c ClientImpl) is selected by the URL scheme or by the
125  * typed configuration object supplied at construction time.
126  *
127  * @tparam ReqT Request message type. Must satisfy @c Serializer::is_supported().
128  * @tparam RespT Response message type. Defaults to @c Traits::EmptyType for fire-and-forget.
129  * @tparam SecT Security mode; defaults to @c SecurityType::kWithoutSecurity.
130  */
131 template <typename ReqT, typename RespT = Traits::EmptyType, SecurityType SecT = SecurityType::kWithoutSecurity>
132 class Client : public Node<ClientImpl, SecT> {
133  public:
134  using UniquePtr = std::unique_ptr<Client<ReqT, RespT, SecT>>; ///< Owning unique-pointer alias.
135  using SharedPtr = std::shared_ptr<Client<ReqT, RespT, SecT>>; ///< Owning shared-pointer alias.
136  using ConnectCallback = NodeImpl::ConnectCallback; ///< Callback type for server presence transitions.
137  using RespCallback = Function<void(const RespT&)>; ///< Callback signature for asynchronous responses.
138 
139  static constexpr ImplType kImplType = kClient; ///< Node role tag (@c kClient).
140  static constexpr bool kHasResp = !std::is_same_v<RespT, Traits::EmptyType>; ///< @c true when response expected.
141  static constexpr Serializer::Type kReqType = Serializer::get_type_of<ReqT>(); ///< Codec for the request.
142  static constexpr Serializer::Type kRespType = Serializer::get_type_of<RespT>(); ///< Codec for the response.
143 
144  static_assert(Serializer::is_supported(kReqType), "<ReqT> is not a supported Serializer type.");
145  static_assert(!kHasResp || Serializer::is_supported(kRespType), "<RespT> is not a supported Serializer type.");
146 
147  /**
148  * @brief Heap-allocates a @c Client and wraps it in a @c std::unique_ptr.
149  *
150  * @param url_str Service URL string.
151  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
152  * @return Owning @c UniquePtr to the new client.
153  */
154  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, InitType type = InitType::kWithInit);
155 
156  /**
157  * @brief Heap-allocates a @c Client and wraps it in a @c std::shared_ptr.
158  *
159  * @param url_str Service URL string.
160  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
161  * @return Owning @c SharedPtr to the new client.
162  */
163  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, InitType type = InitType::kWithInit);
164 
165  /**
166  * @brief Constructs a client from a typed transport configuration object.
167  *
168  * @details
169  * Accepts any @c Conf-derived configuration. A compile-time check
170  * enforces that the configuration permits the client role.
171  *
172  * @tparam ConfT Concrete configuration type derived from @c Conf.
173  * @param conf Populated configuration aggregate.
174  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
175  */
176  // NOLINTNEXTLINE(modernize-use-constraints)
177  template <typename ConfT, typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
178  explicit Client(const ConfT& conf, InitType type = InitType::kWithInit);
179 
180  /**
181  * @brief Constructs a client from a URL string.
182  *
183  * @param url_str Service URL such as @c "someip://30490/0x1/my_method".
184  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
185  */
186  explicit Client(const std::string& url_str, InitType type = InitType::kWithInit);
187 
188  /**
189  * @brief Destroys the client and tears down outstanding promises.
190  *
191  * @details
192  * Required to drain any in-flight @c async_invoke() promises before this
193  * object's locks and maps are destroyed.
194  */
195  ~Client() override;
196 
197  /**
198  * @brief Registers a callback invoked when the server-presence state changes.
199  *
200  * @details
201  * Fires synchronously immediately if a server is already discovered at
202  * registration; otherwise fires asynchronously the first time a matching
203  * server appears, and again on disconnection.
204  *
205  * @param callback @c void(bool) callable -- @c true means connected.
206  */
207  void detect_connected(ConnectCallback&& callback);
208 
209  /**
210  * @brief Blocks until a server is discovered or @p timeout expires.
211  *
212  * @details
213  * A @p timeout of @c 0 is treated as infinite (with a warning). A
214  * negative value also waits indefinitely. @c interrupt() causes the wait
215  * to abort and return @c false.
216  *
217  * @param timeout Maximum wait duration. Default: @c Timeout::kDefaultInterval.
218  * @return @c true if a server appeared; @c false on timeout or interrupt.
219  */
220  bool wait_for_connected(std::chrono::milliseconds timeout = Timeout::kDefaultInterval);
221 
222  /**
223  * @brief Non-blocking query of server presence.
224  *
225  * @return @c true when the transport currently has a matching server.
226  */
227  [[nodiscard]] bool is_connected() const;
228 
229  /**
230  * @brief Synchronous request/response invocation with an output parameter.
231  *
232  * @details
233  * Only available when @c kHasResp is @c true. Serialises @p req, blocks for
234  * up to @p timeout, and deserialises the reply into @p resp. A @p timeout
235  * of @c 0 is treated as infinite.
236  *
237  * @param req Request value.
238  * @param resp Output parameter receiving the deserialised response.
239  * @param timeout Maximum wait for the response.
240  * @return @c true if the response was received in time.
241  */
242  [[nodiscard]] bool invoke(const ReqT& req, RespT& resp,
243  std::chrono::milliseconds timeout = Timeout::kDefaultInterval);
244 
245  /**
246  * @brief Synchronous request/response invocation returning a @c std::optional.
247  *
248  * @details
249  * Convenience wrapper that returns @c std::nullopt on timeout, transport
250  * failure, or codec failure. Only available when @c kHasResp is @c true.
251  *
252  * @param req Request value.
253  * @param timeout Maximum wait for the response.
254  * @return @c std::optional<RespT>; empty on failure.
255  */
256  [[nodiscard]] std::optional<RespT> invoke(const ReqT& req,
257  std::chrono::milliseconds timeout = Timeout::kDefaultInterval);
258 
259  /**
260  * @brief Asynchronous callback-based invocation.
261  *
262  * @details
263  * Only available when @c kHasResp is @c true. The method returns
264  * immediately; @p callback runs on the transport delivery thread or on the
265  * attached @c MessageLoop thread when the response arrives.
266  *
267  * @param req Request value.
268  * @param callback @c void(const RespT&) invoked once the response arrives.
269  * @return @c true if the transport accepted the request.
270  */
271  bool invoke(const ReqT& req, RespCallback&& callback);
272 
273  /**
274  * @brief Asynchronous future-based invocation.
275  *
276  * @details
277  * Returns a @c std::future that is set when the response arrives. On
278  * serialisation, transport, or deserialisation failure the future's
279  * exception state is populated with @c Exception::RuntimeError. Only
280  * available when @c kHasResp is @c true.
281  *
282  * @param req Request value.
283  * @return @c std::future<RespT> resolved when the response is delivered.
284  */
285  [[nodiscard]] std::future<RespT> async_invoke(const ReqT& req);
286 
287  /**
288  * @brief Fire-and-forget request emission.
289  *
290  * @details
291  * Only available when @c RespT == @c EmptyType. Serialises @p req and
292  * passes it to the transport without waiting for or expecting a reply.
293  *
294  * @param req Request value.
295  * @return @c true if the transport accepted the request.
296  */
297  bool send(const ReqT& req);
298 
299  private:
300  bool call_bytes(const Bytes& req_data, NodeImpl::MsgCallback&& callback = nullptr,
301  std::chrono::milliseconds timeout = std::chrono::milliseconds(0));
302 
303  uint64_t future_seq_{0};
304  std::mutex future_mtx_;
305  std::unordered_map<std::uint64_t, std::shared_ptr<std::promise<RespT>>> future_map_;
306 };
307 
308 /**
309  * @class SecurityClient
310  * @brief Convenience alias of @c Client with per-message encryption enabled.
311  *
312  * @details
313  * Equivalent to @c Client<ReqT, RespT, SecurityType::kWithSecurity>. Each
314  * outgoing request is encrypted before transmission and each incoming
315  * response is decrypted before codec dispatch.
316  *
317  * @tparam ReqT Request message type.
318  * @tparam RespT Response message type. Defaults to @c Traits::EmptyType.
319  */
320 template <typename ReqT, typename RespT = Traits::EmptyType>
321 class SecurityClient : public Client<ReqT, RespT, SecurityType::kWithSecurity> {
322  public:
323  using UniquePtr = std::unique_ptr<SecurityClient<ReqT, RespT>>; ///< Owning unique-pointer alias.
324  using SharedPtr = std::shared_ptr<SecurityClient<ReqT, RespT>>; ///< Owning shared-pointer alias.
325 
326  /**
327  * @brief Heap-allocates a @c SecurityClient and wraps it in a @c std::unique_ptr.
328  *
329  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
330  * @param url_str Service URL string.
331  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
332  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
333  * @return Owning @c UniquePtr to the new secure client.
334  */
335  // NOLINTNEXTLINE(modernize-use-constraints)
336  template <typename SecurityConfigT = Security::Config>
337  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
339 
340  /**
341  * @brief Heap-allocates a @c SecurityClient and wraps it in a @c std::shared_ptr.
342  *
343  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
344  * @param url_str Service URL string.
345  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
346  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
347  * @return Owning @c SharedPtr to the new secure client.
348  */
349  // NOLINTNEXTLINE(modernize-use-constraints)
350  template <typename SecurityConfigT = Security::Config>
351  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
353 
354  /**
355  * @brief Constructs a @c SecurityClient from a typed configuration object.
356  *
357  * @tparam ConfT Configuration type derived from @c Conf.
358  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
359  * @param conf Populated configuration aggregate.
360  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
361  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
362  */
363  // NOLINTNEXTLINE(modernize-use-constraints)
364  template <typename ConfT, typename SecurityConfigT = Security::Config,
365  typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
366  explicit SecurityClient(const ConfT& conf, SecurityConfigT&& sec_cfg = {}, InitType type = InitType::kWithInit);
367 
368  /**
369  * @brief Constructs a @c SecurityClient from a URL string and installs the security configuration.
370  *
371  * @details
372  * Builds the base @c Client in @c kWithoutInit mode, installs @p sec_cfg
373  * via @c enable_security(), then calls @c init() unless deferred. When
374  * @c enable_security() fails to produce a usable @c NodeImpl::security the
375  * subsequent @c init() will fail.
376  *
377  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
378  * @param url_str Service URL string.
379  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
380  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
381  */
382  // NOLINTNEXTLINE(modernize-use-constraints)
383  template <typename SecurityConfigT = Security::Config>
384  explicit SecurityClient(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
386 };
387 
388 } // namespace vlink
389 
390 #include "./internal/client-inl.h"
Transport-neutral backbone shared by every method-model client implementation.
Pool-backed type-erased callables: copyable vlink::Function and move-only vlink::MoveFunction.
Common CRTP base for every VLink communication primitive.