View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jeffrey Rosenwald and Jan Wielemaker
    4    E-mail:        jeffrose@acm.org
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2012-2013, Jeffrey Rosenwald
    7		   2018-2020, CWI Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(udp_broadcast,
   37          [ udp_broadcast_initialize/2,         % +IPAddress, +Options
   38            udp_broadcast_close/1,		% +Scope
   39
   40            udp_peer_add/2,                     % +Scope, +IP
   41            udp_peer_del/2,                     % +Scope, ?IP
   42            udp_peer/2                          % +Scope, -IP
   43          ]).   44:- autoload(library(apply),[maplist/2,maplist/3]).   45:- autoload(library(backcomp),[thread_at_exit/1]).   46:- autoload(library(broadcast),
   47	    [broadcast_request/1,broadcast/1,listening/3,listen/3]).   48:- autoload(library(debug),[debug/3]).   49:- autoload(library(error),
   50	    [must_be/2,syntax_error/1,domain_error/2,existence_error/2]).   51:- autoload(library(option),[option/3]).   52:- autoload(library(socket),
   53	    [ tcp_close_socket/1,
   54	      udp_socket/1,
   55	      tcp_bind/2,
   56	      tcp_getopt/2,
   57	      tcp_setopt/2,
   58	      udp_receive/4,
   59	      udp_send/4
   60	    ]).   61
   62
   63% :- debug(udp(broadcast)).

A UDP broadcast proxy

SWI-Prolog's broadcast library provides a means that may be used to facilitate publish and subscribe communication regimes between anonymous members of a community of interest. The members of the community are however, necessarily limited to a single instance of Prolog. The UDP broadcast library removes that restriction. With this library loaded, any member on your local IP subnetwork that also has this library loaded may hear and respond to your broadcasts.

This library support three styles of networking as described below. Each of these networks have their own advantages and disadvantages. Please study the literature to understand the consequences.

broadcast
Broadcast messages are sent to the LAN subnet. The broadcast implementation uses two UDP ports: a public to address the whole group and a private one to address a specific node. Broadcasting is generally a good choice if the subnet is small and traffic is low.
unicast
Unicast sends copies of packages to known peers. Unicast networks can easily be routed. The unicast version uses a single UDP port per node. Unicast is generally a good choice for a small party, in particular if the peers are in different networks.
multicast
Multicast is like broadcast, but it can be configured to work accross networks and may work more efficiently on VLAN networks. Like the broadcast setup, two UDP ports are used. Multicasting can in general deliver the most efficient LAN and WAN networks, but requires properly configured routing between the peers.

After initialization and, in the case of a unicast network managing the set of peers, communication happens through broadcast/1, broadcast_request/1 and listen/1,2,3.

A broadcast/1 or broadcast_request/1 of the shape udp(Scope, Term) or udp(Scope, Term, TimeOut) is forwarded over the UDP network to all peers that joined the same Scope. To prevent the potential for feedback loops, only the plain Term is broadcasted locally. The timeout is optional. It specifies the amount to time to wait for replies to arrive in response to a broadcast_request/1. The default period is 0.250 seconds. The timeout is ignored for broadcasts.

An example of three separate processes cooperating in the same scope called peers:

Process A:

   ?- listen(number(X), between(1, 5, X)).
   true.

   ?-

Process B:

   ?- listen(number(X), between(7, 9, X)).
   true.

   ?-

Process C:

   ?- findall(X, broadcast_request(udp(peers, number(X))), Xs).
   Xs = [1, 2, 3, 4, 5, 7, 8, 9].

   ?-

It is also possible to carry on a private dialog with a single responder. To do this, you supply a compound of the form, Term:PortId, to a UDP scoped broadcast/1 or broadcast_request/1, where PortId is the ip-address and port-id of the intended listener. If you supply an unbound variable, PortId, to broadcast_request, it will be unified with the address of the listener that responds to Term. You may send a directed broadcast to a specific member by simply providing this address in a similarly structured compound to a UDP scoped broadcast/1. The message is sent via unicast to that member only by way of the member's broadcast listener. It is received by the listener just as any other broadcast would be. The listener does not know the difference.

