Sending data to felix-toflx =========================== Various ``send_data`` functions are provided to send data to felix-toflx. felix-toflx is publishing the necessary parameters to establish network connections on the bus. Before data can be sent, the connection to felix-toflx must be established calling ``init_send_data``. felix-client will re-use existing connections if possible. If a connection is lost (e.g. because felix-toflx went down), the client will automatically attempt to re-establish the connection. There is no function to close connections. Synchronous vs asynchronous operations -------------------------------------- Connections can be established synchronously or asynchronously. The synchronous functions block until the connection is established or a timeout occurs. The asynchronous functions return immediately and informs the user using a callback. Sending data is asynchronous by default. Establishing a connection ------------------------- Connections are established using the :cpp:func:`init_send_data` (synchronous) or :cpp:func:`init_send_data_nb` (asynchronous) function. .. doxygenfunction:: FelixClientThreadExtension520::init_send_data(std::uint64_t, std::chrono::milliseconds) :no-link: .. doxygenfunction:: FelixClientThreadExtension520::init_send_data_nb(std::uint64_t) :no-link: Both functions can report a :cpp:class:`felix::BusException` if the lookup in the bus failed. The underlying reason is reported by the exception and can be retrieved by calling :cpp:func:`get_underlying_message`. The synchronous function can also report a :cpp:class:`felix::SendConnectionTimeoutException` if the connection could not be established within the timeout and a :cpp:class:`felix::SendConnectionRefusedException` if the connection was refused by the server. The asynchronous function should call one of the two callbacks :cpp:type:`This callback` or :cpp:type:`This callback` to inform the user about the result of the connection. .. doxygentypedef:: FelixClientThreadInterface::OnConnectCallback :no-link: :outline: .. doxygentypedef:: FelixClientThreadExtension520::OnConnectionRefusedCallback :no-link: :outline: .. tip:: As for subscriptions, it is recommended to use the synchronous function to establish a connection. The asynchronous should only be used if you have a good reason to do so. However, conpared to subscriptions, handling the asynchronous case is much easier since only a connection needs to be established which should always result in one of the two callbacks being called. The connection is either established or refused. For subscriptions, messages need to be exchanged over the network which has an inherent chance to fail and not cause any callback to be called. .. caution:: If you use asynchronous connections, make sure that callbacks are not blocking. Sending data ------------ .. warning:: Calling ``send_data`` without establishing a connection first is deprecated and will be removed in a future release. There are two functions to send data to felix-toflx. The :cpp:func:`first function data, bool flush)>` sends a single message, the :cpp:func:`second one> &msgs)>` sends a list of messages. .. doxygenfunction:: FelixClientThreadExtension520::send_data(std::uint64_t fid, std::span data, bool flush) :no-link: .. doxygenfunction:: FelixClientThreadExtension520::send_data(std::uint64_t fid, const std::vector> &msgs) :no-link: Both functions take the FID of the stream to send data to and the data to be sent as a span (essentially a pointer with a size). The first function also takes a boolean parameter ``flush``. If ``flush`` is set to true, the data is sent immediately. The second function assumes flushes automatically. .. tip:: The :cpp:func:`second one> &msgs)>` function taking multiple messages ensures that the messages are sent in the same network transaction to avoid issues if message timing is important. Both functions make a copy of the data to be sent. Therefore, the user can safely modify or delete the data after the function has returned. Both functions can throw four exceptions indicating failure. If the function returns without exception, the data will be sent: 1. :cpp:class:`felix::SendWhileConnectionDownException`: the connection to felix-toflx is currently down. It was established at some point, but was lost subsequently. The reason might be that the server is down. felix-client will attempt to reconnect automatically. 2. :cpp:class:`felix::ResourceNotAvailableException`: no network resources are available to send the data. This can happen data is sent too fast. The user should wait a bit and try again. If the issue persists, there might be an issue with the server. 3. :cpp:class:`felix::MessageTooBigException`: the message is too big to be sent. The maximum size of a message is defined by the server and specified in the bus. Change the command line parameters of felix-toflx to increase the buffer size. 4. :cpp:class:`felix::BusException`: the lookup in the bus failed. .. caution:: Once the deprecated behavior that ``send_data`` can be called without establishing a connection first is removed, the function will throw a :cpp:class:`felix::SendBeforeOpenException` if the connection was not established before calling ``send_data``. Send methods ............ The send method is determined by the settings in the bus file which are determined by the felix-toflx command line parameters. In general, RDMA relies on the allocation of network buffers to send data while TCP tries to optimize the memory consumption by allocating memory in demand when using unbuffered sending. Here is a list of the possible send methods: - ``buffered, TCP/RDMA, single message``: Copy data into pre-allocated buffers - ``buffered, TCP/RDMA, multiple messages``: Copy data into pre-allocated buffers. Flush buffer at the end. Data might be split into multiple network transactions if they do not fit into the current buffer. - ``unbuffered, TCP, single message``: Allocate memory on demand. Copy data into the allocated memory. Then send it. - ``unbuffered, TCP, multiple messages``: Allocate memory on demand. Copy data into the allocated memory. Flush buffer at the end. Data is guaranteed to be sent in the same network transaction. - ``unbuffered, RDMA, single message``: Copy data into pre-allocated buffers. Flush buffer. - ``unbuffered, RDMA, multiple messages``: Copy data into pre-allocated buffers. Flush buffer at the end. Data is guaranteed to be sent in the same network transaction. Closing send connections ------------------------ There is no function to close the connection to felix-toflx. The connection is closed automatically when the felix-client-thread instance is destroyed. Deprecated API -------------- Calling ``send_data`` without establishing a connection first is deprecated and will be removed in a future release. All ``send_data`` functions taking a pair of ``const uint8_t*`` and ``size_t`` are deprecated. All non-blocking ``send_data`` functions are deprecated. ``send_data`` is now always non-blocking in the sense of the previous non-blocking functions. Example ------- .. code-block:: cpp :linenos: try { fct.init_send_data(fid, 1000ms); } catch (const felix::FelixClientException& e) { std::println("Failed to establish connection: {}", e.what()); return; } const auto data = std::vector{0, 1, 2, 3, 4, 5}; try { client.send_data(fid, data, true); } catch (const felix::FelixClientException& e) { std::println("Failed to send data: {}", e.what()); return; } (Incomplete) example using asynchronous connection establishment (exception handling omitted): .. code-block:: cpp :linenos: const auto fid = 0x1000000000080000; std::atomic_bool connected{false}; void on_connect_callback(const std::uint64_t fid_connected) { std::println("Connected to FID: {:#x}", fid_connected); if (fid_connected == fid) { connected = true; } } fct.init_send_data_nb(fid); connected.wait(false); // Questions: // - What about timeout? // - Cancel wait if the connection was refused? // - If using a timeout, what happens if the connection is established after the timeout? const auto data = std::vector{0, 1, 2, 3, 4, 5}; client.send_data(fid, data, true);