All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zeromq.ZBeacon Maven / Gradle / Ivy

There is a newer version: 0.6.0
Show newest version
package org.zeromq;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import zmq.util.Objects;

public class ZBeacon
{
    public static final long    DEFAULT_BROADCAST_INTERVAL = 1000L;
    public static final String  DEFAULT_BROADCAST_HOST     = "255.255.255.255";
    private static final byte[] DEFAULT_BRODACAST_ADDRESS  = { 0, 0, 0, 0 };

    private final BroadcastClient           broadcastClient;
    private final BroadcastServer           broadcastServer;
    private final AtomicReference   prefix            = new AtomicReference<>(new byte[0]);
    private final AtomicLong                broadcastInterval = new AtomicLong(DEFAULT_BROADCAST_INTERVAL);
    private final AtomicReference listener          = new AtomicReference<>();

    public ZBeacon(int port, byte[] beacon)
    {
        this(DEFAULT_BROADCAST_HOST, port, beacon);
    }

    public ZBeacon(String host, int port, byte[] beacon)
    {
        this(host, port, beacon, true);
    }

    public ZBeacon(String host, int port, byte[] beacon, boolean ignoreLocalAddress)
    {
        this(host, port, beacon, ignoreLocalAddress, false);
    }

    public ZBeacon(String host, int port, byte[] beacon, boolean ignoreLocalAddress, boolean blocking)
    {
        this(host, DEFAULT_BRODACAST_ADDRESS, port, beacon, DEFAULT_BROADCAST_INTERVAL, ignoreLocalAddress, blocking);
    }

    private ZBeacon(String host, byte[] serverAddress, int port, byte[] beacon, long broadcastInterval,
                    boolean ignoreLocalAddress, boolean blocking)
    {
        Objects.requireNonNull(host, "Host cannot be null");
        Objects.requireNonNull(serverAddress, "Server address cannot be null");
        Objects.requireNonNull(beacon, "Beacon cannot be null");
        this.broadcastInterval.set(broadcastInterval);
        broadcastServer = new BroadcastServer(serverAddress, port, ignoreLocalAddress, blocking);
        broadcastServer.setDaemon(true);
        broadcastClient = new BroadcastClient(host, port, this.broadcastInterval, beacon);
        broadcastClient.setDaemon(true);
    }

    public static class Builder
    {
        private String  clientHost         = DEFAULT_BROADCAST_HOST;
        private byte[]  serverAddr         = DEFAULT_BRODACAST_ADDRESS;
        private int     port;
        private long    broadcastInterval  = DEFAULT_BROADCAST_INTERVAL;
        private byte[]  beacon;
        private boolean ignoreLocalAddress = true;
        private boolean blocking           = false;

        public Builder port(int port)
        {
            this.port = port;
            return this;
        }

        public Builder beacon(byte[] beacon)
        {
            this.beacon = beacon;
            return this;
        }

        public Builder client(String host)
        {
            this.clientHost = host;
            return this;
        }

        public Builder server(byte[] addr)
        {
            Utils.checkArgument(addr.length == 4 || addr.length == 16, "Server Address has to be 4 or 16 bytes long");
            this.serverAddr = addr;
            return this;
        }

        public Builder ignoreLocalAddress(boolean ignoreLocalAddress)
        {
            this.ignoreLocalAddress = ignoreLocalAddress;
            return this;
        }

        public Builder blocking(boolean blocking)
        {
            this.blocking = blocking;
            return this;
        }

        public Builder broadcastInterval(long broadcastInterval)
        {
            this.broadcastInterval = broadcastInterval;
            return this;
        }

        public ZBeacon build()
        {
            return new ZBeacon(clientHost, serverAddr, port, beacon, broadcastInterval, ignoreLocalAddress, blocking);
        }
    }

    public void setUncaughtExceptionHandlers(Thread.UncaughtExceptionHandler clientHandler,
                                             Thread.UncaughtExceptionHandler serverHandler)
    {
        broadcastClient.setUncaughtExceptionHandler(clientHandler);
        broadcastServer.setUncaughtExceptionHandler(serverHandler);
    }

    public void start()
    {
        if (listener.get() != null) {
            broadcastServer.start();
        }
        broadcastClient.start();
    }

    public void stop() throws InterruptedException
    {
        if (broadcastClient != null) {
            broadcastClient.interrupt();
            broadcastClient.join();
        }
        if (broadcastServer != null) {
            broadcastServer.interrupt();
            broadcastServer.join();
        }
    }

    public void setPrefix(byte[] prefix)
    {
        this.prefix.set(prefix);
    }