For example, in order to discover who responded with a particular value:

Host B Process 1:

   ?- listen(number(X), between(1, 5, X)).
   true.

   ?-

Host A Process 1:


   ?- listen(number(X), between(7, 9, X)).
   true.

   ?-

Host A Process 2:

   ?- listen(number(X), between(1, 5, X)).
   true.

   ?- bagof(X, broadcast_request(udp(peers,number(X):From,1)), Xs).
   From = ip(192, 168, 1, 103):34855,
   Xs = [7, 8, 9] ;
   From = ip(192, 168, 1, 103):56331,
   Xs = [1, 2, 3, 4, 5] ;
   From = ip(192, 168, 1, 104):3217,
   Xs = [1, 2, 3, 4, 5].

All incomming trafic is handled by a single thread with the alias udp_inbound_proxy. This thread also performs the internal dispatching using broadcast/1 and broadcast_request/1. Future versions may provide for handling these requests in separate threads.

Caveats

While the implementation is mostly transparent, there are some important and subtle differences that must be taken into consideration:

author
- Jeffrey Rosenwald (JeffRose@acm.org), Jan Wielemaker
See also
- tipc.pl */
license
- BSD-2
  279:- multifile
  280    udp_term_string_hook/3,                     % +Scope, ?Term, ?String
  281    udp_unicast_join_hook/3,                    % +Scope, +From, +Data
  282    black_list/1.                               % +Term
  283
  284:- meta_predicate
  285    safely(0),
  286    safely_det(0).  287
  288safely(Predicate) :-
  289    Err = error(_,_),
  290    catch(Predicate, Err,
  291          print_message_fail(Err)).
  292
  293safely_det(Predicate) :-
  294    Err = error(_,_),
  295    catch(Predicate, Err,
  296          print_message_fail(Err)),
  297    !.
  298safely_det(_).
  299
  300print_message_fail(Term) :-
  301    print_message(error, Term),
  302    fail.
  303
  304udp_broadcast_address(IPAddress, Subnet, BroadcastAddress) :-
  305    IPAddress = ip(A1, A2, A3, A4),
  306    Subnet = ip(S1, S2, S3, S4),
  307    BroadcastAddress = ip(B1, B2, B3, B4),
  308
  309    B1 is A1 \/ (S1 xor 255),
  310    B2 is A2 \/ (S2 xor 255),
  311    B3 is A3 \/ (S3 xor 255),
  312    B4 is A4 \/ (S4 xor 255).
 udp_broadcast_service(?Scope, ?Address) is nondet
provides the UDP broadcast address for a given Scope. At present, only one scope is supported, udp_subnet.
 udp_scope(?ScopeName, ?ScopeDef)
  321:- dynamic
  322    udp_scope/2,
  323    udp_scope_peer/2.  324:- volatile
  325    udp_scope/2,
  326    udp_scope_peer/2.  327%
  328%  Here's a UDP proxy to Prolog's broadcast library
  329%
  330%  A sender may extend a broadcast  to  a   subnet  of  a UDP network by
  331%  specifying a =|udp_subnet|= scoping qualifier   in his/her broadcast.
  332%  The qualifier has the effect of  selecting the appropriate multi-cast
  333%  address for the transmission. Thus,  the   sender  of the message has
  334%  control over the scope of his/her traffic on a per-message basis.
  335%
  336%  All in-scope listeners receive the   broadcast and simply rebroadcast
  337%  the message locally. All broadcast replies, if any, are sent directly
  338%  to the sender via the port-id that   was received with the broadcast.
  339%
  340%  Each listener exposes two UDP ports,  a   shared  public port that is
  341%  bound to a well-known port number and   a  private port that uniquely
  342%  indentifies the listener. Broadcasts are received  on the public port
  343%  and replies are  sent  on  the   private  port.  Directed  broadcasts
  344%  (unicasts) are received on the private port   and replies are sent on
  345%  the private port.
  346
  347%  Thread 1 listens for directed traffic on the private port.
  348%
  349
  350:- dynamic
  351    udp_private_socket/3,                       % Port, Socket, FileNo
  352    udp_public_socket/4,                        % Scope, Port, Socket, FileNo
  353    udp_closed/1.				% Scope
  354
  355udp_inbound_proxy :-
  356    thread_at_exit(inbound_proxy_died),
  357    udp_inbound_proxy_loop.
  358
  359udp_inbound_proxy_loop :-
  360    make_private_socket,
  361    forall(udp_scope(Scope, ScopeData),
  362           make_public_socket(ScopeData, Scope)),
  363    retractall(udp_closed(_)),
  364    findall(FileNo, udp_socket_file_no(FileNo), FileNos),
  365    catch(dispatch_inbound(FileNos),
  366          E, dispatch_exception(E)),
  367    udp_inbound_proxy_loop.
  368
  369dispatch_exception(E) :-
  370    E = error(_,_),
  371    !,
  372    print_message(warning, E).
  373dispatch_exception(_).
 make_private_socket is det
