| @@ -0,0 +1,190 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2017 - ROLI Ltd. | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| The code included in this file is provided under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||||
| without fee is hereby granted provided that the above copyright notice and | |||||
| this permission notice appear in all copies. | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| NetworkServiceDiscovery::Advertiser::Advertiser (const String& serviceTypeUID, | |||||
| const String& serviceDescription, | |||||
| int broadcastPortToUse, int connectionPort, | |||||
| RelativeTime minTimeBetweenBroadcasts) | |||||
| : Thread ("Discovery_broadcast"), | |||||
| message (serviceTypeUID), broadcastPort (broadcastPortToUse), | |||||
| minInterval (minTimeBetweenBroadcasts) | |||||
| { | |||||
| message.setAttribute ("id", Uuid().toString()); | |||||
| message.setAttribute ("name", serviceDescription); | |||||
| message.setAttribute ("address", String()); | |||||
| message.setAttribute ("port", connectionPort); | |||||
| startThread (2); | |||||
| } | |||||
| NetworkServiceDiscovery::Advertiser::~Advertiser() | |||||
| { | |||||
| stopThread (2000); | |||||
| socket.shutdown(); | |||||
| } | |||||
| void NetworkServiceDiscovery::Advertiser::run() | |||||
| { | |||||
| if (! socket.bindToPort (0)) | |||||
| { | |||||
| jassertfalse; | |||||
| return; | |||||
| } | |||||
| while (! threadShouldExit()) | |||||
| { | |||||
| sendBroadcast(); | |||||
| wait ((int) minInterval.inMilliseconds()); | |||||
| } | |||||
| } | |||||
| void NetworkServiceDiscovery::Advertiser::sendBroadcast() | |||||
| { | |||||
| auto localAddress = IPAddress::getLocalAddress(); | |||||
| message.setAttribute ("address", localAddress.toString()); | |||||
| auto broadcastAddress = IPAddress::getInterfaceBroadcastAddress (localAddress); | |||||
| auto data = message.createDocument ({}, true, false); | |||||
| socket.write (broadcastAddress.toString(), broadcastPort, data.toRawUTF8(), (int) data.getNumBytesAsUTF8()); | |||||
| } | |||||
| //============================================================================== | |||||
| NetworkServiceDiscovery::AvailableServiceList::AvailableServiceList (const String& serviceType, int broadcastPort) | |||||
| : Thread ("Discovery_listen"), serviceTypeUID (serviceType) | |||||
| { | |||||
| socket.bindToPort (broadcastPort); | |||||
| startThread (2); | |||||
| } | |||||
| NetworkServiceDiscovery::AvailableServiceList::~AvailableServiceList() | |||||
| { | |||||
| socket.shutdown(); | |||||
| stopThread (2000); | |||||
| } | |||||
| void NetworkServiceDiscovery::AvailableServiceList::run() | |||||
| { | |||||
| while (! threadShouldExit()) | |||||
| { | |||||
| if (socket.waitUntilReady (true, 200) == 1) | |||||
| { | |||||
| char buffer[1024]; | |||||
| auto bytesRead = socket.read (buffer, sizeof (buffer) - 1, false); | |||||
| if (bytesRead > 10) | |||||
| if (auto xml = parseXML (String (CharPointer_UTF8 (buffer), | |||||
| CharPointer_UTF8 (buffer + bytesRead)))) | |||||
| if (xml->hasTagName (serviceTypeUID)) | |||||
| handleMessage (*xml); | |||||
| } | |||||
| removeTimedOutServices(); | |||||
| } | |||||
| } | |||||
| std::vector<NetworkServiceDiscovery::Service> NetworkServiceDiscovery::AvailableServiceList::getServices() const | |||||
| { | |||||
| const ScopedLock sl (listLock); | |||||
| auto listCopy = services; | |||||
| return listCopy; | |||||
| } | |||||
| void NetworkServiceDiscovery::AvailableServiceList::handleAsyncUpdate() | |||||
| { | |||||
| if (onChange != nullptr) | |||||
| onChange(); | |||||
| } | |||||
| void NetworkServiceDiscovery::AvailableServiceList::handleMessage (const XmlElement& xml) | |||||
| { | |||||
| Service service; | |||||
| service.instanceID = xml.getStringAttribute ("id"); | |||||
| if (service.instanceID.trim().isNotEmpty()) | |||||
| { | |||||
| service.description = xml.getStringAttribute ("name"); | |||||
| service.address = IPAddress (xml.getStringAttribute ("address")); | |||||
| service.port = xml.getIntAttribute ("port"); | |||||
| service.lastSeen = Time::getCurrentTime(); | |||||
| handleMessage (service); | |||||
| } | |||||
| } | |||||
| static void sortServiceList (std::vector<NetworkServiceDiscovery::Service>& services) | |||||
| { | |||||
| auto compareServices = [] (const NetworkServiceDiscovery::Service& s1, | |||||
| const NetworkServiceDiscovery::Service& s2) | |||||
| { | |||||
| return s1.instanceID < s2.instanceID; | |||||
| }; | |||||
| std::sort (services.begin(), services.end(), compareServices); | |||||
| } | |||||
| void NetworkServiceDiscovery::AvailableServiceList::handleMessage (const Service& service) | |||||
| { | |||||
| const ScopedLock sl (listLock); | |||||
| for (auto& s : services) | |||||
| { | |||||
| if (s.instanceID == service.instanceID) | |||||
| { | |||||
| if (s.description != service.description | |||||
| || s.address != service.address | |||||
| || s.port != service.port) | |||||
| { | |||||
| s = service; | |||||
| triggerAsyncUpdate(); | |||||
| } | |||||
| s.lastSeen = service.lastSeen; | |||||
| return; | |||||
| } | |||||
| } | |||||
| services.push_back (service); | |||||
| sortServiceList (services); | |||||
| triggerAsyncUpdate(); | |||||
| } | |||||
| void NetworkServiceDiscovery::AvailableServiceList::removeTimedOutServices() | |||||
| { | |||||
| const double timeoutSeconds = 5.0; | |||||
| auto oldestAllowedTime = Time::getCurrentTime() - RelativeTime::seconds (timeoutSeconds); | |||||
| const ScopedLock sl (listLock); | |||||
| auto oldEnd = std::end (services); | |||||
| auto newEnd = std::remove_if (std::begin (services), oldEnd, | |||||
| [=] (const Service& s) { return s.lastSeen < oldestAllowedTime; }); | |||||
| if (newEnd != oldEnd) | |||||
| { | |||||
| services.erase (newEnd, oldEnd); | |||||
| triggerAsyncUpdate(); | |||||
| } | |||||
| } | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,126 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2017 - ROLI Ltd. | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| The code included in this file is provided under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||||
| without fee is hereby granted provided that the above copyright notice and | |||||
| this permission notice appear in all copies. | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| /** | |||||
| Contains classes that implement a simple protocol for broadcasting the availability | |||||
| and location of a discoverable service on the local network, and for maintaining a | |||||
| list of known services. | |||||
| */ | |||||
| struct NetworkServiceDiscovery | |||||
| { | |||||
| /** An object which runs a thread to repeatedly broadcast the existence of a | |||||
| discoverable service. | |||||
| To use, simply create an instance of an Advertiser and it'll broadcast until | |||||
| you delete it. | |||||
| */ | |||||
| struct Advertiser : private Thread | |||||
| { | |||||
| /** Creates and starts an Advertiser thread, broadcasting with the given properties. | |||||
| @param serviceTypeUID A user-supplied string to define the type of service this represents | |||||
| @param serviceDescription A description string that will appear in the Service::description field for clients | |||||
| @param broadcastPort The port number on which to broadcast the service discovery packets | |||||
| @param connectionPort The port number that will be sent to appear in the Service::port field | |||||
| @param minTimeBetweenBroadcasts The interval to wait between sending broadcast messages | |||||
| */ | |||||
| Advertiser (const String& serviceTypeUID, | |||||
| const String& serviceDescription, | |||||
| int broadcastPort, | |||||
| int connectionPort, | |||||
| RelativeTime minTimeBetweenBroadcasts = RelativeTime::seconds (1.5)); | |||||
| /** Destructor */ | |||||
| ~Advertiser(); | |||||
| private: | |||||
| XmlElement message; | |||||
| const int broadcastPort; | |||||
| const RelativeTime minInterval; | |||||
| DatagramSocket socket { true }; | |||||
| void run() override; | |||||
| void sendBroadcast(); | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Contains information about a service that has been found on the network. | |||||
| @see AvailableServiceList, Advertiser | |||||
| */ | |||||
| struct Service | |||||
| { | |||||
| String instanceID; /**< A UUID that identifies the particular instance of the Advertiser class. */ | |||||
| String description; /**< The service description as sent by the Advertiser */ | |||||
| IPAddress address; /**< The IP address of the advertiser */ | |||||
| int port; /**< The port number of the advertiser */ | |||||
| Time lastSeen; /**< The time of the last ping received from the advertiser */ | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Watches the network for broadcasts from Advertiser objects, and keeps a list of | |||||
| all the currently active instances. | |||||
| Just create an instance of AvailableServiceList and it will start listening - you | |||||
| can register a callback with its onChange member to find out when services | |||||
| appear/disappear, and you can call getServices() to find out the current list. | |||||
| @see Service, Advertiser | |||||
| */ | |||||
| struct AvailableServiceList : private Thread, | |||||
| private AsyncUpdater | |||||
| { | |||||
| /** Creates an AvailableServiceList that will bind to the given port number and watch | |||||
| the network for Advertisers broadcasting the given service type. | |||||
| This will only detect broadcasts from an Advertiser object with a matching | |||||
| serviceTypeUID value, and where the broadcastPort matches. | |||||
| */ | |||||
| AvailableServiceList (const String& serviceTypeUID, int broadcastPort); | |||||
| /** Destructor */ | |||||
| ~AvailableServiceList(); | |||||
| /** A lambda that can be set to recieve a callback when the list changes */ | |||||
| std::function<void()> onChange; | |||||
| /** Returns a list of the currently known services. */ | |||||
| std::vector<Service> getServices() const; | |||||
| private: | |||||
| DatagramSocket socket { true }; | |||||
| String serviceTypeUID; | |||||
| CriticalSection listLock; | |||||
| std::vector<Service> services; | |||||
| void run() override; | |||||
| void handleAsyncUpdate() override; | |||||
| void handleMessage (const XmlElement&); | |||||
| void handleMessage (const Service&); | |||||
| void removeTimedOutServices(); | |||||
| }; | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -66,6 +66,7 @@ | |||||
| #include "interprocess/juce_InterprocessConnection.cpp" | #include "interprocess/juce_InterprocessConnection.cpp" | ||||
| #include "interprocess/juce_InterprocessConnectionServer.cpp" | #include "interprocess/juce_InterprocessConnectionServer.cpp" | ||||
| #include "interprocess/juce_ConnectedChildProcess.cpp" | #include "interprocess/juce_ConnectedChildProcess.cpp" | ||||
| #include "interprocess/juce_NetworkServiceDiscovery.cpp" | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MAC || JUCE_IOS | #if JUCE_MAC || JUCE_IOS | ||||
| @@ -81,6 +81,7 @@ | |||||
| #include "interprocess/juce_InterprocessConnection.h" | #include "interprocess/juce_InterprocessConnection.h" | ||||
| #include "interprocess/juce_InterprocessConnectionServer.h" | #include "interprocess/juce_InterprocessConnectionServer.h" | ||||
| #include "interprocess/juce_ConnectedChildProcess.h" | #include "interprocess/juce_ConnectedChildProcess.h" | ||||
| #include "interprocess/juce_NetworkServiceDiscovery.h" | |||||
| #if JUCE_LINUX | #if JUCE_LINUX | ||||
| #include "native/juce_linux_EventLoop.h" | #include "native/juce_linux_EventLoop.h" | ||||