| @@ -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_InterprocessConnectionServer.cpp" | |||
| #include "interprocess/juce_ConnectedChildProcess.cpp" | |||
| #include "interprocess/juce_NetworkServiceDiscovery.cpp" | |||
| //============================================================================== | |||
| #if JUCE_MAC || JUCE_IOS | |||
| @@ -81,6 +81,7 @@ | |||
| #include "interprocess/juce_InterprocessConnection.h" | |||
| #include "interprocess/juce_InterprocessConnectionServer.h" | |||
| #include "interprocess/juce_ConnectedChildProcess.h" | |||
| #include "interprocess/juce_NetworkServiceDiscovery.h" | |||
| #if JUCE_LINUX | |||
| #include "native/juce_linux_EventLoop.h" | |||