Create our private socket. This socket is used for messages that are directed to me. Note that we only need this for broadcast networks. If we use a unicast network we use our public port to contact this specific server.
  383make_private_socket :-
  384    udp_private_socket(_Port, S, _F),
  385    !,
  386    (   (   udp_scope(Scope, broadcast(_,_,_))
  387        ;   udp_scope(Scope, multicast(_,_))
  388        ),
  389        \+ udp_closed(Scope)
  390    ->  true
  391    ;   tcp_close_socket(S),
  392        retractall(udp_private_socket(_,_,_))
  393    ).
  394make_private_socket :-
  395    udp_scope(_, broadcast(_,_,_)),
  396    !,
  397    udp_socket(S),
  398    tcp_bind(S, Port),
  399    tcp_getopt(S, file_no(F)),
  400    tcp_setopt(S, broadcast),
  401    assertz(udp_private_socket(Port, S, F)).
  402make_private_socket :-
  403    udp_scope(_, multicast(_,_)),
  404    !,
  405    udp_socket(S),
  406    tcp_bind(S, Port),
  407    tcp_getopt(S, file_no(F)),
  408    assertz(udp_private_socket(Port, S, F)).
  409make_private_socket.
 make_public_socket(+ScopeData, +Scope)
Create the public port Scope.
  415make_public_socket(_, Scope) :-
  416    udp_public_socket(Scope, _Port, S, _),
  417    !,
  418    (   udp_closed(Scope)
  419    ->  tcp_close_socket(S),
  420        retractall(udp_public_socket(Scope, _, _, _))
  421    ;   true
  422    ).
  423make_public_socket(broadcast(_SubNet, _Broadcast, Port), Scope) :-
  424    udp_socket(S),
  425    tcp_setopt(S, reuseaddr),
  426    tcp_bind(S, Port),
  427    tcp_getopt(S, file_no(F)),
  428    assertz(udp_public_socket(Scope, Port, S, F)).
  429make_public_socket(multicast(Group, Port), Scope) :-
  430    udp_socket(S),
  431    tcp_setopt(S, reuseaddr),
  432    tcp_bind(S, Port),
  433    tcp_setopt(S, ip_add_membership(Group)),
  434    tcp_getopt(S, file_no(F)),
  435    assertz(udp_public_socket(Scope, Port, S, F)).
  436make_public_socket(unicast(Port), Scope) :-
  437    udp_socket(S),
  438    tcp_bind(S, Port),
  439    tcp_getopt(S, file_no(F)),
  440    assertz(udp_public_socket(Scope, Port, S, F)).
  441
  442udp_socket_file_no(FileNo) :-
  443    udp_private_socket(_,_,FileNo).
  444udp_socket_file_no(FileNo) :-
  445    udp_public_socket(_,_,_,FileNo).
 dispatch_inbound(+FileNos)
Dispatch inbound traffic. This loop uses wait_for_input/3 to wait for one or more UDP sockets and dispatches the requests using the internal broadcast service. For an incomming broadcast request we send the reply only to the requester and therefore we must use a socket that is not in broadcast mode.
  455dispatch_inbound(FileNos) :-
  456    debug(udp(broadcast), 'Waiting for ~p', [FileNos]),
  457    wait_for_input(FileNos, Ready, infinite),
  458    debug(udp(broadcast), 'Ready: ~p', [Ready]),
  459    maplist(dispatch_ready, Ready),
  460    dispatch_inbound(FileNos).
  461
  462dispatch_ready(FileNo) :-
  463    udp_private_socket(_Port, Private, FileNo),
  464    !,
  465    udp_receive(Private, Data, From, [max_message_size(65535)]),
  466    debug(udp(broadcast), 'Inbound on private port', []),
  467    (   in_scope(Scope, From),
  468        udp_term_string(Scope, Term, Data) % only accept valid data
  469    ->  ld_dispatch(Private, Term, From, Scope)
  470    ;   true
  471    ).
  472dispatch_ready(FileNo) :-
  473    udp_public_socket(Scope, _PublicPort, Public, FileNo),
  474    !,
  475    udp_receive(Public, Data, From, [max_message_size(65535)]),
  476    debug(udp(broadcast), 'Inbound on public port from ~p for scope ~p',
  477          [From, Scope]),
  478    (   in_scope(Scope, From),
  479        udp_term_string(Scope, Term, Data) % only accept valid data
  480    ->  (   udp_scope(Scope, unicast(_))
  481        ->  ld_dispatch(Public, Term, From, Scope)
  482        ;   udp_private_socket(_PrivatePort, Private, _FileNo),
  483            ld_dispatch(Private, Term, From, Scope)
  484        )
  485    ;   udp_scope(Scope, unicast(_)),
  486        udp_term_string(Scope, Term, Data),
  487        unicast_out_of_scope_request(Scope, From, Term)
  488    ->  true
  489    ;   true
  490    ).
  491
  492in_scope(Scope, Address) :-
  493    udp_scope(Scope, ScopeData),
  494    in_scope(ScopeData, Scope, Address),
  495    !.
  496in_scope(Scope, From) :-
  497    debug(udp(broadcast), 'Out-of-scope ~p datagram from ~p',
  498          [Scope, From]),
  499    fail.
  500
  501in_scope(broadcast(Subnet, Broadcast, _PublicPort), _Scope, IP:_FromPort) :-
  502    udp_broadcast_address(IP, Subnet, Broadcast).
  503in_scope(multicast(_Group, _Port), _Scope, _From).
  504in_scope(unicast(_PublicPort), Scope, IP:_) :-
  505    udp_peer(Scope, IP:_).
 ld_dispatch(+PrivateSocket, +Term, +From, +Scope)
Locally dispatch Term received from From. If it concerns a broadcast request, send the replies to PrivateSocket to From. The multifile hook black_list/1 can be used to ignore certain messages.
  514ld_dispatch(_S, Term, From, _Scope) :-
  515    debug(udp(broadcast), 'ld_dispatch(~p) from ~p', [Term, From]),
  516    fail.
  517ld_dispatch(_S, Term, _From, _Scope) :-
  518    blacklisted(Term), !.
  519ld_dispatch(S, request(Key, Term), From, Scope) :-
  520    !,
  521    forall(safely(broadcast_request(Term)),
  522           safely((udp_term_string(Scope, reply(Key,Term), Message),
  523                   udp_send(S, Message, From, [])))).
  524ld_dispatch(_S, send(Term), _From, _Scope) :-
  525    safely_det(broadcast(Term)).
  526ld_dispatch(_S, reply(Key, Term), From, _Scope) :-
  527    (   reply_queue(Key, Queue)
  528    ->  safely(thread_send_message(Queue, Term:From))
  529    ;   true
  530    ).
  531
  532blacklisted(send(Term))      :- black_list(Term).
  533blacklisted(request(_,Term)) :- black_list(Term).
  534blacklisted(reply(_,Term))   :- black_list(Term).
 reload_udp_proxy