    public byte[] getPrefix()
    {
        return prefix.get();
    }

    public void setListener(Listener listener)
    {
        this.listener.set(listener);
    }

    public Listener getListener()
    {
        return listener.get();
    }

    /**
     * All beacons with matching prefix are passed to a listener.
     */
    public interface Listener
    {
        void onBeacon(InetAddress sender, byte[] beacon);
    }

    /**
     * The broadcast client periodically sends beacons via UDP to the network.
     */
    private static class BroadcastClient extends Thread
    {
        private DatagramChannel         broadcastChannel;
        private final InetSocketAddress broadcastInetSocketAddress;
        private final AtomicLong        broadcastInterval;
        private final byte[]            beacon;

        public BroadcastClient(String host, int port, AtomicLong broadcastInterval, byte[] beacon)
        {
            this.broadcastInterval = broadcastInterval;
            this.beacon = beacon;
            try {
                broadcastInetSocketAddress = new InetSocketAddress(InetAddress.getByName(host), port);
            }
            catch (UnknownHostException unknownHostException) {
                throw new RuntimeException(unknownHostException);
            }
        }

        @Override
        public void run()
        {
            try {
                broadcastChannel = DatagramChannel.open();
                broadcastChannel.socket().setBroadcast(true);
                while (!interrupted()) {
                    try {
                        broadcastChannel.send(ByteBuffer.wrap(beacon), broadcastInetSocketAddress);
                        Thread.sleep(broadcastInterval.get());
                    }
                    catch (InterruptedException | ClosedByInterruptException interruptedException) {
                        // Re-interrupt the thread so the caller can handle it.
                        Thread.currentThread().interrupt();
                        break;
                    }
                    catch (Exception exception) {
                        throw new RuntimeException(exception);
                    }
                }
            }
            catch (IOException ioException) {
                throw new RuntimeException(ioException);
            }
            finally {
                try {
                    broadcastChannel.close();
                }
                catch (IOException ioException) {
                    throw new RuntimeException(ioException);
                }
            }
        }

    }

    /**
     * The broadcast server receives beacons.
     */
    private class BroadcastServer extends Thread
    {
        private final DatagramChannel handle;            // Socket for send/recv
        private final boolean         ignoreLocalAddress;

        public BroadcastServer(byte[] serverAddress, int port, boolean ignoreLocalAddress, boolean blocking)
        {
            this.ignoreLocalAddress = ignoreLocalAddress;
            try {
                // Create UDP socket
                handle = DatagramChannel.open();
                handle.configureBlocking(blocking);
                DatagramSocket sock = handle.socket();
                sock.setReuseAddress(true);
                sock.bind(new InetSocketAddress(InetAddress.getByAddress(serverAddress), port));
            }
            catch (IOException ioException) {
                throw new RuntimeException(ioException);
            }
        }

        @Override
        public void run()
        {
            ByteBuffer buffer = ByteBuffer.allocate(65535);
            SocketAddress sender;
            int size;
            while (!interrupted()) {
                buffer.clear();
                try {
                    int read = buffer.remaining();
                    sender = handle.receive(buffer);
                    if (sender == null) {
                        continue;
                    }

                    InetAddress senderAddress = ((InetSocketAddress) sender).getAddress();

                    if (ignoreLocalAddress
                            && (InetAddress.getLocalHost().getHostAddress().equals(senderAddress.getHostAddress())
                                    || senderAddress.isAnyLocalAddress() || senderAddress.isLoopbackAddress())) {
                        continue;
                    }

                    size = read - buffer.remaining();
                    handleMessage(buffer, size, senderAddress);
                }
                catch (ClosedChannelException ioException) {
                    break;
                }
                catch (IOException ioException) {
                    throw new RuntimeException(ioException);
                }
            }
            handle.socket().close();
        }

        private void handleMessage(ByteBuffer buffer, int size, InetAddress from)
        {
            byte[] prefix = ZBeacon.this.prefix.get();
            if (size < prefix.length) {
                return;
            }
            ByteBuffer buf = buffer.duplicate();
            buf.position(0);
            for (int i = 0; i < prefix.length; i++) {
                if (buf.get() != prefix[i]) {
                    return;
                }
            }
            // prefix matched
            listener.get().onBeacon(from, Arrays.copyOf(buffer.array(), size));
        }
    }

    public long getBroadcastInterval()
    {
        return broadcastInterval.get();
    }

    public void setBroadcastInterval(long broadcastInterval)
    {
        this.broadcastInterval.set(broadcastInterval);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy