* @brief A minimal RPC @b client using LibC functionality to setup the UDP socket, * and synchronous blocking POSIX calls to send and receive packets - * @b one at a time! */
| 6799 | * @b one at a time! |
| 6800 | */ |
| 6801 | class rpc_libc_client { |
| 6802 | int socket_descriptor_; |
| 6803 | sockaddr_in server_address_; |
| 6804 | std::size_t concurrency_; |
| 6805 | |
| 6806 | public: |
| 6807 | rpc_libc_client(std::string const &server_address_str, std::uint16_t port, std::size_t concurrency) |
| 6808 | : concurrency_(concurrency) { |
| 6809 | |
| 6810 | auto [socket_descriptor, server_address] = rpc_client_socket(server_address_str, port); |
| 6811 | socket_descriptor_ = socket_descriptor; |
| 6812 | server_address_ = server_address; |
| 6813 | |
| 6814 | // Let's make sure we don't block forever on `recvfrom` |
| 6815 | struct timeval duration; |
| 6816 | duration.tv_sec = 0; |
| 6817 | duration.tv_usec = to_microseconds(rpc_batch_timeout_k).count(); |
| 6818 | if (setsockopt(socket_descriptor_, SOL_SOCKET, SO_RCVTIMEO, &duration, sizeof(duration)) < 0) |
| 6819 | raise_system_error("Failed to set sockets batch timeout"); |
| 6820 | } |
| 6821 | |
| 6822 | ~rpc_libc_client() noexcept { close(socket_descriptor_); } |
| 6823 | |
| 6824 | rpc_batch_result operator()() noexcept { |
| 6825 | rpc_batch_result result; |
| 6826 | |
| 6827 | sockaddr_in reply_addr; |
| 6828 | socklen_t reply_len = sizeof(reply_addr); |
| 6829 | rpc_buffer_t send_buffer, receive_buffer; |
| 6830 | send_buffer.fill('X'); |
| 6831 | |
| 6832 | for (std::size_t i = 0; i < concurrency_; ++i) { |
| 6833 | auto send_time = std::chrono::steady_clock::now(); |
| 6834 | ssize_t sent_length = sendto(socket_descriptor_, send_buffer.data(), send_buffer.size(), 0, |
| 6835 | reinterpret_cast<sockaddr *>(&server_address_), sizeof(server_address_)); |
| 6836 | result.sent_packets++; |
| 6837 | if (sent_length <= 0) continue; |
| 6838 | |
| 6839 | // In general, `select` is used to monitor multiple file descriptors or sockets at once |
| 6840 | // to see if they are ready for I/O, but in this case we use it to constrain the time |
| 6841 | // we are willing to wait for a single response. |
| 6842 | struct timeval expiry; |
| 6843 | expiry.tv_sec = 0; |
| 6844 | expiry.tv_usec = to_microseconds(rpc_packet_timeout_k).count(); |
| 6845 | fd_set available_descriptors; |
| 6846 | FD_ZERO(&available_descriptors); |
| 6847 | FD_SET(socket_descriptor_, &available_descriptors); |
| 6848 | if (select(socket_descriptor_ + 1, &available_descriptors, nullptr, nullptr, &expiry) <= 0) continue; |
| 6849 | |
| 6850 | ssize_t received_length = recvfrom(socket_descriptor_, receive_buffer.data(), receive_buffer.size(), 0, |
| 6851 | reinterpret_cast<sockaddr *>(&reply_addr), &reply_len); |
| 6852 | if (received_length <= 0) continue; |
| 6853 | auto response_time = std::chrono::steady_clock::now(); |
| 6854 | auto diff = response_time - send_time; |
| 6855 | result.batch_latency += diff; |
| 6856 | result.max_packet_latency = std::max(result.max_packet_latency, diff); |
| 6857 | result.received_packets++; |
| 6858 | } |
nothing calls this directly
no outgoing calls
no test coverage detected