Update the UDP relaying proxy service. The proxy consists of three forwarding mechanisms:
  550reload_udp_proxy :-
  551    reload_outbound_proxy,
  552    reload_inbound_proxy.
  553
  554reload_outbound_proxy :-
  555    listening(udp_broadcast, udp(_,_), _),
  556    !.
  557reload_outbound_proxy :-
  558    listen(udp_broadcast, udp(Scope,Message),
  559           udp_broadcast(Message, Scope, 0.25)),
  560    listen(udp_broadcast, udp(Scope,Message,Timeout),
  561           udp_broadcast(Message, Scope, Timeout)),
  562    listen(udp_broadcast, udp_subnet(Message),  % backward compatibility
  563           udp_broadcast(Message, subnet, 0.25)),
  564    listen(udp_broadcast, udp_subnet(Message,Timeout),
  565           udp_broadcast(Message, subnet, Timeout)).
  566
  567reload_inbound_proxy :-
  568    catch(thread_signal(udp_inbound_proxy, throw(udp_reload)),
  569          error(existence_error(thread, _),_),
  570          fail),
  571    !.
  572reload_inbound_proxy :-
  573    thread_create(udp_inbound_proxy, _,
  574                  [ alias(udp_inbound_proxy),
  575                    detached(true)
  576                  ]).
  577
  578inbound_proxy_died :-
  579    thread_self(Self),
  580    thread_property(Self, status(Status)),
  581    (   catch(recreate_proxy(Status), _, fail)
  582    ->  print_message(informational,
  583                      httpd_restarted_worker(Self))
  584    ;   done_status_message_level(Status, Level),
  585        print_message(Level,
  586                      httpd_stopped_worker(Self, Status))
  587    ).
  588
  589recreate_proxy(exception(Error)) :-
  590    recreate_on_error(Error),
  591    reload_inbound_proxy.
  592
  593recreate_on_error('$aborted').
  594recreate_on_error(time_limit_exceeded).
  595
  596done_status_message_level(true, silent) :- !.
  597done_status_message_level(exception('$aborted'), silent) :- !.
  598done_status_message_level(_, informational).
 udp_broadcast_close(+Scope)
Close a UDP broadcast scope.
  605udp_broadcast_close(Scope) :-
  606    udp_scope(Scope, _ScopeData),
  607    !,
  608    assert(udp_closed(Scope)),
  609    reload_udp_proxy.
  610udp_broadcast_close(_).
 udp_broadcast(+What, +Scope, +TimeOut)
Send a broadcast request to my UDP peers in Scope. What is either of the shape Term:Address to send Term to a specific address or query the address from which term is answered or it is a plain Term.

If Term is nonground, it is considered is a request (see broadcast_request/1) and the predicate succeeds for each answer received within TimeOut seconds. If Term is ground it is considered an asynchronous broadcast and udp_broadcast/3 is deterministic.

  624udp_broadcast(Term:To, Scope, _Timeout) :-
  625    ground(Term), ground(To),           % broadcast to single listener
  626    !,
  627    udp_basic_broadcast(send(Term), Scope, single(To)).
  628udp_broadcast(Term, Scope, _Timeout) :-
  629    ground(Term),                       % broadcast to all listeners
  630    !,
  631    udp_basic_broadcast(send(Term), Scope, broadcast).
  632udp_broadcast(Term:To, Scope, Timeout) :-
  633    ground(To),                         % request to single listener
  634    !,
  635    setup_call_cleanup(
  636        request_queue(Id, Queue),
  637        ( udp_basic_broadcast(request(Id, Term), Scope, single(To)),
  638          udp_br_collect_replies(Queue, Timeout, Term:To)
  639        ),
  640        destroy_request_queue(Queue)).
  641udp_broadcast(Term:From, Scope, Timeout) :-
  642    !,                                  % request to all listeners, collect sender
  643    setup_call_cleanup(
  644        request_queue(Id, Queue),
  645        ( udp_basic_broadcast(request(Id, Term), Scope, broadcast),
  646          udp_br_collect_replies(Queue, Timeout, Term:From)
  647        ),
  648        destroy_request_queue(Queue)).
  649udp_broadcast(Term, Scope, Timeout) :-  % request to all listeners
  650    udp_broadcast(Term:_, Scope, Timeout).
  651
  652:- dynamic
  653    reply_queue/2.  654
  655request_queue(Id, Queue) :-
  656    Id is random(1<<63),
  657    message_queue_create(Queue),
  658    asserta(reply_queue(Id, Queue)).
  659
  660destroy_request_queue(Queue) :-         % leave queue to GC
  661    retractall(reply_queue(_, Queue)).
 udp_basic_broadcast(+Term, +Dest) is multi
