├── .gitignore ├── .gitmodules ├── .idea └── codeStyleSettings.xml ├── .travis.yml ├── CMakeLists.txt ├── README.md ├── docs ├── Doxyfile ├── customdoxygen.css ├── doxy-boot.js ├── footer.html └── header.html ├── include └── betabugs │ └── networking │ ├── detail │ └── std_chrono_time_traits.hpp │ ├── service_announcer.hpp │ └── service_discoverer.hpp └── tests ├── test_all.cpp ├── test_announcer.cpp ├── test_basics.cpp └── test_limits.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/html"] 2 | path = docs/html 3 | url = https://github.com/beschulz/asio_service_discovery.git 4 | branch = gh-pages 5 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 54 | 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - gcc 4 | # - clang 5 | os: 6 | - linux 7 | - osx 8 | 9 | before_install: 10 | # g++4.8.1 11 | - if [ "$CXX" == "g++" ]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; fi 12 | 13 | # Get Clang 3.4 14 | - if [ "$CXX" == "clang++" ]; then wget https://github.com/Viq111/travis-container-packets/releases/download/clang%2Blibcxx-3.4.2/clang_libcxx.tar.bz2; fi 15 | - if [ "$CXX" == "clang++" ]; then tar -xjf clang_libcxx.tar.bz2; fi 16 | - if [ "$CXX" == "clang++" ]; then rm clang_libcxx.tar.bz2 && mv clang_libcxx clang; fi 17 | - if [ "$CXX" == "clang++" ]; then export PATH=$(pwd)/clang/bin:$PATH; fi 18 | - if [ "$CXX" == "clang++" ]; then export LIBRARY_PATH=$(pwd)/clang/lib:$LIBRARY_PATH; fi 19 | - if [ "$CXX" == "clang++" ]; then export LD_LIBRARY_PATH=$(pwd)/clang/lib:$LD_LIBRARY_PATH; fi 20 | - if [ "$CXX" == "clang++" ]; then export CPLUS_INCLUDE_PATH=$(pwd)/clang/include/c++/v1:$CPLUS_INCLUDE_PATH; fi 21 | - if [ "$CXX" == "clang++" ]; then export CXXFLAGS="-stdlib=libc++"; fi 22 | 23 | - sudo apt-get update -qq 24 | 25 | install: 26 | # g++4.8.1 27 | - if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8; fi 28 | - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8"; fi 29 | 30 | # Get boost 31 | - wget https://github.com/Viq111/travis-container-packets/releases/download/boost-1.57.0/boost.tar.bz2 32 | - tar -xjf boost.tar.bz2 33 | - rm boost.tar.bz2 34 | - export BOOST_ROOT=$(pwd)/boost 35 | 36 | # Get CMake 3.1 37 | - wget https://github.com/Viq111/travis-container-packets/releases/download/cmake-3.1.2/cmake.tar.bz2 38 | - tar -xjf cmake.tar.bz2 39 | - rm cmake.tar.bz2 40 | - export PATH=$(pwd)/cmake/bin:$PATH 41 | 42 | script: 43 | - mkdir -p build || exit $? 44 | - cd build || exit $? 45 | - cmake .. || exit $? 46 | - make all || exit $? 47 | - ./test_all || exit $? 48 | - cd .. || exit $? 49 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.4) 2 | project(asio_service_discovery) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wconversion") 5 | 6 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wdocumentation") 8 | endif() 9 | 10 | # this is here, so that users can use a custom boost installation 11 | # used also by travis CI 12 | if(EXISTS "$ENV{BOOST_ROOT}") 13 | link_directories("$ENV{BOOST_ROOT}/lib") 14 | include_directories("$ENV{BOOST_ROOT}/include") 15 | endif(EXISTS "$ENV{BOOST_ROOT}") 16 | 17 | # create documentation 18 | # add a target to generate API documentation with Doxygen 19 | find_package(Doxygen) 20 | if(DOXYGEN_FOUND) 21 | add_custom_target( 22 | doc 23 | ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile 24 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs 25 | COMMENT "Generating API documentation with Doxygen" VERBATIM 26 | ) 27 | endif(DOXYGEN_FOUND) 28 | 29 | 30 | file(GLOB tests RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/tests" "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp") 31 | foreach(test_file ${tests}) 32 | message(${test_file}) 33 | 34 | get_filename_component(test_name ${test_file} NAME_WE) 35 | 36 | add_executable(${test_name} "${CMAKE_CURRENT_SOURCE_DIR}/tests/${test_file}") 37 | target_include_directories(${test_name} PUBLIC "./include") 38 | target_link_libraries(${test_name} "boost_system" "boost_unit_test_framework") 39 | endforeach(test_file) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asio_service_discovery 2 | 3 | [![Build Status](https://travis-ci.org/beschulz/asio_service_discovery.svg?branch=master)](https://travis-ci.org/beschulz/asio_service_discovery) 4 | 5 | Components for service discovery via udp multicasting. It's using boost::asio for async networking. It's non-blocking and non-locking. 6 | 7 | The best way to get started is having a [look at the tests](tests). 8 | Basic functionality is derived from [boost::asios udp multicast example](http://www.boost.org/doc/libs/1_37_0/doc/html/boost_asio/example/multicast/) 9 | . 10 | 11 | [Read the API-Documentation](http://beschulz.github.io/asio_service_discovery/) 12 | 13 | > **Note** 14 | > the max packet size for a udp packet is limited. This library supports whatever the maximum size for udp packets on the machine(s) it's running on is (~8kb on my machine). But keep that in mind, when choosing a service name. But anything below a kb will probably be allright. If you get a "Message to long" error, be sure, that you did not chose a ridiculously long service name. 15 | 16 | ## requirements 17 | 18 | - asio_service_discovery is using boost::asio, therefore you need the boost asio headers and you need to link agains boost_system. 19 | - You also need a compiler that supports C++11 20 | - If you want to run the tests, you also need to install cmake 21 | 22 | ## How it works 23 | 24 | There are two components: service_announcer and service_discoverer. 25 | 26 | ### [The Announcer](include/betabugs/networking/service_announcer.hpp) 27 | 28 | The announcer multicasts information about the service it's announcing in one second intervals. 29 | The packet format is: service_name:computer_name:port 30 | You have to pass service_name and service_port to the service_announcer. they can be freely chosen. 31 | 32 | ### [The Discoverer](include/betabugs/networking/service_discoverer.hpp) 33 | 34 | The discoverer listens for incomming multicast packets that match the service_name it was configured with. 35 | It hold a set of service_discoverer::service objects. Each time a packet comes in, it is parsed and if the 36 | service name matches, a service_discoverer::service objects is constructed and added to the set. After that 37 | the optional callback is called. 38 | 39 | ## a simple example 40 | 41 | ### The Announcer 42 | 43 | ``` 44 | boost::asio::io_service io_service; 45 | betabugs::networking::service_announcer announcer(io_service, "my_service", 1337); 46 | io_service.run(); 47 | ``` 48 | 49 | ### The Discoverer 50 | 51 | ``` 52 | boost::asio::io_service io_service; 53 | betabugs::networking::service_discoverer discoverer(io_service, "my_service", 54 | [](const service_discoverer::services& services){ 55 | std::clog << "my_service is available on the following machines:" << std::endl; 56 | for(const auto& service : services) 57 | { 58 | std::clog << " " << service << std::endl; 59 | } 60 | }); 61 | io_service.run(); 62 | ``` 63 | 64 | ## License 65 | 66 | This library is Distributed under the [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt) . 67 | 68 | ## Bugs 69 | 70 | In case you find any bugs, please don't hesitate to contact me. Also pull-requests are highly apprechiated. 71 | 72 | ## Platform support 73 | 74 | The discovery *should* work on any platform, that is supported by boost::asio. It works like a charm on OSX and iOS. If you plan on using this on Linux, Android or Windows, please report you experience. 75 | -------------------------------------------------------------------------------- /docs/Doxyfile: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.8.6 2 | 3 | #--------------------------------------------------------------------------- 4 | # Project related configuration options 5 | #--------------------------------------------------------------------------- 6 | DOXYFILE_ENCODING = UTF-8 7 | PROJECT_NAME = "Asio Service Discovery" 8 | PROJECT_NUMBER = 9 | PROJECT_BRIEF = 10 | PROJECT_LOGO = 11 | OUTPUT_DIRECTORY = 12 | CREATE_SUBDIRS = NO 13 | OUTPUT_LANGUAGE = English 14 | BRIEF_MEMBER_DESC = YES 15 | REPEAT_BRIEF = YES 16 | ABBREVIATE_BRIEF = 17 | ALWAYS_DETAILED_SEC = NO 18 | INLINE_INHERITED_MEMB = NO 19 | FULL_PATH_NAMES = NO 20 | STRIP_FROM_PATH = 21 | STRIP_FROM_INC_PATH = 22 | SHORT_NAMES = NO 23 | JAVADOC_AUTOBRIEF = NO 24 | QT_AUTOBRIEF = NO 25 | MULTILINE_CPP_IS_BRIEF = NO 26 | INHERIT_DOCS = YES 27 | SEPARATE_MEMBER_PAGES = NO 28 | TAB_SIZE = 4 29 | ALIASES = 30 | TCL_SUBST = 31 | OPTIMIZE_OUTPUT_FOR_C = NO 32 | OPTIMIZE_OUTPUT_JAVA = NO 33 | OPTIMIZE_FOR_FORTRAN = NO 34 | OPTIMIZE_OUTPUT_VHDL = NO 35 | EXTENSION_MAPPING = 36 | MARKDOWN_SUPPORT = YES 37 | AUTOLINK_SUPPORT = YES 38 | BUILTIN_STL_SUPPORT = NO 39 | CPP_CLI_SUPPORT = NO 40 | SIP_SUPPORT = NO 41 | IDL_PROPERTY_SUPPORT = YES 42 | DISTRIBUTE_GROUP_DOC = NO 43 | SUBGROUPING = YES 44 | INLINE_GROUPED_CLASSES = NO 45 | INLINE_SIMPLE_STRUCTS = NO 46 | TYPEDEF_HIDES_STRUCT = NO 47 | LOOKUP_CACHE_SIZE = 0 48 | #--------------------------------------------------------------------------- 49 | # Build related configuration options 50 | #--------------------------------------------------------------------------- 51 | EXTRACT_ALL = NO 52 | EXTRACT_PRIVATE = NO 53 | EXTRACT_PACKAGE = NO 54 | EXTRACT_STATIC = NO 55 | EXTRACT_LOCAL_CLASSES = YES 56 | EXTRACT_LOCAL_METHODS = NO 57 | EXTRACT_ANON_NSPACES = NO 58 | HIDE_UNDOC_MEMBERS = NO 59 | HIDE_UNDOC_CLASSES = NO 60 | HIDE_FRIEND_COMPOUNDS = NO 61 | HIDE_IN_BODY_DOCS = NO 62 | INTERNAL_DOCS = NO 63 | CASE_SENSE_NAMES = NO 64 | HIDE_SCOPE_NAMES = NO 65 | SHOW_INCLUDE_FILES = YES 66 | SHOW_GROUPED_MEMB_INC = NO 67 | FORCE_LOCAL_INCLUDES = NO 68 | INLINE_INFO = YES 69 | SORT_MEMBER_DOCS = YES 70 | SORT_BRIEF_DOCS = NO 71 | SORT_MEMBERS_CTORS_1ST = NO 72 | SORT_GROUP_NAMES = NO 73 | SORT_BY_SCOPE_NAME = NO 74 | STRICT_PROTO_MATCHING = NO 75 | GENERATE_TODOLIST = YES 76 | GENERATE_TESTLIST = YES 77 | GENERATE_BUGLIST = YES 78 | GENERATE_DEPRECATEDLIST= YES 79 | ENABLED_SECTIONS = 80 | MAX_INITIALIZER_LINES = 30 81 | SHOW_USED_FILES = YES 82 | SHOW_FILES = YES 83 | SHOW_NAMESPACES = YES 84 | FILE_VERSION_FILTER = 85 | LAYOUT_FILE = 86 | CITE_BIB_FILES = 87 | #--------------------------------------------------------------------------- 88 | # Configuration options related to warning and progress messages 89 | #--------------------------------------------------------------------------- 90 | QUIET = NO 91 | WARNINGS = YES 92 | WARN_IF_UNDOCUMENTED = YES 93 | WARN_IF_DOC_ERROR = YES 94 | WARN_NO_PARAMDOC = NO 95 | WARN_FORMAT = "$file:$line: $text" 96 | WARN_LOGFILE = 97 | #--------------------------------------------------------------------------- 98 | # Configuration options related to the input files 99 | #--------------------------------------------------------------------------- 100 | INPUT = ../README.md ../include 101 | INPUT_ENCODING = UTF-8 102 | FILE_PATTERNS = 103 | RECURSIVE = YES 104 | EXCLUDE = 105 | EXCLUDE_SYMLINKS = NO 106 | EXCLUDE_PATTERNS = 107 | EXCLUDE_SYMBOLS = 108 | EXAMPLE_PATH = 109 | EXAMPLE_PATTERNS = 110 | EXAMPLE_RECURSIVE = YES 111 | IMAGE_PATH = 112 | INPUT_FILTER = 113 | FILTER_PATTERNS = 114 | FILTER_SOURCE_FILES = NO 115 | FILTER_SOURCE_PATTERNS = 116 | USE_MDFILE_AS_MAINPAGE = README.md 117 | #--------------------------------------------------------------------------- 118 | # Configuration options related to source browsing 119 | #--------------------------------------------------------------------------- 120 | SOURCE_BROWSER = NO 121 | INLINE_SOURCES = NO 122 | STRIP_CODE_COMMENTS = YES 123 | REFERENCED_BY_RELATION = NO 124 | REFERENCES_RELATION = NO 125 | REFERENCES_LINK_SOURCE = YES 126 | SOURCE_TOOLTIPS = YES 127 | USE_HTAGS = NO 128 | VERBATIM_HEADERS = YES 129 | #--------------------------------------------------------------------------- 130 | # Configuration options related to the alphabetical class index 131 | #--------------------------------------------------------------------------- 132 | ALPHABETICAL_INDEX = YES 133 | COLS_IN_ALPHA_INDEX = 5 134 | IGNORE_PREFIX = 135 | #--------------------------------------------------------------------------- 136 | # Configuration options related to the HTML output 137 | #--------------------------------------------------------------------------- 138 | GENERATE_HTML = YES 139 | HTML_OUTPUT = html 140 | HTML_FILE_EXTENSION = .html 141 | HTML_HEADER = header.html 142 | HTML_FOOTER = footer.html 143 | HTML_STYLESHEET = customdoxygen.css 144 | HTML_EXTRA_STYLESHEET = 145 | HTML_EXTRA_FILES = 146 | HTML_COLORSTYLE_HUE = 220 147 | HTML_COLORSTYLE_SAT = 100 148 | HTML_COLORSTYLE_GAMMA = 80 149 | HTML_TIMESTAMP = YES 150 | HTML_DYNAMIC_SECTIONS = NO 151 | HTML_INDEX_NUM_ENTRIES = 100 152 | GENERATE_DOCSET = NO 153 | DOCSET_FEEDNAME = "Doxygen generated docs" 154 | DOCSET_BUNDLE_ID = org.doxygen.Project 155 | DOCSET_PUBLISHER_ID = org.doxygen.Publisher 156 | DOCSET_PUBLISHER_NAME = Publisher 157 | GENERATE_HTMLHELP = NO 158 | CHM_FILE = 159 | HHC_LOCATION = 160 | GENERATE_CHI = NO 161 | CHM_INDEX_ENCODING = 162 | BINARY_TOC = NO 163 | TOC_EXPAND = NO 164 | GENERATE_QHP = NO 165 | QCH_FILE = 166 | QHP_NAMESPACE = org.doxygen.Project 167 | QHP_VIRTUAL_FOLDER = doc 168 | QHP_CUST_FILTER_NAME = 169 | QHP_CUST_FILTER_ATTRS = 170 | QHP_SECT_FILTER_ATTRS = 171 | QHG_LOCATION = 172 | GENERATE_ECLIPSEHELP = NO 173 | ECLIPSE_DOC_ID = org.doxygen.Project 174 | DISABLE_INDEX = NO 175 | GENERATE_TREEVIEW = NO 176 | ENUM_VALUES_PER_LINE = 4 177 | TREEVIEW_WIDTH = 250 178 | EXT_LINKS_IN_WINDOW = NO 179 | FORMULA_FONTSIZE = 10 180 | FORMULA_TRANSPARENT = YES 181 | USE_MATHJAX = NO 182 | MATHJAX_FORMAT = HTML-CSS 183 | MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest 184 | MATHJAX_EXTENSIONS = 185 | MATHJAX_CODEFILE = 186 | SEARCHENGINE = YES 187 | SERVER_BASED_SEARCH = NO 188 | EXTERNAL_SEARCH = NO 189 | SEARCHENGINE_URL = 190 | SEARCHDATA_FILE = searchdata.xml 191 | EXTERNAL_SEARCH_ID = 192 | EXTRA_SEARCH_MAPPINGS = 193 | #--------------------------------------------------------------------------- 194 | # Configuration options related to the LaTeX output 195 | #--------------------------------------------------------------------------- 196 | GENERATE_LATEX = NO 197 | LATEX_OUTPUT = latex 198 | LATEX_CMD_NAME = latex 199 | MAKEINDEX_CMD_NAME = makeindex 200 | COMPACT_LATEX = NO 201 | PAPER_TYPE = a4 202 | EXTRA_PACKAGES = 203 | LATEX_HEADER = 204 | LATEX_FOOTER = 205 | LATEX_EXTRA_FILES = 206 | PDF_HYPERLINKS = YES 207 | USE_PDFLATEX = YES 208 | LATEX_BATCHMODE = NO 209 | LATEX_HIDE_INDICES = NO 210 | LATEX_SOURCE_CODE = NO 211 | LATEX_BIB_STYLE = plain 212 | #--------------------------------------------------------------------------- 213 | # Configuration options related to the RTF output 214 | #--------------------------------------------------------------------------- 215 | GENERATE_RTF = NO 216 | RTF_OUTPUT = rtf 217 | COMPACT_RTF = NO 218 | RTF_HYPERLINKS = NO 219 | RTF_STYLESHEET_FILE = 220 | RTF_EXTENSIONS_FILE = 221 | #--------------------------------------------------------------------------- 222 | # Configuration options related to the man page output 223 | #--------------------------------------------------------------------------- 224 | GENERATE_MAN = NO 225 | MAN_OUTPUT = man 226 | MAN_EXTENSION = .3 227 | MAN_LINKS = NO 228 | #--------------------------------------------------------------------------- 229 | # Configuration options related to the XML output 230 | #--------------------------------------------------------------------------- 231 | GENERATE_XML = NO 232 | XML_OUTPUT = xml 233 | XML_SCHEMA = 234 | XML_DTD = 235 | XML_PROGRAMLISTING = YES 236 | #--------------------------------------------------------------------------- 237 | # Configuration options related to the DOCBOOK output 238 | #--------------------------------------------------------------------------- 239 | GENERATE_DOCBOOK = NO 240 | DOCBOOK_OUTPUT = docbook 241 | #--------------------------------------------------------------------------- 242 | # Configuration options for the AutoGen Definitions output 243 | #--------------------------------------------------------------------------- 244 | GENERATE_AUTOGEN_DEF = NO 245 | #--------------------------------------------------------------------------- 246 | # Configuration options related to the Perl module output 247 | #--------------------------------------------------------------------------- 248 | GENERATE_PERLMOD = NO 249 | PERLMOD_LATEX = NO 250 | PERLMOD_PRETTY = YES 251 | PERLMOD_MAKEVAR_PREFIX = 252 | #--------------------------------------------------------------------------- 253 | # Configuration options related to the preprocessor 254 | #--------------------------------------------------------------------------- 255 | ENABLE_PREPROCESSING = YES 256 | MACRO_EXPANSION = NO 257 | EXPAND_ONLY_PREDEF = NO 258 | SEARCH_INCLUDES = YES 259 | INCLUDE_PATH = 260 | INCLUDE_FILE_PATTERNS = 261 | PREDEFINED = 262 | EXPAND_AS_DEFINED = 263 | SKIP_FUNCTION_MACROS = YES 264 | #--------------------------------------------------------------------------- 265 | # Configuration options related to external references 266 | #--------------------------------------------------------------------------- 267 | TAGFILES = 268 | GENERATE_TAGFILE = 269 | ALLEXTERNALS = NO 270 | EXTERNAL_GROUPS = YES 271 | EXTERNAL_PAGES = YES 272 | PERL_PATH = /usr/bin/perl 273 | #--------------------------------------------------------------------------- 274 | # Configuration options related to the dot tool 275 | #--------------------------------------------------------------------------- 276 | CLASS_DIAGRAMS = YES 277 | MSCGEN_PATH = 278 | DIA_PATH = 279 | HIDE_UNDOC_RELATIONS = YES 280 | HAVE_DOT = NO 281 | DOT_NUM_THREADS = 0 282 | DOT_FONTNAME = Helvetica 283 | DOT_FONTSIZE = 10 284 | DOT_FONTPATH = 285 | CLASS_GRAPH = YES 286 | COLLABORATION_GRAPH = YES 287 | GROUP_GRAPHS = YES 288 | UML_LOOK = NO 289 | UML_LIMIT_NUM_FIELDS = 10 290 | TEMPLATE_RELATIONS = NO 291 | INCLUDE_GRAPH = YES 292 | INCLUDED_BY_GRAPH = YES 293 | CALL_GRAPH = NO 294 | CALLER_GRAPH = NO 295 | GRAPHICAL_HIERARCHY = YES 296 | DIRECTORY_GRAPH = YES 297 | DOT_IMAGE_FORMAT = png 298 | INTERACTIVE_SVG = NO 299 | DOT_PATH = 300 | DOTFILE_DIRS = 301 | MSCFILE_DIRS = 302 | DIAFILE_DIRS = 303 | DOT_GRAPH_MAX_NODES = 50 304 | MAX_DOT_GRAPH_DEPTH = 0 305 | DOT_TRANSPARENT = NO 306 | DOT_MULTI_TARGETS = NO 307 | GENERATE_LEGEND = YES 308 | DOT_CLEANUP = YES 309 | -------------------------------------------------------------------------------- /docs/customdoxygen.css: -------------------------------------------------------------------------------- 1 | h1, .h1, h2, .h2, h3, .h3{ 2 | font-weight: 200 !important; 3 | } 4 | 5 | #navrow1, #navrow2, #navrow3, #navrow4, #navrow5{ 6 | border-bottom: 1px solid #EEEEEE; 7 | } 8 | 9 | .adjust-right { 10 | margin-left: 30px !important; 11 | font-size: 1.15em !important; 12 | } 13 | .navbar{ 14 | border: 0px solid #222 !important; 15 | } 16 | 17 | 18 | /* Sticky footer styles 19 | -------------------------------------------------- */ 20 | html, 21 | body { 22 | height: 100%; 23 | /* The html and body elements cannot have any padding or margin. */ 24 | } 25 | 26 | /* Wrapper for page content to push down footer */ 27 | #wrap { 28 | min-height: 100%; 29 | height: auto; 30 | /* Negative indent footer by its height */ 31 | margin: 0 auto -60px; 32 | /* Pad bottom by footer height */ 33 | padding: 0 0 60px; 34 | } 35 | 36 | /* Set the fixed height of the footer here */ 37 | #footer { 38 | font-size: 0.9em; 39 | padding: 8px 0px; 40 | background-color: #f5f5f5; 41 | } 42 | 43 | .footer-row { 44 | line-height: 44px; 45 | } 46 | 47 | #footer > .container { 48 | padding-left: 15px; 49 | padding-right: 15px; 50 | } 51 | 52 | .footer-follow-icon { 53 | margin-left: 3px; 54 | text-decoration: none !important; 55 | } 56 | 57 | .footer-follow-icon img { 58 | width: 20px; 59 | } 60 | 61 | .footer-link { 62 | padding-top: 5px; 63 | display: inline-block; 64 | color: #999999; 65 | text-decoration: none; 66 | } 67 | 68 | .footer-copyright { 69 | text-align: center; 70 | } 71 | 72 | 73 | @media (min-width: 992px) { 74 | .footer-row { 75 | text-align: left; 76 | } 77 | 78 | .footer-icons { 79 | text-align: right; 80 | } 81 | } 82 | @media (max-width: 991px) { 83 | .footer-row { 84 | text-align: center; 85 | } 86 | 87 | .footer-icons { 88 | text-align: center; 89 | } 90 | } 91 | 92 | /* DOXYGEN Code Styles 93 | ----------------------------------- */ 94 | 95 | 96 | a.qindex { 97 | font-weight: bold; 98 | } 99 | 100 | a.qindexHL { 101 | font-weight: bold; 102 | background-color: #9CAFD4; 103 | color: #ffffff; 104 | border: 1px double #869DCA; 105 | } 106 | 107 | .contents a.qindexHL:visited { 108 | color: #ffffff; 109 | } 110 | 111 | a.code, a.code:visited, a.line, a.line:visited { 112 | color: #4665A2; 113 | } 114 | 115 | a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { 116 | color: #4665A2; 117 | } 118 | 119 | /* @end */ 120 | 121 | dl.el { 122 | margin-left: -1cm; 123 | } 124 | 125 | pre.fragment { 126 | border: 1px solid #C4CFE5; 127 | background-color: #FBFCFD; 128 | padding: 4px 6px; 129 | margin: 4px 8px 4px 2px; 130 | overflow: auto; 131 | word-wrap: break-word; 132 | font-size: 9pt; 133 | line-height: 125%; 134 | font-family: monospace, fixed; 135 | font-size: 105%; 136 | } 137 | 138 | div.fragment { 139 | padding: 4px 6px; 140 | margin: 4px 8px 4px 2px; 141 | border: 1px solid #C4CFE5; 142 | } 143 | 144 | div.line { 145 | font-family: monospace, fixed; 146 | font-size: 13px; 147 | min-height: 13px; 148 | line-height: 1.0; 149 | text-wrap: unrestricted; 150 | white-space: -moz-pre-wrap; /* Moz */ 151 | white-space: -pre-wrap; /* Opera 4-6 */ 152 | white-space: -o-pre-wrap; /* Opera 7 */ 153 | white-space: pre-wrap; /* CSS3 */ 154 | word-wrap: break-word; /* IE 5.5+ */ 155 | text-indent: -53px; 156 | padding-left: 53px; 157 | padding-bottom: 0px; 158 | margin: 0px; 159 | -webkit-transition-property: background-color, box-shadow; 160 | -webkit-transition-duration: 0.5s; 161 | -moz-transition-property: background-color, box-shadow; 162 | -moz-transition-duration: 0.5s; 163 | -ms-transition-property: background-color, box-shadow; 164 | -ms-transition-duration: 0.5s; 165 | -o-transition-property: background-color, box-shadow; 166 | -o-transition-duration: 0.5s; 167 | transition-property: background-color, box-shadow; 168 | transition-duration: 0.5s; 169 | } 170 | 171 | div.line.glow { 172 | background-color: cyan; 173 | box-shadow: 0 0 10px cyan; 174 | } 175 | 176 | 177 | span.lineno { 178 | padding-right: 4px; 179 | text-align: right; 180 | border-right: 2px solid #0F0; 181 | background-color: #E8E8E8; 182 | white-space: pre; 183 | } 184 | span.lineno a { 185 | background-color: #D8D8D8; 186 | } 187 | 188 | span.lineno a:hover { 189 | background-color: #C8C8C8; 190 | } 191 | 192 | div.groupHeader { 193 | margin-left: 16px; 194 | margin-top: 12px; 195 | font-weight: bold; 196 | } 197 | 198 | div.groupText { 199 | margin-left: 16px; 200 | font-style: italic; 201 | } 202 | 203 | /* @group Code Colorization */ 204 | 205 | span.keyword { 206 | color: #008000 207 | } 208 | 209 | span.keywordtype { 210 | color: #604020 211 | } 212 | 213 | span.keywordflow { 214 | color: #e08000 215 | } 216 | 217 | span.comment { 218 | color: #800000 219 | } 220 | 221 | span.preprocessor { 222 | color: #806020 223 | } 224 | 225 | span.stringliteral { 226 | color: #002080 227 | } 228 | 229 | span.charliteral { 230 | color: #008080 231 | } 232 | 233 | span.vhdldigit { 234 | color: #ff00ff 235 | } 236 | 237 | span.vhdlchar { 238 | color: #000000 239 | } 240 | 241 | span.vhdlkeyword { 242 | color: #700070 243 | } 244 | 245 | span.vhdllogic { 246 | color: #ff0000 247 | } 248 | 249 | blockquote { 250 | background-color: #F7F8FB; 251 | border-left: 2px solid #9CAFD4; 252 | margin: 0 24px 0 4px; 253 | padding: 0 12px 0 16px; 254 | } 255 | 256 | -------------------------------------------------------------------------------- /docs/doxy-boot.js: -------------------------------------------------------------------------------- 1 | $( document ).ready(function() { 2 | 3 | $("div.headertitle").addClass("page-header"); 4 | $("div.title").addClass("h1"); 5 | 6 | $('li > a[href="index.html"] > span').before(" "); 7 | $('li > a[href="modules.html"] > span').before(" "); 8 | $('li > a[href="namespaces.html"] > span').before(" "); 9 | $('li > a[href="annotated.html"] > span').before(" "); 10 | $('li > a[href="classes.html"] > span').before(" "); 11 | $('li > a[href="inherits.html"] > span').before(" "); 12 | $('li > a[href="functions.html"] > span').before(" "); 13 | $('li > a[href="functions_func.html"] > span').before(" "); 14 | $('li > a[href="functions_vars.html"] > span').before(" "); 15 | $('li > a[href="functions_enum.html"] > span').before(" "); 16 | $('li > a[href="functions_eval.html"] > span').before(" "); 17 | $('img[src="ftv2ns.png"]').replaceWith('N '); 18 | $('img[src="ftv2cl.png"]').replaceWith('C '); 19 | 20 | $("ul.tablist").addClass("nav nav-pills nav-justified"); 21 | $("ul.tablist").css("margin-top", "0.5em"); 22 | $("ul.tablist").css("margin-bottom", "0.5em"); 23 | $("li.current").addClass("active"); 24 | $("iframe").attr("scrolling", "yes"); 25 | 26 | $("#nav-path > ul").addClass("breadcrumb"); 27 | 28 | $("table.params").addClass("table"); 29 | $("div.ingroups").wrapInner(""); 30 | $("div.levels").css("margin", "0.5em"); 31 | $("div.levels > span").addClass("btn btn-default btn-xs"); 32 | $("div.levels > span").css("margin-right", "0.25em"); 33 | 34 | $("table.directory").addClass("table table-striped"); 35 | $("div.summary > a").addClass("btn btn-default btn-xs"); 36 | $("table.fieldtable").addClass("table"); 37 | $(".fragment").addClass("well"); 38 | $(".memitem").addClass("panel panel-default"); 39 | $(".memproto").addClass("panel-heading"); 40 | $(".memdoc").addClass("panel-body"); 41 | $("span.mlabel").addClass("label label-info"); 42 | 43 | $("table.memberdecls").addClass("table"); 44 | $("[class^=memitem]").addClass("active"); 45 | 46 | $("div.ah").addClass("btn btn-default"); 47 | $("span.mlabels").addClass("pull-right"); 48 | $("table.mlabels").css("width", "100%") 49 | $("td.mlabels-right").addClass("pull-right"); 50 | 51 | $("div.ttc").addClass("panel panel-primary"); 52 | $("div.ttname").addClass("panel-heading"); 53 | $("div.ttname a").css("color", 'white'); 54 | $("div.ttdef,div.ttdoc,div.ttdeci").addClass("panel-body"); 55 | 56 | $('#MSearchBox').parent().remove(); 57 | 58 | $('div.fragment.well div.line:first').css('margin-top', '15px'); 59 | $('div.fragment.well div.line:last').css('margin-bottom', '15px'); 60 | 61 | $('table.doxtable').removeClass('doxtable').addClass('table table-striped table-bordered').each(function(){ 62 | $(this).prepend(''); 63 | $(this).find('tbody > tr:first').prependTo($(this).find('thead')); 64 | 65 | $(this).find('td > span.success').parent().addClass('success'); 66 | $(this).find('td > span.warning').parent().addClass('warning'); 67 | $(this).find('td > span.danger').parent().addClass('danger'); 68 | }); 69 | 70 | 71 | 72 | if($('div.fragment.well div.ttc').length > 0) 73 | { 74 | $('div.fragment.well div.line:first').parent().removeClass('fragment well'); 75 | } 76 | 77 | $('table.memberdecls').find('.memItemRight').each(function(){ 78 | $(this).contents().appendTo($(this).siblings('.memItemLeft')); 79 | $(this).siblings('.memItemLeft').attr('align', 'left'); 80 | }); 81 | 82 | function getOriginalWidthOfImg(img_element) { 83 | var t = new Image(); 84 | t.src = (img_element.getAttribute ? img_element.getAttribute("src") : false) || img_element.src; 85 | return t.width; 86 | } 87 | 88 | $('div.dyncontent').find('img').each(function(){ 89 | if(getOriginalWidthOfImg($(this)[0]) > $('#content>div.container').width()) 90 | $(this).css('width', '100%'); 91 | }); 92 | 93 | $(".memitem").removeClass('memitem'); 94 | $(".memproto").removeClass('memproto'); 95 | $(".memdoc").removeClass('memdoc'); 96 | $("span.mlabel").removeClass('mlabel'); 97 | $("table.memberdecls").removeClass('memberdecls'); 98 | $("[class^=memitem]").removeClass('memitem'); 99 | $("span.mlabels").removeClass('mlabels'); 100 | $("table.mlabels").removeClass('mlabels'); 101 | $("td.mlabels-right").removeClass('mlabels-right'); 102 | $(".navpath").removeClass('navpath'); 103 | $("li.navelem").removeClass('navelem'); 104 | $("a.el").removeClass('el'); 105 | $("div.ah").removeClass('ah'); 106 | $("div.header").removeClass("header"); 107 | 108 | $('.mdescLeft').each(function(){ 109 | if($(this).html()==" ") { 110 | $(this).siblings('.mdescRight').attr('colspan', 2); 111 | $(this).remove(); 112 | } 113 | }); 114 | $('td.memItemLeft').each(function(){ 115 | if($(this).siblings('.memItemRight').html()=="") { 116 | $(this).attr('colspan', 2); 117 | $(this).siblings('.memItemRight').remove(); 118 | } 119 | }); 120 | }); -------------------------------------------------------------------------------- /docs/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | $projectname: $title 18 | $title 19 | 20 | 21 | $treeview 22 | $search 23 | $mathjax 24 | 25 | $extrastylesheet 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 | View on GitHub 38 | 39 |

