11 #ifndef BB_SERVICE_DISCOVERER_HPP_INCLUDED
12 #define BB_SERVICE_DISCOVERER_HPP_INCLUDED
19 #include <boost/asio.hpp>
20 #include "detail/std_chrono_time_traits.hpp"
23 namespace networking {
65 std::chrono::steady_clock::time_point last_seen;
67 bool operator<(
const service& o)
const
74 bool operator==(
const service& o)
const
81 double age_in_seconds()
const
83 auto age = std::chrono::steady_clock::now() - last_seen;
84 return std::chrono::duration_cast<std::chrono::duration<double>>(age).count();
88 friend std::ostream& operator<<(std::ostream& os,
const service_discoverer::service& service)
90 os << service.service_name <<
" on " << service.computer_name <<
"(" << service.endpoint <<
") " <<
91 service.age_in_seconds() <<
" seconds ago";
113 const std::string& listen_for_service,
115 const std::chrono::steady_clock::duration max_idle = std::chrono::seconds(30),
116 const size_t max_services = 10,
117 const unsigned short multicast_port = 30001,
118 const boost::asio::ip::address& listen_address = boost::asio::ip::address::from_string(
"0.0.0.0"),
119 const boost::asio::ip::address& multicast_address = boost::asio::ip::address::from_string(
"239.255.0.1")
121 : listen_for_service_(listen_for_service)
122 , socket_(io_service)
123 , idle_check_timer_(io_service)
124 , on_services_changed_(on_services_changed)
125 , max_idle_(max_idle)
126 , max_services_(max_services)
128 assert(max_services_ > 0);
131 boost::asio::ip::udp::endpoint listen_endpoint(
132 listen_address, multicast_port);
133 socket_.open(listen_endpoint.protocol());
134 socket_.set_option(boost::asio::ip::udp::socket::reuse_address(
true));
135 socket_.bind(listen_endpoint);
139 boost::asio::ip::multicast::join_group(multicast_address));
145 void handle_message(
const std::string& message,
const boost::asio::ip::udp::endpoint& sender_endpoint)
147 std::vector<std::string> tokens;
149 std::istringstream f(message);
151 while (getline(f, s,
':'))
154 if (tokens.size() != 3)
156 std::cerr <<
"invalid number of tokens in received service announcement: " << std::endl;
157 std::cerr <<
" message: " << message << std::endl;
158 std::cerr <<
" tokens: " << tokens.size() << std::endl;
162 assert(tokens.size() == 3);
164 std::string service_name = tokens[0];
165 std::string computer_name = tokens[1];
166 std::string port_string = tokens[2];
169 unsigned long port = 0;
173 port = std::stoul(port_string);
175 catch (
const std::exception& e)
177 std::cerr <<
"failed to parse port number from: " << port_string << std::endl;
181 if (port > std::numeric_limits<unsigned short>::max())
183 std::cerr <<
"failed to parse port number from: " << port_string << std::endl;
187 auto discovered_service = service
191 boost::asio::ip::tcp::endpoint(sender_endpoint.address(), (
unsigned short)port),
192 std::chrono::steady_clock::now()
195 if (service_name == listen_for_service_)
200 discovered_services_.erase(discovered_service);
201 discovered_services_.insert(discovered_service);
203 remove_idle_services();
206 if (discovered_services_.size() > max_services_)
209 services::iterator oldest_pos =
211 discovered_services_.begin(),
212 discovered_services_.end(),
213 [](
const service& a,
const service& b)
215 return a.last_seen < b.last_seen;
218 assert(oldest_pos != discovered_services_.end());
219 discovered_services_.erase(oldest_pos);
225 boost::system::error_code ec;
226 idle_check_timer_.cancel(ec);
228 std::cerr << ec.message();
232 services::iterator oldest_pos =
234 discovered_services_.begin(),
235 discovered_services_.end(),
236 [](
const service& a,
const service& b)
238 return a.last_seen < b.last_seen;
241 assert(oldest_pos != discovered_services_.end());
243 idle_check_timer_.expires_at(oldest_pos->last_seen + max_idle_);
244 idle_check_timer_.async_wait(
245 [
this](
const boost::system::error_code& ec)
247 if (!ec && remove_idle_services())
249 on_services_changed_(discovered_services_);
256 on_services_changed_(discovered_services_);
260 std::clog <<
"ignoring: " << discovered_service << std::endl;
267 socket_.async_receive(boost::asio::null_buffers(),
268 [
this](
const boost::system::error_code& error,
unsigned int)
272 std::cerr << error.message() << std::endl;
276 size_t bytes_available = socket_.available();
278 auto receive_buffer = std::make_shared<std::vector<char>>(bytes_available);
279 auto sender_endpoint = std::make_shared<boost::asio::ip::udp::endpoint>();
281 socket_.async_receive_from(
282 boost::asio::buffer(receive_buffer->data(), receive_buffer->size()), *sender_endpoint,
283 [
this, receive_buffer, sender_endpoint]
284 (
const boost::system::error_code& error,
size_t bytes_recvd)
288 std::cerr << error.message() << std::endl;
292 this->handle_message({receive_buffer->data(), receive_buffer->data() + bytes_recvd}, *sender_endpoint);
302 bool remove_idle_services()
304 auto dead_line = std::chrono::steady_clock::now() - max_idle_;
305 bool services_removed =
false;
307 for (services::const_iterator i = discovered_services_.begin(); i != discovered_services_.end();)
309 if (i->last_seen < dead_line)
311 i = discovered_services_.erase(i);
312 services_removed =
true;
318 return services_removed;
321 typedef boost::asio::basic_deadline_timer<
322 std::chrono::steady_clock,
323 detail::std_chrono_time_traits<std::chrono::steady_clock>> steady_clock_deadline_timer_t;
325 const std::string listen_for_service_;
326 boost::asio::ip::udp::socket socket_;
327 steady_clock_deadline_timer_t idle_check_timer_;
329 const std::chrono::steady_clock::duration max_idle_;
330 const size_t max_services_;
std::string service_name
the name of the service
Definition: service_discoverer.hpp:62
std::function< void(const services &services)> on_services_changed_t
this callback gets called, when ever the set of available services changes
Definition: service_discoverer.hpp:100
boost::asio::ip::tcp::endpoint endpoint
enpoint you should connect to. Even though, it's an tcp endpoint, it's up to you, what you do with th...
Definition: service_discoverer.hpp:64
service_discoverer(boost::asio::io_service &io_service, const std::string &listen_for_service, const on_services_changed_t on_services_changed, const std::chrono::steady_clock::duration max_idle=std::chrono::seconds(30), const size_t max_services=10, const unsigned short multicast_port=30001, const boost::asio::ip::address &listen_address=boost::asio::ip::address::from_string("0.0.0.0"), const boost::asio::ip::address &multicast_address=boost::asio::ip::address::from_string("239.255.0.1"))
Definition: service_discoverer.hpp:112
std::set< service > services
a set of discovered services
Definition: service_discoverer.hpp:97
Definition: service_discoverer.hpp:54
std::string computer_name
the name of the computer the service is running on
Definition: service_discoverer.hpp:63
Definition: service_discoverer.hpp:60