Create a UDP private socket and use it to send Term to Address. If Address is our broadcast address, set the socket in broadcast mode.

This predicate succeeds with a choice point. Committing the choice point closes S.

Arguments:
Dest- is one of single(Target) or broadcast.
  674udp_basic_broadcast(Term, Scope, Dest) :-
  675    debug(udp(broadcast), 'UDP proxy outbound ~p to ~p', [Term, Dest]),
  676    udp_term_string(Scope, Term, String),
  677    udp_send_message(Dest, String, Scope).
  678
  679udp_send_message(single(Address), String, Scope) :-
  680    (   udp_scope(Scope, unicast(_))
  681    ->  udp_public_socket(Scope, _Port, S, _)
  682    ;   udp_private_socket(_Port, S, _F)
  683    ),
  684    safely(udp_send(S, String, Address, [])).
  685udp_send_message(broadcast, String, Scope) :-
  686    (   udp_scope(Scope, unicast(_))
  687    ->  udp_public_socket(Scope, _Port, S, _),
  688        forall(udp_peer(Scope, Address),
  689               ( debug(udp(broadcast), 'Unicast to ~p', [Address]),
  690                 safely(udp_send(S, String, Address, []))))
  691    ;   udp_scope(Scope, broadcast(_SubNet, Broadcast, Port))
  692    ->  udp_private_socket(_PrivatePort, S, _F),
  693        udp_send(S, String, Broadcast:Port, [])
  694    ;   udp_scope(Scope, multicast(Group, Port))
  695    ->  udp_private_socket(_PrivatePort, S, _F),
  696        udp_send(S, String, Group:Port, [])
  697    ).
  698
  699% ! udp_br_collect_replies(+Queue, +TimeOut, -TermAndFrom) is nondet.
  700%
  701%   Collect replies on Socket for  TimeOut   seconds.  Succeed  for each
  702%   received message.
  703
  704udp_br_collect_replies(Queue, Timeout, Reply) :-
  705    get_time(Start),
  706    Deadline is Start+Timeout,
  707    repeat,
  708       (   thread_get_message(Queue, Reply,
  709                              [ deadline(Deadline)
  710                              ])
  711       ->  true
  712       ;   !,
  713           fail
  714       ).
 udp_broadcast_initialize(+IPAddress, +Options) is semidet
Initialized UDP broadcast bridge. IPAddress is the IP address on the network we want to broadcast on. IP addresses are terms ip(A,B,C,D) or an atom or string of the format A.B.C.D. Options processed:
scope(+ScopeName)
Name of the scope. Default is subnet.
subnet_mask(+SubNet)
Subnet to broadcast on. This uses the same syntax as IPAddress. Default classifies the network as class A, B or C depending on the the first octet and applies the default mask.
port(+Port)
Public port to use. Default is 20005.
method(+Method)
Method to send a message to multiple peers. One of
broadcast
Use UDP broadcast messages to the LAN. This is the default
multicast
Use UDP multicast messages. This can be used on WAN networks, provided the intermediate routers understand multicast.
unicast
Send the messages individually to all registered peers.

