18 #ifndef GZ_TRANSPORT_DISCOVERY_HH_
19 #define GZ_TRANSPORT_DISCOVERY_HH_
33 #include <sys/types.h>
35 #include <sys/socket.h>
39 #include <arpa/inet.h>
43 #include <netinet/in.h>
49 #pragma warning(push, 0)
54 #pragma warning(disable: 4503)
56 #pragma warning(disable: 4996)
59 #include <gz/msgs/discovery.pb.h>
62 #include <condition_variable>
71 #include <gz/msgs/Utility.hh>
73 #include "gz/transport/config.hh"
74 #include "gz/transport/Export.hh"
86 inline namespace IGNITION_TRANSPORT_VERSION_NAMESPACE {
116 template<
typename Pub>
128 const bool _verbose =
false)
129 : multicastGroup(_ip),
133 silenceInterval(kDefSilenceInterval),
134 activityInterval(kDefActivityInterval),
135 heartbeatInterval(kDefHeartbeatInterval),
136 connectionCb(nullptr),
137 disconnectionCb(nullptr),
140 numHeartbeatsUninitialized(0),
145 if (
env(
"IGN_IP", ignIp) && !ignIp.
empty())
146 this->hostInterfaces = {ignIp};
154 WORD wVersionRequested;
158 wVersionRequested = MAKEWORD(2, 2);
160 if (WSAStartup(wVersionRequested, &wsaData) != 0)
166 for (
const auto &netIface : this->hostInterfaces)
168 auto succeed = this->RegisterNetIface(netIface);
173 if (netIface == this->hostAddr && !succeed)
175 this->RegisterNetIface(
"127.0.0.1");
176 std::cerr <<
"Did you set the environment variable IGN_IP with a "
178 <<
" [" << netIface <<
"] seems an invalid local IP "
180 <<
" Using 127.0.0.1 as hostname." <<
std::endl;
181 this->hostAddr =
"127.0.0.1";
190 if (setsockopt(this->sockets.at(0), SOL_SOCKET, SO_REUSEADDR,
191 reinterpret_cast<const char *
>(&reuseAddr),
sizeof(reuseAddr)) != 0)
193 std::cerr <<
"Error setting socket option (SO_REUSEADDR)."
205 if (setsockopt(this->sockets.at(0), SOL_SOCKET, SO_REUSEPORT,
206 reinterpret_cast<const char *
>(&reusePort),
sizeof(reusePort)) != 0)
208 std::cerr <<
"Error setting socket option (SO_REUSEPORT)."
214 sockaddr_in localAddr;
215 memset(&localAddr, 0,
sizeof(localAddr));
216 localAddr.sin_family = AF_INET;
217 localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
218 localAddr.sin_port = htons(
static_cast<u_short
>(this->port));
220 if (bind(this->sockets.at(0),
221 reinterpret_cast<sockaddr *
>(&localAddr),
sizeof(sockaddr_in)) < 0)
228 memset(&this->mcastAddr, 0,
sizeof(this->mcastAddr));
229 this->mcastAddr.sin_family = AF_INET;
230 this->mcastAddr.sin_addr.s_addr =
231 inet_addr(this->multicastGroup.c_str());
232 this->mcastAddr.sin_port = htons(
static_cast<u_short
>(this->port));
236 if (
env(
"IGN_RELAY", ignRelay) && !ignRelay.
empty())
242 for (
auto const &relayAddr : relays)
243 this->AddRelayAddress(relayAddr);
246 this->PrintCurrentState();
253 this->exitMutex.lock();
255 this->exitMutex.unlock();
258 if (this->threadReception.joinable())
259 this->threadReception.join();
267 for (
const auto &sock : this->sockets)
290 this->enabled =
true;
294 this->timeNextHeartbeat = now;
295 this->timeNextActivity = now;
298 this->threadReception =
std::thread(&Discovery::RecvMessages,
this);
314 if (!this->info.AddPublisher(_publisher))
349 cb = this->connectionCb;
353 pub.SetTopic(_topic);
354 pub.SetPUuid(this->pUuid);
361 found = this->info.Publishers(_topic, addresses);
367 for (
const auto &proc : addresses)
369 for (
const auto &node : proc.second)
417 return this->info.Publishers(_topic, _publishers);
438 if (!this->info.Publisher(_topic, this->pUuid, _nUuid, inf))
442 this->info.DelPublisherByNode(_topic, this->pUuid, _nUuid);
450 msgs::Discovery::UNADVERTISE, inf);
461 return this->hostAddr;
471 return this->activityInterval;
482 return this->heartbeatInterval;
492 return this->silenceInterval;
501 this->activityInterval = _ms;
510 this->heartbeatInterval = _ms;
519 this->silenceInterval = _ms;
529 this->connectionCb = _cb;
539 this->disconnectionCb = _cb;
548 this->registrationCb = _cb;
557 this->unregistrationCb = _cb;
571 std::cout <<
"\tActivity: " << this->activityInterval
573 std::cout <<
"\tHeartbeat: " << this->heartbeatInterval
575 std::cout <<
"\tSilence: " << this->silenceInterval
584 if (this->activity.empty())
588 for (
auto &proc : this->activity)
608 this->info.TopicList(_topics);
617 if (!this->initialized)
619 this->initializedCv.wait(lk, [
this]{
return this->initialized;});
626 private:
void UpdateActivity()
639 if (now < this->timeNextActivity)
642 disconnectCb = this->disconnectionCb;
644 for (
auto it = this->activity.cbegin(); it != this->activity.cend();)
647 auto elapsed = now - it->second;
650 if (std::chrono::duration_cast<std::chrono::milliseconds>
651 (elapsed).count() > this->silenceInterval)
654 this->info.DelPublishersByProc(it->first);
659 this->activity.
erase(it++);
675 for (
auto const &uuid : uuids)
678 publisher.SetPUuid(uuid);
679 disconnectCb(publisher);
684 private:
void UpdateHeartbeat()
691 if (now < this->timeNextHeartbeat)
695 Publisher pub(
"",
"", this->pUuid,
"", AdvertiseOptions());
703 this->info.PublishersByProc(this->pUuid, nodes);
706 for (
const auto &topic : nodes)
708 for (
const auto &node : topic.second)
711 msgs::Discovery::ADVERTISE, node);
717 if (!this->initialized)
719 if (this->numHeartbeatsUninitialized == 2u)
722 this->initialized =
true;
725 this->initializedCv.notify_all();
727 ++this->numHeartbeatsUninitialized;
744 private:
int NextTimeout()
const
747 auto timeUntilNextHeartbeat = this->timeNextHeartbeat - now;
748 auto timeUntilNextActivity = this->timeNextActivity - now;
750 int t =
static_cast<int>(
751 std::chrono::duration_cast<std::chrono::milliseconds>
752 (
std::min(timeUntilNextHeartbeat, timeUntilNextActivity)).count());
753 int t2 =
std::min(t, this->kTimeout);
758 private:
void RecvMessages()
760 bool timeToExit =
false;
764 int timeout = this->NextTimeout();
768 this->RecvDiscoveryUpdate();
771 this->PrintCurrentState();
774 this->UpdateHeartbeat();
775 this->UpdateActivity();
787 private:
void RecvDiscoveryUpdate()
789 char rcvStr[Discovery::kMaxRcvStr];
790 sockaddr_in clntAddr;
791 socklen_t addrLen =
sizeof(clntAddr);
793 int64_t received = recvfrom(this->sockets.at(0),
794 reinterpret_cast<raw_type *
>(rcvStr),
796 reinterpret_cast<sockaddr *
>(&clntAddr),
797 reinterpret_cast<socklen_t *
>(&addrLen));
801 memcpy(&len, &rcvStr[0],
sizeof(len));
825 if (len +
sizeof(len) ==
static_cast<uint16_t
>(received))
827 std::string srcAddr = inet_ntoa(clntAddr.sin_addr);
828 uint16_t srcPort = ntohs(clntAddr.sin_port);
832 std::cout <<
"\nReceived discovery update from "
833 << srcAddr <<
": " << srcPort <<
std::endl;
836 this->DispatchDiscoveryMsg(srcAddr, rcvStr +
sizeof(len), len);
839 else if (received < 0)
841 std::cerr <<
"Discovery::RecvDiscoveryUpdate() recvfrom error"
850 private:
void DispatchDiscoveryMsg(
const std::string &_fromIp,
851 char *_msg, uint16_t _len)
853 gz::msgs::Discovery msg;
858 if (!msg.ParseFromArray(_msg, _len))
862 if (this->Version() != msg.version())
868 if (recvPUuid == this->pUuid)
880 if (msg.has_flags() && msg.flags().relay())
883 msg.mutable_flags()->set_relay(
false);
884 msg.mutable_flags()->set_no_relay(
true);
885 this->SendMulticast(msg);
889 this->AddRelayAddress(_fromIp);
897 else if (!msg.has_flags() || !msg.flags().no_relay())
899 msg.mutable_flags()->set_relay(
true);
900 this->SendUnicast(msg);
903 bool isSenderLocal = (
std::find(this->hostInterfaces.begin(),
904 this->hostInterfaces.end(), _fromIp) != this->hostInterfaces.end()) ||
905 (_fromIp.
find(
"127.") == 0);
908 DiscoveryCallback<Pub> connectCb;
909 DiscoveryCallback<Pub> disconnectCb;
910 DiscoveryCallback<Pub> registerCb;
911 DiscoveryCallback<Pub> unregisterCb;
915 connectCb = this->connectionCb;
916 disconnectCb = this->disconnectionCb;
917 registerCb = this->registrationCb;
918 unregisterCb = this->unregistrationCb;
923 case msgs::Discovery::ADVERTISE:
927 publisher.SetFromDiscovery(msg);
941 added = this->info.AddPublisher(publisher);
944 if (added && connectCb)
947 connectCb(publisher);
952 case msgs::Discovery::SUBSCRIBE:
958 recvTopic = msg.sub().topic();
962 std::cerr <<
"Subscription discovery message is missing "
963 <<
"Subscriber information.\n";
968 Addresses_M<Pub> addresses;
971 if (!this->info.HasAnyPublishers(recvTopic, this->pUuid))
976 if (!this->info.Publishers(recvTopic, addresses))
980 for (
const auto &nodeInfo : addresses[this->pUuid])
992 msgs::Discovery::ADVERTISE, nodeInfo);
997 case msgs::Discovery::NEW_CONNECTION:
1001 publisher.SetFromDiscovery(msg);
1004 registerCb(publisher);
1008 case msgs::Discovery::END_CONNECTION:
1012 publisher.SetFromDiscovery(msg);
1015 unregisterCb(publisher);
1019 case msgs::Discovery::HEARTBEAT:
1024 case msgs::Discovery::BYE:
1029 this->activity.erase(recvPUuid);
1035 pub.SetPUuid(recvPUuid);
1043 this->info.DelPublishersByProc(recvPUuid);
1048 case msgs::Discovery::UNADVERTISE:
1052 publisher.SetFromDiscovery(msg);
1065 disconnectCb(publisher);
1071 this->info.DelPublisherByNode(publisher.Topic(),
1072 publisher.PUuid(), publisher.NUuid());
1079 std::cerr <<
"Unknown message type [" << msg.type() <<
"].\n";
1091 private:
template<
typename T>
1093 const msgs::Discovery::Type _type,
1094 const T &_pub)
const
1096 gz::msgs::Discovery discoveryMsg;
1097 discoveryMsg.set_version(this->Version());
1098 discoveryMsg.set_type(_type);
1099 discoveryMsg.set_process_uuid(this->pUuid);
1103 case msgs::Discovery::ADVERTISE:
1104 case msgs::Discovery::UNADVERTISE:
1105 case msgs::Discovery::NEW_CONNECTION:
1106 case msgs::Discovery::END_CONNECTION:
1108 _pub.FillDiscovery(discoveryMsg);
1111 case msgs::Discovery::SUBSCRIBE:
1113 discoveryMsg.mutable_sub()->set_topic(_pub.Topic());
1116 case msgs::Discovery::HEARTBEAT:
1117 case msgs::Discovery::BYE:
1120 std::cerr <<
"Discovery::SendMsg() error: Unrecognized message"
1121 <<
" type [" << _type <<
"]" <<
std::endl;
1128 this->SendMulticast(discoveryMsg);
1136 discoveryMsg.mutable_flags()->set_relay(
true);
1137 this->SendUnicast(discoveryMsg);
1142 std::cout <<
"\t* Sending " << msgs::ToString(_type)
1143 <<
" msg [" << _pub.Topic() <<
"]" <<
std::endl;
1149 private:
void SendUnicast(
const msgs::Discovery &_msg)
const
1153 #if GOOGLE_PROTOBUF_VERSION >= 3004000
1154 size_t msgSizeFull = _msg.ByteSizeLong();
1156 int msgSizeFull = _msg.ByteSize();
1158 if (msgSizeFull +
sizeof(msgSize) > this->kMaxRcvStr)
1160 std::cerr <<
"Discovery message too large to send. Discovery won't "
1161 <<
"work. This shouldn't happen.\n";
1164 msgSize = msgSizeFull;
1166 uint16_t totalSize =
sizeof(msgSize) + msgSize;
1167 char *buffer =
static_cast<char *
>(
new char[totalSize]);
1168 memcpy(&buffer[0], &msgSize,
sizeof(msgSize));
1170 if (_msg.SerializeToArray(buffer +
sizeof(msgSize), msgSize))
1173 for (
const auto &sockAddr : this->relayAddrs)
1176 auto sent = sendto(this->sockets.at(0),
1177 reinterpret_cast<const raw_type *
>(
1178 reinterpret_cast<const unsigned char*
>(buffer)),
1180 reinterpret_cast<const sockaddr *
>(&sockAddr),
1183 if (sent != totalSize)
1194 std::cerr <<
"Discovery::SendUnicast: Error serializing data."
1203 private:
void SendMulticast(
const msgs::Discovery &_msg)
const
1207 #if GOOGLE_PROTOBUF_VERSION >= 3004000
1208 size_t msgSizeFull = _msg.ByteSizeLong();
1210 int msgSizeFull = _msg.ByteSize();
1212 if (msgSizeFull +
sizeof(msgSize) > this->kMaxRcvStr)
1214 std::cerr <<
"Discovery message too large to send. Discovery won't "
1215 <<
"work. This shouldn't happen.\n";
1219 msgSize = msgSizeFull;
1220 uint16_t totalSize =
sizeof(msgSize) + msgSize;
1221 char *buffer =
static_cast<char *
>(
new char[totalSize]);
1222 memcpy(&buffer[0], &msgSize,
sizeof(msgSize));
1224 if (_msg.SerializeToArray(buffer +
sizeof(msgSize), msgSize))
1228 for (
const auto &sock : this->Sockets())
1231 if (sendto(sock,
reinterpret_cast<const raw_type *
>(
1232 reinterpret_cast<const unsigned char*
>(buffer)),
1234 reinterpret_cast<const sockaddr *
>(this->MulticastAddr()),
1235 sizeof(*(this->MulticastAddr()))) != totalSize)
1245 if (errno != EPERM && errno != ENOBUFS)
1247 std::cerr <<
"Exception sending a multicast message:"
1256 std::cerr <<
"Discovery::SendMulticast: Error serializing data."
1267 return this->sockets;
1272 private:
const sockaddr_in *MulticastAddr()
const
1274 return &this->mcastAddr;
1279 private: uint8_t Version()
const
1282 static int topicStats =
1283 (
env(
"IGN_TRANSPORT_TOPIC_STATISTICS", ignStats) && ignStats ==
"1");
1284 return this->kWireVersion + (topicStats * 100);
1291 private:
bool RegisterNetIface(
const std::string &_ip)
1294 int sock =
static_cast<int>(socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP));
1304 struct in_addr ifAddr;
1305 ifAddr.s_addr = inet_addr(_ip.
c_str());
1306 if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF,
1307 reinterpret_cast<const char*
>(&ifAddr),
sizeof(ifAddr)) != 0)
1309 std::cerr <<
"Error setting socket option (IP_MULTICAST_IF)."
1314 this->sockets.push_back(sock);
1319 struct ip_mreq group;
1320 group.imr_multiaddr.s_addr =
1321 inet_addr(this->multicastGroup.c_str());
1322 group.imr_interface.s_addr = inet_addr(_ip.
c_str());
1323 if (setsockopt(this->sockets.at(0), IPPROTO_IP, IP_ADD_MEMBERSHIP,
1324 reinterpret_cast<const char*
>(&group),
sizeof(group)) != 0)
1326 std::cerr <<
"Error setting socket option (IP_ADD_MEMBERSHIP)."
1336 private:
void AddRelayAddress(
const std::string &_ip)
1339 for (
auto const &addr : this->relayAddrs)
1341 if (addr.sin_addr.s_addr == inet_addr(_ip.
c_str()))
1346 memset(&addr, 0,
sizeof(addr));
1347 addr.sin_family = AF_INET;
1348 addr.sin_addr.s_addr = inet_addr(_ip.
c_str());
1349 addr.sin_port = htons(
static_cast<u_short
>(this->port));
1351 this->relayAddrs.push_back(addr);
1357 private:
static const unsigned int kDefActivityInterval = 100;
1362 private:
static const unsigned int kDefHeartbeatInterval = 1000;
1367 private:
static const unsigned int kDefSilenceInterval = 3000;
1373 private:
const int kTimeout = 250;
1376 private:
static const uint16_t kMaxRcvStr =
1381 private:
static const uint8_t kWireVersion = 10;
1398 private:
unsigned int silenceInterval;
1403 private:
unsigned int activityInterval;
1408 private:
unsigned int heartbeatInterval;
1411 private: DiscoveryCallback<Pub> connectionCb;
1414 private: DiscoveryCallback<Pub> disconnectionCb;
1417 private: DiscoveryCallback<Pub> registrationCb;
1420 private: DiscoveryCallback<Pub> unregistrationCb;
1423 private: TopicStorage<Pub> info;
1432 private:
bool verbose;
1438 private: sockaddr_in mcastAddr;
1462 private:
bool initialized;
1465 private:
unsigned int numHeartbeatsUninitialized;
1474 private:
bool enabled;
A class for customizing the publication options for a topic or service advertised....
Definition: gz/transport/AdvertiseOptions.hh:58
A discovery class that implements a distributed topic discovery protocol. It uses UDP multicast for s...
Definition: gz/transport/Discovery.hh:118
void WaitForInit() const
Check if ready/initialized. If not, then wait on the initializedCv condition variable.
Definition: gz/transport/Discovery.hh:613
void Start()
Start the discovery service. You probably want to register the callbacks for receiving discovery noti...
Definition: gz/transport/Discovery.hh:281
bool Advertise(const Pub &_publisher)
Advertise a new message.
Definition: gz/transport/Discovery.hh:305
unsigned int ActivityInterval() const
The discovery checks the validity of the topic information every 'activity interval' milliseconds.
Definition: gz/transport/Discovery.hh:468
unsigned int HeartbeatInterval() const
Each node broadcasts periodic heartbeats to keep its topic information alive in other nodes....
Definition: gz/transport/Discovery.hh:479
const TopicStorage< Pub > & Info() const
Get the discovery information.
Definition: gz/transport/Discovery.hh:403
std::string HostAddr() const
Get the IP address of this host.
Definition: gz/transport/Discovery.hh:458
void Unregister(const MessagePublisher &_pub) const
Unregister a node from this process as a remote subscriber.
Definition: gz/transport/Discovery.hh:395
std::map< std::string, Timestamp > activity
Activity information. Every time there is a message from a remote node, its activity information is u...
Definition: gz/transport/Discovery.hh:1429
void Register(const MessagePublisher &_pub) const
Register a node from this process as a remote subscriber.
Definition: gz/transport/Discovery.hh:387
bool Unadvertise(const std::string &_topic, const std::string &_nUuid)
Unadvertise a new message. Broadcast a discovery message that will cancel all the discovery informati...
Definition: gz/transport/Discovery.hh:427
void SetHeartbeatInterval(const unsigned int _ms)
Set the heartbeat interval.
Definition: gz/transport/Discovery.hh:507
unsigned int SilenceInterval() const
Get the maximum time allowed without receiving any discovery information from a node before canceling...
Definition: gz/transport/Discovery.hh:489
void PrintCurrentState() const
Print the current discovery state.
Definition: gz/transport/Discovery.hh:561
void SetSilenceInterval(const unsigned int _ms)
Set the maximum silence interval.
Definition: gz/transport/Discovery.hh:516
bool Publishers(const std::string &_topic, Addresses_M< Pub > &_publishers) const
Get all the publishers' information known for a given topic.
Definition: gz/transport/Discovery.hh:413
Discovery(const std::string &_pUuid, const std::string &_ip, const int _port, const bool _verbose=false)
Constructor.
Definition: gz/transport/Discovery.hh:125
void RegistrationsCb(const DiscoveryCallback< Pub > &_cb)
Register a callback to receive an event when a new remote node subscribes to a topic within this proc...
Definition: gz/transport/Discovery.hh:545
void ConnectionsCb(const DiscoveryCallback< Pub > &_cb)
Register a callback to receive discovery connection events. Each time a new topic is connected,...
Definition: gz/transport/Discovery.hh:526
bool Discover(const std::string &_topic) const
Request discovery information about a topic. When using this method, the user might want to use SetCo...
Definition: gz/transport/Discovery.hh:337
void SetActivityInterval(const unsigned int _ms)
Set the activity interval.
Definition: gz/transport/Discovery.hh:498
void DisconnectionsCb(const DiscoveryCallback< Pub > &_cb)
Register a callback to receive discovery disconnection events. Each time a topic is no longer active,...
Definition: gz/transport/Discovery.hh:536
void UnregistrationsCb(const DiscoveryCallback< Pub > &_cb)
Register a callback to receive an event when a remote node unsubscribes to a topic within this proces...
Definition: gz/transport/Discovery.hh:554
virtual ~Discovery()
Destructor.
Definition: gz/transport/Discovery.hh:250
void TopicList(std::vector< std::string > &_topics) const
Get the list of topics currently advertised in the network.
Definition: gz/transport/Discovery.hh:604
This class stores all the information about a message publisher.
Definition: gz/transport/Publisher.hh:177
This class stores all the information about a publisher. It stores the topic name that publishes,...
Definition: gz/transport/Publisher.hh:40
T duration_cast(T... args)
void raw_type
Definition: gz/transport/Discovery.hh:45
std::chrono::steady_clock::time_point Timestamp
Definition: gz/transport/TransportTypes.hh:153
std::vector< std::string > split(const std::string &_orig, char _delim)
split at a one character delimiter to get a vector of something
bool env(const std::string &_name, std::string &_value)
Find the environment variable '_name' and return its value.
@ ALL
Topic/service available to any subscriber (default scope).
@ HOST
Topic/service only available to subscribers in the same machine as the publisher.
@ PROCESS
Topic/service only available to subscribers in the same process as the publisher.
std::vector< std::string > determineInterfaces()
Determine the list of network interfaces for this machine. Reference: https://github....
DestinationType
Options for sending discovery messages.
Definition: gz/transport/Discovery.hh:89
@ UNICAST
Send data via unicast only.
@ ALL
Send data via unicast and multicast.
@ MULTICAST
Send data via multicast only.
bool pollSockets(const std::vector< int > &_sockets, const int _timeout)
std::string determineHost()
Determine IP or hostname. Reference: https://github.com/ros/ros_comm/blob/hydro-devel/clients/ roscpp...
Definition: gz/transport/AdvertiseOptions.hh:29