PublicShow sourceudp_broadcast.pl -- 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 seperate 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
Source udp_broadcast_close(+Scope)
Close a UDP broadcast scope.
Source 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.

Source udp_peer_add(+Scope, +Address) is det
Source udp_peer_del(+Scope, ?Address) is det
Source 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.
 udp_term_string_hook(+Scope, +Term, -String) is det[multifile]
udp_term_string_hook(+Scope, -Term, +String) is semidet[multifile]
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_unicast_join_hook(+Scope, +From, +Data) is semidet[multifile]
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.