For compatibility reasons Options may be the subnet mask.

  743udp_broadcast_initialize(IP, Options) :-
  744    with_mutex(udp_broadcast,
  745               udp_broadcast_initialize_sync(IP, Options)).
  746
  747udp_broadcast_initialize_sync(IP, Options) :-
  748    nonvar(Options),
  749    Options = ip(_,_,_,_),
  750    !,
  751    udp_broadcast_initialize(IP, [subnet_mask(Options)]).
  752udp_broadcast_initialize_sync(IP, Options) :-
  753    to_ip4(IP, IPAddress),
  754    option(method(Method), Options, broadcast),
  755    must_be(oneof([broadcast, multicast, unicast]), Method),
  756    udp_broadcast_initialize_sync(Method, IPAddress, Options),
  757    reload_udp_proxy.
  758
  759udp_broadcast_initialize_sync(broadcast, IPAddress, Options) :-
  760    option(subnet_mask(Subnet), Options, _),
  761    mk_subnet(Subnet, IPAddress, Subnet4),
  762    option(port(Port), Options, 20005),
  763    option(scope(Scope), Options, subnet),
  764
  765    udp_broadcast_address(IPAddress, Subnet4, Broadcast),
  766    udp_broadcast_close(Scope),
  767    assertz(udp_scope(Scope, broadcast(Subnet4, Broadcast, Port))).
  768udp_broadcast_initialize_sync(unicast, _IPAddress, Options) :-
  769    option(port(Port), Options, 20005),
  770    option(scope(Scope), Options, subnet),
  771    udp_broadcast_close(Scope),
  772    assertz(udp_scope(Scope, unicast(Port))).
  773udp_broadcast_initialize_sync(multicast, IPAddress, Options) :-
  774    option(port(Port), Options, 20005),
  775    option(scope(Scope), Options, subnet),
  776    udp_broadcast_close(Scope),
  777    multicast_address(IPAddress),
  778    assertz(udp_scope(Scope, multicast(IPAddress, Port))).
  779
  780to_ip4(Atomic, ip(A,B,C,D)) :-
  781    atomic(Atomic),
  782    !,
  783    (   split_string(Atomic, ".", "", Strings),
  784        maplist(number_string, [A,B,C,D], Strings)
  785    ->  true
  786    ;   syntax_error(illegal_ip_address)
  787    ).
  788to_ip4(IP, IP).
  789
  790mk_subnet(Var, IP, Subnet) :-
  791    var(Var),
  792    !,
  793    (   default_subnet(IP, Subnet)
  794    ->  true
  795    ;   domain_error(ip_with_subnet, IP)
  796    ).
  797mk_subnet(Subnet, _, Subnet4) :-
  798    to_ip4(Subnet, Subnet4).
  799
  800default_subnet(ip(A,_,_,_), ip(A,0,0,0)) :-
  801    between(1,126, A), !.
  802default_subnet(ip(A,B,_,_), ip(A,B,0,0)) :-
  803    between(128,191, A), !.
  804default_subnet(ip(A,B,C,_), ip(A,B,C,0)) :-
  805    between(192,223, A), !.
  806
  807multicast_address(ip(A,_,_,_)) :-
  808    between(224, 239, A),
  809    !.
  810multicast_address(IP) :-
  811    domain_error(multicast_network, IP).
  812
  813
  814		 /*******************************
  815		 *          UNICAST PEERS	*
  816		 *******************************/
 udp_peer_add(+Scope, +Address) is det
 udp_peer_del(+Scope, ?Address) is det
 udp_peer(?Scope, ?Address) is nondet
Manage and query the set of known peers for a unicast network. Address is either a term IP:Port or a plain IP address. In the latter case the default port registered with the scope is used.
Arguments:
Address- has canonical form ip(A,B,C,D):Port.
  828udp_peer_add(Scope, Address) :-
  829    must_be(ground, Address),
  830    peer_address(Address, Scope, Canonical),
  831    (   udp_scope_peer(Scope, Canonical)
  832    ->  true
  833    ;   assertz(udp_scope_peer(Scope, Canonical))
  834    ).
  835
  836udp_peer_del(Scope, Address) :-
  837    peer_address(Address, Scope, Canonical),
  838    retractall(udp_scope_peer(Scope, Canonical)).
  839
  840udp_peer(Scope, IPAddress) :-
  841    udp_scope_peer(Scope, IPAddress).
  842
  843peer_address(IP:Port, _Scope, IPAddress:Port) :-
  844    !,
  845    to_ip4(IP, IPAddress).
  846peer_address(IP, Scope, IPAddress:Port) :-
  847    (   udp_scope(Scope, unicast(Port))
  848    ->  true
  849    ;   existence_error(udp_scope, Scope)
  850    ),
  851    to_ip4(IP, IPAddress).
  852
  853
  854
  855		 /*******************************
  856		 *             HOOKS		*
  857		 *******************************/
 udp_term_string_hook(+Scope, +Term, -String) is det