$projectname $projectnumber

40 |

Components for service discovery via udp multicasting. It's using boost::asio for async networking. It's non-blocking and non-locking.

41 | 42 |
43 | Download this project as a .zip file 44 | Download this project as a tar.gz file 45 |
46 |
47 |
48 | 49 | 56 |
57 |
58 |
59 |
60 |
61 |
62 | 63 | -------------------------------------------------------------------------------- /include/betabugs/networking/detail/std_chrono_time_traits.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // service_discoverer.hpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2015 Benjamin Schulz (beschulz at betabugs dot de) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | // Created by Benjamin Schulz on 14/03/15. 11 | // 12 | 13 | #ifndef _ASIO_SERVICE_DISCOVERY_STD_CHRONO_TIME_TRAITS_HPP_ 14 | #define _ASIO_SERVICE_DISCOVERY_STD_CHRONO_TIME_TRAITS_HPP_ 15 | 16 | #pragma once 17 | 18 | #include 19 | 20 | namespace betabugs { 21 | namespace networking { 22 | namespace detail { 23 | 24 | /*! 25 | * Time trait class to be used with boost::asio::deadline_timer. 26 | * Enabled usage of std::crono with boost::asio::deadline_timer 27 | * 28 | * typedef boost::asio::basic_deadline_timer< 29 | * std::system_clock, 30 | * std_chrono_time_traits> my_system_clock_deadline_timer 31 | * 32 | * http://stackoverflow.com/questions/16721243/boostasiodeadline-timer-with-stdchrono-time-values 33 | * 34 | * */ 35 | template 36 | struct std_chrono_time_traits 37 | { 38 | typedef typename Clock::time_point time_type; 39 | typedef typename Clock::duration duration_type; 40 | 41 | static time_type now() 42 | { 43 | return Clock::now(); 44 | } 45 | 46 | static time_type add(time_type t, duration_type d) 47 | { 48 | return t + d; 49 | } 50 | 51 | static duration_type subtract(time_type t1, time_type t2) 52 | { 53 | return t1 - t2; 54 | } 55 | 56 | static bool less_than(time_type t1, time_type t2) 57 | { 58 | return t1 < t2; 59 | } 60 | 61 | static boost::posix_time::time_duration 62 | to_posix_duration(duration_type d1) 63 | { 64 | using std::chrono::duration_cast; 65 | auto in_sec = duration_cast(d1); 66 | auto in_usec = duration_cast(d1 - in_sec); 67 | boost::posix_time::time_duration result = 68 | boost::posix_time::seconds(in_sec.count()) + 69 | boost::posix_time::microseconds(in_usec.count()); 70 | return result; 71 | } 72 | }; 73 | 74 | } 75 | } 76 | } 77 | 78 | #endif //_ASIO_SERVICE_DISCOVERY_STD_CHRONO_TIME_TRAITS_HPP_ 79 | -------------------------------------------------------------------------------- /include/betabugs/networking/service_announcer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // service_announcer.hpp 3 | // ~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2015 Benjamin Schulz (beschulz at betabugs dot de) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BB_SERVICE_ANNOUNCER_HPP_INCLUDED 12 | #define BB_SERVICE_ANNOUNCER_HPP_INCLUDED 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace betabugs { 21 | namespace networking { 22 | 23 | /*! 24 | * class to announce a named network service, that can be discovered via the service_discoverer. 25 | * 26 | * example: 27 | * @code 28 | * boost::asio::io_service io_service; 29 | * 30 | * service_announcer announcer(io_service, "my_awesome_service", 1337); 31 | * 32 | * io_service.run(); 33 | * @endcode 34 | * 35 | * Note: In case of error, this class just prints to std::cerr 36 | * In case of an unknown service, this class prints it to std::clog 37 | * */ 38 | class service_announcer 39 | { 40 | public: 41 | /*! 42 | * announce service named service_name listening on service_port in one second intervals. 43 | * Note, that it is not required, that the service actually listens on service_port and that 44 | * there is no coupling between the announcer and you service. 45 | * */ 46 | service_announcer( 47 | boost::asio::io_service& io_service, ///< io_service to use 48 | const std::string& service_name, ///< the name of the announced service 49 | const unsigned short service_port, ///< the port where the service listens on 50 | const unsigned short multicast_port = 30001, ///< the port this udp multicast sender sends to 51 | const boost::asio::ip::address& multicast_address = boost::asio::ip::address::from_string("239.255.0.1") ///< mulicast address to use. see: http://en.wikipedia.org/wiki/Multicast_address 52 | ) 53 | : endpoint_(multicast_address, multicast_port) 54 | , socket_(io_service, endpoint_.protocol()) 55 | , timer_(io_service) 56 | , service_name_(service_name) 57 | , service_port_(service_port) 58 | { 59 | // TODO: implement via multiple sockets: 60 | // http://atastypixel.com/blog/the-making-of-talkie-multi-interface-broadcasting-and-multicast/ 61 | write_message(); 62 | } 63 | 64 | private: 65 | void handle_send_to(const boost::system::error_code& error) 66 | { 67 | if (error) 68 | { 69 | std::cerr << error.message() << std::endl; 70 | } 71 | else 72 | { 73 | timer_.expires_from_now(boost::posix_time::seconds(1)); 74 | timer_.async_wait( 75 | [this](const boost::system::error_code& error) 76 | { 77 | this->handle_timeout(error); 78 | }); 79 | } 80 | } 81 | 82 | void handle_timeout(const boost::system::error_code& error) 83 | { 84 | if (!error) 85 | { 86 | write_message(); 87 | } 88 | else 89 | { 90 | std::cerr << error.message() << std::endl; 91 | } 92 | } 93 | 94 | void write_message() 95 | { 96 | std::ostringstream os; 97 | boost::system::error_code error_code; 98 | 99 | // "my_service_name:my_computer:2052" 100 | os << service_name_ 101 | << ":" << boost::asio::ip::host_name(error_code) 102 | << ":" << service_port_; 103 | 104 | if (error_code) 105 | { 106 | std::cerr << error_code.message() << std::endl; 107 | } 108 | 109 | message_ = os.str(); 110 | 111 | socket_.async_send_to( 112 | boost::asio::buffer(message_), endpoint_, 113 | [this](const boost::system::error_code& error, std::size_t /*bytes_transferred*/) 114 | { 115 | this->handle_send_to(error); 116 | } 117 | ); 118 | } 119 | 120 | private: 121 | boost::asio::ip::udp::endpoint endpoint_; 122 | boost::asio::ip::udp::socket socket_; 123 | boost::asio::deadline_timer timer_; 124 | std::string message_; 125 | const std::string service_name_; 126 | const unsigned short service_port_; 127 | }; 128 | 129 | } 130 | } 131 | 132 | #endif /* BB_SERVICE_ANNOUNCER_HPP_INCLUDED */ 133 | -------------------------------------------------------------------------------- /include/betabugs/networking/service_discoverer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // service_discoverer.hpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2015 Benjamin Schulz (beschulz at betabugs dot de) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BB_SERVICE_DISCOVERER_HPP_INCLUDED 12 | #define BB_SERVICE_DISCOVERER_HPP_INCLUDED 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "detail/std_chrono_time_traits.hpp" 21 | 22 | #ifdef max // cleanup after windows 23 | # undef max 24 | #endif 25 | 26 | namespace betabugs { 27 | namespace networking { 28 | 29 | /*! 30 | * Class to discover services announced by the service_announcer 31 | * use service_discoverer discoverer(io_service, name_of_my_service, on_service_discovered); 32 | * on_service_discovered is a std::function, that gets a services set passed as its first and only 33 | * argument. 34 | * 35 | * example: 36 | * @code 37 | * 38 | * boost::asio::io_service io_service; 39 | * 40 | * service_discoverer discoverer( 41 | * io_service, 42 | * "my_awesome_service", 43 | * [](const service_discoverer::services& services) 44 | * { 45 | * for(auto& service : services) 46 | * { 47 | * std::cout << "discovered: " service << std::endl; 48 | * } 49 | * } 50 | * ); 51 | * 52 | * io_service.run(); 53 | * @endcode 54 | * 55 | * Note: In case of error, this class just prints to std::cerr 56 | * 57 | * */ 58 | class service_discoverer 59 | { 60 | public: 61 | /*! 62 | * Represents a discovered service 63 | * */ 64 | struct service 65 | { 66 | /* const */ std::string service_name; ///< the name of the service 67 | /* const */ std::string computer_name; ///< the name of the computer the service is running on 68 | /* const */ 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 the data. 69 | /* const */ std::chrono::steady_clock::time_point last_seen; 70 | 71 | bool operator<(const service& o) const 72 | { 73 | // last_seen is ignored 74 | return std::tie(service_name, computer_name, endpoint) < 75 | std::tie(o.service_name, o.computer_name, o.endpoint); 76 | } 77 | 78 | bool operator==(const service& o) const 79 | { 80 | // again, last_seen is ignored 81 | return std::tie(service_name, computer_name, endpoint) == 82 | std::tie(o.service_name, o.computer_name, o.endpoint); 83 | } 84 | 85 | double age_in_seconds() const 86 | { 87 | auto age = std::chrono::steady_clock::now() - last_seen; 88 | return std::chrono::duration_cast>(age).count(); 89 | } 90 | 91 | // this uses "name injection" 92 | friend std::ostream& operator<<(std::ostream& os, const service_discoverer::service& service) 93 | { 94 | os << service.service_name << " on " << service.computer_name << "(" << service.endpoint << ") " << 95 | service.age_in_seconds() << " seconds ago"; 96 | return os; 97 | } 98 | }; 99 | 100 | /// a set of discovered services 101 | typedef std::set services; 102 | 103 | /// this callback gets called, when ever the set of available services changes 104 | typedef std::function on_services_changed_t; 105 | 106 | /*! 107 | * Constructs a service_discoverer. 108 | * ================================ 109 | * 110 | * listen for udp multicast packets announcing listen_for_service on listen_address:multicast_port. 111 | * 112 | * Call on_services_changed each time a service has been discovered or a service has been idle for too long. 113 | * To protect against malicious announcers, there is a limit (max_services) on how many services will end up in the set of 114 | * discovered services. 115 | * */ 116 | service_discoverer(boost::asio::io_service& io_service, ///< io_service to use 117 | const std::string& listen_for_service, ///< the service to watch out for 118 | const on_services_changed_t on_services_changed, ///< gets called when ever the set of discovered services changes 119 | const std::chrono::steady_clock::duration max_idle = std::chrono::seconds(30), ///< services not seen for this amount of time will be removed from the set 120 | const size_t max_services = 10, ///< maximum number of services to hold 121 | const unsigned short multicast_port = 30001, ///< the udp multicast port to listen on 122 | const boost::asio::ip::address& listen_address = boost::asio::ip::address::from_string("0.0.0.0"), ///< address to listen on 123 | const boost::asio::ip::address& multicast_address = boost::asio::ip::address::from_string("239.255.0.1") ///< must match the one used in service_announcer 124 | ) 125 | : listen_for_service_(listen_for_service) 126 | , socket_(io_service) 127 | , idle_check_timer_(io_service) 128 | , on_services_changed_(on_services_changed) 129 | , max_idle_(max_idle) 130 | , max_services_(max_services) 131 | { 132 | assert(max_services_ > 0); 133 | 134 | // Create the socket so that multiple may be bound to the same address. 135 | boost::asio::ip::udp::endpoint listen_endpoint( 136 | listen_address, multicast_port); 137 | socket_.open(listen_endpoint.protocol()); 138 | socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true)); 139 | socket_.bind(listen_endpoint); 140 | 141 | // Join the multicast group. 142 | socket_.set_option( 143 | boost::asio::ip::multicast::join_group(multicast_address)); 144 | 145 | start_receive(); 146 | } 147 | 148 | private: 149 | void handle_message(const std::string& message, const boost::asio::ip::udp::endpoint& sender_endpoint) 150 | { 151 | std::vector tokens; 152 | { // simpleton "parser" 153 | std::istringstream f(message); 154 | std::string s; 155 | while (getline(f, s, ':')) 156 | tokens.push_back(s); 157 | 158 | if (tokens.size() != 3) 159 | { 160 | std::cerr << "invalid number of tokens in received service announcement: " << std::endl; 161 | std::cerr << " message: " << message << std::endl; 162 | std::cerr << " tokens: " << tokens.size() << std::endl; 163 | return; 164 | } 165 | } 166 | assert(tokens.size() == 3); 167 | 168 | std::string service_name = tokens[0]; 169 | std::string computer_name = tokens[1]; 170 | std::string port_string = tokens[2]; 171 | 172 | // unsigned long, because it's the smalles value that suports unsigned parsing via stl :/ 173 | unsigned long port = 0; 174 | 175 | try 176 | { 177 | port = std::stoul(port_string); 178 | } 179 | catch (const std::exception& e) 180 | { 181 | std::cerr << "failed to parse port number from: " << port_string << std::endl; 182 | return; 183 | } 184 | 185 | if (port > std::numeric_limits::max()) 186 | { 187 | std::cerr << "failed to parse port number from: " << port_string << std::endl; 188 | return; 189 | } 190 | 191 | auto discovered_service = service 192 | { 193 | service_name, 194 | computer_name, 195 | boost::asio::ip::tcp::endpoint(sender_endpoint.address(), (unsigned short)port), 196 | std::chrono::steady_clock::now() 197 | }; 198 | 199 | if (service_name == listen_for_service_) 200 | { 201 | // we need to do a replace here, because discovered_service might compare equal 202 | // to an item already in the set. In this case no assignment would be performed and 203 | // therefore last_seen would not be updated 204 | discovered_services_.erase(discovered_service); 205 | discovered_services_.insert(discovered_service); 206 | 207 | remove_idle_services(); 208 | 209 | // if we have to much services, we need to drop the oldest one 210 | if (discovered_services_.size() > max_services_) 211 | { 212 | // determine service whose last_seen time point is the smallest (i.e. the oldest) 213 | services::iterator oldest_pos = 214 | std::min_element( 215 | discovered_services_.begin(), 216 | discovered_services_.end(), 217 | [](const service& a, const service& b) 218 | { 219 | return a.last_seen < b.last_seen; 220 | } 221 | ); 222 | assert(oldest_pos != discovered_services_.end()); 223 | discovered_services_.erase(oldest_pos); 224 | } 225 | 226 | { // manage the idle_check_timer in case the service dies and we receive no other announcements 227 | 228 | { // cancel the idle_check_timer 229 | boost::system::error_code ec; 230 | idle_check_timer_.cancel(ec); 231 | if (ec) 232 | std::cerr << ec.message(); 233 | } 234 | 235 | { // determine new point in time for the timer 236 | services::iterator oldest_pos = 237 | std::min_element( 238 | discovered_services_.begin(), 239 | discovered_services_.end(), 240 | [](const service& a, const service& b) 241 | { 242 | return a.last_seen < b.last_seen; 243 | } 244 | ); 245 | assert(oldest_pos != discovered_services_.end()); 246 | 247 | idle_check_timer_.expires_at(oldest_pos->last_seen + max_idle_); 248 | idle_check_timer_.async_wait( 249 | [this](const boost::system::error_code& ec) 250 | { 251 | if (!ec && remove_idle_services()) 252 | { 253 | on_services_changed_(discovered_services_); 254 | } 255 | } 256 | ); 257 | } 258 | } 259 | 260 | on_services_changed_(discovered_services_); 261 | } 262 | else 263 | { 264 | std::clog << "ignoring: " << discovered_service << std::endl; 265 | } 266 | } 267 | 268 | void start_receive() 269 | { 270 | // first do a receive with null_buffers to determine the size 271 | socket_.async_receive(boost::asio::null_buffers(), 272 | [this](const boost::system::error_code& error, unsigned int) 273 | { 274 | if (error) 275 | { 276 | std::cerr << error.message() << std::endl; 277 | } 278 | else 279 | { 280 | size_t bytes_available = socket_.available(); 281 | 282 | auto receive_buffer = std::make_shared>(bytes_available); 283 | auto sender_endpoint = std::make_shared(); 284 | 285 | socket_.async_receive_from( 286 | boost::asio::buffer(receive_buffer->data(), receive_buffer->size()), *sender_endpoint, 287 | [this, receive_buffer, sender_endpoint] // we hold on to the shared_ptrs, so that it does not delete it's contents 288 | (const boost::system::error_code& error, size_t bytes_recvd) 289 | { 290 | if (error) 291 | { 292 | std::cerr << error.message() << std::endl; 293 | } 294 | else 295 | { 296 | this->handle_message({receive_buffer->data(), receive_buffer->data() + bytes_recvd}, *sender_endpoint); 297 | start_receive(); 298 | } 299 | }); 300 | } 301 | }); 302 | } 303 | 304 | 305 | // throw out services that have not been seen for to long, returns true, if at least one service was removed, false otherwise. 306 | bool remove_idle_services() 307 | { 308 | auto dead_line = std::chrono::steady_clock::now() - max_idle_; 309 | bool services_removed = false; 310 | 311 | for (services::const_iterator i = discovered_services_.begin(); i != discovered_services_.end();) 312 | { 313 | if (i->last_seen < dead_line) 314 | { 315 | i = discovered_services_.erase(i); 316 | services_removed = true; 317 | } 318 | else 319 | ++i; 320 | } 321 | 322 | return services_removed; 323 | } 324 | 325 | typedef boost::asio::basic_deadline_timer< 326 | std::chrono::steady_clock, 327 | detail::std_chrono_time_traits> steady_clock_deadline_timer_t; 328 | 329 | const std::string listen_for_service_; 330 | boost::asio::ip::udp::socket socket_; 331 | steady_clock_deadline_timer_t idle_check_timer_; 332 | const on_services_changed_t on_services_changed_; 333 | const std::chrono::steady_clock::duration max_idle_; 334 | const size_t max_services_; 335 | 336 | services discovered_services_; 337 | }; 338 | 339 | } 340 | } 341 | 342 | #endif /* BB_SERVICE_DISCOVERER_HPP_INCLUDED */ 343 | -------------------------------------------------------------------------------- /tests/test_all.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Benjamin Schulz on 14/03/15. 3 | // 4 | 5 | #ifndef BOOST_TEST_MODULE 6 | # define BOOST_TEST_MODULE all_tests 7 | # define BOOST_TEST_DYN_LINK 8 | # include 9 | #endif /* BOOST_TEST_MODULE */ 10 | 11 | #include "test_basics.cpp" 12 | #include "test_limits.cpp" 13 | -------------------------------------------------------------------------------- /tests/test_announcer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, const char* const argv[]) 4 | { 5 | (void) argc; 6 | (void) argv; 7 | 8 | boost::asio::io_service io_service; 9 | betabugs::networking::service_announcer announcer(io_service, "my_service", 1337); 10 | io_service.run(); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /tests/test_basics.cpp: -------------------------------------------------------------------------------- 1 | #ifndef BOOST_TEST_MODULE 2 | # define BOOST_TEST_MODULE basic_tests 3 | # define BOOST_TEST_DYN_LINK 4 | # include 5 | #endif /* BOOST_TEST_MODULE */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace betabugs::networking; 12 | 13 | BOOST_AUTO_TEST_SUITE(basic_tests) 14 | 15 | BOOST_AUTO_TEST_CASE(test_equality) 16 | { 17 | service_discoverer::service a{ 18 | "service_name", 19 | "computer_name", 20 | boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string("1.2.3.4"), 1337), 21 | std::chrono::steady_clock::now() 22 | }; 23 | 24 | service_discoverer::service b = a; 25 | BOOST_CHECK_EQUAL(a, b); 26 | 27 | // equality ignores last_seen 28 | service_discoverer::service c = a; 29 | BOOST_CHECK_EQUAL(a, c); 30 | c.last_seen = std::chrono::steady_clock::now(); 31 | BOOST_CHECK_EQUAL(a, c); 32 | 33 | service_discoverer::service d = a; 34 | BOOST_CHECK_EQUAL(a, d); 35 | d.endpoint.port(1338); 36 | BOOST_CHECK_NE(a, d); 37 | BOOST_CHECK_LT(a, d); 38 | BOOST_CHECK(!(d < a)); 39 | 40 | service_discoverer::service e = a; 41 | e.endpoint.address(boost::asio::ip::address::from_string("1.2.3.5")); 42 | BOOST_CHECK_NE(a, e); 43 | BOOST_CHECK_LT(a, e); 44 | BOOST_CHECK(!(e < a)); 45 | 46 | service_discoverer::service f = a; 47 | service_discoverer::service g = a; 48 | f.endpoint.port(1338); 49 | g.endpoint.address(boost::asio::ip::address::from_string("1.2.3.5")); 50 | BOOST_CHECK_NE(f, g); 51 | BOOST_CHECK_LT(f, g); 52 | BOOST_CHECK(!(g < f)); 53 | } 54 | 55 | BOOST_AUTO_TEST_CASE(test_basic_functionality) 56 | { 57 | boost::asio::io_service io_service; 58 | 59 | service_announcer announcer(io_service, "my_service", 1337); 60 | 61 | bool did_discover_service = false; 62 | service_discoverer discoverer(io_service, "my_service", 63 | [&io_service, &did_discover_service] 64 | (const service_discoverer::services& services) 65 | { 66 | BOOST_CHECK(!services.empty()); 67 | 68 | for (const auto& service : services) 69 | { 70 | BOOST_CHECK_EQUAL(service.service_name, "my_service"); 71 | BOOST_CHECK_EQUAL(service.computer_name, boost::asio::ip::host_name()); 72 | BOOST_CHECK_EQUAL(service.endpoint.port(), 1337); 73 | } 74 | 75 | did_discover_service = !services.empty(); 76 | }); 77 | 78 | boost::asio::deadline_timer timer(io_service); 79 | timer.expires_from_now(boost::posix_time::seconds(2)); 80 | 81 | timer.async_wait( 82 | [&io_service, &did_discover_service] 83 | (const boost::system::error_code& error) 84 | { 85 | BOOST_CHECK(!error); 86 | BOOST_CHECK(did_discover_service); 87 | io_service.stop(); 88 | }); 89 | 90 | io_service.run(); 91 | } 92 | 93 | // Test, that only services subscribed to are passed to the callback 94 | BOOST_AUTO_TEST_CASE(test_service_filtering) 95 | { 96 | boost::asio::io_service io_service; 97 | 98 | bool did_discover_service = false; 99 | 100 | service_announcer announcer(io_service, "my_service", 1337); 101 | service_announcer announcer2(io_service, "my_service2", 1338); 102 | 103 | service_discoverer discoverer(io_service, "my_service", 104 | [&io_service, &did_discover_service] 105 | (const service_discoverer::services& services) 106 | { 107 | BOOST_CHECK(!services.empty()); 108 | 109 | for (const auto& service : services) 110 | { 111 | BOOST_CHECK_EQUAL(service.service_name, "my_service"); 112 | BOOST_CHECK_EQUAL(service.computer_name, boost::asio::ip::host_name()); 113 | BOOST_CHECK_EQUAL(service.endpoint.port(), 1337); 114 | } 115 | 116 | did_discover_service = !services.empty(); 117 | }); 118 | 119 | boost::asio::deadline_timer timer(io_service); 120 | timer.expires_from_now(boost::posix_time::seconds(2)); 121 | 122 | timer.async_wait( 123 | [&io_service, &did_discover_service] 124 | (const boost::system::error_code& error) 125 | { 126 | BOOST_CHECK(!error); 127 | BOOST_CHECK(did_discover_service); 128 | io_service.stop(); 129 | }); 130 | 131 | io_service.run(); 132 | } 133 | 134 | BOOST_AUTO_TEST_CASE(test_threaded) 135 | { 136 | std::atomic did_discover_service; 137 | 138 | std::thread announcer_thread([&did_discover_service]() 139 | { 140 | boost::asio::io_service io_service; 141 | 142 | service_announcer announcer(io_service, "my_service", 1337); 143 | 144 | boost::asio::deadline_timer timer(io_service); 145 | timer.expires_from_now(boost::posix_time::seconds(2)); 146 | 147 | timer.async_wait( 148 | [&io_service, &did_discover_service] 149 | (const boost::system::error_code& error) 150 | { 151 | BOOST_CHECK(!error); 152 | BOOST_CHECK(did_discover_service); 153 | io_service.stop(); 154 | }); 155 | 156 | io_service.run(); 157 | }); 158 | 159 | std::thread discoverer_thread([&did_discover_service]() 160 | { 161 | boost::asio::io_service io_service; 162 | 163 | service_discoverer discoverer(io_service, "my_service", 164 | [&io_service, &did_discover_service] 165 | (const service_discoverer::services& services) 166 | { 167 | BOOST_CHECK(!services.empty()); 168 | 169 | for (const auto& service : services) 170 | { 171 | BOOST_CHECK_EQUAL(service.service_name, "my_service"); 172 | BOOST_CHECK_EQUAL(service.computer_name, boost::asio::ip::host_name()); 173 | BOOST_CHECK_EQUAL(service.endpoint.port(), 1337); 174 | } 175 | 176 | did_discover_service = !services.empty(); 177 | 178 | io_service.stop(); 179 | }); 180 | 181 | boost::asio::deadline_timer timer(io_service); 182 | timer.expires_from_now(boost::posix_time::seconds(2)); 183 | 184 | timer.async_wait( 185 | [&io_service, &did_discover_service] 186 | (const boost::system::error_code& error) 187 | { 188 | BOOST_CHECK(!error); 189 | BOOST_CHECK(did_discover_service); 190 | io_service.stop(); 191 | }); 192 | 193 | io_service.run(); 194 | }); 195 | 196 | announcer_thread.join(); 197 | discoverer_thread.join(); 198 | 199 | BOOST_CHECK(did_discover_service); 200 | } 201 | 202 | BOOST_AUTO_TEST_CASE(test_overflow) 203 | { 204 | boost::asio::io_service io_service; 205 | 206 | std::string ridiculously_long_service_name; 207 | const char hex_chars[] = "0123456789abcdef"; 208 | 209 | for (std::size_t i = 0; i != 1024 * 8; ++i) 210 | { 211 | ridiculously_long_service_name.push_back(hex_chars[i % sizeof(hex_chars)]); 212 | } 213 | 214 | service_announcer announcer(io_service, ridiculously_long_service_name, 1337); 215 | 216 | bool did_discover_service = false; 217 | service_discoverer discoverer(io_service, ridiculously_long_service_name, 218 | [&io_service, &did_discover_service, &ridiculously_long_service_name] 219 | (const service_discoverer::services& services) 220 | { 221 | BOOST_CHECK(!services.empty()); 222 | 223 | for (const auto& service : services) 224 | { 225 | BOOST_CHECK_EQUAL(service.service_name, ridiculously_long_service_name); 226 | BOOST_CHECK_EQUAL(service.computer_name, boost::asio::ip::host_name()); 227 | BOOST_CHECK_EQUAL(service.endpoint.port(), 1337); 228 | } 229 | 230 | did_discover_service = !services.empty(); 231 | }); 232 | 233 | boost::asio::deadline_timer timer(io_service); 234 | timer.expires_from_now(boost::posix_time::seconds(2)); 235 | 236 | timer.async_wait( 237 | [&io_service, &did_discover_service] 238 | (const boost::system::error_code& error) 239 | { 240 | BOOST_CHECK(!error); 241 | BOOST_CHECK(did_discover_service); 242 | io_service.stop(); 243 | }); 244 | 245 | io_service.run(); 246 | } 247 | 248 | BOOST_AUTO_TEST_CASE(test_multiple_services) 249 | { 250 | boost::asio::io_service io_service; 251 | 252 | service_announcer announcer1(io_service, "my_service", 1337); 253 | service_announcer announcer2(io_service, "my_service", 1338); 254 | 255 | std::size_t number_of_discovered_services = 0; 256 | service_discoverer discoverer(io_service, "my_service", 257 | [&io_service, &number_of_discovered_services] 258 | (const service_discoverer::services& services) 259 | { 260 | BOOST_CHECK(!services.empty()); 261 | 262 | for (const auto& service : services) 263 | { 264 | BOOST_CHECK_EQUAL(service.service_name, "my_service"); 265 | BOOST_CHECK_EQUAL(service.computer_name, boost::asio::ip::host_name()); 266 | } 267 | 268 | number_of_discovered_services = services.size(); 269 | }); 270 | 271 | boost::asio::deadline_timer timer(io_service); 272 | timer.expires_from_now(boost::posix_time::seconds(2)); 273 | 274 | timer.async_wait( 275 | [&io_service, &number_of_discovered_services] 276 | (const boost::system::error_code& error) 277 | { 278 | BOOST_CHECK(!error); 279 | BOOST_CHECK_EQUAL(number_of_discovered_services, 2); 280 | io_service.stop(); 281 | }); 282 | 283 | io_service.run(); 284 | } 285 | 286 | BOOST_AUTO_TEST_SUITE_END() 287 | -------------------------------------------------------------------------------- /tests/test_limits.cpp: -------------------------------------------------------------------------------- 1 | #ifndef BOOST_TEST_MODULE 2 | # define BOOST_TEST_MODULE limit_tests 3 | # define BOOST_TEST_DYN_LINK 4 | # include 5 | #endif /* BOOST_TEST_MODULE */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace betabugs::networking; 12 | 13 | BOOST_AUTO_TEST_SUITE(basic_limits) 14 | 15 | BOOST_AUTO_TEST_CASE(test_max_services) 16 | { 17 | boost::asio::io_service io_service; 18 | 19 | using namespace betabugs::networking; 20 | 21 | unsigned short port = 1337; 22 | std::array announcers{{ 23 | {io_service, "test_service", ++port}, 24 | {io_service, "test_service", ++port}, 25 | {io_service, "test_service", ++port}, 26 | {io_service, "test_service", ++port}, 27 | {io_service, "test_service", ++port}, 28 | }}; 29 | 30 | size_t max_found_services = 0; 31 | service_discoverer discoverer( 32 | io_service, 33 | "test_service", 34 | [&max_found_services](const service_discoverer::services& services) 35 | { 36 | BOOST_CHECK_LE(services.size(), 3); 37 | max_found_services = std::max(max_found_services, services.size()); 38 | }, 39 | std::chrono::seconds(5), 40 | 3 41 | ); 42 | 43 | boost::asio::deadline_timer timer(io_service); 44 | timer.expires_from_now(boost::posix_time::seconds(5)); 45 | 46 | timer.async_wait( 47 | [&io_service](const boost::system::error_code& error) 48 | { 49 | BOOST_CHECK(!error); 50 | io_service.stop(); 51 | } 52 | ); 53 | 54 | io_service.run(); 55 | BOOST_CHECK_EQUAL(max_found_services, 3); 56 | } 57 | 58 | BOOST_AUTO_TEST_CASE(test_max_idle) 59 | { 60 | boost::asio::io_service io_service; 61 | 62 | using namespace betabugs::networking; 63 | 64 | unsigned short port = 1337; 65 | auto announcer_busy = std::make_shared(io_service, "test_service", ++port); 66 | auto announcer_idle = std::make_shared(io_service, "test_service", ++port); 67 | 68 | size_t max_found_services = 0; 69 | service_discoverer discoverer( 70 | io_service, 71 | "test_service", 72 | [&max_found_services, &announcer_idle](const service_discoverer::services& services) 73 | { 74 | for (const auto& service : services) 75 | { 76 | BOOST_CHECK_LE(service.age_in_seconds(), 2.0f); 77 | } 78 | 79 | // drop the service, so that it will be thrown out 80 | announcer_idle.reset(); 81 | 82 | max_found_services = std::max(max_found_services, services.size()); 83 | }, 84 | std::chrono::seconds(2), 85 | 1000 86 | ); 87 | 88 | boost::asio::deadline_timer timer(io_service); 89 | timer.expires_from_now(boost::posix_time::seconds(5)); 90 | 91 | timer.async_wait( 92 | [&io_service](const boost::system::error_code& error) 93 | { 94 | BOOST_CHECK(!error); 95 | io_service.stop(); 96 | } 97 | ); 98 | 99 | io_service.run(); 100 | BOOST_CHECK_EQUAL(max_found_services, 2); 101 | } 102 | 103 | // test that the deadline timer fires and removes the idle service 104 | BOOST_AUTO_TEST_CASE(test_max_idle_with_no_other_service) 105 | { 106 | boost::asio::io_service io_service; 107 | 108 | using namespace betabugs::networking; 109 | 110 | unsigned short port = 1337; 111 | auto announcer_idle = std::make_shared(io_service, "test_service", ++port); 112 | 113 | size_t min_found_services = std::numeric_limits::max(); 114 | size_t max_found_services = 0; 115 | 116 | service_discoverer discoverer( 117 | io_service, 118 | "test_service", 119 | [&min_found_services, &max_found_services, &announcer_idle](const service_discoverer::services& services) 120 | { 121 | for (const auto& service : services) 122 | { 123 | BOOST_CHECK_LE(service.age_in_seconds(), 2.0f); 124 | } 125 | 126 | // drop the service, so that it will be thrown out 127 | announcer_idle.reset(); 128 | 129 | max_found_services = std::max(max_found_services, services.size()); 130 | min_found_services = std::min(min_found_services, services.size()); 131 | }, 132 | std::chrono::seconds(2), 133 | 1000 134 | ); 135 | 136 | boost::asio::deadline_timer timer(io_service); 137 | timer.expires_from_now(boost::posix_time::seconds(4)); 138 | 139 | timer.async_wait( 140 | [&io_service](const boost::system::error_code& error) 141 | { 142 | BOOST_CHECK(!error); 143 | io_service.stop(); 144 | } 145 | ); 146 | 147 | io_service.run(); 148 | BOOST_CHECK_EQUAL(max_found_services, 1); 149 | BOOST_CHECK_EQUAL(min_found_services, 0); 150 | } 151 | 152 | BOOST_AUTO_TEST_SUITE_END() 153 | --------------------------------------------------------------------------------