diff --git a/include/networktables/NetworkTable.h b/include/networktables/NetworkTable.h index 5d8e9b7762..8ee47a2e2c 100644 --- a/include/networktables/NetworkTable.h +++ b/include/networktables/NetworkTable.h @@ -26,7 +26,7 @@ class NetworkTable : public ITable { typedef std::pair Listener; std::vector m_listeners; - static std::string s_ip_address; + static std::vector s_ip_addresses; static std::string s_persistent_filename; static bool s_client; static bool s_running; @@ -74,6 +74,12 @@ class NetworkTable : public ITable { */ static void SetIPAddress(llvm::StringRef address); + /** + * @param addresses the addresses that network tables will connect to in + * client mode (in round robin order) + */ + static void SetIPAddress(llvm::ArrayRef addresses); + /** * @param port the port number that network tables will connect to in client * mode or listen to in server mode diff --git a/include/ntcore_c.h b/include/ntcore_c.h index 52b06c0d33..634380c314 100644 --- a/include/ntcore_c.h +++ b/include/ntcore_c.h @@ -353,6 +353,19 @@ void NT_StopServer(void); */ void NT_StartClient(const char *server_name, unsigned int port); +/** Starts Client + * Starts a client using the specified (server, port) combinations. The + * client will attempt to connect to each server in round robin fashion. + * + * @param count length of the server_names and ports arrays + * @param server_names array of server names (each a UTF-8 string, null + * terminated) + * @param ports array of ports to communicate over (one for each server) + * + */ +void NT_StartClientMulti(size_t count, const char **server_names, + const unsigned int *ports); + /** Stop Client * Stops the client if it is running. */ diff --git a/include/ntcore_cpp.h b/include/ntcore_cpp.h index 5fdff76b45..7ed5140c72 100644 --- a/include/ntcore_cpp.h +++ b/include/ntcore_cpp.h @@ -234,6 +234,7 @@ void StartServer(StringRef persist_filename, const char* listen_address, unsigned int port); void StopServer(); void StartClient(const char* server_name, unsigned int port); +void StartClient(ArrayRef> servers); void StopClient(); void StopRpcServer(); void StopNotifier(); diff --git a/java/lib/NetworkTablesJNI.cpp b/java/lib/NetworkTablesJNI.cpp index 3fa06e4a6d..f425433811 100644 --- a/java/lib/NetworkTablesJNI.cpp +++ b/java/lib/NetworkTablesJNI.cpp @@ -25,6 +25,7 @@ static jclass connectionInfoCls = nullptr; static jclass entryInfoCls = nullptr; static jclass keyNotDefinedEx = nullptr; static jclass persistentEx = nullptr; +static jclass illegalArgEx = nullptr; // Thread-attached environment for listener callbacks. static JNIEnv *listenerEnv = nullptr; @@ -102,6 +103,12 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { if (!persistentEx) return JNI_ERR; env->DeleteLocalRef(local); + local = env->FindClass("java/lang/IllegalArgumentException"); + if (!local) return JNI_ERR; + illegalArgEx = static_cast(env->NewGlobalRef(local)); + if (!illegalArgEx) return JNI_ERR; + env->DeleteLocalRef(local); + // Initial configuration of listener start/exit nt::SetListenerOnStart(ListenerOnStart); nt::SetListenerOnExit(ListenerOnExit); @@ -121,6 +128,7 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { if (entryInfoCls) env->DeleteGlobalRef(entryInfoCls); if (keyNotDefinedEx) env->DeleteGlobalRef(keyNotDefinedEx); if (persistentEx) env->DeleteGlobalRef(persistentEx); + if (illegalArgEx) env->DeleteGlobalRef(illegalArgEx); jvm = nullptr; } @@ -1196,12 +1204,48 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI * Method: startClient * Signature: (Ljava/lang/String;I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient__Ljava_lang_String_2I (JNIEnv *env, jclass, jstring serverName, jint port) { nt::StartClient(JavaStringRef(env, serverName).c_str(), port); } +/* + * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Method: startClient + * Signature: ([Ljava/lang/String;[I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient___3Ljava_lang_String_2_3I + (JNIEnv *env, jclass, jobjectArray serverNames, jintArray ports) +{ + int len = env->GetArrayLength(serverNames); + if (len != env->GetArrayLength(ports)) { + env->ThrowNew(illegalArgEx, + "serverNames and ports arrays must be the same size"); + return; + } + jint* portInts = env->GetIntArrayElements(ports, nullptr); + if (!portInts) return; + + std::vector names; + std::vector> servers; + names.reserve(len); + servers.reserve(len); + for (int i = 0; i < len; ++i) { + JavaLocal elem( + env, static_cast(env->GetObjectArrayElement(serverNames, i))); + if (!elem) { + env->ThrowNew(illegalArgEx, "null string in serverNames"); + return; + } + names.emplace_back(JavaStringRef(env, elem).str()); + servers.emplace_back(std::make_pair(nt::StringRef(names.back()), + portInts[i])); + } + env->ReleaseIntArrayElements(ports, portInts, JNI_ABORT); + nt::StartClient(servers); +} + /* * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI * Method: stopClient diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java index c3752625dc..ad1a876e8b 100644 --- a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java +++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java @@ -23,7 +23,7 @@ public class NetworkTable implements ITable, IRemote { private static boolean client = false; private static boolean running = false; private static int port = DEFAULT_PORT; - private static String ipAddress = ""; + private static String[] ipAddresses = new String[0]; private static String persistentFilename = "networktables.ini"; private synchronized static void checkInit() { @@ -38,9 +38,12 @@ public class NetworkTable implements ITable, IRemote { public synchronized static void initialize() { if (running) shutdown(); - if (client) - NetworkTablesJNI.startClient(ipAddress, port); - else + if (client) { + int[] ports = new int[ipAddresses.length]; + for (int i=0; i> servers) { + std::vector connectors; + for (const auto& server : servers) { + std::string server_name(server.first); + unsigned int port = server.second; + connectors.emplace_back([=]() -> std::unique_ptr { + return TCPConnector::connect(server_name.c_str(), + static_cast(port), 1); + }); + } + DispatcherBase::StartClient(std::move(connectors)); +} + Dispatcher::Dispatcher() : Dispatcher(Storage::GetInstance(), Notifier::GetInstance()) {} @@ -80,22 +94,26 @@ void DispatcherBase::StartServer(StringRef persist_filename, m_clientserver_thread = std::thread(&Dispatcher::ServerThreadMain, this); } -void DispatcherBase::StartClient( - std::function()> connect) { +void DispatcherBase::StartClient(Connector connector) { + std::vector connectors; + connectors.push_back(connector); + StartClient(std::move(connectors)); +} + +void DispatcherBase::StartClient(std::vector&& connectors) { { std::lock_guard lock(m_user_mutex); if (m_active) return; m_active = true; + m_client_connectors = std::move(connectors); } m_server = false; - using namespace std::placeholders; m_storage.SetOutgoing(std::bind(&Dispatcher::QueueOutgoing, this, _1, _2, _3), m_server); m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this); - m_clientserver_thread = - std::thread(&Dispatcher::ClientThreadMain, this, connect); + m_clientserver_thread = std::thread(&Dispatcher::ClientThreadMain, this); } void DispatcherBase::Stop() { @@ -105,6 +123,10 @@ void DispatcherBase::Stop() { m_flush_cv.notify_one(); // wake up client thread with a reconnect + { + std::lock_guard lock(m_user_mutex); + m_client_connectors.resize(0); + } ClientReconnect(); // wake up server thread by shutting down the socket @@ -287,11 +309,20 @@ void DispatcherBase::ServerThreadMain() { } } -void DispatcherBase::ClientThreadMain( - std::function()> connect) { +void DispatcherBase::ClientThreadMain() { + std::size_t i = 0; while (m_active) { // sleep between retries - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + Connector connect; + + // get next server to connect to + { + std::lock_guard lock(m_user_mutex); + if (m_client_connectors.empty()) continue; + if (i >= m_client_connectors.size()) i = 0; + connect = m_client_connectors[i++]; + } // try to connect (with timeout) DEBUG("client trying to connect"); diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 26f5e76328..12e5507667 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -32,11 +32,14 @@ namespace nt { class DispatcherBase { friend class DispatcherTest; public: + typedef std::function()> Connector; + virtual ~DispatcherBase(); void StartServer(StringRef persist_filename, std::unique_ptr acceptor); - void StartClient(std::function()> connect); + void StartClient(Connector connector); + void StartClient(std::vector&& connectors); void Stop(); void SetUpdateRate(double interval); void SetIdentity(llvm::StringRef name); @@ -55,8 +58,7 @@ class DispatcherBase { private: void DispatchThreadMain(); void ServerThreadMain(); - void ClientThreadMain( - std::function()> connect); + void ClientThreadMain(); bool ClientHandshake( NetworkConnection& conn, @@ -80,6 +82,7 @@ class DispatcherBase { std::thread m_clientserver_thread; std::unique_ptr m_server_acceptor; + std::vector m_client_connectors; // Mutex for user-accessible items mutable std::mutex m_user_mutex; @@ -112,6 +115,7 @@ class Dispatcher : public DispatcherBase { void StartServer(StringRef persist_filename, const char* listen_address, unsigned int port); void StartClient(const char* server_name, unsigned int port); + void StartClient(ArrayRef> servers); private: Dispatcher(); diff --git a/src/networktables/NetworkTable.cpp b/src/networktables/NetworkTable.cpp index 267da1f085..23a089725c 100644 --- a/src/networktables/NetworkTable.cpp +++ b/src/networktables/NetworkTable.cpp @@ -11,7 +11,7 @@ using llvm::StringRef; const char NetworkTable::PATH_SEPARATOR_CHAR = '/'; -std::string NetworkTable::s_ip_address; +std::vector NetworkTable::s_ip_addresses; std::string NetworkTable::s_persistent_filename = "networktables.ini"; bool NetworkTable::s_client = false; bool NetworkTable::s_running = false; @@ -19,9 +19,13 @@ unsigned int NetworkTable::s_port = NT_DEFAULT_PORT; void NetworkTable::Initialize() { if (s_running) Shutdown(); - if (s_client) - nt::StartClient(s_ip_address.c_str(), s_port); - else + if (s_client) { + std::vector> servers; + servers.reserve(s_ip_addresses.size()); + for (const auto& ip_address : s_ip_addresses) + servers.emplace_back(std::make_pair(ip_address, s_port)); + nt::StartClient(servers); + } else nt::StartServer(s_persistent_filename, "", s_port); s_running = true; } @@ -50,7 +54,14 @@ void NetworkTable::SetTeam(int team) { SetIPAddress(tmp); } -void NetworkTable::SetIPAddress(StringRef address) { s_ip_address = address; } +void NetworkTable::SetIPAddress(StringRef address) { + s_ip_addresses.clear(); + s_ip_addresses.emplace_back(address); +} + +void NetworkTable::SetIPAddress(llvm::ArrayRef addresses) { + s_ip_addresses = addresses; +} void NetworkTable::SetPort(unsigned int port) { s_port = port; } diff --git a/src/ntcore_c.cpp b/src/ntcore_c.cpp index 05a3aa1999..15e87850a0 100644 --- a/src/ntcore_c.cpp +++ b/src/ntcore_c.cpp @@ -345,6 +345,15 @@ void NT_StartClient(const char *server_name, unsigned int port) { nt::StartClient(server_name, port); } +void NT_StartClientMulti(size_t count, const char **server_names, + const unsigned int *ports) { + std::vector> servers; + servers.reserve(count); + for (size_t i=0; i> servers) { + Dispatcher::GetInstance().StartClient(servers); +} + void StopClient() { Dispatcher::GetInstance().Stop(); }