udp_term_string_hook(+Scope, -Term, +String) is semidet
Hook for serializing the message Term. The default writes %prolog\n, followed by the Prolog term in quoted notation while ignoring operators. This hook may use alternative serialization such as fast_term_serialized/2, use library(ssl) to realise encrypted messages, etc.
Arguments:
Scope- is the scope for which the message is broadcasted. This can be used to use different serialization for different scopes.
Term- encapsulates the term broadcasted by the application as follows:
send(ApplTerm)
Is sent by broadcast(udp(Scope, ApplTerm))
request(Id, ApplTerm)
Is sent by broadcast_request/1, where Id is a unique large (64 bit) integer.
reply(Id, ApplTerm)
Is sent to reply on a broadcast_request/1 request that has been received. Arguments are the same as above.
throws
- The hook may throw udp(invalid_message) to stop processing the message.
 udp_term_string(+Scope, +Term, -String) is det
udp_term_string(+Scope, -Term, +String) is semidet
Serialize an arbitrary Prolog term as a string. The string is prefixed by a magic key to ensure we only accept messages that are meant for us.

In mode (+,-), Term is written with the options ignore_ops(true) and quoted(true).

This predicate first calls udp_term_string_hook/3.

  897udp_term_string(Scope, Term, String) :-
  898    catch(udp_term_string_hook(Scope, Term, String), udp(Error), true),
  899    !,
  900    (   var(Error)
  901    ->  true
  902    ;   Error == invalid_message
  903    ->  fail
  904    ;   throw(udp(Error))
  905    ).
  906udp_term_string(_Scope, Term, String) :-
  907    (   var(String)
  908    ->  format(string(String), '%-prolog-\n~W',
  909               [ Term,
  910                 [ ignore_ops(true),
  911                   quoted(true)
  912                 ]
  913               ])
  914    ;   sub_string(String, 0, _, _, '%-prolog-\n'),
  915        term_string(Term, String,
  916                    [ syntax_errors(quiet)
  917                    ])
  918    ).
 unicast_out_of_scope_request(+Scope, +From, +Data) is semidet
 udp_unicast_join_hook(+Scope, +From, +Data) is semidet
This multifile hook is called if an UDP package is received on the port of the unicast network identified by Scope. From is the origin IP and port and Data is the message data that is deserialized as defined for the scope (see udp_term_string/3).

This hook is intended to initiate a new node joining the network of peers. We could in theory also omit the in-scope test and use a normal broadcast to join. Using a different channal however provides a basic level of security. A possibe implementation is below. The first fragment is a hook added to the server, the second is a predicate added to a client and the last initiates the request in the client. The excanged term (join(X)) can be used to exchange a welcome handshake.

:- multifile udp_broadcast:udp_unicast_join_hook/3.
udp_broadcast:udp_unicast_join_hook(Scope, From, join(welcome)) :-
    udp_peer_add(Scope, From),
join_request(Scope, Address, Reply) :-
    udp_peer_add(Scope, Address),
    broadcast_request(udp(Scope, join(X))).
?- join_request(myscope, "1.2.3.4":10001, Reply).
Reply = welcome.
  956unicast_out_of_scope_request(Scope, From, send(Term)) :-
  957    udp_unicast_join_hook(Scope, From, Term).
  958unicast_out_of_scope_request(Scope, From, request(Key, Term)) :-
  959    udp_unicast_join_hook(Scope, From, Term),
  960    udp_public_socket(Scope, _Port, Socket, _FileNo),
  961    safely((udp_term_string(Scope, reply(Key,Term), Message),
  962            udp_send(Socket, Message, From, [])))