diff --git a/AUTHORS b/AUTHORS index 4e3502a01..e3a538082 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,6 +32,7 @@ Erik Algell Erik Rigtorp Fabien Ninoles Frank Denis +Frederic Delechamp George Neill Gerard Toonstra Ghislain Putois diff --git a/pom.xml b/pom.xml index 8eba1de78..8c03c0bb8 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,11 @@ 4.11 test + + org.abstractj.kalium + kalium + 0.5.0 + diff --git a/src/checkstyle/eclipse-format-profile.xml b/src/checkstyle/eclipse-format-profile.xml new file mode 100644 index 000000000..dcddcd774 --- /dev/null +++ b/src/checkstyle/eclipse-format-profile.xml @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/zeromq/ManagedContext.java b/src/main/java/org/zeromq/ManagedContext.java index 275bfb759..9eec67abd 100644 --- a/src/main/java/org/zeromq/ManagedContext.java +++ b/src/main/java/org/zeromq/ManagedContext.java @@ -1,20 +1,21 @@ package org.zeromq; -import zmq.Ctx; -import zmq.SocketBase; -import zmq.ZMQ; - import java.util.HashSet; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import zmq.Ctx; +import zmq.SocketBase; +import zmq.ZMQ; + // This is to avoid people trying to initialize a Context class ManagedContext { static { // Release ManagedSocket resources when catching SIGINT - Runtime.getRuntime().addShutdownHook(new Thread() { + Runtime.getRuntime().addShutdownHook(new Thread() + { @Override public void run() { @@ -23,8 +24,8 @@ public void run() }); } - private final Lock lock; - private final Ctx ctx; + private final Lock lock; + private final Ctx ctx; private final Set sockets; private ManagedContext() diff --git a/src/main/java/org/zeromq/Utils.java b/src/main/java/org/zeromq/Utils.java index 054c7624d..c67fa8f36 100644 --- a/src/main/java/org/zeromq/Utils.java +++ b/src/main/java/org/zeromq/Utils.java @@ -4,10 +4,12 @@ public class Utils { - private Utils() {} + private Utils() + { + } public static int findOpenPort() throws IOException { - return zmq.Utils.findOpenPort(); + return zmq.util.Utils.findOpenPort(); } } diff --git a/src/main/java/org/zeromq/ZActor.java b/src/main/java/org/zeromq/ZActor.java index 5814a0b44..6f00252f4 100644 --- a/src/main/java/org/zeromq/ZActor.java +++ b/src/main/java/org/zeromq/ZActor.java @@ -243,15 +243,13 @@ public String premiere(final Socket pipe) } @Override - public List createSockets(final ZContext ctx, - final Object[] args) + public List createSockets(final ZContext ctx, final Object[] args) { return Collections.emptyList(); } @Override - public void start(final Socket pipe, final List sockets, - final ZPoller poller) + public void start(final Socket pipe, final List sockets, final ZPoller poller) { // do nothing } @@ -264,16 +262,14 @@ public long looping(Socket pipe, ZPoller poller) } @Override - public boolean backstage(final Socket pipe, final ZPoller poller, - final int events) + public boolean backstage(final Socket pipe, final ZPoller poller, final int events) { // stop looping return false; } @Override - public boolean stage(final Socket socket, final Socket pipe, - final ZPoller poller, int events) + public boolean stage(final Socket socket, final Socket pipe, final ZPoller poller, int events) { // stop looping return false; @@ -336,16 +332,14 @@ public String premiere(final Socket pipe) } @Override - public List createSockets(final ZContext ctx, - final Object[] args) + public List createSockets(final ZContext ctx, final Object[] args) { shadow.createSockets(ctx, args); return main.createSockets(ctx, args); } @Override - public void start(final Socket pipe, final List sockets, - final ZPoller poller) + public void start(final Socket pipe, final List sockets, final ZPoller poller) { shadow.start(pipe, sockets, poller); main.start(pipe, sockets, poller); @@ -359,16 +353,14 @@ public long looping(Socket pipe, ZPoller poller) } @Override - public boolean backstage(final Socket pipe, final ZPoller poller, - final int events) + public boolean backstage(final Socket pipe, final ZPoller poller, final int events) { shadow.backstage(pipe, poller, events); return main.backstage(pipe, poller, events); } @Override - public boolean stage(final Socket socket, final Socket pipe, - final ZPoller poller, final int events) + public boolean stage(final Socket socket, final Socket pipe, final ZPoller poller, final int events) { shadow.stage(socket, pipe, poller, events); return main.stage(socket, pipe, poller, events); @@ -446,8 +438,8 @@ public ZActor(final SelectorCreator selector, final Actor actor, final String mo * @param args * the optional arguments that will be passed to the distant actor */ - public ZActor(final ZContext context, final SelectorCreator selector, - final Actor actor, final String motdelafin, final Object[] args) + public ZActor(final ZContext context, final SelectorCreator selector, final Actor actor, final String motdelafin, + final Object[] args) { super(context, selector, new ActorFortune(actor), motdelafin, args); } @@ -470,8 +462,7 @@ public String premiere(Socket mic, Object[] args) } @Override - public Star create(ZContext ctx, Socket pipe, Selector sel, - int count, Star previous, Object[] args) + public Star create(ZContext ctx, Socket pipe, Selector sel, int count, Star previous, Object[] args) { Star star = new ZActor.Double(ctx, pipe, sel, actor, args); return star; @@ -508,8 +499,7 @@ private static final class Double implements EventsHandler, Star private final ZContext context; // creates a new double - public Double(final ZContext ctx, final Socket pipe, - final Selector selector, final Actor actor, + public Double(final ZContext ctx, final Socket pipe, final Selector selector, final Actor actor, final Object[] args) { this.context = ctx; diff --git a/src/main/java/org/zeromq/ZAgent.java b/src/main/java/org/zeromq/ZAgent.java index 8e102c409..25ae42775 100644 --- a/src/main/java/org/zeromq/ZAgent.java +++ b/src/main/java/org/zeromq/ZAgent.java @@ -80,13 +80,6 @@ public interface ZAgent */ boolean sign(); - /** - * Forcely destroys the Star. - * @deprecated not sure it is useful or recommended - */ - @Deprecated - void nova(); - /** * Returns the socket used for communication. * For advanced usage. @@ -210,12 +203,6 @@ public Socket pipe() { return pipe; } - - @Override - public void nova() - { - pipe.close(); - } } /** diff --git a/src/main/java/org/zeromq/ZBeacon.java b/src/main/java/org/zeromq/ZBeacon.java index 17fab6441..2409d048b 100644 --- a/src/main/java/org/zeromq/ZBeacon.java +++ b/src/main/java/org/zeromq/ZBeacon.java @@ -13,17 +13,17 @@ public class ZBeacon { - public static final long DEFAULT_BROADCAST_INTERVAL = 1000L; - public static final String DEFAULT_BROADCAST_HOST = "255.255.255.255"; + public static final long DEFAULT_BROADCAST_INTERVAL = 1000L; + public static final String DEFAULT_BROADCAST_HOST = "255.255.255.255"; - private final int port; - private InetAddress broadcastInetAddress; + private final int port; + private InetAddress broadcastInetAddress; private final BroadcastClient broadcastClient; private final BroadcastServer broadcastServer; - private final byte[] beacon; - private byte[] prefix = {}; - private long broadcastInterval = DEFAULT_BROADCAST_INTERVAL; - private Listener listener = null; + private final byte[] beacon; + private byte[] prefix = {}; + private long broadcastInterval = DEFAULT_BROADCAST_INTERVAL; + private Listener listener = null; public ZBeacon(int port, byte[] beacon) { @@ -52,7 +52,8 @@ public ZBeacon(String host, int port, byte[] beacon, boolean ignoreLocalAddress) broadcastClient.setDaemon(true); } - public void setUncaughtExceptionHandlers(Thread.UncaughtExceptionHandler clientHandler, Thread.UncaughtExceptionHandler serverHandler) + public void setUncaughtExceptionHandlers(Thread.UncaughtExceptionHandler clientHandler, + Thread.UncaughtExceptionHandler serverHandler) { broadcastClient.setUncaughtExceptionHandler(clientHandler); broadcastServer.setUncaughtExceptionHandler(serverHandler); @@ -111,7 +112,7 @@ public interface Listener */ private class BroadcastClient extends Thread { - private DatagramChannel broadcastChannel; + private DatagramChannel broadcastChannel; private final InetSocketAddress broadcastInetSocketAddress; public BroadcastClient() @@ -158,8 +159,8 @@ public void run() */ private class BroadcastServer extends Thread { - private DatagramChannel handle; // Socket for send/recv - private final boolean ignoreLocalAddress; + private DatagramChannel handle; // Socket for send/recv + private final boolean ignoreLocalAddress; public BroadcastServer(boolean ignoreLocalAddress) { @@ -194,10 +195,9 @@ public void run() InetAddress senderAddress = ((InetSocketAddress) sender).getAddress(); - if (ignoreLocalAddress && - (InetAddress.getLocalHost().getHostAddress().equals(senderAddress.getHostAddress()) - || senderAddress.isAnyLocalAddress() - || senderAddress.isLoopbackAddress())) { + if (ignoreLocalAddress + && (InetAddress.getLocalHost().getHostAddress().equals(senderAddress.getHostAddress()) + || senderAddress.isAnyLocalAddress() || senderAddress.isLoopbackAddress())) { continue; } diff --git a/src/main/java/org/zeromq/ZContext.java b/src/main/java/org/zeromq/ZContext.java index c2ed561f7..019144474 100644 --- a/src/main/java/org/zeromq/ZContext.java +++ b/src/main/java/org/zeromq/ZContext.java @@ -23,7 +23,7 @@ public class ZContext implements Closeable /** * Reference to underlying Context object */ - private volatile Context context; // Created lazily, use getContext() to access. + private volatile Context context; // Created lazily, use getContext() to access. /** * List of sockets managed by this ZContext diff --git a/src/main/java/org/zeromq/ZFrame.java b/src/main/java/org/zeromq/ZFrame.java index bf0ebc0dc..2bf0427fc 100644 --- a/src/main/java/org/zeromq/ZFrame.java +++ b/src/main/java/org/zeromq/ZFrame.java @@ -20,8 +20,8 @@ public class ZFrame { - public static final int MORE = ZMQ.SNDMORE; - public static final int REUSE = 128; // no effect at java + public static final int MORE = ZMQ.SNDMORE; + public static final int REUSE = 128; // no effect at java public static final int DONTWAIT = ZMQ.DONTWAIT; private boolean more; @@ -367,7 +367,7 @@ public static ZFrame recvFrame(Socket socket) public static ZFrame recvFrame(Socket socket, int flags) { ZFrame f = new ZFrame(); - byte [] data = f.recv(socket, flags); + byte[] data = f.recv(socket, flags); if (data == null) { return null; } @@ -382,7 +382,7 @@ public void print(String prefix) if (prefix != null) { pw.printf("%s", prefix); } - byte []data = getData(); + byte[] data = getData(); int size = size(); boolean isBin = false; diff --git a/src/main/java/org/zeromq/ZLoop.java b/src/main/java/org/zeromq/ZLoop.java index 9e0d27614..55d6b8b9b 100644 --- a/src/main/java/org/zeromq/ZLoop.java +++ b/src/main/java/org/zeromq/ZLoop.java @@ -24,10 +24,10 @@ public interface IZLoopHandler private class SPoller { - PollItem item; + PollItem item; IZLoopHandler handler; - Object arg; - int errors; // If too many errors, kill poller + Object arg; + int errors; // If too many errors, kill poller protected SPoller(PollItem item, IZLoopHandler handler, Object arg) { @@ -41,14 +41,13 @@ protected SPoller(PollItem item, IZLoopHandler handler, Object arg) private class STimer { - int delay; - int times; + int delay; + int times; IZLoopHandler handler; - Object arg; - long when; // Clock time when alarm goes off + Object arg; + long when; // Clock time when alarm goes off - public STimer(int delay, int times, IZLoopHandler handler, - Object arg) + public STimer(int delay, int times, IZLoopHandler handler, Object arg) { this.delay = delay; this.times = times; @@ -59,25 +58,25 @@ public STimer(int delay, int times, IZLoopHandler handler, } - private final Context context; // Context managing the pollers. - private final List pollers; // List of poll items - private final List timers; // List of timers - private int pollSize; // Size of poll set - private Poller pollset; // zmq_poll set - private SPoller[] pollact; // Pollers for this poll set - private boolean dirty; // True if pollset needs rebuilding - private boolean verbose; // True if verbose tracing wanted - private final List zombies; // List of timers to kill - private final List newTimers; // List of timers to add + private final Context context; // Context managing the pollers. + private final List pollers; // List of poll items + private final List timers; // List of timers + private int pollSize; // Size of poll set + private Poller pollset; // zmq_poll set + private SPoller[] pollact; // Pollers for this poll set + private boolean dirty; // True if pollset needs rebuilding + private boolean verbose; // True if verbose tracing wanted + private final List zombies; // List of timers to kill + private final List newTimers; // List of timers to add public ZLoop(Context context) { assert (context != null); this.context = context; - pollers = new ArrayList(); - timers = new ArrayList(); - zombies = new ArrayList(); + pollers = new ArrayList(); + timers = new ArrayList(); + zombies = new ArrayList(); newTimers = new ArrayList(); } @@ -88,7 +87,7 @@ public ZLoop(ZContext ctx) public void destroy() { - // do nothing + context.close(); } // We hold an array of pollers that matches the pollset, so we can @@ -152,9 +151,11 @@ public int addPoller(PollItem pollItem, IZLoopHandler handler, Object arg) dirty = true; if (verbose) { - System.out.printf("I: zloop: register %s poller (%s, %s)\n", - pollItem.getSocket() != null ? pollItem.getSocket().getType() : "RAW", - pollItem.getSocket(), pollItem.getRawSocket()); + System.out.printf( + "I: zloop: register %s poller (%s, %s)\n", + pollItem.getSocket() != null ? pollItem.getSocket().getType() : "RAW", + pollItem.getSocket(), + pollItem.getRawSocket()); } return 0; } @@ -175,9 +176,11 @@ public void removePoller(PollItem pollItem) } } if (verbose) { - System.out.printf("I: zloop: cancel %s poller (%s, %s)", - pollItem.getSocket() != null ? pollItem.getSocket().getType() : "RAW", - pollItem.getSocket(), pollItem.getRawSocket()); + System.out.printf( + "I: zloop: cancel %s poller (%s, %s)", + pollItem.getSocket() != null ? pollItem.getSocket().getType() : "RAW", + pollItem.getSocket(), + pollItem.getRawSocket()); } } @@ -264,7 +267,7 @@ public int start() System.out.printf("I: zloop: interrupted (%d)\n", rc); } rc = 0; - break; // Context has been shut down + break; // Context has been shut down } // Handle any timers that have now expired Iterator it = timers.iterator(); @@ -276,7 +279,7 @@ public int start() } rc = timer.handler.handle(this, null, timer.arg); if (rc == -1) { - break; // Timer handler signalled break + break; // Timer handler signaled break } if (timer.times != 0 && --timer.times == 0) { it.remove(); @@ -295,9 +298,11 @@ public int start() SPoller poller = pollact[itemNbr]; if (pollset.getItem(itemNbr).isError()) { if (verbose) { - System.out.printf("I: zloop: can't poll %s socket (%s, %s)\n", - poller.item.getSocket() != null ? poller.item.getSocket().getType() : "RAW", - poller.item.getSocket(), poller.item.getRawSocket()); + System.out.printf( + "I: zloop: can't poll %s socket (%s, %s)\n", + poller.item.getSocket() != null ? poller.item.getSocket().getType() : "RAW", + poller.item.getSocket(), + poller.item.getRawSocket()); } // Give handler one chance to handle error, then kill // poller because it'll disrupt the reactor otherwise. @@ -306,18 +311,20 @@ public int start() } } else { - poller.errors = 0; // A non-error happened + poller.errors = 0; // A non-error happened } if (pollset.getItem(itemNbr).readyOps() > 0) { if (verbose) { - System.out.printf("I: zloop: call %s socket handler (%s, %s)\n", - poller.item.getSocket() != null ? poller.item.getSocket().getType() : "RAW", - poller.item.getSocket(), poller.item.getRawSocket()); + System.out.printf( + "I: zloop: call %s socket handler (%s, %s)\n", + poller.item.getSocket() != null ? poller.item.getSocket().getType() : "RAW", + poller.item.getSocket(), + poller.item.getRawSocket()); } rc = poller.handler.handle(this, poller.item, poller.arg); if (rc == -1) { - break; // Poller handler signalled break + break; // Poller handler signaled break } } } diff --git a/src/main/java/org/zeromq/ZMQ.java b/src/main/java/org/zeromq/ZMQ.java index fa6be6a3c..59af81236 100644 --- a/src/main/java/org/zeromq/ZMQ.java +++ b/src/main/java/org/zeromq/ZMQ.java @@ -10,11 +10,12 @@ import java.util.concurrent.atomic.AtomicBoolean; import zmq.Ctx; -import zmq.DecoderBase; -import zmq.EncoderBase; import zmq.SocketBase; import zmq.ZError; import zmq.ZError.CtxTerminatedException; +import zmq.io.coder.IDecoder; +import zmq.io.coder.IEncoder; +import zmq.io.mechanism.Mechanisms; public class ZMQ { @@ -28,29 +29,29 @@ public class ZMQ * Socket flag to indicate a nonblocking send or recv mode. */ public static final int DONTWAIT = zmq.ZMQ.ZMQ_DONTWAIT; - public static final int NOBLOCK = zmq.ZMQ.ZMQ_DONTWAIT; + public static final int NOBLOCK = zmq.ZMQ.ZMQ_DONTWAIT; // Socket types, used when creating a Socket. /** * Flag to specify a exclusive pair of sockets. */ - public static final int PAIR = zmq.ZMQ.ZMQ_PAIR; + public static final int PAIR = zmq.ZMQ.ZMQ_PAIR; /** * Flag to specify a PUB socket, receiving side must be a SUB or XSUB. */ - public static final int PUB = zmq.ZMQ.ZMQ_PUB; + public static final int PUB = zmq.ZMQ.ZMQ_PUB; /** * Flag to specify the receiving part of the PUB or XPUB socket. */ - public static final int SUB = zmq.ZMQ.ZMQ_SUB; + public static final int SUB = zmq.ZMQ.ZMQ_SUB; /** * Flag to specify a REQ socket, receiving side must be a REP. */ - public static final int REQ = zmq.ZMQ.ZMQ_REQ; + public static final int REQ = zmq.ZMQ.ZMQ_REQ; /** * Flag to specify the receiving part of a REQ socket. */ - public static final int REP = zmq.ZMQ.ZMQ_REP; + public static final int REP = zmq.ZMQ.ZMQ_REP; /** * Flag to specify a DEALER socket (aka XREQ). * DEALER is really a combined ventilator / sink @@ -59,65 +60,72 @@ public class ZMQ * you shuffle messages out to N nodes and shuffle the replies * back, in a raw bidirectional asynch pattern. */ - public static final int DEALER = zmq.ZMQ.ZMQ_DEALER; + public static final int DEALER = zmq.ZMQ.ZMQ_DEALER; /** * Old alias for DEALER flag. * Flag to specify a XREQ socket, receiving side must be a XREP. * * @deprecated As of release 3.0 of zeromq, replaced by {@link #DEALER} */ - public static final int XREQ = DEALER; + @Deprecated + public static final int XREQ = DEALER; /** * Flag to specify ROUTER socket (aka XREP). * ROUTER is the socket that creates and consumes request-reply * routing envelopes. It is the only socket type that lets you route * messages to specific connections if you know their identities. */ - public static final int ROUTER = zmq.ZMQ.ZMQ_ROUTER; + public static final int ROUTER = zmq.ZMQ.ZMQ_ROUTER; /** * Old alias for ROUTER flag. * Flag to specify the receiving part of a XREQ socket. * * @deprecated As of release 3.0 of zeromq, replaced by {@link #ROUTER} */ - public static final int XREP = ROUTER; + @Deprecated + public static final int XREP = ROUTER; /** * Flag to specify the receiving part of a PUSH socket. */ - public static final int PULL = zmq.ZMQ.ZMQ_PULL; + public static final int PULL = zmq.ZMQ.ZMQ_PULL; /** * Flag to specify a PUSH socket, receiving side must be a PULL. */ - public static final int PUSH = zmq.ZMQ.ZMQ_PUSH; + public static final int PUSH = zmq.ZMQ.ZMQ_PUSH; /** * Flag to specify a XPUB socket, receiving side must be a SUB or XSUB. * Subscriptions can be received as a message. Subscriptions start with * a '1' byte. Unsubscriptions start with a '0' byte. */ - public static final int XPUB = zmq.ZMQ.ZMQ_XPUB; + public static final int XPUB = zmq.ZMQ.ZMQ_XPUB; /** * Flag to specify the receiving part of the PUB or XPUB socket. Allows */ - public static final int XSUB = zmq.ZMQ.ZMQ_XSUB; - + public static final int XSUB = zmq.ZMQ.ZMQ_XSUB; + /** + * Flag to specify a STREAM socket. + */ + public static final int STREAM = zmq.ZMQ.ZMQ_STREAM; /** * Flag to specify a STREAMER device. */ - public static final int STREAMER = zmq.ZMQ.ZMQ_STREAMER; + @Deprecated + public static final int STREAMER = zmq.ZMQ.ZMQ_STREAMER; /** * Flag to specify a FORWARDER device. */ - public static final int FORWARDER = zmq.ZMQ.ZMQ_FORWARDER; + @Deprecated + public static final int FORWARDER = zmq.ZMQ.ZMQ_FORWARDER; /** * Flag to specify a QUEUE device. */ - public static final int QUEUE = zmq.ZMQ.ZMQ_QUEUE; - + @Deprecated + public static final int QUEUE = zmq.ZMQ.ZMQ_QUEUE; /** * @see org.zeromq.ZMQ#PULL */ @Deprecated - public static final int UPSTREAM = PULL; + public static final int UPSTREAM = PULL; /** * @see org.zeromq.ZMQ#PUSH */ @@ -130,7 +138,7 @@ public class ZMQ * established to a remote peer. This can happen either synchronous * or asynchronous. Value is the FD of the newly connected socket. */ - public static final int EVENT_CONNECTED = zmq.ZMQ.ZMQ_EVENT_CONNECTED; + public static final int EVENT_CONNECTED = zmq.ZMQ.ZMQ_EVENT_CONNECTED; /** * EVENT_CONNECT_DELAYED: synchronous connect failed, it's being polled. * The EVENT_CONNECT_DELAYED event triggers when an immediate connection @@ -142,7 +150,7 @@ public class ZMQ * @see org.zeromq.ZMQ#EVENT_CONNECT_DELAYED */ @Deprecated - public static final int EVENT_DELAYED = EVENT_CONNECT_DELAYED; + public static final int EVENT_DELAYED = EVENT_CONNECT_DELAYED; /** * EVENT_CONNECT_RETRIED: asynchronous connect / reconnection attempt. * The EVENT_CONNECT_RETRIED event triggers when a connection attempt is @@ -154,74 +162,70 @@ public class ZMQ * @see org.zeromq.ZMQ#EVENT_CONNECT_RETRIED */ @Deprecated - public static final int EVENT_RETRIED = EVENT_CONNECT_RETRIED; - + public static final int EVENT_RETRIED = EVENT_CONNECT_RETRIED; /** * EVENT_LISTENING: socket bound to an address, ready to accept connections. * The EVENT_LISTENING event triggers when a socket's successfully bound to * a an interface. Value is the FD of the newly bound socket. */ - public static final int EVENT_LISTENING = zmq.ZMQ.ZMQ_EVENT_LISTENING; + public static final int EVENT_LISTENING = zmq.ZMQ.ZMQ_EVENT_LISTENING; /** * EVENT_BIND_FAILED: socket could not bind to an address. * The EVENT_BIND_FAILED event triggers when a socket could not bind to a * given interface. Value is the errno generated by the bind call. */ - public static final int EVENT_BIND_FAILED = zmq.ZMQ.ZMQ_EVENT_BIND_FAILED; - + public static final int EVENT_BIND_FAILED = zmq.ZMQ.ZMQ_EVENT_BIND_FAILED; /** * EVENT_ACCEPTED: connection accepted to bound interface. * The EVENT_ACCEPTED event triggers when a connection from a remote peer * has been established with a socket's listen address. Value is the FD of * the accepted socket. */ - public static final int EVENT_ACCEPTED = zmq.ZMQ.ZMQ_EVENT_ACCEPTED; + public static final int EVENT_ACCEPTED = zmq.ZMQ.ZMQ_EVENT_ACCEPTED; /** * EVENT_ACCEPT_FAILED: could not accept client connection. * The EVENT_ACCEPT_FAILED event triggers when a connection attempt to a * socket's bound address fails. Value is the errno generated by accept. */ - public static final int EVENT_ACCEPT_FAILED = zmq.ZMQ.ZMQ_EVENT_ACCEPT_FAILED; - + public static final int EVENT_ACCEPT_FAILED = zmq.ZMQ.ZMQ_EVENT_ACCEPT_FAILED; /** * EVENT_CLOSED: connection closed. * The EVENT_CLOSED event triggers when a connection's underlying * descriptor has been closed. Value is the former FD of the for the * closed socket. FD has been closed already! */ - public static final int EVENT_CLOSED = zmq.ZMQ.ZMQ_EVENT_CLOSED; + public static final int EVENT_CLOSED = zmq.ZMQ.ZMQ_EVENT_CLOSED; /** * EVENT_CLOSE_FAILED: connection couldn't be closed. * The EVENT_CLOSE_FAILED event triggers when a descriptor could not be * released back to the OS. Implementation note: ONLY FOR IPC SOCKETS. * Value is the errno generated by unlink. */ - public static final int EVENT_CLOSE_FAILED = zmq.ZMQ.ZMQ_EVENT_CLOSE_FAILED; + public static final int EVENT_CLOSE_FAILED = zmq.ZMQ.ZMQ_EVENT_CLOSE_FAILED; /** * EVENT_DISCONNECTED: broken session. * The EVENT_DISCONNECTED event triggers when the stream engine (tcp and * ipc specific) detects a corrupted / broken session. Value is the FD of * the socket. */ - public static final int EVENT_DISCONNECTED = zmq.ZMQ.ZMQ_EVENT_DISCONNECTED; + public static final int EVENT_DISCONNECTED = zmq.ZMQ.ZMQ_EVENT_DISCONNECTED; /** * EVENT_MONITOR_STOPPED: monitor has been stopped. * The EVENT_MONITOR_STOPPED event triggers when the monitor for a socket is * stopped. */ public static final int EVENT_MONITOR_STOPPED = zmq.ZMQ.ZMQ_EVENT_MONITOR_STOPPED; - /** * EVENT_ALL: all events known. * The EVENT_ALL constant can be used to set up a monitor for all known events. */ - public static final int EVENT_ALL = zmq.ZMQ.ZMQ_EVENT_ALL; + public static final int EVENT_ALL = zmq.ZMQ.ZMQ_EVENT_ALL; public static final byte[] MESSAGE_SEPARATOR = new byte[0]; public static final byte[] SUBSCRIPTION_ALL = new byte[0]; - public static final Charset CHARSET = Charset.forName("UTF-8"); + public static final Charset CHARSET = zmq.ZMQ.CHARSET; private ZMQ() { @@ -242,7 +246,7 @@ public static Context context(int ioThreads) public static class Context implements Closeable { private final AtomicBoolean closed = new AtomicBoolean(false); - private final Ctx ctx; + private final Ctx ctx; /** * Class constructor. @@ -295,7 +299,16 @@ public boolean setMaxSockets(int maxSockets) return ctx.set(zmq.ZMQ.ZMQ_MAX_SOCKETS, maxSockets); } + /** + * @deprecated use {@link #isBlocky()} instead + */ + @Deprecated public boolean getBlocky() + { + return isBlocky(); + } + + public boolean isBlocky() { return ctx.get(zmq.ZMQ.ZMQ_BLOCKY) != 0; } @@ -305,11 +318,20 @@ public boolean setBlocky(boolean block) return ctx.set(zmq.ZMQ.ZMQ_BLOCKY, block ? 1 : 0); } + public boolean isIpv6() + { + return ctx.get(zmq.ZMQ.ZMQ_IPV6) != 0; + } + + public boolean setIpv6(boolean ipv6) + { + return ctx.set(zmq.ZMQ.ZMQ_IPV6, ipv6 ? 1 : 0); + } + /** * This is an explicit "destructor". It can be called to ensure the corresponding 0MQ * Context has been disposed of. */ - public void term() { if (closed.compareAndSet(false, true)) { @@ -339,6 +361,18 @@ public Selector selector() return ctx.createSelector(); } + /** + * Closes a Selector that was created within this context. + * + * @param the Selector to close. + * @return true if the selector was closed. otherwise false + * (mostly because it was not created by the context). + */ + public boolean close(Selector selector) + { + return ctx.closeSelector(selector); + } + /** * Create a new Poller within this context, with a default size. * @@ -368,15 +402,15 @@ public void close() } } - public static class Socket implements Closeable + public static final class Socket implements Closeable { // This port range is defined by IANA for dynamic or private ports // We use this when choosing a port for dynamic binding. private static final int DYNFROM = 0xc000; - private static final int DYNTO = 0xffff; + private static final int DYNTO = 0xffff; - private final Ctx ctx; - private final SocketBase base; + private final Ctx ctx; + private final SocketBase base; private final AtomicBoolean isClosed = new AtomicBoolean(false); /** @@ -428,7 +462,7 @@ public void close() * * @return the socket type. */ - public final int getType() + public int getType() { return base.getSocketOpt(zmq.ZMQ.ZMQ_TYPE); } @@ -438,19 +472,23 @@ public final int getType() * * @return the linger period. */ - public final long getLinger() + public long getLinger() { return base.getSocketOpt(zmq.ZMQ.ZMQ_LINGER); } - private void setsockopt(int option, Object value) + private boolean setSocketOpt(int option, Object value) { try { - base.setSocketOpt(option, value); + boolean set = base.setSocketOpt(option, value); + set &= base.errno() != ZError.EINVAL; + return set; } catch (CtxTerminatedException e) { + return false; } } + /** * The 'ZMQ_LINGER' option shall retrieve the period for pending outbound * messages to linger in memory after closing the socket. Value of -1 means @@ -461,10 +499,11 @@ private void setsockopt(int option, Object value) * * @param value * the linger period in milliseconds. + * @return true if the option was set, otherwise false */ - public final void setLinger(long value) + public boolean setLinger(Number value) { - base.setSocketOpt(zmq.ZMQ.ZMQ_LINGER, (int) value); + return base.setSocketOpt(zmq.ZMQ.ZMQ_LINGER, value.intValue()); } /** @@ -472,16 +511,17 @@ public final void setLinger(long value) * * @return the reconnectIVL. */ - public final long getReconnectIVL() + public long getReconnectIVL() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RECONNECT_IVL); } /** + * @return true if the option was set, otherwise false */ - public final void setReconnectIVL(long value) + public boolean setReconnectIVL(Number value) { - base.setSocketOpt(zmq.ZMQ.ZMQ_RECONNECT_IVL, (int) value); + return base.setSocketOpt(zmq.ZMQ.ZMQ_RECONNECT_IVL, value.intValue()); } /** @@ -489,16 +529,30 @@ public final void setReconnectIVL(long value) * * @return the backlog. */ - public final long getBacklog() + public int getBacklog() { return base.getSocketOpt(zmq.ZMQ.ZMQ_BACKLOG); } /** + * @return true if the option was set, otherwise false */ - public final void setBacklog(long value) + public boolean setBacklog(Number value) + { + return setSocketOpt(zmq.ZMQ.ZMQ_BACKLOG, value.intValue()); + } + + public int getTos() { - setsockopt(zmq.ZMQ.ZMQ_BACKLOG, (int) value); + return base.getSocketOpt(zmq.ZMQ.ZMQ_TOS); + } + + /** + * @return true if the option was set, otherwise false + */ + public boolean setTos(int value) + { + return setSocketOpt(zmq.ZMQ.ZMQ_TOS, value); } /** @@ -506,16 +560,17 @@ public final void setBacklog(long value) * * @return the reconnectIVLMax. */ - public final long getReconnectIVLMax() + public long getReconnectIVLMax() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RECONNECT_IVL_MAX); } /** + * @return true if the option was set, otherwise false */ - public final void setReconnectIVLMax(long value) + public boolean setReconnectIVLMax(Number value) { - setsockopt(zmq.ZMQ.ZMQ_RECONNECT_IVL_MAX, (int) value); + return setSocketOpt(zmq.ZMQ.ZMQ_RECONNECT_IVL_MAX, value.intValue()); } /** @@ -523,16 +578,17 @@ public final void setReconnectIVLMax(long value) * * @return the maxMsgSize. */ - public final long getMaxMsgSize() + public long getMaxMsgSize() { - return (Long) base.getsockoptx(zmq.ZMQ.ZMQ_MAXMSGSIZE); + return (Long) base.getSocketOptx(zmq.ZMQ.ZMQ_MAXMSGSIZE); } /** + * @return true if the option was set, otherwise false */ - public final void setMaxMsgSize(long value) + public boolean setMaxMsgSize(long value) { - setsockopt(zmq.ZMQ.ZMQ_MAXMSGSIZE, value); + return setSocketOpt(zmq.ZMQ.ZMQ_MAXMSGSIZE, value); } /** @@ -540,16 +596,17 @@ public final void setMaxMsgSize(long value) * * @return the SndHWM. */ - public final long getSndHWM() + public int getSndHWM() { return base.getSocketOpt(zmq.ZMQ.ZMQ_SNDHWM); } /** + * @return true if the option was set, otherwise false */ - public final void setSndHWM(long value) + public boolean setSndHWM(Number value) { - setsockopt(zmq.ZMQ.ZMQ_SNDHWM, (int) value); + return setSocketOpt(zmq.ZMQ.ZMQ_SNDHWM, value.intValue()); } /** @@ -557,16 +614,17 @@ public final void setSndHWM(long value) * * @return the recvHWM period. */ - public final long getRcvHWM() + public int getRcvHWM() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RCVHWM); } /** + * @return true if the option was set, otherwise false */ - public final void setRcvHWM(long value) + public boolean setRcvHWM(Number value) { - setsockopt(zmq.ZMQ.ZMQ_RCVHWM, (int) value); + return setSocketOpt(zmq.ZMQ.ZMQ_RCVHWM, value.intValue()); } /** @@ -575,7 +633,7 @@ public final void setRcvHWM(long value) * @return the High Water Mark. */ @Deprecated - public final long getHWM() + public long getHWM() { return -1; } @@ -592,11 +650,14 @@ public final long getHWM() * * @param hwm * the number of messages to queue. + * @return true if the option was set, otherwise false */ - public final void setHWM(long hwm) + public boolean setHWM(Number hwm) { - setSndHWM(hwm); - setRcvHWM(hwm); + boolean set = true; + set |= setSndHWM(hwm); + set |= setRcvHWM(hwm); + return set; } /** @@ -605,12 +666,22 @@ public final void setHWM(long hwm) * @return the number of messages to swap at most. */ @Deprecated - public final long getSwap() + public long getSwap() { // not support at zeromq 3 return -1L; } + public boolean setConflate(boolean conflate) + { + return setSocketOpt(zmq.ZMQ.ZMQ_CONFLATE, conflate); + } + + public boolean isConflate() + { + return base.getSocketOpt(zmq.ZMQ.ZMQ_CONFLATE) != 0; + } + /** * Get the Swap. The 'ZMQ_SWAP' option shall set the disk offload (swap) size for the * specified 'socket'. A socket which has 'ZMQ_SWAP' set to a non-zero value may exceed its @@ -621,7 +692,7 @@ public final long getSwap() * The value of 'ZMQ_SWAP' defines the maximum size of the swap space in bytes. */ @Deprecated - public final void setSwap(long value) + public boolean setSwap(long value) { throw new UnsupportedOperationException(); } @@ -631,9 +702,9 @@ public final void setSwap(long value) * * @return the affinity. */ - public final long getAffinity() + public long getAffinity() { - return (Long) base.getsockoptx(zmq.ZMQ.ZMQ_AFFINITY); + return (Long) base.getSocketOptx(zmq.ZMQ.ZMQ_AFFINITY); } /** @@ -652,10 +723,11 @@ public final long getAffinity() * * @param value * the io_thread affinity. + * @return true if the option was set, otherwise false */ - public final void setAffinity(long value) + public boolean setAffinity(long value) { - setsockopt(zmq.ZMQ.ZMQ_AFFINITY, value); + return setSocketOpt(zmq.ZMQ.ZMQ_AFFINITY, value); } /** @@ -663,9 +735,9 @@ public final void setAffinity(long value) * * @return the Identitiy. */ - public final byte[] getIdentity() + public byte[] getIdentity() { - return (byte[]) base.getsockoptx(zmq.ZMQ.ZMQ_IDENTITY); + return (byte[]) base.getSocketOptx(zmq.ZMQ.ZMQ_IDENTITY); } /** @@ -684,10 +756,11 @@ public final byte[] getIdentity() * binary zero are reserved for use by 0MQ infrastructure. * * @param identity + * @return true if the option was set, otherwise false */ - public final void setIdentity(byte[] identity) + public boolean setIdentity(byte[] identity) { - setsockopt(zmq.ZMQ.ZMQ_IDENTITY, identity); + return setSocketOpt(zmq.ZMQ.ZMQ_IDENTITY, identity); } /** @@ -695,7 +768,7 @@ public final void setIdentity(byte[] identity) * * @return the Rate. */ - public final long getRate() + public long getRate() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RATE); } @@ -705,8 +778,9 @@ public final long getRate() * transports such as in the man page of zmq_pgm[7] using the specified 'socket'. * * @param value maximum send or receive data rate for multicast, default 100 + * @return true if the option was set, otherwise false */ - public final void setRate(long value) + public boolean setRate(long value) { throw new UnsupportedOperationException(); } @@ -716,7 +790,7 @@ public final void setRate(long value) * * @return the RecoveryIntervall. */ - public final long getRecoveryInterval() + public long getRecoveryInterval() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RECOVERY_IVL); } @@ -732,8 +806,9 @@ public final long getRecoveryInterval() * of 1Gbps requires a 7GB in-memory buffer. {Purpose of this Method} * * @param value recovery interval for multicast in milliseconds, default 10000 + * @return true if the option was set, otherwise false */ - public final void setRecoveryInterval(long value) + public boolean setRecoveryInterval(long value) { throw new UnsupportedOperationException(); } @@ -745,17 +820,18 @@ public final void setRecoveryInterval(long value) * first two frames is discarded. * See also ZMQ_REQ_RELAXED. * @param correlate Whether to enable outgoing request ids. + * @return true if the option was set, otherwise false */ - public final void setReqCorrelate(boolean correlate) + public boolean setReqCorrelate(boolean correlate) { - setsockopt(zmq.ZMQ.ZMQ_REQ_CORRELATE, correlate ? 1 : 0); + return setSocketOpt(zmq.ZMQ.ZMQ_REQ_CORRELATE, correlate); } /** * @see #setReqCorrelate(boolean) * @return state of the ZMQ_REQ_CORRELATE option. */ - public final boolean getReqCorrelate() + public boolean getReqCorrelate() { return base.getSocketOpt(zmq.ZMQ.ZMQ_REQ_CORRELATE) > 0; } @@ -766,17 +842,18 @@ public final boolean getReqCorrelate() * sequentially. If enabled, also enable ZMQ_REQ_CORRELATE in order to * receive the correct responses to requests. @param relaxed + * @return true if the option was set, otherwise false */ - public final void setReqRelaxed(boolean relaxed) + public boolean setReqRelaxed(boolean relaxed) { - setsockopt(zmq.ZMQ.ZMQ_REQ_RELAXED, relaxed ? 1 : 0); + return setSocketOpt(zmq.ZMQ.ZMQ_REQ_RELAXED, relaxed); } /** * @see #setReqRelaxed(boolean) * @return state of the ZMQ_REQ_RELAXED option. */ - public final boolean getReqRelaxed() + public boolean getReqRelaxed() { return base.getSocketOpt(zmq.ZMQ.ZMQ_REQ_CORRELATE) > 0; } @@ -787,7 +864,7 @@ public final boolean getReqRelaxed() * @return the Multicast Loop. */ @Deprecated - public final boolean hasMulticastLoop() + public boolean hasMulticastLoop() { return false; } @@ -803,7 +880,7 @@ public final boolean hasMulticastLoop() * @param multicastLoop */ @Deprecated - public final void setMulticastLoop(boolean multicastLoop) + public boolean setMulticastLoop(boolean multicastLoop) { throw new UnsupportedOperationException(); } @@ -813,7 +890,7 @@ public final void setMulticastLoop(boolean multicastLoop) * * @return the Multicast Hops. */ - public final long getMulticastHops() + public long getMulticastHops() { return base.getSocketOpt(zmq.ZMQ.ZMQ_MULTICAST_HOPS); } @@ -825,7 +902,7 @@ public final long getMulticastHops() * * @param value time-to-live field in every multicast packet, default 1 */ - public final void setMulticastHops(long value) + public boolean setMulticastHops(long value) { throw new UnsupportedOperationException(); } @@ -835,7 +912,7 @@ public final void setMulticastHops(long value) * * @return the Receive Timeout in milliseconds. */ - public final int getReceiveTimeOut() + public int getReceiveTimeOut() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RCVTIMEO); } @@ -848,10 +925,11 @@ public final int getReceiveTimeOut() * a null and an EAGAIN error. * * @param value Timeout for receive operation in milliseconds. Default -1 (infinite) + * @return true if the option was set, otherwise false */ - public final void setReceiveTimeOut(int value) + public boolean setReceiveTimeOut(int value) { - setsockopt(zmq.ZMQ.ZMQ_RCVTIMEO, value); + return setSocketOpt(zmq.ZMQ.ZMQ_RCVTIMEO, value); } /** @@ -859,7 +937,7 @@ public final void setReceiveTimeOut(int value) * * @return the Send Timeout in milliseconds. */ - public final int getSendTimeOut() + public int getSendTimeOut() { return base.getSocketOpt(zmq.ZMQ.ZMQ_SNDTIMEO); } @@ -872,21 +950,24 @@ public final int getSendTimeOut() * returning with false and an EAGAIN error. * * @param value Timeout for send operation in milliseconds. Default -1 (infinite) + * @return true if the option was set, otherwise false */ - public final void setSendTimeOut(int value) + public boolean setSendTimeOut(int value) { - setsockopt(zmq.ZMQ.ZMQ_SNDTIMEO, value); + return setSocketOpt(zmq.ZMQ.ZMQ_SNDTIMEO, value); } - /** - * Override SO_KEEPALIVE socket option (where supported by OS) to enable keep-alive packets for a socket - * connection. Possible values are -1, 0, 1. The default value -1 will skip all overrides and do the OS default. - * - * @param value The value of 'ZMQ_TCP_KEEPALIVE' to turn TCP keepalives on (1) or off (0). - */ - public void setTCPKeepAlive(long value) + /** + * Override SO_KEEPALIVE socket option (where supported by OS) to enable keep-alive packets for a socket + * connection. Possible values are -1, 0, 1. The default value -1 will skip all overrides and do the OS default. + * + * @param value The value of 'ZMQ_TCP_KEEPALIVE' to turn TCP keepalives on (1) or off (0). + * @return true if the option was set, otherwise false + */ + @Deprecated + public boolean setTCPKeepAlive(Number value) { - setsockopt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE, value); + return setTCPKeepAlive(value.intValue()); } /** @@ -894,9 +975,10 @@ public void setTCPKeepAlive(long value) * * @return the keep alive setting. */ + @Deprecated public long getTCPKeepAliveSetting() { - return base.getSocketOpt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE); + return getTCPKeepAlive(); } /** @@ -904,10 +986,11 @@ public long getTCPKeepAliveSetting() * do the OS default. * * @param value The value of 'ZMQ_TCP_KEEPALIVE_CNT' defines the number of keepalives before death. + * @return true if the option was set, otherwise false */ - public void setTCPKeepAliveCount(long value) + public boolean setTCPKeepAliveCount(Number value) { - setsockopt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE_CNT, value); + return setSocketOpt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE_CNT, value.intValue()); } /** @@ -925,11 +1008,12 @@ public long getTCPKeepAliveCount() * and do the OS default. * * @param value The value of 'ZMQ_TCP_KEEPALIVE_INTVL' defines the interval between keepalives. Unit is OS - * dependant. + * dependent. + * @return true if the option was set, otherwise false */ - public void setTCPKeepAliveInterval(long value) + public boolean setTCPKeepAliveInterval(Number value) { - setsockopt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE_INTVL, value); + return setSocketOpt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE_INTVL, value.intValue()); } /** @@ -941,16 +1025,18 @@ public long getTCPKeepAliveInterval() { return base.getSocketOpt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE_INTVL); } + /** * Override TCP_KEEPCNT (or TCP_KEEPALIVE on some OS) socket option (where supported by OS). The default value * -1 will skip all overrides and do the OS default. * * @param value The value of 'ZMQ_TCP_KEEPALIVE_IDLE' defines the interval between the last data packet sent - * over the socket and the first keepalive probe. Unit is OS dependant. + * over the socket and the first keepalive probe. Unit is OS dependent. + * @return true if the option was set, otherwise false */ - public void setTCPKeepAliveIdle(long value) + public boolean setTCPKeepAliveIdle(Number value) { - setsockopt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE_IDLE, value); + return setSocketOpt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE_IDLE, value.intValue()); } /** @@ -968,7 +1054,7 @@ public long getTCPKeepAliveIdle() * * @return the kernel send buffer size. */ - public final long getSendBufferSize() + public int getSendBufferSize() { return base.getSocketOpt(zmq.ZMQ.ZMQ_SNDBUF); } @@ -981,10 +1067,11 @@ public final long getSendBufferSize() * * @param value underlying kernel transmit buffer size for the 'socket' in bytes * A value of zero means leave the OS default unchanged. + * @return true if the option was set, otherwise false */ - public final void setSendBufferSize(long value) + public boolean setSendBufferSize(int value) { - setsockopt(zmq.ZMQ.ZMQ_SNDBUF, (int) value); + return setSocketOpt(zmq.ZMQ.ZMQ_SNDBUF, value); } /** @@ -992,7 +1079,7 @@ public final void setSendBufferSize(long value) * * @return the kernel receive buffer size. */ - public final long getReceiveBufferSize() + public int getReceiveBufferSize() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RCVBUF); } @@ -1005,10 +1092,11 @@ public final long getReceiveBufferSize() * * @param value Underlying kernel receive buffer size for the 'socket' in bytes. * A value of zero means leave the OS default unchanged. + * @return true if the option was set, otherwise false */ - public final void setReceiveBufferSize(long value) + public boolean setReceiveBufferSize(int value) { - setsockopt(zmq.ZMQ.ZMQ_RCVBUF, (int) value); + return setSocketOpt(zmq.ZMQ.ZMQ_RCVBUF, value); } /** @@ -1020,7 +1108,7 @@ public final void setReceiveBufferSize(long value) * * @return true if there are more messages to receive. */ - public final boolean hasReceiveMore() + public boolean hasReceiveMore() { return base.getSocketOpt(zmq.ZMQ.ZMQ_RCVMORE) == 1; } @@ -1036,9 +1124,9 @@ public final boolean hasReceiveMore() * * @return the underlying file descriptor. */ - public final SelectableChannel getFD() + public SelectableChannel getFD() { - return (SelectableChannel) base.getsockoptx(zmq.ZMQ.ZMQ_FD); + return (SelectableChannel) base.getSocketOptx(zmq.ZMQ.ZMQ_FD); } /** @@ -1048,7 +1136,7 @@ public final SelectableChannel getFD() * * @return the mask of outstanding events. */ - public final int getEvents() + public int getEvents() { return base.getSocketOpt(zmq.ZMQ.ZMQ_EVENTS); } @@ -1064,10 +1152,16 @@ public final int getEvents() * message shall be accepted if it matches at least one filter. * * @param topic + * @return true if the option was set, otherwise false */ - public final void subscribe(byte[] topic) + public boolean subscribe(byte[] topic) + { + return setSocketOpt(zmq.ZMQ.ZMQ_SUBSCRIBE, topic); + } + + public boolean subscribe(String topic) { - setsockopt(zmq.ZMQ.ZMQ_SUBSCRIBE, topic); + return setSocketOpt(zmq.ZMQ.ZMQ_SUBSCRIBE, topic); } /** @@ -1078,28 +1172,68 @@ public final void subscribe(byte[] topic) * place and functional. * * @param topic + * @return true if the option was set, otherwise false */ - public final void unsubscribe(byte[] topic) + public boolean unsubscribe(byte[] topic) + { + return setSocketOpt(zmq.ZMQ.ZMQ_UNSUBSCRIBE, topic); + } + + public boolean unsubscribe(String topic) { - setsockopt(zmq.ZMQ.ZMQ_UNSUBSCRIBE, topic); + return setSocketOpt(zmq.ZMQ.ZMQ_UNSUBSCRIBE, topic); } /** * Set custom Encoder * @param cls + * @return true if the option was set, otherwise false */ - public final void setEncoder(Class cls) + @Deprecated + public boolean setEncoder(Class cls) { - base.setSocketOpt(zmq.ZMQ.ZMQ_ENCODER, cls); + return setSocketOpt(zmq.ZMQ.ZMQ_ENCODER, cls); } /** * Set custom Decoder * @param cls + * @return true if the option was set, otherwise false */ - public final void setDecoder(Class cls) + @Deprecated + public boolean setDecoder(Class cls) { - base.setSocketOpt(zmq.ZMQ.ZMQ_DECODER, cls); + return setSocketOpt(zmq.ZMQ.ZMQ_DECODER, cls); + } + + public boolean setMsgAllocationHeapThreshold(int threshold) + { + return setSocketOpt(zmq.ZMQ.ZMQ_MSG_ALLOCATION_HEAP_THRESHOLD, threshold); + } + + public int getMsgAllocationHeapThreshold() + { + return base.getSocketOpt(zmq.ZMQ.ZMQ_MSG_ALLOCATION_HEAP_THRESHOLD); + } + + public boolean setConnectRid(String rid) + { + return setSocketOpt(zmq.ZMQ.ZMQ_CONNECT_RID, rid); + } + + public boolean setConnectRid(byte[] rid) + { + return setSocketOpt(zmq.ZMQ.ZMQ_CONNECT_RID, rid); + } + + public boolean setRouterRaw(boolean raw) + { + return setSocketOpt(zmq.ZMQ.ZMQ_ROUTER_RAW, raw); + } + + public boolean setProbeRouter(boolean probe) + { + return setSocketOpt(zmq.ZMQ.ZMQ_PROBE_ROUTER, probe); } /** @@ -1107,10 +1241,11 @@ public final void setDecoder(Class cls) * * @param mandatory A value of false is the default and discards the message silently when it cannot be routed. * A value of true returns an EHOSTUNREACH error code if the message cannot be routed. + * @return true if the option was set, otherwise false */ - public final void setRouterMandatory(boolean mandatory) + public boolean setRouterMandatory(boolean mandatory) { - setsockopt(zmq.ZMQ.ZMQ_ROUTER_MANDATORY, mandatory ? 1 : 0); + return base.setSocketOpt(zmq.ZMQ.ZMQ_ROUTER_MANDATORY, mandatory); } /** @@ -1119,10 +1254,11 @@ public final void setRouterMandatory(boolean mandatory) * * @param handover A value of false, (default) the ROUTER socket shall reject clients trying to connect with an already-used identity * A value of true, the ROUTER socket shall hand-over the connection to the new client and disconnect the existing one + * @return true if the option was set, otherwise false */ - public final void setRouterHandover(boolean handover) + public boolean setRouterHandover(boolean handover) { - setsockopt(zmq.ZMQ.ZMQ_ROUTER_HANDOVER, handover ? 1 : 0); + return setSocketOpt(zmq.ZMQ.ZMQ_ROUTER_HANDOVER, handover); } /** @@ -1130,20 +1266,38 @@ public final void setRouterHandover(boolean handover) * * @param verbose A value of false is the default and passes only new subscription messages to upstream. * A value of true passes all subscription messages upstream. + * @return true if the option was set, otherwise false */ - public final void setXpubVerbose(boolean verbose) + public boolean setXpubVerbose(boolean verbose) { - setsockopt(zmq.ZMQ.ZMQ_XPUB_VERBOSE, verbose ? 1 : 0); + return setSocketOpt(zmq.ZMQ.ZMQ_XPUB_VERBOSE, verbose); + } + + public boolean setXpubNoDrop(boolean noDrop) + { + return setSocketOpt(zmq.ZMQ.ZMQ_XPUB_NODROP, noDrop); } /** * @see #setIPv4Only (boolean) * * @return the IPV4ONLY + * @deprecated use {@link #isIPv6()} instead (inverted logic: ipv4 = true <==> ipv6 = false) + */ + @Deprecated + public boolean getIPv4Only() + { + return !isIpv6(); + } + + /** + * @see #setIPv6 (boolean) + * + * @return the IPV6 config */ - public final boolean getIPv4Only() + public boolean isIpv6() { - return base.getSocketOpt(zmq.ZMQ.ZMQ_IPV4ONLY) == 1; + return (Boolean) base.getSocketOptx(zmq.ZMQ.ZMQ_IPV6); } /** @@ -1151,10 +1305,26 @@ public final boolean getIPv4Only() * An IPv6 socket lets applications connect to and accept connections from both IPv4 and IPv6 hosts. * * @param v4only A value of true will use IPv4 sockets, while the value of false will use IPv6 sockets + * @return true if the option was set, otherwise false + * @deprecated use {@link #setIPv6(boolean)} instead (inverted logic: ipv4 = true <==> ipv6 = false) */ - public void setIPv4Only(boolean v4only) + @Deprecated + public boolean setIPv4Only(boolean v4only) { - setsockopt(zmq.ZMQ.ZMQ_IPV4ONLY, v4only ? 1 : 0); + return setIPv6(!v4only); + } + + /** + * The 'ZMQ_IPV6' option shall set the underlying native socket type. + * An IPv6 socket lets applications connect to and accept connections from both IPv4 and IPv6 hosts. + * + * @param v4only A value of true will use IPv6 sockets, while the value of false will use IPv4 sockets + * @return true if the option was set, otherwise false + * @see #isIPv6() + */ + public boolean setIPv6(boolean v6) + { + return setSocketOpt(zmq.ZMQ.ZMQ_IPV6, v6); } /** @@ -1172,20 +1342,22 @@ public int getTCPKeepAlive() * connection. Possible values are -1, 0, 1. The default value -1 will skip all overrides and do the OS default. * * @param optVal The value of 'ZMQ_TCP_KEEPALIVE' to turn TCP keepalives on (1) or off (0). + * @return true if the option was set, otherwise false */ - public void setTCPKeepAlive(int optVal) + public boolean setTCPKeepAlive(int optVal) { - setsockopt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE, optVal); + return setSocketOpt(zmq.ZMQ.ZMQ_TCP_KEEPALIVE, optVal); } /** * @see #setDelayAttachOnConnect(boolean) * - * @return the keep alive setting. + * @deprecated use {@link #setImmediate(boolean)} instead (inverted logic: immediate = true <==> delay attach on connect = false) */ + @Deprecated public boolean getDelayAttachOnConnect() { - return base.getSocketOpt(zmq.ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT) == 1; + return !isImmediate(); } /** @@ -1196,10 +1368,213 @@ public boolean getDelayAttachOnConnect() * prevent queues from filling on pipes awaiting connection * * @param value The value of 'ZMQ_DELAY_ATTACH_ON_CONNECT'. Default false. + * @return true if the option was set + * @deprecated use {@link #setImmediate(boolean)} instead (warning, the boolean is inverted) + */ + @Deprecated + public boolean setDelayAttachOnConnect(boolean value) + { + return setImmediate(!value); + } + + /** + * @see #setImmediate(boolean) + */ + public boolean isImmediate() + { + return (boolean) base.getSocketOptx(zmq.ZMQ.ZMQ_IMMEDIATE); + } + + /** + * Accept messages immediately or only when connections are made + * + * If set to false, will delay the attachment of a pipe on connect until the underlying connection + * has completed. This will cause the socket to block if there are no other connections, but will + * prevent queues from filling on pipes awaiting connection + * + * @param value The value of 'ZMQ_IMMEDIATE'. Default false. + * @return true if the option was set + */ + public boolean setImmediate(boolean value) + { + return setSocketOpt(zmq.ZMQ.ZMQ_IMMEDIATE, value); + } + + public boolean setSocksProxy(String proxy) + { + return setSocketOpt(zmq.ZMQ.ZMQ_SOCKS_PROXY, proxy); + } + + public boolean setSocksProxy(byte[] proxy) + { + return setSocketOpt(zmq.ZMQ.ZMQ_SOCKS_PROXY, proxy); + } + + public String getSocksProxy() + { + return (String) base.getSocketOptx(zmq.ZMQ.ZMQ_SOCKS_PROXY); + } + + public String getLastEndpoint() + { + return (String) base.getSocketOptx(zmq.ZMQ.ZMQ_LAST_ENDPOINT); + } + + /** + * Sets the domain used for ZAP authentication + * @param domain the domain of ZAP authentication + * @return true if the option was set + * @see #getZapDomain() + */ + public boolean setZapDomain(String domain) + { + return setSocketOpt(zmq.ZMQ.ZMQ_ZAP_DOMAIN, domain); + } + + public boolean setZapDomain(byte[] domain) + { + return setSocketOpt(zmq.ZMQ.ZMQ_ZAP_DOMAIN, domain); + } + + /** + * Gets the domain used for ZAP authentication + * @return the domain of ZAP authentication + * @see #setZapDomain(String) + */ + public String getZapDomain() + { + return (String) base.getSocketOptx(zmq.ZMQ.ZMQ_ZAP_DOMAIN); + } + + /** + * Sets the role used for ZAP authentication. + * @param server true if the role of the socket should be server ZAP authentication + * @return true if the option was set + * @see #isAsServerPlain() + */ + public boolean setAsServerPlain(boolean server) + { + return setSocketOpt(zmq.ZMQ.ZMQ_PLAIN_SERVER, server); + } + + /** + * Gets the role used for ZAP authentication. + * @return true if the role of the socket should be server ZAP authentication + * @see #setAsServerPlain(boolean) + */ + public boolean isAsServerPlain() + { + return (Boolean) base.getSocketOptx(zmq.ZMQ.ZMQ_PLAIN_SERVER); + } + + public boolean setPlainUsername(String username) + { + return base.setSocketOpt(zmq.ZMQ.ZMQ_PLAIN_USERNAME, username); + } + + public boolean setPlainPassword(String password) + { + return base.setSocketOpt(zmq.ZMQ.ZMQ_PLAIN_PASSWORD, password); + } + + public boolean setPlainUsername(byte[] username) + { + return base.setSocketOpt(zmq.ZMQ.ZMQ_PLAIN_USERNAME, username); + } + + public boolean setPlainPassword(byte[] password) + { + return base.setSocketOpt(zmq.ZMQ.ZMQ_PLAIN_PASSWORD, password); + } + + public String getPlainUsername() + { + return (String) base.getSocketOptx(zmq.ZMQ.ZMQ_PLAIN_USERNAME); + } + + public String getPlainPassword() + { + return (String) base.getSocketOptx(zmq.ZMQ.ZMQ_PLAIN_PASSWORD); + } + + /** + * Sets the role used for ZAP authentication. + * @param server true if the role of the socket should be server ZAP authentication + * @return true if the option was set + * @see #isAsServerCurve() + */ + public boolean setAsServerCurve(boolean server) + { + return setSocketOpt(zmq.ZMQ.ZMQ_CURVE_SERVER, server); + } + + /** + * Gets the role used for ZAP authentication. + * @return true if the role of the socket should be server ZAP authentication + * @see #setAsServerCurve(boolean) */ - public void setDelayAttachOnConnect(boolean value) + public boolean isAsServerCurve() + { + return (boolean) base.getSocketOptx(zmq.ZMQ.ZMQ_CURVE_SERVER); + } + + public boolean setCurvePublicKey(byte[] key) + { + return setSocketOpt(zmq.ZMQ.ZMQ_CURVE_PUBLICKEY, key); + } + + public boolean setCurveServerKey(byte[] key) + { + return setSocketOpt(zmq.ZMQ.ZMQ_CURVE_SERVERKEY, key); + } + + public boolean setCurveSecretKey(byte[] key) + { + return setSocketOpt(zmq.ZMQ.ZMQ_CURVE_SECRETKEY, key); + } + + public byte[] getCurvePublicKey() + { + return (byte[]) base.getSocketOptx(zmq.ZMQ.ZMQ_CURVE_PUBLICKEY); + } + + public byte[] getCurveServerKey() + { + return (byte[]) base.getSocketOptx(zmq.ZMQ.ZMQ_CURVE_SERVERKEY); + } + + public byte[] getCurveSecretKey() + { + return (byte[]) base.getSocketOptx(zmq.ZMQ.ZMQ_CURVE_SECRETKEY); + } + + public static enum Mechanism + { + NULL(Mechanisms.NULL), + PLAIN(Mechanisms.PLAIN), + CURVE(Mechanisms.CURVE); + + private final Mechanisms mech; + + Mechanism(Mechanisms zmq) + { + this.mech = zmq; + } + + private static Mechanism find(Mechanisms mech) + { + for (Mechanism candidate : values()) { + if (candidate.mech == mech) { + return candidate; + } + } + return null; + } + } + + public Mechanism getMechanism() { - setsockopt(zmq.ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT, value ? 1 : 0); + return Mechanism.find((Mechanisms) base.getSocketOptx(zmq.ZMQ.ZMQ_MECHANISM)); } /** @@ -1207,11 +1582,13 @@ public void setDelayAttachOnConnect(boolean value) * * @param addr * the endpoint to bind to. + * @return true if the socket was bound, otherwise false. */ - public final void bind(String addr) + public boolean bind(String addr) { - base.bind(addr); + boolean rc = base.bind(addr); mayRaise(); + return rc; } /** @@ -1241,14 +1618,14 @@ public int bindToRandomPort(String addr, int min, int max) { int port; Random rand = new Random(); -// int port = min; -// while (port <= max) { + // int port = min; + // while (port <= max) { for (int i = 0; i < 100; i++) { // hardcoded to 100 tries. should this be parametrised port = rand.nextInt(max - min + 1) + min; if (base.bind(String.format("%s:%s", addr, port))) { return port; } -// port++; + // port++; } throw new ZMQException("Could not bind socket to random port.", ZError.EADDRINUSE); } @@ -1258,11 +1635,13 @@ public int bindToRandomPort(String addr, int min, int max) * * @param addr * the endpoint to connect to. + * @return true if the socket was connected, otherwise false. */ - public final void connect(String addr) + public boolean connect(String addr) { - base.connect(addr); + boolean rc = base.connect(addr); mayRaise(); + return rc; } /** @@ -1272,7 +1651,7 @@ public final void connect(String addr) * the endpoint to disconnect from. * @return true if successful. */ - public final boolean disconnect(String addr) + public boolean disconnect(String addr) { return base.termEndpoint(addr); } @@ -1284,37 +1663,37 @@ public final boolean disconnect(String addr) * the endpoint to unbind from. * @return true if successful. */ - public final boolean unbind(String addr) + public boolean unbind(String addr) { return base.termEndpoint(addr); } - public final boolean send(String data) + public boolean send(String data) { return send(data.getBytes(CHARSET), 0); } - public final boolean sendMore(String data) + public boolean sendMore(String data) { return send(data.getBytes(CHARSET), zmq.ZMQ.ZMQ_SNDMORE); } - public final boolean send(String data, int flags) + public boolean send(String data, int flags) { return send(data.getBytes(CHARSET), flags); } - public final boolean send(byte[] data) + public boolean send(byte[] data) { return send(data, 0); } - public final boolean sendMore(byte[] data) + public boolean sendMore(byte[] data) { return send(data, zmq.ZMQ.ZMQ_SNDMORE); } - public final boolean send(byte[] data, int flags) + public boolean send(byte[] data, int flags) { zmq.Msg msg = new zmq.Msg(data); if (base.send(msg, flags)) { @@ -1325,7 +1704,7 @@ public final boolean send(byte[] data, int flags) return false; } - public final boolean send(byte[] data, int off, int length, int flags) + public boolean send(byte[] data, int off, int length, int flags) { byte[] copy = new byte[length]; System.arraycopy(data, off, copy, 0, length); @@ -1345,7 +1724,7 @@ public final boolean send(byte[] data, int off, int length, int flags) * @param flags the flags to apply to the send operation * @return the number of bytes sent, -1 on error */ - public final int sendByteBuffer(ByteBuffer data, int flags) + public int sendByteBuffer(ByteBuffer data, int flags) { zmq.Msg msg = new zmq.Msg(data); if (base.send(msg, flags)) { @@ -1355,12 +1734,13 @@ public final int sendByteBuffer(ByteBuffer data, int flags) mayRaise(); return -1; } + /** * Receive a message. * * @return the message received, as an array of bytes; null on error. */ - public final byte[] recv() + public byte[] recv() { return recv(0); } @@ -1372,7 +1752,7 @@ public final byte[] recv() * the flags to apply to the receive operation. * @return the message received, as an array of bytes; null on error. */ - public final byte[] recv(int flags) + public byte[] recv(int flags) { zmq.Msg msg = base.recv(flags); @@ -1399,7 +1779,7 @@ public final byte[] recv(int flags) * the flags to apply to the receive operation. * @return the number of bytes read, -1 on error */ - public final int recv(byte[] buffer, int offset, int len, int flags) + public int recv(byte[] buffer, int offset, int len, int flags) { zmq.Msg msg = base.recv(flags); @@ -1417,7 +1797,7 @@ public final int recv(byte[] buffer, int offset, int len, int flags) * @param flags the flags to apply to the receive operation * @return the number of bytes read, -1 on error */ - public final int recvByteBuffer(ByteBuffer buffer, int flags) + public int recvByteBuffer(ByteBuffer buffer, int flags) { zmq.Msg msg = base.recv(flags); @@ -1429,11 +1809,12 @@ public final int recvByteBuffer(ByteBuffer buffer, int flags) mayRaise(); return -1; } + /** * * @return the message received, as a String object; null on no message. */ - public final String recvStr() + public String recvStr() { return recvStr(0); } @@ -1443,7 +1824,7 @@ public final String recvStr() * @param flags the flags to apply to the receive operation. * @return the message received, as a String object; null on no message. */ - public final String recvStr(int flags) + public String recvStr(int flags) { byte[] msg = recv(flags); @@ -1474,6 +1855,11 @@ private void mayRaise() throw new ZMQException(errno); } } + + public int errno() + { + return base.errno(); + } } public static class Poller @@ -1481,19 +1867,21 @@ public static class Poller /** * These values can be ORed to specify what we want to poll for. */ - public static final int POLLIN = zmq.ZMQ.ZMQ_POLLIN; + public static final int POLLIN = zmq.ZMQ.ZMQ_POLLIN; public static final int POLLOUT = zmq.ZMQ.ZMQ_POLLOUT; public static final int POLLERR = zmq.ZMQ.ZMQ_POLLERR; - private static final int SIZE_DEFAULT = 32; + private static final int SIZE_DEFAULT = 32; private static final int SIZE_INCREMENT = 16; - private Selector selector; - private Context context; + private final Selector selector; + private final Context context; + private PollItem[] items; + private int next; + private int used; + private long timeout; - private int next; - private int used; // When socket is removed from polling, store free slots here private LinkedList freeSlots; @@ -1514,9 +1902,9 @@ protected Poller(Context context, int size) selector = context.selector(); assert (selector != null); - items = new PollItem[size]; + items = new PollItem[size]; timeout = -1L; - next = 0; + next = 0; freeSlots = new LinkedList(); } @@ -1714,6 +2102,7 @@ public Socket getSocket(int index) * @return the current poll timeout in milliseconds. * @deprecated Timeout handling has been moved to the poll() methods. */ + @Deprecated public long getTimeout() { return this.timeout; @@ -1726,6 +2115,7 @@ public long getTimeout() * the desired poll timeout in milliseconds. * @deprecated Timeout handling has been moved to the poll() methods. */ + @Deprecated public void setTimeout(long timeout) { if (timeout >= -1L) { @@ -1794,7 +2184,7 @@ public int poll(long tout) if (items.length <= 0 || next <= 0) { return 0; } - zmq.PollItem[] pollItems = new zmq.PollItem[used]; + zmq.poll.PollItem[] pollItems = new zmq.poll.PollItem[used]; for (int i = 0, j = 0; i < next; i++) { if (items[i] != null) { pollItems[j++] = items[i].base; @@ -1805,11 +2195,11 @@ public int poll(long tout) return zmq.ZMQ.poll(selector, pollItems, used, tout); } catch (ZError.IOException e) { - if (context != null && context.isTerminated()) { + if (context.isTerminated()) { return 0; } else { - throw(e); + throw (e); } } } @@ -1865,22 +2255,22 @@ public boolean pollerr(int index) public static class PollItem { - private final zmq.PollItem base; - private final Socket socket; + private final zmq.poll.PollItem base; + private final Socket socket; public PollItem(Socket socket, int ops) { this.socket = socket; - base = new zmq.PollItem(socket.base, ops); + base = new zmq.poll.PollItem(socket.base, ops); } public PollItem(SelectableChannel channel, int ops) { - base = new zmq.PollItem(channel, ops); + base = new zmq.poll.PollItem(channel, ops); socket = null; } - final zmq.PollItem base() + final zmq.poll.PollItem base() { return base; } @@ -1973,7 +2363,7 @@ public int getCode() public static Error findByCode(int code) { - for (Error e : Error.class.getEnumConstants()) { + for (Error e : Error.values()) { if (e.getCode() == code) { return e; } @@ -2009,12 +2399,36 @@ public static boolean proxy(Socket frontend, Socket backend, Socket capture) return zmq.ZMQ.proxy(frontend.base, backend.base, capture != null ? capture.base : null); } + public static boolean proxy(Socket frontend, Socket backend, Socket capture, Socket control) + { + return zmq.ZMQ.proxy( + frontend.base, + backend.base, + capture == null ? null : capture.base, + control == null ? null : control.base); + } + + public static int poll(Selector selector, PollItem[] items, long timeout) + { + return poll(selector, items, items.length, timeout); + } + + public static int poll(Selector selector, PollItem[] items, int count, long timeout) + { + zmq.poll.PollItem[] pollItems = new zmq.poll.PollItem[count]; + for (int i = 0; i < count; i++) { + pollItems[i] = items[i].base; + } + + return zmq.ZMQ.poll(selector, pollItems, count, timeout); + } + /** * @return Major version number of the ZMQ library. */ public static int getMajorVersion() { - return zmq.ZMQ.ZMQ_VERSION_MAJOR; + return zmq.ZMQ.ZMQ_VERSION_MAJOR; } /** @@ -2022,7 +2436,7 @@ public static int getMajorVersion() */ public static int getMinorVersion() { - return zmq.ZMQ.ZMQ_VERSION_MINOR; + return zmq.ZMQ.ZMQ_VERSION_MINOR; } /** @@ -2030,7 +2444,7 @@ public static int getMinorVersion() */ public static int getPatchVersion() { - return zmq.ZMQ.ZMQ_VERSION_PATCH; + return zmq.ZMQ.ZMQ_VERSION_PATCH; } /** @@ -2038,9 +2452,7 @@ public static int getPatchVersion() */ public static int getFullVersion() { - return zmq.ZMQ.makeVersion(zmq.ZMQ.ZMQ_VERSION_MAJOR, - zmq.ZMQ.ZMQ_VERSION_MINOR, - zmq.ZMQ.ZMQ_VERSION_PATCH); + return zmq.ZMQ.makeVersion(zmq.ZMQ.ZMQ_VERSION_MAJOR, zmq.ZMQ.ZMQ_VERSION_MINOR, zmq.ZMQ.ZMQ_VERSION_PATCH); } /** @@ -2050,11 +2462,9 @@ public static int getFullVersion() * * @return Comparible single int version number. */ - public static int makeVersion(final int major, - final int minor, - final int patch) + public static int makeVersion(final int major, final int minor, final int patch) { - return zmq.ZMQ.makeVersion(major, minor, patch); + return zmq.ZMQ.makeVersion(major, minor, patch); } /** @@ -2062,9 +2472,7 @@ public static int makeVersion(final int major, */ public static String getVersionString() { - return "" + zmq.ZMQ.ZMQ_VERSION_MAJOR + "." + - zmq.ZMQ.ZMQ_VERSION_MINOR + "." + - zmq.ZMQ.ZMQ_VERSION_PATCH; + return "" + zmq.ZMQ.ZMQ_VERSION_MAJOR + "." + zmq.ZMQ.ZMQ_VERSION_MINOR + "." + zmq.ZMQ.ZMQ_VERSION_PATCH; } /** @@ -2073,7 +2481,7 @@ public static String getVersionString() */ public static class Event { - private final int event; + private final int event; private final Object value; private final String address; diff --git a/src/main/java/org/zeromq/ZMsg.java b/src/main/java/org/zeromq/ZMsg.java index b2f763b0d..72c406028 100644 --- a/src/main/java/org/zeromq/ZMsg.java +++ b/src/main/java/org/zeromq/ZMsg.java @@ -9,7 +9,6 @@ import java.util.Collection; import java.util.Deque; import java.util.Iterator; -import java.util.NoSuchElementException; import org.zeromq.ZMQ.Socket; @@ -46,14 +45,13 @@ public class ZMsg implements Iterable, Deque /** * Hold internal list of ZFrame objects */ - private ArrayDeque frames; + private final ArrayDeque frames = new ArrayDeque<>(); /** * Class Constructor */ public ZMsg() { - frames = new ArrayDeque(); } /** @@ -62,14 +60,10 @@ public ZMsg() */ public void destroy() { - if (frames == null) { - return; - } for (ZFrame f : frames) { f.destroy(); } frames.clear(); - frames = null; } /** @@ -91,9 +85,6 @@ public long contentSize() */ public void addString(String str) { - if (frames == null) { - frames = new ArrayDeque(); - } frames.add(new ZFrame(str)); } @@ -105,16 +96,16 @@ public void addString(String str) */ public ZMsg duplicate() { - if (frames != null) { + if (frames.isEmpty()) { + return null; + } + else { ZMsg msg = new ZMsg(); for (ZFrame f : frames) { msg.add(f.duplicate()); } return msg; } - else { - return null; - } } /** @@ -345,10 +336,6 @@ public boolean equals(Object o) } ZMsg zMsg = (ZMsg) o; - if (frames == null || zMsg.frames == null) { - return false; - } - //based on AbstractList Iterator e1 = frames.iterator(); Iterator e2 = zMsg.frames.iterator(); @@ -365,7 +352,7 @@ public boolean equals(Object o) @Override public int hashCode() { - if (frames == null || frames.size() == 0) { + if (frames.isEmpty()) { return 0; } @@ -383,23 +370,18 @@ public int hashCode() **/ public void dump(Appendable out) { - if (frames == null) { - return; - } try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.printf("--------------------------------------\n"); for (ZFrame frame : frames) { - pw.printf("[%03d] %s\n", frame.size(), - frame.toString()); + pw.printf("[%03d] %s\n", frame.size(), frame.toString()); } out.append(sw.getBuffer()); sw.close(); } catch (IOException e) { - throw new RuntimeException("Message dump exception " - + super.toString(), e); + throw new RuntimeException("Message dump exception " + super.toString(), e); } } @@ -456,7 +438,6 @@ public boolean add(byte[] data) @Override public Iterator iterator() { - // TODO Auto-generated method stub return frames.iterator(); } @@ -512,27 +493,18 @@ public T[] toArray(T[] arg0) @Override public boolean add(ZFrame e) { - if (frames == null) { - frames = new ArrayDeque(); - } return frames.add(e); } @Override public void addFirst(ZFrame e) { - if (frames == null) { - frames = new ArrayDeque(); - } frames.addFirst(e); } @Override public void addLast(ZFrame e) { - if (frames == null) { - frames = new ArrayDeque(); - } frames.addLast(e); } @@ -557,49 +529,30 @@ public ZFrame element() @Override public ZFrame getFirst() { - try { - return frames.getFirst(); - } - catch (NoSuchElementException e) { - return null; - } + return frames.peekFirst(); } @Override public ZFrame getLast() { - try { - return frames.getLast(); - } - catch (NoSuchElementException e) { - return null; - } + return frames.peekLast(); } @Override public boolean offer(ZFrame e) { - if (frames == null) { - frames = new ArrayDeque(); - } return frames.offer(e); } @Override public boolean offerFirst(ZFrame e) { - if (frames == null) { - frames = new ArrayDeque(); - } return frames.offerFirst(e); } @Override public boolean offerLast(ZFrame e) { - if (frames == null) { - frames = new ArrayDeque(); - } return frames.offerLast(e); } @@ -612,23 +565,13 @@ public ZFrame peek() @Override public ZFrame peekFirst() { - try { - return frames.peekFirst(); - } - catch (NoSuchElementException e) { - return null; - } + return frames.peekFirst(); } @Override public ZFrame peekLast() { - try { - return frames.peekLast(); - } - catch (NoSuchElementException e) { - return null; - } + return frames.peekLast(); } @Override @@ -652,15 +595,7 @@ public ZFrame pollLast() @Override public ZFrame pop() { - if (frames == null) { - frames = new ArrayDeque(); - } - try { - return frames.pop(); - } - catch (NoSuchElementException e) { - return null; - } + return frames.poll(); } /** @@ -681,9 +616,6 @@ public String popString() @Override public void push(ZFrame e) { - if (frames == null) { - frames = new ArrayDeque(); - } frames.push(e); } @@ -702,12 +634,7 @@ public boolean remove(Object o) @Override public ZFrame removeFirst() { - try { - return frames.removeFirst(); - } - catch (NoSuchElementException e) { - return null; - } + return frames.pollFirst(); } @Override @@ -719,12 +646,7 @@ public boolean removeFirstOccurrence(Object o) @Override public ZFrame removeLast() { - try { - return frames.removeLast(); - } - catch (NoSuchElementException e) { - return null; - } + return frames.pollLast(); } @Override @@ -739,6 +661,16 @@ public int size() return frames.size(); } + public void append(ZMsg msg) + { + if (msg == null) { + return; + } + for (ZFrame frame : msg.frames) { + add(frame); + } + } + /** * Returns pretty string representation of multipart message: * [ frame0, frame1, ..., frameN ] diff --git a/src/main/java/org/zeromq/ZPoller.java b/src/main/java/org/zeromq/ZPoller.java index bbf00e685..439da3eb2 100644 --- a/src/main/java/org/zeromq/ZPoller.java +++ b/src/main/java/org/zeromq/ZPoller.java @@ -62,7 +62,7 @@

* The motivations of this rewriting are: *
- * - the bare poller use {@link zmq.ZMQ#poll(zmq.PollItem[], int, long) this method} who does not allow + * - the bare poller use {@link zmq.ZMQ#poll(zmq.poll.PollItem[], int, long) this method} who does not allow * to choose the selector used for polling, relying on a ThreadLocal, which is dangerous. *
* - the bare poller use algorithms tailored for languages with manual allocation. @@ -98,7 +98,7 @@ public static interface EventsHandler public static interface ItemHolder { // the inner ZMQ poll item - zmq.PollItem item(); + zmq.poll.PollItem item(); // the related ZeroMQ socket Socket socket(); @@ -149,7 +149,7 @@ public ZPollItem(final SelectableChannel channel, final EventsHandler handler, f } @Override - public zmq.PollItem item() + public zmq.poll.PollItem item() { return base(); } @@ -233,12 +233,12 @@ public EventsHandler handler() /* * These values can be ORed to specify what we want to poll for. */ - public static final int POLLIN = Poller.POLLIN; + public static final int POLLIN = Poller.POLLIN; public static final int POLLOUT = Poller.POLLOUT; public static final int POLLERR = Poller.POLLERR; // same values with shorter writing - public static final int IN = POLLIN; + public static final int IN = POLLIN; public static final int OUT = POLLOUT; public static final int ERR = POLLERR; @@ -317,7 +317,7 @@ public ZPoller(final ItemCreator creator, final Selector selector) { this.creator = creator; this.selector = selector; - items = new HashMap>(); + items = new HashMap<>(); all = createContainer(0); } @@ -363,8 +363,7 @@ public EventsHandler getGlobalHandler() * @param events the events to listen to, as a mask composed by ORing POLLIN, POLLOUT and POLLERR. * @return true if registered, otherwise false */ - public final boolean register(final Socket socket, - final EventsHandler handler, final int events) + public final boolean register(final Socket socket, final EventsHandler handler, final int events) { if (socket == null) { return false; @@ -390,8 +389,7 @@ public final boolean register(final Socket socket, final int events) * @param events the events to listen to, as a mask composed by XORing POLLIN, POLLOUT and POLLERR. * @return true if registered, otherwise false */ - public final boolean register(final SelectableChannel channel, - final EventsHandler handler, final int events) + public final boolean register(final SelectableChannel channel, final EventsHandler handler, final int events) { if (channel == null) { return false; @@ -475,7 +473,7 @@ protected int poll(final long timeout, final boolean dispatchEvents) { // get all the raw items final Collection all = items(); - final Set pollItems = new HashSet(all.size()); + final Set pollItems = new HashSet(all.size()); for (ItemHolder holder : all) { pollItems.add(holder.item()); } @@ -496,13 +494,11 @@ protected int poll(final long timeout, final boolean dispatchEvents) } // does the effective polling - protected int poll(final Selector selector, final long tout, - final Collection items) + + protected int poll(final Selector selector, final long tout, final Collection items) { final int size = items.size(); - return zmq.ZMQ.poll(selector, - items.toArray(new zmq.PollItem[size]), size, - tout); + return zmq.ZMQ.poll(selector, items.toArray(new zmq.poll.PollItem[size]), size, tout); } /** @@ -525,7 +521,7 @@ protected boolean dispatch(final Collection all, int size) // no handler, short-circuit continue; } - final zmq.PollItem item = holder.item(); + final zmq.poll.PollItem item = holder.item(); final int events = item.readyOps(); if (events <= 0) { @@ -598,7 +594,7 @@ public boolean readable(final Socket socket) // checks for read event public boolean readable(final Object socketOrChannel) { - final zmq.PollItem it = filter(socketOrChannel, READABLE); + final zmq.poll.PollItem it = filter(socketOrChannel, READABLE); if (it == null) { return false; } @@ -650,7 +646,7 @@ public boolean writable(final Socket socket) // checks for write event public boolean writable(final Object socketOrChannel) { - final zmq.PollItem it = filter(socketOrChannel, WRITABLE); + final zmq.poll.PollItem it = filter(socketOrChannel, WRITABLE); if (it == null) { return false; } @@ -702,7 +698,7 @@ public boolean error(final Socket socket) // checks for error event public boolean error(final Object socketOrChannel) { - final zmq.PollItem it = filter(socketOrChannel, ERR); + final zmq.poll.PollItem it = filter(socketOrChannel, ERR); if (it == null) { return false; } @@ -761,15 +757,13 @@ public void destroy() private static class SimpleCreator implements ItemCreator { @Override - public ItemHolder create(final Socket socket, - final EventsHandler handler, final int events) + public ItemHolder create(final Socket socket, final EventsHandler handler, final int events) { return new ZPollItem(socket, handler, events); } @Override - public ItemHolder create(final SelectableChannel channel, - final EventsHandler handler, final int events) + public ItemHolder create(final SelectableChannel channel, final EventsHandler handler, final int events) { return new ZPollItem(channel, handler, events); } @@ -809,7 +803,7 @@ protected boolean add(Object socketOrChannel, final ItemHolder holder) // create the container of holders protected Set createContainer(int size) { - return new HashSet(size); + return new HashSet<>(size); } // gets all the items of this poller @@ -829,7 +823,7 @@ protected Iterable items(final Object socketOrChannel) } // filters items to get the first one matching the criteria, or null if none found - protected zmq.PollItem filter(final Object socketOrChannel, int events) + protected zmq.poll.PollItem filter(final Object socketOrChannel, int events) { if (socketOrChannel == null) { return null; @@ -837,7 +831,7 @@ protected zmq.PollItem filter(final Object socketOrChannel, int events) final Iterable items = items(socketOrChannel); for (ItemHolder item : items) { - final zmq.PollItem it = item.item(); + final zmq.poll.PollItem it = item.item(); if ((it.zinterestOps() & events) > 0) { return it; } diff --git a/src/main/java/org/zeromq/ZProxy.java b/src/main/java/org/zeromq/ZProxy.java index dc3939fd0..94bcf809d 100644 --- a/src/main/java/org/zeromq/ZProxy.java +++ b/src/main/java/org/zeromq/ZProxy.java @@ -122,8 +122,8 @@ public class ZProxy */ public static enum Plug { - FRONT, // The position of the frontend socket. - BACK, // The position of the backend socket. + FRONT, // The position of the frontend socket. + BACK, // The position of the backend socket. CAPTURE; // The position of the capture socket. } @@ -177,8 +177,7 @@ public static interface Proxy * @param args the optional array of arguments that has been passed at the creation of the ZProxy. * @return true to continue the proxy, false to exit */ - boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, - Socket capture, Object[] args); + boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture, Object[] args); /** * Handles a custom command not recognized by the proxy. @@ -200,21 +199,21 @@ boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, public abstract static class SimpleProxy implements Proxy { @Override - public boolean restart(ZMsg cfg, Socket socket, Plug place, - Object[] args) + public boolean restart(ZMsg cfg, Socket socket, Plug place, Object[] args) { return true; } @Override - public boolean configure(Socket pipe, ZMsg cfg, - Socket frontend, Socket backend, Socket capture, Object[] args) + public boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture, + Object[] args) { return true; } @Override - public boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, Socket capture, Object[] args) + public boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, Socket capture, + Object[] args) { return true; } @@ -236,8 +235,8 @@ public boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, * @return the created proxy. */ // creates a new proxy - public static ZProxy newZProxy(ZContext ctx, String name, - SelectorCreator selector, Proxy sockets, String motdelafin, Object... args) + public static ZProxy newZProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, + String motdelafin, Object... args) { return new ZProxy(ctx, name, selector, sockets, new ZPump(), null, args); } @@ -260,8 +259,8 @@ public static ZProxy newZProxy(ZContext ctx, String name, Proxy sockets, String * @return the created proxy. */ // creates a new low-level proxy - public static ZProxy newProxy(ZContext ctx, String name, - SelectorCreator selector, Proxy sockets, String motdelafin, Object... args) + public static ZProxy newProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, String motdelafin, + Object... args) { return new ZProxy(ctx, name, selector, sockets, new ZmqPump(), motdelafin, args); } @@ -418,11 +417,7 @@ public String restart(ZMsg hot) } else { msg.add(Boolean.toString(true)); - // FIXME better way to append 1 message into another ? - for (int index = 0; index < hot.size(); ++index) { - ZFrame frame = hot.pop(); - msg.add(frame); - } + msg.append(hot); } String status = EXITED; @@ -570,12 +565,12 @@ public static enum State } // state responses from the control pipe - public static final String STARTED = State.STARTED.name(); - public static final String PAUSED = State.PAUSED.name(); - public static final String STOPPED = State.STOPPED.name(); - public static final String EXITED = State.EXITED.name(); + public static final String STARTED = State.STARTED.name(); + public static final String PAUSED = State.PAUSED.name(); + public static final String STOPPED = State.STOPPED.name(); + public static final String EXITED = State.EXITED.name(); // defines the very first time where no command changing the state has been issued - public static final String ALIVE = State.ALIVE.name(); + public static final String ALIVE = State.ALIVE.name(); private static final AtomicInteger counter = new AtomicInteger(); @@ -591,8 +586,7 @@ public static enum State * @param selector the creator of the selector used for the proxy. * @param creator the creator of the sockets of the proxy. */ - public ZProxy(SelectorCreator selector, Proxy creator, - String motdelafin, Object ... args) + public ZProxy(SelectorCreator selector, Proxy creator, String motdelafin, Object... args) { this(null, null, selector, creator, null, motdelafin, args); } @@ -604,8 +598,7 @@ public ZProxy(SelectorCreator selector, Proxy creator, * @param selector the creator of the selector used for the proxy. * @param creator the creator of the sockets of the proxy. */ - public ZProxy(String name, SelectorCreator selector, - Proxy creator, String motdelafin, Object... args) + public ZProxy(String name, SelectorCreator selector, Proxy creator, String motdelafin, Object... args) { this(null, name, selector, creator, null, motdelafin, args); } @@ -621,8 +614,8 @@ public ZProxy(String name, SelectorCreator selector, * @param sockets the creator of the sockets of the proxy. * @param pump the pump used for the proxy */ - public ZProxy(ZContext ctx, String name, SelectorCreator selector, - Proxy sockets, Pump pump, String motdelafin, Object... args) + public ZProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, Pump pump, String motdelafin, + Object... args) { super(); @@ -674,8 +667,7 @@ public static interface Pump * * @return false in case of error or interruption, true if successfully transferred the message */ - boolean flow(Plug src, Socket source, Socket capture, - Plug dst, Socket destination); + boolean flow(Plug src, Socket source, Socket capture, Plug dst, Socket destination); } // acts in background to proxy messages @@ -748,9 +740,9 @@ public List createSockets(ZContext ctx, Object[] args) this.args = new Object[args.length - 1]; System.arraycopy(args, 1, this.args, 0, this.args.length); - frontend = provider.create(ctx, Plug.FRONT, this.args); - capture = provider.create(ctx, Plug.CAPTURE, this.args); - backend = provider.create(ctx, Plug.BACK, this.args); + frontend = provider.create(ctx, Plug.FRONT, this.args); + capture = provider.create(ctx, Plug.CAPTURE, this.args); + backend = provider.create(ctx, Plug.BACK, this.args); assert (frontend != null); assert (backend != null); @@ -837,9 +829,9 @@ private boolean start(ZPoller poller) if (!state.started) { state.started = true; - provider.configure(frontend, Plug.FRONT, args); - provider.configure(backend, Plug.BACK, args); - provider.configure(capture, Plug.CAPTURE, args); + provider.configure(frontend, Plug.FRONT, args); + provider.configure(backend, Plug.BACK, args); + provider.configure(capture, Plug.CAPTURE, args); } pause(poller, false); return true; @@ -899,22 +891,15 @@ public long looping(Socket pipe, ZPoller poller) // a message has been received for the proxy to process @Override - public boolean stage(Socket socket, Socket pipe, ZPoller poller, - int events) + public boolean stage(Socket socket, Socket pipe, ZPoller poller, int events) { if (socket == frontend) { // Process a request. - return transport.flow( - Plug.FRONT, frontend, - capture, - Plug.BACK, backend); + return transport.flow(Plug.FRONT, frontend, capture, Plug.BACK, backend); } if (socket == backend) { // Process a reply. - return transport.flow( - Plug.BACK, backend, - capture, - Plug.FRONT, frontend); + return transport.flow(Plug.BACK, backend, capture, Plug.FRONT, frontend); } return false; } @@ -939,10 +924,10 @@ public boolean looped(Socket pipe, ZPoller poller) cold = provider.restart(dup, frontend, Plug.FRONT, this.args); dup.destroy(); dup = cfg.duplicate(); - cold |= provider.restart(dup, backend, Plug.BACK, this.args); + cold |= provider.restart(dup, backend, Plug.BACK, this.args); dup.destroy(); dup = cfg.duplicate(); - cold |= provider.restart(dup, capture, Plug.CAPTURE, this.args); + cold |= provider.restart(dup, capture, Plug.CAPTURE, this.args); dup.destroy(); cfg.destroy(); @@ -1016,8 +1001,7 @@ public ZPump(Transformer transformer) } @Override - public boolean flow(Plug splug, Socket source, Socket capture, - Plug dplug, Socket destination) + public boolean flow(Plug splug, Socket source, Socket capture, Plug dplug, Socket destination) { boolean success = false; @@ -1033,7 +1017,7 @@ public boolean flow(Plug splug, Socket source, Socket capture, // TODO what if the transformer modifies or destroys the original message ? ZMsg cpt = transformer.transform(msg, splug, Plug.CAPTURE); -// boolean destroy = !msg.equals(cpt); // TODO ?? which one + // boolean destroy = !msg.equals(cpt); // TODO ?? which one boolean destroy = msg != cpt; success = cpt.send(capture, destroy); if (!success) { @@ -1060,8 +1044,7 @@ private static final class ZmqPump implements Pump { // transfers each message as a whole by sending each packet received to the capture socket @Override - public boolean flow(Plug splug, Socket source, Socket capture, - Plug dplug, Socket destination) + public boolean flow(Plug splug, Socket source, Socket capture, Plug dplug, Socket destination) { boolean rc; diff --git a/src/main/java/org/zeromq/ZSocket.java b/src/main/java/org/zeromq/ZSocket.java index 004dc60fe..2140b4d4a 100644 --- a/src/main/java/org/zeromq/ZSocket.java +++ b/src/main/java/org/zeromq/ZSocket.java @@ -1,13 +1,13 @@ package org.zeromq; +import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicBoolean; + import zmq.Msg; import zmq.SocketBase; import zmq.ZError; import zmq.ZMQ; -import java.nio.charset.Charset; -import java.util.concurrent.atomic.AtomicBoolean; - /** * ZeroMQ sockets present an abstraction of an asynchronous message queue, with the exact queuing * semantics depending on the socket type in use. Where conventional sockets transfer streams of @@ -21,7 +21,7 @@ public class ZSocket implements AutoCloseable { public static final Charset UTF8 = Charset.forName("UTF-8"); - private final SocketBase socketBase; + private final SocketBase socketBase; private final AtomicBoolean isClosed = new AtomicBoolean(false); @@ -243,7 +243,7 @@ private void setOption(int option, Object value) private Object getOption(int option) { - return socketBase.getsockoptx(option); + return socketBase.getSocketOptx(option); } /** diff --git a/src/main/java/org/zeromq/ZStar.java b/src/main/java/org/zeromq/ZStar.java index c72f62951..911a55939 100644 --- a/src/main/java/org/zeromq/ZStar.java +++ b/src/main/java/org/zeromq/ZStar.java @@ -293,8 +293,8 @@ public ZStar(SelectorCreator selector, Fortune fortune, String motdelafin, Objec * @param motdelafin the final word used to mark the end of the star. Null to disable this mechanism. * @param bags the optional arguments that will be passed to the distant star */ - public ZStar(final ZContext context, final SelectorCreator selector, - final Fortune fortune, String motdelafin, final Object[] bags) + public ZStar(final ZContext context, final SelectorCreator selector, final Fortune fortune, String motdelafin, + final Object[] bags) { super(); assert (fortune != null); @@ -329,7 +329,7 @@ public ZStar(final ZContext context, final SelectorCreator selector, } } if (set == null) { - set = new SimpleSet(); + set = new SimpleSet(); } final List train = new ArrayList(6 + bags.length); @@ -383,9 +383,7 @@ private static final class Plateau implements IAttachedRunnable, Exit private final CountDownLatch exit = new CountDownLatch(1); @Override - public void run(final Object[] train, - final ZContext chef, - final Socket mic) + public void run(final Object[] train, final ZContext chef, final Socket mic) { final int mandat = 6; @@ -394,8 +392,8 @@ public void run(final Object[] train, final Entourage entourage = (Entourage) train[4]; - final ZContext producer = (ZContext) train[3]; - final SelectorCreator feather = (SelectorCreator) train[2]; + final ZContext producer = (ZContext) train[3]; + final SelectorCreator feather = (SelectorCreator) train[2]; final Set set = (Set) train[0]; // the word informing the world that the plateau is closed and the star vanished @@ -477,8 +475,8 @@ public void run(final Object[] train, /******************************************************************************/ // starts the performance - private void showMustGoOn(final ZContext chef, final Set set, final Selector story, - final Socket phone, final Fortune fortune, final Object[] bags) + private void showMustGoOn(final ZContext chef, final Set set, final Selector story, final Socket phone, + final Fortune fortune, final Object[] bags) { int shows = 0; /** on the spot lights, the star in only an actor **/ @@ -508,8 +506,7 @@ private void showMustGoOn(final ZContext chef, final Set set, final Selector sto break; } } - } - while (actor.renews()); + } while (actor.renews()); // star is leaving the Plateau and the show } @@ -585,12 +582,6 @@ public boolean sign() return agent.sign(); } - @Override - public void nova() - { - agent.nova(); - } - public static interface Set { /** diff --git a/src/main/java/org/zeromq/ZThread.java b/src/main/java/org/zeromq/ZThread.java index 535f8e060..aa4096c24 100644 --- a/src/main/java/org/zeromq/ZThread.java +++ b/src/main/java/org/zeromq/ZThread.java @@ -21,13 +21,13 @@ public interface IDetachedRunnable private static class ShimThread extends Thread { - private ZContext ctx; + private ZContext ctx; private IAttachedRunnable attachedRunnable; private IDetachedRunnable detachedRunnable; - private Object[] args; - private Socket pipe; + private Object[] args; + private Socket pipe; - protected ShimThread(ZContext ctx, IAttachedRunnable runnable, Object [] args, Socket pipe) + protected ShimThread(ZContext ctx, IAttachedRunnable runnable, Object[] args, Socket pipe) { assert (ctx != null); assert (pipe != null); @@ -71,7 +71,7 @@ public void run() // and is used to simulate a separate process. It gets no ctx, and no // pipe. - public static void start(IDetachedRunnable runnable, Object ... args) + public static void start(IDetachedRunnable runnable, Object... args) { // Prepare child thread Thread shim = new ShimThread(runnable, args); @@ -84,7 +84,7 @@ public static void start(IDetachedRunnable runnable, Object ... args) // pipe back to its parent. It must monitor its pipe, and exit if the // pipe becomes unreadable. Returns pipe, or null if there was an error. - public static Socket fork(ZContext ctx, IAttachedRunnable runnable, Object ... args) + public static Socket fork(ZContext ctx, IAttachedRunnable runnable, Object... args) { Socket pipe = ctx.createSocket(ZMQ.PAIR); diff --git a/src/main/java/zmq/Address.java b/src/main/java/zmq/Address.java deleted file mode 100644 index b6b956e0e..000000000 --- a/src/main/java/zmq/Address.java +++ /dev/null @@ -1,92 +0,0 @@ -package zmq; - -import java.net.Inet6Address; -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -public class Address -{ - public interface IZAddress - { - String toString(); - void resolve(String name, boolean ip4only); - SocketAddress address(); - }; - - private final String protocol; - private final String address; - private final boolean ipv4only; - - private IZAddress resolved; - - public Address(final String protocol, final String address, final boolean ipv4only) - { - this.protocol = protocol; - this.address = address; - this.ipv4only = ipv4only; - resolved = null; - } - - public Address(SocketAddress socketAddress) - { - InetSocketAddress sockAddr = (InetSocketAddress) socketAddress; - this.address = sockAddr.getAddress().getHostAddress() + ":" + sockAddr.getPort(); - protocol = "tcp"; - resolved = null; - ipv4only = !(sockAddr.getAddress() instanceof Inet6Address); - } - - @Override - public String toString() - { - if (protocol.equals("tcp") && isResolved()) { - return resolved.toString(); - } - else if (protocol.equals("ipc") && isResolved()) { - return resolved.toString(); - } - else if (!protocol.isEmpty() && !address.isEmpty()) { - return protocol + "://" + address; - } - else { - return ""; - } - } - - public String protocol() - { - return protocol; - } - - public String address() - { - return address; - } - - public IZAddress resolved() - { - return resolved; - } - - public boolean isResolved() - { - return resolved != null; - } - - public boolean resolve() - { - if (protocol.equals("tcp")) { - resolved = new TcpAddress(); - resolved.resolve(address, ipv4only); - return true; - } - else if (protocol.equals("ipc")) { - resolved = new IpcAddress(); - resolved.resolve(address, true); - return true; - } - else { - return false; - } - } -} diff --git a/src/main/java/zmq/Command.java b/src/main/java/zmq/Command.java index cbc7ee3da..b944aad93 100644 --- a/src/main/java/zmq/Command.java +++ b/src/main/java/zmq/Command.java @@ -1,13 +1,15 @@ package zmq; // This structure defines the commands that can be sent between threads. -class Command +public class Command { // Object to process the command. - private final ZObject destination; - private final Type type; + final ZObject destination; + final Type type; + final Object arg; - public enum Type { + public enum Type + { // Sent to I/O thread to let it know that it should // terminate itself. STOP, @@ -49,38 +51,34 @@ public enum Type { REAP, // Closed socket notifies the reaper that it's already deallocated. REAPED, + // TODO V4 provide a description for Command#INPROC_CONNECTED + INPROC_CONNECTED, // Sent by reaper thread to the term thread when all the sockets // are successfully deallocated. DONE } - Object arg; - - public Command(ZObject destination, Type type) + Command(ZObject destination, Type type) { this(destination, type, null); } - public Command(ZObject destination, Type type, Object arg) + Command(ZObject destination, Type type, Object arg) { this.destination = destination; this.type = type; this.arg = arg; } - public ZObject destination() - { - return destination; - } - - public Type type() + public final void process() { - return type; + destination.processCommand(this); } @Override public String toString() { - return "Cmd" + "[" + destination + ", " + destination.getTid() + ", " + type + (arg == null ? "" : ", " + arg) + "]"; + return "Cmd" + "[" + destination + ", " + (destination == null ? "Reaper" : destination.getTid() + ", ") + type + + (arg == null ? "" : ", " + arg) + "]"; } } diff --git a/src/main/java/zmq/Config.java b/src/main/java/zmq/Config.java index 48ef2c035..1277caf5c 100644 --- a/src/main/java/zmq/Config.java +++ b/src/main/java/zmq/Config.java @@ -5,10 +5,10 @@ public enum Config // Number of new messages in message pipe needed to trigger new memory // allocation. Setting this parameter to 256 decreases the impact of // memory allocation by approximately 99.6% - MESSAGE_PIPE_GRANULARITY (256), + MESSAGE_PIPE_GRANULARITY(256), // Commands in pipe per allocation event. - COMMAND_PIPE_GRANULARITY (16), + COMMAND_PIPE_GRANULARITY(16), // Determines how often does socket poll for new commands when it // still has unprocessed messages to handle. Thus, if it is set to 100, @@ -16,45 +16,54 @@ public enum Config // If there are no unprocessed messages available, poll is done // immediately. Decreasing the value trades overall latency for more // real-time behaviour (less latency peaks). - INBOUND_POLL_RATE (100), + INBOUND_POLL_RATE(100), // Maximal batching size for engines with receiving functionality. // So, if there are 10 messages that fit into the batch size, all of // them may be read by a single 'recv' system call, thus avoiding // unnecessary network stack traversals. - IN_BATCH_SIZE (8192), + IN_BATCH_SIZE(8192), // Maximal batching size for engines with sending functionality. // So, if there are 10 messages that fit into the batch size, all of // them may be written by a single 'send' system call, thus avoiding // unnecessary network stack traversals. - OUT_BATCH_SIZE (8192), + OUT_BATCH_SIZE(8192), // Maximal delta between high and low watermark. - MAX_WM_DELTA (1024), + MAX_WM_DELTA(1024), // Maximum number of events the I/O thread can process in one go. - MAX_IO_EVENTS (256), + MAX_IO_EVENTS(256), // Maximal delay to process command in API thread (in CPU ticks). // 3,000,000 ticks equals to 1 - 2 milliseconds on current CPUs. // Note that delay is only applied when there is continuous stream of // messages to process. If not so, commands are processed immediately. - MAX_COMMAND_DELAY (3000000), + MAX_COMMAND_DELAY(3000000), // Low-precision clock precision in CPU ticks. 1ms. Value of 1000000 // should be OK for CPU frequencies above 1GHz. If should work // reasonably well for CPU frequencies above 500MHz. For lower CPU // frequencies you may consider lowering this value to get best // possible latencies. - CLOCK_PRECISION (1000000), + CLOCK_PRECISION(1000000), // Maximum transport data unit size for PGM (TPDU). - PGM_MAX_TPDU (1500), + PGM_MAX_TPDU(1500), // On some OSes the signaler has to be emulated using a TCP // connection. In such cases following port is used. - SIGNALER_PORT (5905); + // If 0, it lets the OS choose a free port without requiring use of a + // global mutex. The original implementation of a Windows signaler + // socket used port 5905 instead of letting the OS choose a free port. + // https://github.com/zeromq/libzmq/issues/1542 + SIGNALER_PORT(0), + + // Threshold of message size for choosing if message data should + // be allocated on heap or on direct memory. + // This has a direct impact on transmission of large messages + MSG_ALLOCATION_HEAP_THRESHOLD(1024 * 1024); private final int value; diff --git a/src/main/java/zmq/Ctx.java b/src/main/java/zmq/Ctx.java index 6e80afea2..6d90d3314 100644 --- a/src/main/java/zmq/Ctx.java +++ b/src/main/java/zmq/Ctx.java @@ -15,6 +15,11 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import zmq.io.IOThread; +import zmq.pipe.Pipe; +import zmq.socket.Sockets; +import zmq.util.Errno; + //Context object encapsulates all the global state associated with // the library. @@ -24,12 +29,12 @@ public class Ctx // Information associated with inproc endpoint. Note that endpoint options // are registered as well so that the peer can access them without a need - // for synchronisation, handshaking or similar. + // for synchronization, handshaking or similar. - static class Endpoint + public static class Endpoint { public final SocketBase socket; - public final Options options; + public final Options options; public Endpoint(SocketBase socket, Options options) { @@ -38,6 +43,28 @@ public Endpoint(SocketBase socket, Options options) } } + + private static class PendingConnection + { + private final Endpoint endpoint; + private final Pipe connectPipe; + private final Pipe bindPipe; + + public PendingConnection(Endpoint endpoint, Pipe connectPipe, Pipe bindPipe) + { + super(); + this.endpoint = endpoint; + this.connectPipe = connectPipe; + this.bindPipe = bindPipe; + } + } + + private static enum Side + { + CONNECT, + BIND + } + // Used to check whether the object is a context. private int tag; @@ -51,13 +78,13 @@ public Endpoint(SocketBase socket, Options options) // If true, init has been called but no socket has been created // yet. Launching of I/O threads is delayed. - private AtomicBoolean starting = new AtomicBoolean(true); + private final AtomicBoolean starting = new AtomicBoolean(true); // If true, zmq_term was already called. private boolean terminating; - // Synchronisation of accesses to global slot-related data: - // sockets, emptySlots, terminating. It also synchronises + // Synchronization of accesses to global slot-related data: + // sockets, emptySlots, terminating. It also synchronizes // access to zombie sockets as such (as opposed to slots) and provides // a memory barrier to ensure that all CPU cores see the same data. private final Lock slotSync; @@ -65,7 +92,7 @@ public Endpoint(SocketBase socket, Options options) // A list of poll selectors opened under this context. When the context is // destroyed, each of the selectors is closed to ensure resource // deallocation. - public List selectors; + private final List selectors = new ArrayList<>(); // The reaper thread. private Reaper reaper; @@ -74,7 +101,7 @@ public Endpoint(SocketBase socket, Options options) private final List ioThreads; // Array of pointers to mailboxes for both application and I/O threads. - private int slotCount; + private int slotCount; private Mailbox[] slots; // Mailbox for zmq_term thread. @@ -83,7 +110,7 @@ public Endpoint(SocketBase socket, Options options) // List of inproc endpoints within this context. private final Map endpoints; - // Synchronisation of access to the list of inproc endpoints. + // Synchronization of access to the list of inproc endpoints. private final Lock endpointsSync; // Maximum socket ID. @@ -98,11 +125,17 @@ public Endpoint(SocketBase socket, Options options) // Does context wait (possibly forever) on termination? private boolean blocky; - // Synchronisation of access to context options. + // Synchronization of access to context options. private final Lock optSync; - public static final int TERM_TID = 0; - public static final int REAPER_TID = 1; + static final int TERM_TID = 0; + private static final int REAPER_TID = 1; + + private final Map pendingConnections = new HashMap<>(); + + private boolean ipv6; + + private final Errno errno = new Errno(); public Ctx() { @@ -113,28 +146,32 @@ public Ctx() slots = null; maxSockets = ZMQ.ZMQ_MAX_SOCKETS_DFLT; ioThreadCount = ZMQ.ZMQ_IO_THREADS_DFLT; + + ipv6 = false; blocky = true; slotSync = new ReentrantLock(); endpointsSync = new ReentrantLock(); optSync = new ReentrantLock(); - termMailbox = new Mailbox("terminater"); + termMailbox = new Mailbox(this, "terminater", -1); - emptySlots = new ArrayDeque(); - ioThreads = new ArrayList(); - sockets = new ArrayList(); - selectors = new ArrayList(); - endpoints = new HashMap(); + emptySlots = new ArrayDeque<>(); + ioThreads = new ArrayList<>(); + sockets = new ArrayList<>(); + endpoints = new HashMap<>(); } private void destroy() throws IOException { + assert (sockets.isEmpty()); + for (IOThread it : ioThreads) { it.stop(); } for (IOThread it : ioThreads) { it.close(); } + ioThreads.clear(); for (Selector selector : selectors) { if (selector != null) { @@ -143,9 +180,13 @@ private void destroy() throws IOException } selectors.clear(); + // Deallocate the reaper thread object. if (reaper != null) { reaper.close(); } + // Deallocate the array of mailboxes. No special work is + // needed as mailboxes themselves were deallocated with their + // corresponding io_thread/socket objects. termMailbox.close(); tag = 0xdeadbeef; @@ -166,11 +207,16 @@ public boolean checkTag() public void terminate() { - tag = 0xdeadbeef; + // Connect up any pending inproc connections, otherwise we will hang + for (Entry pending : pendingConnections.entrySet()) { + SocketBase s = createSocket(ZMQ.ZMQ_PAIR); + s.bind(pending.getKey()); + s.close(); + } - if (!starting.get()) { - slotSync.lock(); - try { + slotSync.lock(); + try { + if (!starting.get()) { // Check whether termination was already underway, but interrupted and now // restarted. boolean restarted = terminating; @@ -188,23 +234,20 @@ public void terminate() reaper.stop(); } } - } - finally { slotSync.unlock(); - } - // Wait till reaper thread closes all the sockets. - Command cmd = termMailbox.recv(WAIT_FOREVER); - if (cmd == null) { - throw new IllegalStateException(); - } - assert (cmd.type() == Command.Type.DONE); - slotSync.lock(); - try { + + // Wait till reaper thread closes all the sockets. + Command cmd = termMailbox.recv(WAIT_FOREVER); + if (cmd == null) { + throw new IllegalStateException(); + } + assert (cmd.type == Command.Type.DONE); + slotSync.lock(); assert (sockets.isEmpty()); } - finally { - slotSync.unlock(); - } + } + finally { + slotSync.unlock(); } // Deallocate the resources. @@ -216,6 +259,29 @@ public void terminate() } } + final void shutdown() + { + slotSync.lock(); + try { + if (!starting.get() && !terminating) { + terminating = true; + // Send stop command to sockets so that any blocking calls + // can be interrupted. If there are no sockets we can ask reaper + // thread to stop. + for (SocketBase socket : sockets) { + socket.stop(); + } + if (sockets.isEmpty()) { + reaper.stop(); + } + + } + } + finally { + slotSync.unlock(); + } + } + public boolean set(int option, int optval) { if (option == ZMQ.ZMQ_MAX_SOCKETS && optval >= 1) { @@ -227,8 +293,7 @@ public boolean set(int option, int optval) optSync.unlock(); } } - else - if (option == ZMQ.ZMQ_IO_THREADS && optval >= 0) { + else if (option == ZMQ.ZMQ_IO_THREADS && optval >= 0) { optSync.lock(); try { ioThreadCount = optval; @@ -237,8 +302,7 @@ public boolean set(int option, int optval) optSync.unlock(); } } - else - if (option == ZMQ.ZMQ_BLOCKY && optval >= 0) { + else if (option == ZMQ.ZMQ_BLOCKY && optval >= 0) { optSync.lock(); try { blocky = (optval != 0); @@ -247,6 +311,15 @@ public boolean set(int option, int optval) optSync.unlock(); } } + else if (option == ZMQ.ZMQ_IPV6 && optval >= 0) { + optSync.lock(); + try { + ipv6 = (optval != 0); + } + finally { + optSync.unlock(); + } + } else { return false; } @@ -265,6 +338,9 @@ else if (option == ZMQ.ZMQ_IO_THREADS) { else if (option == ZMQ.ZMQ_BLOCKY) { rc = blocky ? 1 : 0; } + else if (option == ZMQ.ZMQ_IPV6) { + rc = ipv6 ? 1 : 0; + } else { throw new IllegalArgumentException("option = " + option); } @@ -297,7 +373,7 @@ public SocketBase createSocket(int type) int sid = maxSocketId.incrementAndGet(); // Create the socket and register its mailbox. - s = SocketBase.create(type, this, slot, sid); + s = Sockets.create(type, this, slot, sid); if (s == null) { emptySlots.addLast(slot); return null; @@ -312,7 +388,52 @@ public SocketBase createSocket(int type) return s; } - public void destroySocket(SocketBase socket) + private void initSlots() + { + slotSync.lock(); + try { + // Initialize the array of mailboxes. Additional two slots are for + // zmq_term thread and reaper thread. + int ios; + optSync.lock(); + try { + ios = ioThreadCount; + slotCount = maxSockets + ioThreadCount + 2; + } + finally { + optSync.unlock(); + } + slots = new Mailbox[slotCount]; + + // Initialize the infrastructure for zmq_term thread. + slots[TERM_TID] = termMailbox; + + // Create the reaper thread. + reaper = new Reaper(this, REAPER_TID); + slots[REAPER_TID] = reaper.getMailbox(); + reaper.start(); + + // Create I/O thread objects and launch them. + for (int i = 2; i != ios + 2; i++) { + IOThread ioThread = new IOThread(this, i); + //alloc_assert (io_thread); + ioThreads.add(ioThread); + slots[i] = ioThread.getMailbox(); + ioThread.start(); + } + + // In the unused part of the slot array, create a list of empty slots. + for (int i = slotCount - 1; i >= ios + 2; i--) { + emptySlots.add(i); + slots[i] = null; + } + } + finally { + slotSync.unlock(); + } + } + + void destroySocket(SocketBase socket) { slotSync.lock(); @@ -346,10 +467,24 @@ public Selector createSelector() return selector; } catch (IOException e) { - throw new RuntimeException(e); + throw new ZError.IOException(e); } } + public boolean closeSelector(Selector selector) + { + boolean rc = selectors.remove(selector); + if (rc) { + try { + selector.close(); + } + catch (IOException e) { + throw new ZError.IOException(e); + } + } + return rc; + } + // Returns reaper thread object. ZObject getReaper() { @@ -359,6 +494,7 @@ ZObject getReaper() // Send command to the destination thread. void sendCommand(int tid, final Command command) { + // System.out.println(Thread.currentThread().getName() + ": Sending command " + command); slots[tid].send(command); } @@ -405,6 +541,23 @@ boolean registerEndpoint(String addr, Endpoint endpoint) return true; } + boolean unregisterEndpoint(String addr, SocketBase socket) + { + endpointsSync.lock(); + + try { + Endpoint endpoint = endpoints.get(addr); + if (endpoint != null && socket == endpoint.socket) { + endpoints.remove(addr); + return true; + } + } + finally { + endpointsSync.unlock(); + } + return false; + } + void unregisterEndpoints(SocketBase socket) { endpointsSync.lock(); @@ -446,52 +599,96 @@ Endpoint findEndpoint(String addr) return endpoint; } - private void initSlots() + void pendConnection(String addr, Endpoint endpoint, Pipe[] pipes) { - slotSync.lock(); + PendingConnection pendingConnection = new PendingConnection(endpoint, pipes[0], pipes[1]); + endpointsSync.lock(); try { - // Initialize the array of mailboxes. Additional two slots are for - // zmq_term thread and reaper thread. - int slotCount; - int ios; - optSync.lock(); - try { - ios = ioThreadCount; - slotCount = maxSockets + ioThreadCount + 2; + Endpoint existing = endpoints.get(addr); + if (existing == null) { + // Still no bind. + endpoint.socket.incSeqnum(); + pendingConnections.put(addr, pendingConnection); } - finally { - optSync.unlock(); + else { + // Bind has happened in the mean time, connect directly + connectInprocSockets(existing.socket, existing.options, pendingConnection, Side.CONNECT); } - slots = new Mailbox[slotCount]; - //alloc_assert (slots); + } + finally { + endpointsSync.unlock(); + } + } - // Initialize the infrastructure for zmq_term thread. - slots[TERM_TID] = termMailbox; + void connectPending(String addr, SocketBase bindSocket) + { + endpointsSync.lock(); + try { + PendingConnection pending = pendingConnections.remove(addr); + if (pending != null) { + connectInprocSockets(bindSocket, endpoints.get(addr).options, pending, Side.BIND); + } + } + finally { + endpointsSync.unlock(); + } + } - // Create the reaper thread. - reaper = new Reaper(this, REAPER_TID); - //alloc_assert (reaper); - slots[REAPER_TID] = reaper.getMailbox(); - reaper.start(); + private void connectInprocSockets(SocketBase bindSocket, Options bindOptions, PendingConnection pendingConnection, + Side side) + { + bindSocket.incSeqnum(); - // Create I/O thread objects and launch them. - for (int i = 2; i != ios + 2; i++) { - IOThread ioThread = new IOThread(this, i); - //alloc_assert (io_thread); - ioThreads.add(ioThread); - slots[i] = ioThread.getMailbox(); - ioThread.start(); - } + pendingConnection.bindPipe.setTid(bindSocket.getTid()); + if (!bindOptions.recvIdentity) { + Msg msg = pendingConnection.bindPipe.read(); + assert (msg != null); + } - // In the unused part of the slot array, create a list of empty slots. - for (int i = (int) slotCount - 1; - i >= (int) ios + 2; i--) { - emptySlots.add(i); - slots[i] = null; - } + int sndhwm = 0; + if (pendingConnection.endpoint.options.sendHwm != 0 && bindOptions.recvHwm != 0) { + sndhwm = pendingConnection.endpoint.options.sendHwm + bindOptions.recvHwm; } - finally { - slotSync.unlock(); + int rcvhwm = 0; + if (pendingConnection.endpoint.options.recvHwm != 0 && bindOptions.sendHwm != 0) { + rcvhwm = pendingConnection.endpoint.options.recvHwm + bindOptions.sendHwm; + } + boolean conflate = pendingConnection.endpoint.options.conflate + && (pendingConnection.endpoint.options.type == ZMQ.ZMQ_DEALER + || pendingConnection.endpoint.options.type == ZMQ.ZMQ_PULL + || pendingConnection.endpoint.options.type == ZMQ.ZMQ_PUSH + || pendingConnection.endpoint.options.type == ZMQ.ZMQ_PUB + || pendingConnection.endpoint.options.type == ZMQ.ZMQ_SUB); + int[] hwms = { conflate ? -1 : sndhwm, conflate ? -1 : rcvhwm }; + pendingConnection.connectPipe.setHwms(hwms[1], hwms[0]); + pendingConnection.bindPipe.setHwms(hwms[0], hwms[1]); + + if (side == Side.BIND) { + Command cmd = new Command(null, Command.Type.BIND, pendingConnection.bindPipe); + bindSocket.processCommand(cmd); + bindSocket.sendInprocConnected(pendingConnection.endpoint.socket); } + else { + pendingConnection.connectPipe.sendBind(bindSocket, pendingConnection.bindPipe, false); + } + + // When a ctx is terminated all pending inproc connection will be + // connected, but the socket will already be closed and the pipe will be + // in waiting_for_delimiter state, which means no more writes can be done + // and the identity write fails and causes an assert. Check if the socket + // is open before sending. + if (pendingConnection.endpoint.options.recvIdentity && pendingConnection.endpoint.socket.checkTag()) { + Msg id = new Msg(bindOptions.identitySize); + id.put(bindOptions.identity, 0, bindOptions.identitySize); + id.setFlags(Msg.IDENTITY); + boolean written = pendingConnection.bindPipe.write(id); + assert (written); + pendingConnection.bindPipe.flush(); + } + } + + public Errno errno() + { + return errno; } } diff --git a/src/main/java/zmq/Dealer.java b/src/main/java/zmq/Dealer.java deleted file mode 100644 index c833a50d2..000000000 --- a/src/main/java/zmq/Dealer.java +++ /dev/null @@ -1,129 +0,0 @@ -package zmq; - -public class Dealer extends SocketBase -{ - public static class DealerSession extends SessionBase - { - public DealerSession(IOThread ioThread, boolean connect, - SocketBase socket, final Options options, - final Address addr) - { - super(ioThread, connect, socket, options, addr); - } - } - - // Messages are fair-queued from inbound pipes. And load-balanced to - // the outbound pipes. - private final FQ fq; - private final LB lb; - - // Have we prefetched a message. - private boolean prefetched; - - private Msg prefetchedMsg; - - // Holds the prefetched message. - public Dealer(Ctx parent, int tid, int sid) - { - super(parent, tid, sid); - - prefetched = false; - options.type = ZMQ.ZMQ_DEALER; - - fq = new FQ(); - lb = new LB(); - // TODO: Uncomment the following line when DEALER will become true DEALER - // rather than generic dealer socket. - // If the socket is closing we can drop all the outbound requests. There'll - // be noone to receive the replies anyway. - // options.delayOnClose = false; - - options.recvIdentity = true; - } - - @Override - protected void xattachPipe(Pipe pipe, boolean icanhasall) - { - assert (pipe != null); - fq.attach(pipe); - lb.attach(pipe); - } - - @Override - protected boolean xsend(Msg msg) - { - return lb.send(msg, errno); - } - - @Override - protected Msg xrecv() - { - return xxrecv(); - } - - private Msg xxrecv() - { - Msg msg = null; - // If there is a prefetched message, return it. - if (prefetched) { - msg = prefetchedMsg; - prefetched = false; - prefetchedMsg = null; - return msg; - } - - // DEALER socket doesn't use identities. We can safely drop it and - while (true) { - msg = fq.recv(errno); - if (msg == null) { - return null; - } - if ((msg.flags() & Msg.IDENTITY) == 0) { - break; - } - } - return msg; - } - - @Override - protected boolean xhasIn() - { - // We may already have a message pre-fetched. - if (prefetched) { - return true; - } - - // Try to read the next message to the pre-fetch buffer. - prefetchedMsg = xxrecv(); - if (prefetchedMsg == null) { - return false; - } - prefetched = true; - return true; - } - - @Override - protected boolean xhasOut() - { - return lb.hasOut(); - } - - @Override - protected void xreadActivated(Pipe pipe) - { - fq.activated(pipe); - } - - @Override - protected void xwriteActivated(Pipe pipe) - { - lb.activated(pipe); - } - - @Override - protected void xpipeTerminated(Pipe pipe) - { - fq.terminated(pipe); - lb.terminated(pipe); - } -} diff --git a/src/main/java/zmq/Decoder.java b/src/main/java/zmq/Decoder.java deleted file mode 100644 index 867e681eb..000000000 --- a/src/main/java/zmq/Decoder.java +++ /dev/null @@ -1,180 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -// Helper base class for decoders that know the amount of data to read -// in advance at any moment. Knowing the amount in advance is a property -// of the protocol used. 0MQ framing protocol is based size-prefixed -// paradigm, which qualifies it to be parsed by this class. -// On the other hand, XML-based transports (like XMPP or SOAP) don't allow -// for knowing the size of data to read in advance and should use different -// decoding algorithms. -// -// This class implements the state machine that parses the incoming buffer. -// Derived class should implement individual state machine actions. - -public class Decoder extends DecoderBase -{ - private static final int ONE_BYTE_SIZE_READY = 0; - private static final int EIGHT_BYTE_SIZE_READY = 1; - private static final int FLAGS_READY = 2; - private static final int MESSAGE_READY = 3; - - private final byte[] tmpbuf; - private final ByteBuffer tmpbufWrap; - private Msg inProgress; - private final long maxmsgsize; - private IMsgSink msgSink; - - public Decoder(int bufsize, long maxmsgsize) - { - super(bufsize); - this.maxmsgsize = maxmsgsize; - tmpbuf = new byte[8]; - tmpbufWrap = ByteBuffer.wrap(tmpbuf); - tmpbufWrap.limit(1); - - // At the beginning, read one byte and go to oneByteSizeReady state. - nextStep(tmpbufWrap, ONE_BYTE_SIZE_READY); - } - - // Set the receiver of decoded messages. - @Override - public void setMsgSink(IMsgSink msgSink) - { - this.msgSink = msgSink; - } - - @Override - protected boolean next() - { - switch(state()) { - case ONE_BYTE_SIZE_READY: - return oneByteSizeReady(); - case EIGHT_BYTE_SIZE_READY: - return eightByteSizeReady(); - case FLAGS_READY: - return flagsReady(); - case MESSAGE_READY: - return messageReady(); - default: - return false; - } - } - - private boolean oneByteSizeReady() - { - // First byte of size is read. If it is 0xff(-1 for java byte) read 8-byte size. - // Otherwise allocate the buffer for message data and read the - // message data into it. - tmpbufWrap.position(0); - byte first = tmpbuf[0]; - if (first == -1) { - tmpbufWrap.limit(8); - nextStep(tmpbufWrap, EIGHT_BYTE_SIZE_READY); - } - else { - // There has to be at least one byte (the flags) in the message). - if (first == 0) { - decodingError(); - return false; - } - - int size = (int) first; - if (size < 0) { - size = (0xFF) & first; - } - - // inProgress is initialised at this point so in theory we should - // close it before calling msgInitWithSize, however, it's a 0-byte - // message and thus we can treat it as uninitialised... - if (maxmsgsize >= 0 && (long) (size - 1) > maxmsgsize) { - decodingError(); - return false; - - } - else { - inProgress = getMsgAllocator().allocate(size - 1); - } - - tmpbufWrap.limit(1); - nextStep(tmpbufWrap, FLAGS_READY); - } - return true; - - } - - private boolean eightByteSizeReady() - { - // 8-byte payload length is read. Allocate the buffer - // for message body and read the message data into it. - tmpbufWrap.position(0); - tmpbufWrap.limit(8); - final long payloadLength = tmpbufWrap.getLong(0); - - // There has to be at least one byte (the flags) in the message). - if (payloadLength <= 0) { - decodingError(); - return false; - } - - // Message size must not exceed the maximum allowed size. - if (maxmsgsize >= 0 && payloadLength - 1 > maxmsgsize) { - decodingError(); - return false; - } - - // Message size must fit within range of size_t data type. - if (payloadLength - 1 > Integer.MAX_VALUE) { - decodingError(); - return false; - } - - final int msgSize = (int) (payloadLength - 1); - // inProgress is initialized at this point so in theory we should - // close it before calling init_size, however, it's a 0-byte - // message and thus we can treat it as uninitialized... - inProgress = getMsgAllocator().allocate(msgSize); - - tmpbufWrap.limit(1); - nextStep(tmpbufWrap, FLAGS_READY); - - return true; - } - - private boolean flagsReady() - { - // Store the flags from the wire into the message structure. - - int first = tmpbuf[0]; - - inProgress.setFlags(first & Msg.MORE); - - nextStep(inProgress, - MESSAGE_READY); - - return true; - - } - - private boolean messageReady() - { - // Message is completely read. Push it further and start reading - // new message. (inProgress is a 0-byte message after this point.) - - if (msgSink == null) { - return false; - } - - int rc = msgSink.pushMsg(inProgress); - if (rc != 0) { - return false; - } - - tmpbufWrap.position(0); - tmpbufWrap.limit(1); - nextStep(tmpbufWrap, ONE_BYTE_SIZE_READY); - - return true; - } -} diff --git a/src/main/java/zmq/DecoderBase.java b/src/main/java/zmq/DecoderBase.java deleted file mode 100644 index b1d5a4cd9..000000000 --- a/src/main/java/zmq/DecoderBase.java +++ /dev/null @@ -1,184 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -// Helper base class for decoders that know the amount of data to read -// in advance at any moment. Knowing the amount in advance is a property -// of the protocol used. 0MQ framing protocol is based size-prefixed -// paradigm, which qualifies it to be parsed by this class. -// On the other hand, XML-based transports (like XMPP or SOAP) don't allow -// for knowing the size of data to read in advance and should use different -// decoding algorithms. -// -// This class implements the state machine that parses the incoming buffer. -// Derived class should implement individual state machine actions. - -public abstract class DecoderBase implements IDecoder -{ - // Where to store the read data. - private ByteBuffer readBuf; - private MsgAllocator msgAllocator = new MsgAllocatorHeap(); - - // The buffer for data to decode. - private int bufsize; - private ByteBuffer buf; - - private int state; - - boolean zeroCopy; - - public DecoderBase(int bufsize) - { - state = -1; - this.bufsize = bufsize; - if (bufsize > 0) { - buf = ByteBuffer.allocateDirect(bufsize); - } - readBuf = null; - zeroCopy = false; - } - - // Returns a buffer to be filled with binary data. - public ByteBuffer getBuffer() - { - // If we are expected to read large message, we'll opt for zero- - // copy, i.e. we'll ask caller to fill the data directly to the - // message. Note that subsequent read(s) are non-blocking, thus - // each single read reads at most SO_RCVBUF bytes at once not - // depending on how large is the chunk returned from here. - // As a consequence, large messages being received won't block - // other engines running in the same I/O thread for excessive - // amounts of time. - - if (readBuf.remaining() >= bufsize) { - zeroCopy = true; - return readBuf.duplicate(); - } - else { - zeroCopy = false; - buf.clear(); - return buf; - } - } - - // Processes the data in the buffer previously allocated using - // get_buffer function. size_ argument specifies nemuber of bytes - // actually filled into the buffer. Function returns number of - // bytes actually processed. - public int processBuffer(ByteBuffer buf, int size) - { - // Check if we had an error in previous attempt. - if (state() < 0) { - return -1; - } - - // In case of zero-copy simply adjust the pointers, no copying - // is required. Also, run the state machine in case all the data - // were processed. - if (zeroCopy) { - readBuf.position(readBuf.position() + size); - - while (readBuf.remaining() == 0) { - if (!next()) { - if (state() < 0) { - return -1; - } - return size; - } - } - return size; - } - - int pos = 0; - while (true) { - // Try to get more space in the message to fill in. - // If none is available, return. - while (readBuf.remaining() == 0) { - if (!next()) { - if (state() < 0) { - return -1; - } - - return pos; - } - } - - // If there are no more data in the buffer, return. - if (pos == size) { - return pos; - } - - // Copy the data from buffer to the message. - int toCopy = Math.min(readBuf.remaining(), size - pos); - int limit = buf.limit(); - buf.limit(buf.position() + toCopy); - readBuf.put(buf); - buf.limit(limit); - pos += toCopy; - } - } - - protected void nextStep(Msg msg, int state) - { - nextStep(msg.buf(), state); - } - - protected void nextStep(byte[] buf, int toRead, int state) - { - readBuf = ByteBuffer.wrap(buf); - readBuf.limit(toRead); - this.state = state; - } - - protected void nextStep(ByteBuffer buf, int state) - { - readBuf = buf; - this.state = state; - } - - protected int state() - { - return state; - } - - protected void state(int state) - { - this.state = state; - } - - protected void decodingError() - { - state(-1); - } - - // Returns true if the decoder has been fed all required data - // but cannot proceed with the next decoding step. - // False is returned if the decoder has encountered an error. - @Override - public boolean stalled() - { - // Check whether there was decoding error. - if (!next()) { - return false; - } - - while (readBuf.remaining() == 0) { - if (!next()) { - return next(); - } - } - return false; - } - - public MsgAllocator getMsgAllocator() - { - return msgAllocator; - } - - public void setMsgAllocator(MsgAllocator msgAllocator) - { - this.msgAllocator = msgAllocator; - } - - protected abstract boolean next(); -} diff --git a/src/main/java/zmq/Encoder.java b/src/main/java/zmq/Encoder.java deleted file mode 100644 index 0542421e9..000000000 --- a/src/main/java/zmq/Encoder.java +++ /dev/null @@ -1,96 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -public class Encoder extends EncoderBase -{ - private static final int SIZE_READY = 0; - private static final int MESSAGE_READY = 1; - - private Msg inProgress; - private final byte[] tmpbuf; - private final ByteBuffer tmpbufWrap; - private IMsgSource msgSource; - - public Encoder(int bufsize) - { - super(bufsize); - tmpbuf = new byte[10]; - tmpbufWrap = ByteBuffer.wrap(tmpbuf); - // Write 0 bytes to the batch and go to messageReady state. - nextStep((byte[]) null, 0, MESSAGE_READY, true); - } - - @Override - public void setMsgSource(IMsgSource msgSource) - { - this.msgSource = msgSource; - } - - @Override - protected boolean next() - { - switch(state()) { - case SIZE_READY: - return sizeReady(); - case MESSAGE_READY: - return messageReady(); - default: - return false; - } - } - - private boolean sizeReady() - { - // Write message body into the buffer. - nextStep(inProgress.buf(), - MESSAGE_READY, !inProgress.hasMore()); - return true; - } - - private boolean messageReady() - { - // Destroy content of the old message. - //inProgress.close (); - - // Read new message. If there is none, return false. - // Note that new state is set only if write is successful. That way - // unsuccessful write will cause retry on the next state machine - // invocation. - - if (msgSource == null) { - return false; - } - - inProgress = msgSource.pullMsg(); - if (inProgress == null) { - return false; - } - - // Get the message size. - int size = inProgress.size(); - - // Account for the 'flags' byte. - size++; - - // For messages less than 255 bytes long, write one byte of message size. - // For longer messages write 0xff escape character followed by 8-byte - // message size. In both cases 'flags' field follows. - tmpbufWrap.position(0); - if (size < 255) { - tmpbufWrap.limit(2); - tmpbuf[0] = (byte) size; - tmpbuf[1] = (byte) (inProgress.flags() & Msg.MORE); - nextStep(tmpbufWrap, SIZE_READY, false); - } - else { - tmpbufWrap.limit(10); - tmpbuf[0] = (byte) 0xff; - tmpbufWrap.putLong(1, size); - tmpbuf[9] = (byte) (inProgress.flags() & Msg.MORE); - nextStep(tmpbufWrap, SIZE_READY, false); - } - - return true; - } -} diff --git a/src/main/java/zmq/EncoderBase.java b/src/main/java/zmq/EncoderBase.java deleted file mode 100644 index c4971f5b9..000000000 --- a/src/main/java/zmq/EncoderBase.java +++ /dev/null @@ -1,190 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; - -public abstract class EncoderBase implements IEncoder -{ - // Where to get the data to write from. - private ByteBuffer writeBuf; - private FileChannel writeChannel; - private int writePos; - - // Next step. If set to -1, it means that associated data stream - // is dead. - private int next; - - // If true, first byte of the message is being written. - @SuppressWarnings("unused") - private boolean beginning; - - // How much data to write before next step should be executed. - private int toWrite; - - // The buffer for encoded data. - private ByteBuffer buffer; - - private int bufferSize; - - private boolean error; - - protected EncoderBase(int bufferSize) - { - this.bufferSize = bufferSize; - buffer = ByteBuffer.allocateDirect(bufferSize); - error = false; - } - - // The function returns a batch of binary data. The data - // are filled to a supplied buffer. If no buffer is supplied (data_ - // points to NULL) decoder object will provide buffer of its own. - - @Override - public Transfer getData(ByteBuffer buffer) - { - if (buffer == null) { - buffer = this.buffer; - } - - buffer.clear(); - - while (buffer.hasRemaining()) { - // If there are no more data to return, run the state machine. - // If there are still no data, return what we already have - // in the buffer. - if (toWrite == 0) { - // If we are to encode the beginning of a new message, - // adjust the message offset. - - if (!next()) { - break; - } - } - - // If there is file channel to send, - // send current buffer and the channel together - - if (writeChannel != null) { - buffer.flip(); - Transfer t = new Transfer.FileChannelTransfer(buffer, writeChannel, - (long) writePos, (long) toWrite); - writePos = 0; - toWrite = 0; - - return t; - } - // If there are no data in the buffer yet and we are able to - // fill whole buffer in a single go, let's use zero-copy. - // There's no disadvantage to it as we cannot stuck multiple - // messages into the buffer anyway. Note that subsequent - // write(s) are non-blocking, thus each single write writes - // at most SO_SNDBUF bytes at once not depending on how large - // is the chunk returned from here. - // As a consequence, large messages being sent won't block - // other engines running in the same I/O thread for excessive - // amounts of time. - if (this.buffer.position() == 0 && toWrite >= bufferSize) { - Transfer t = new Transfer.ByteBufferTransfer(writeBuf); - writePos = 0; - toWrite = 0; - - return t; - } - - // Copy data to the buffer. If the buffer is full, return. - int remaining = buffer.remaining(); - if (toWrite <= remaining) { - buffer.put(writeBuf); - writePos = 0; - toWrite = 0; - } - else { - writeBuf.limit(writePos + remaining); - buffer.put(writeBuf); - writePos += remaining; - toWrite -= remaining; - writeBuf.limit(writePos + toWrite); - } - } - - buffer.flip(); - return new Transfer.ByteBufferTransfer(buffer); - } - - @Override - public boolean hasData() - { - return toWrite > 0; - } - - protected int state() - { - return next; - } - - protected void state(int state) - { - next = state; - } - - protected void encodingError() - { - error = true; - } - - public final boolean isError() - { - return error; - } - - protected abstract boolean next(); - - protected void nextStep(Msg msg, int state, boolean beginning) - { - if (msg == null) { - nextStep(null, 0, state, beginning); - } - else { - nextStep(msg.buf(), state, beginning); - } - } - - protected void nextStep(byte[] buf, int toWrite, - int next, boolean beginning) - { - if (buf != null) { - writeBuf = ByteBuffer.wrap(buf); - writeBuf.limit(toWrite); - } - else { - writeBuf = null; - } - writeChannel = null; - writePos = 0; - this.toWrite = toWrite; - this.next = next; - this.beginning = beginning; - } - - protected void nextStep(ByteBuffer buf, - int next, boolean beginning) - { - writeBuf = buf; - writeChannel = null; - writePos = buf.position(); - this.toWrite = buf.remaining(); - this.next = next; - this.beginning = beginning; - } - - protected void nextStep(FileChannel ch, long pos, long toWrite, - int next, boolean beginning) - { - writeBuf = null; - writeChannel = ch; - writePos = (int) pos; - this.toWrite = (int) toWrite; - this.next = next; - this.beginning = beginning; - } -} diff --git a/src/main/java/zmq/IDecoder.java b/src/main/java/zmq/IDecoder.java deleted file mode 100644 index 5d22764dd..000000000 --- a/src/main/java/zmq/IDecoder.java +++ /dev/null @@ -1,14 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -public interface IDecoder -{ - public void setMsgSink(IMsgSink msgSink); - - public ByteBuffer getBuffer(); - - public int processBuffer(ByteBuffer buffer, int size); - - public boolean stalled(); -} diff --git a/src/main/java/zmq/IEncoder.java b/src/main/java/zmq/IEncoder.java deleted file mode 100644 index 1633aada6..000000000 --- a/src/main/java/zmq/IEncoder.java +++ /dev/null @@ -1,16 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -public interface IEncoder -{ - // Set message producer. - public void setMsgSource(IMsgSource msgSource); - - // The function returns a batch of binary data. The data - // are filled to a supplied buffer. If no buffer is supplied (data_ - // is null) encoder will provide buffer of its own. - public Transfer getData(ByteBuffer buffer); - - public boolean hasData(); -} diff --git a/src/main/java/zmq/IMsgSink.java b/src/main/java/zmq/IMsgSink.java deleted file mode 100644 index e8fe305a7..000000000 --- a/src/main/java/zmq/IMsgSink.java +++ /dev/null @@ -1,8 +0,0 @@ -package zmq; - -public interface IMsgSink -{ - // Delivers a message. Returns true if successful; false otherwise. - // The function takes ownership of the passed message. - public int pushMsg(Msg msg); -} diff --git a/src/main/java/zmq/IMsgSource.java b/src/main/java/zmq/IMsgSource.java deleted file mode 100644 index ef47b8971..000000000 --- a/src/main/java/zmq/IMsgSource.java +++ /dev/null @@ -1,7 +0,0 @@ -package zmq; - -public interface IMsgSource -{ - // Fetch a message. Returns a Msg instance if successful; null otherwise. - public Msg pullMsg(); -} diff --git a/src/main/java/zmq/IpcAddress.java b/src/main/java/zmq/IpcAddress.java deleted file mode 100644 index ff6beaced..000000000 --- a/src/main/java/zmq/IpcAddress.java +++ /dev/null @@ -1,48 +0,0 @@ -package zmq; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.UnknownHostException; - -public class IpcAddress implements Address.IZAddress -{ - private String name; - private InetSocketAddress address; - - @Override - public String toString() - { - if (name == null) { - return ""; - } - - return "ipc://" + name; - } - - @Override - public void resolve(String name, boolean ip4only) - { - this.name = name; - - int hash = name.hashCode(); - if (hash < 0) { - hash = -hash; - } - hash = hash % 55536; - hash += 10000; - - try { - address = new InetSocketAddress(InetAddress.getByName(null), hash); - } - catch (UnknownHostException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public SocketAddress address() - { - return address; - } -} diff --git a/src/main/java/zmq/IpcConnecter.java b/src/main/java/zmq/IpcConnecter.java deleted file mode 100644 index 8db80c570..000000000 --- a/src/main/java/zmq/IpcConnecter.java +++ /dev/null @@ -1,11 +0,0 @@ -package zmq; - -public class IpcConnecter extends TcpConnecter -{ - public IpcConnecter(IOThread ioThread, - SessionBase session, final Options options, - final Address addr, boolean wait) - { - super(ioThread, session, options, addr, wait); - } -} diff --git a/src/main/java/zmq/Mailbox.java b/src/main/java/zmq/Mailbox.java index fba018e8c..373554ed2 100644 --- a/src/main/java/zmq/Mailbox.java +++ b/src/main/java/zmq/Mailbox.java @@ -6,8 +6,10 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -public class Mailbox - implements Closeable +import zmq.pipe.YPipe; +import zmq.util.Errno; + +public final class Mailbox implements Closeable { // The pipe to store actual commands. private final YPipe cpipe; @@ -17,22 +19,25 @@ public class Mailbox // There's only one thread receiving from the mailbox, but there // is arbitrary number of threads sending. Given that ypipe requires - // synchronised access on both of its endpoints, we have to synchronise + // synchronized access on both of its endpoints, we have to synchronize // the sending side. private final Lock sync; - // True if the underlying pipe is active, ie. when we are allowed to + // True if the underlying pipe is active, i.e. when we are allowed to // read commands from it. private boolean active; // mailbox name, for better debugging private final String name; - public Mailbox(String name) + private final Errno errno; + + public Mailbox(Ctx ctx, String name, int tid) { + this.errno = ctx.errno(); cpipe = new YPipe(Config.COMMAND_PIPE_GRANULARITY.getValue()); sync = new ReentrantLock(); - signaler = new Signaler(); + signaler = new Signaler(ctx, tid, errno); // Get the pipe into passive state. That way, if the users starts by // polling on the associated file descriptor it will get woken up when @@ -50,7 +55,7 @@ public SelectableChannel getFd() return signaler.getFd(); } - public void send(final Command cmd) + void send(final Command cmd) { boolean ok = false; sync.lock(); @@ -79,16 +84,19 @@ public Command recv(long timeout) // If there are no more commands available, switch into passive state. active = false; - signaler.recv(); } // Wait for signal from the command sender. boolean rc = signaler.waitEvent(timeout); if (!rc) { + assert (errno.get() == ZError.EAGAIN || errno.get() == ZError.EINTR); return null; } - // We've got the signal. Now we can switch into active state. + // Receive the signal. + signaler.recv(); + + // Switch into active state. active = true; // Get a command. diff --git a/src/main/java/zmq/Msg.java b/src/main/java/zmq/Msg.java index 01cc75c70..f318db18e 100644 --- a/src/main/java/zmq/Msg.java +++ b/src/main/java/zmq/Msg.java @@ -1,25 +1,95 @@ package zmq; +import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.SocketChannel; + +import zmq.io.Metadata; +import zmq.util.Wire; public class Msg { - enum Type { + // dynamic message building used when the size is not known in advance + public static final class Builder extends Msg + { + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + public Builder() + { + super(); + } + + @Override + public int size() + { + return out.size(); + } + + @Override + protected Msg put(int index, byte b) + { + out.write(b); + return this; + } + + @Override + public Msg put(byte[] src, int off, int len) + { + if (src == null) { + return this; + } + out.write(src, off, len); + setWriteIndex(getWriteIndex() + len); + return this; + } + + @Override + public Msg put(ByteBuffer src, int off, int len) + { + if (src == null) { + return this; + } + for (int idx = off; idx < off + len; ++idx) { + out.write(src.get(idx)); + } + setWriteIndex(getWriteIndex() + len); + return this; + } + + @Override + public void setFlags(int flags) + { + super.setFlags(flags); + } + + public Msg build() + { + return new Msg(this, out); + } + } + + enum Type + { DATA, DELIMITER } - public static final int MORE = 1; - public static final int COMMAND = 2; - public static final int IDENTITY = 64; - public static final int SHARED = 128; + public static final int MORE = 1; // Followed by more parts + public static final int COMMAND = 2; // Command frame (see ZMTP spec) + public static final int CREDENTIAL = 32; + public static final int IDENTITY = 64; + public static final int SHARED = 128; + + private Metadata metadata; + private int flags; + private Type type; - private int flags; - private Type type; + // the file descriptor where this message originated, needs to be 64bit due to alignment + private SocketChannel fileDesc; - private int size; - private byte[] data; + private int size; + private byte[] data; private final ByteBuffer buf; // keep track of relative write position private int writeIndex = 0; @@ -28,11 +98,7 @@ enum Type { public Msg() { - this.type = Type.DATA; - this.flags = 0; - this.size = 0; - this.buf = ByteBuffer.wrap(new byte[0]).order(ByteOrder.BIG_ENDIAN); - this.data = buf.array(); + this(0); } public Msg(int capacity) @@ -83,11 +149,18 @@ public Msg(final Msg m) this.size = m.size; this.buf = m.buf != null ? m.buf.duplicate() : null; if (m.data != null) { - this.data = new byte[this.size]; - System.arraycopy(m.data, 0, this.data, 0, m.size); + this.data = new byte[this.size]; + System.arraycopy(m.data, 0, this.data, 0, m.size); } } + public Msg(Msg src, ByteArrayOutputStream out) + { + this(ByteBuffer.wrap(out.toByteArray())); + this.type = src.type; + this.flags = src.flags; + } + public boolean isIdentity() { return (flags & IDENTITY) == IDENTITY; @@ -113,6 +186,16 @@ public boolean hasMore() return (flags & MORE) > 0; } + public boolean isCommand() + { + return (flags & COMMAND) == COMMAND; + } + + public boolean isCredential() + { + return (flags & CREDENTIAL) == CREDENTIAL; + } + public void setFlags(int flags) { this.flags |= flags; @@ -121,6 +204,7 @@ public void setFlags(int flags) public void initDelimiter() { type = Type.DELIMITER; + metadata = null; flags = 0; } @@ -148,6 +232,33 @@ public void resetFlags(int f) flags = flags & ~f; } + public void setFd(SocketChannel fileDesc) + { + this.fileDesc = fileDesc; + } + + // TODO V4 use the source channel + public SocketChannel fd() + { + return fileDesc; + } + + public Metadata getMetadata() + { + return metadata; + } + + public Msg setMetadata(Metadata metadata) + { + this.metadata = metadata; + return this; + } + + public void resetMetadata() + { + setMetadata(null); + } + public byte get() { return get(readIndex++); @@ -163,7 +274,12 @@ public Msg put(byte b) return put(writeIndex++, b); } - public Msg put(int index, byte b) + public Msg put(int b) + { + return put(writeIndex++, (byte) b); + } + + protected Msg put(int index, byte b) { buf.put(index, b); return this; @@ -186,6 +302,19 @@ public Msg put(byte[] src, int off, int len) return this; } + public Msg put(ByteBuffer src, int off, int len) + { + if (src == null) { + return this; + } + int position = src.position(); + int limit = src.limit(); + src.limit(off + len).position(off); + put(src); + src.limit(limit).position(position); + return this; + } + public Msg put(ByteBuffer src) { ByteBuffer dup = buf.duplicate(); @@ -204,7 +333,7 @@ public int getBytes(int index, byte[] dst, int off, int len) dup.put(dst, off, count); } else { - System.arraycopy(data, index, dst, off, count); + System.arraycopy(data, index, dst, off, count); } return count; @@ -225,4 +354,34 @@ public String toString() { return String.format("#zmq.Msg{type=%s, size=%s, flags=%s}", type, size, flags); } + + protected final int getWriteIndex() + { + return writeIndex; + } + + protected final void setWriteIndex(int writeIndex) + { + this.writeIndex = writeIndex; + } + + public long getLong(int offset) + { + return Wire.getUInt64(buf, offset); + } + + public int getInt(int offset) + { + return Wire.getUInt32(buf, offset); + } + + public void transfer(ByteBuffer destination, int srcOffset, int srcLength) + { + int position = buf.position(); + int limit = buf.limit(); + + buf.limit(srcOffset + srcLength).position(srcOffset); + destination.put(buf); + buf.limit(limit).position(position); + } } diff --git a/src/main/java/zmq/MsgAllocator.java b/src/main/java/zmq/MsgAllocator.java deleted file mode 100644 index 14d661759..000000000 --- a/src/main/java/zmq/MsgAllocator.java +++ /dev/null @@ -1,13 +0,0 @@ -package zmq; - -public interface MsgAllocator -{ - /** - * Allocate a Msg based on users needs (e.g., using DirectByteBuffer with additional space in - * front for header information). - * - * @param size The size of the Msg. - * @return Msg The ByteBuffer Msg to meet space requirements - */ - Msg allocate(int size); -} diff --git a/src/main/java/zmq/MsgAllocatorHeap.java b/src/main/java/zmq/MsgAllocatorHeap.java deleted file mode 100644 index 8e206febd..000000000 --- a/src/main/java/zmq/MsgAllocatorHeap.java +++ /dev/null @@ -1,10 +0,0 @@ -package zmq; - -public class MsgAllocatorHeap implements MsgAllocator -{ - @Override - public Msg allocate(int size) - { - return new Msg(size); - } -} diff --git a/src/main/java/zmq/MsgAllocatorOffHeap.java b/src/main/java/zmq/MsgAllocatorOffHeap.java deleted file mode 100644 index d324624e6..000000000 --- a/src/main/java/zmq/MsgAllocatorOffHeap.java +++ /dev/null @@ -1,12 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -public class MsgAllocatorOffHeap implements MsgAllocator -{ - @Override - public Msg allocate(int size) - { - return new Msg(ByteBuffer.allocateDirect(size)); - } -} diff --git a/src/main/java/zmq/MultiMap.java b/src/main/java/zmq/MultiMap.java deleted file mode 100644 index b98004f07..000000000 --- a/src/main/java/zmq/MultiMap.java +++ /dev/null @@ -1,280 +0,0 @@ -package zmq; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -public class MultiMap, V> implements Map -{ - private long id; - private final HashMap values; - private final TreeMap> keys; - - public MultiMap() - { - id = 0; - values = new HashMap(); - keys = new TreeMap>(); - } - - public class MultiMapEntry implements Map.Entry - { - private K key; - private V value; - public MultiMapEntry(K key, V value) - { - this.key = key; - this.value = value; - } - @Override - public K getKey() - { - return key; - } - - @Override - public V getValue() - { - return value; - } - - @Override - public V setValue(V value) - { - V old = this.value; - this.value = value; - return old; - } - - } - - public class MultiMapEntrySet implements Set>, Iterator> - { - private MultiMap map; - private Iterator>> it; - private Iterator iit; - private K key; - private long id; - - public MultiMapEntrySet(MultiMap map) - { - this.map = map; - } - - @Override - public boolean add(Map.Entry arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addAll(Collection> arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean contains(Object arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean containsAll(Collection arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isEmpty() - { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator> iterator() - { - it = map.keys.entrySet().iterator(); - return this; - } - - @Override - public boolean remove(Object arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removeAll(Collection arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean retainAll(Collection arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public int size() - { - throw new UnsupportedOperationException(); - } - - @Override - public Object[] toArray() - { - throw new UnsupportedOperationException(); - } - - @Override - public T[] toArray(T[] arg0) - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasNext() - { - if (iit == null || !iit.hasNext()) { - if (!it.hasNext()) { - return false; - } - - Map.Entry> item = it.next(); - key = item.getKey(); - iit = item.getValue().iterator(); - } - - return true; - } - - @Override - public Map.Entry next() - { - id = iit.next(); - return new MultiMapEntry(key, map.values.get(id)); - } - - @Override - public void remove() - { - iit.remove(); - map.values.remove(id); - if (map.keys.get(key).isEmpty()) { - it.remove(); - } - } - - } - - @Override - public void clear() - { - keys.clear(); - values.clear(); - } - - @Override - public boolean containsKey(Object key) - { - return keys.containsKey(key); - } - - @Override - public boolean containsValue(Object value) - { - return values.containsValue(value); - } - - @Override - public Set> entrySet() - { - return new MultiMapEntrySet(this); - } - - @Override - public V get(Object key) - { - ArrayList l = keys.get(key); - if (l == null) { - return null; - } - return values.get(l.get(0)); - } - - @Override - public boolean isEmpty() - { - return keys.isEmpty(); - } - - @Override - public Set keySet() - { - return keys.keySet(); - } - - @Override - public V put(K key, V value) - { - ArrayList ids = keys.get(key); - if (ids == null) { - ids = new ArrayList(); - ids.add(id); - keys.put(key, ids); - } - else { - ids.add(id); - } - values.put(id, value); - id++; - - return null; - } - - @Override - public void putAll(Map src) - { - for (Entry o : src.entrySet()) { - put(o.getKey(), o.getValue()); - } - } - - @Override - public V remove(Object key) - { - ArrayList l = keys.get(key); - if (l == null) { - return null; - } - V old = values.remove(l.remove(0)); - if (l.isEmpty()) { - keys.remove(key); - } - return old; - } - - @Override - public int size() - { - return values.size(); - } - - @Override - public Collection values() - { - return values.values(); - } -} diff --git a/src/main/java/zmq/Options.java b/src/main/java/zmq/Options.java index 717ee8ba1..d41be31dd 100644 --- a/src/main/java/zmq/Options.java +++ b/src/main/java/zmq/Options.java @@ -3,20 +3,29 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import zmq.TcpAddress.TcpAddressMask; + +import zmq.io.coder.IDecoder; +import zmq.io.coder.IEncoder; +import zmq.io.mechanism.Mechanisms; +import zmq.io.net.ipc.IpcAddress; +import zmq.io.net.tcp.TcpAddress; +import zmq.io.net.tcp.TcpAddress.TcpAddressMask; +import zmq.util.Errno; +import zmq.util.ValueReference; +import zmq.util.Z85; public class Options { // High-water marks for message pipes. - int sendHwm; - int recvHwm; + public int sendHwm; + public int recvHwm; // I/O thread affinity. - long affinity; + public long affinity; // Socket identity - byte identitySize; - byte[] identity; // [256]; + public byte identitySize; + public byte[] identity; // [256]; // Last socket endpoint resolved URI String lastEndpoint; @@ -31,77 +40,117 @@ public class Options int multicastHops; // SO_SNDBUF and SO_RCVBUF to be passed to underlying transport sockets. - int sndbuf; - int rcvbuf; + public int sndbuf; + public int rcvbuf; + + // Type of service (containing DSCP and ECN socket options) + public int tos; // Socket type. - int type; + public int type; // Linger time, in milliseconds. - int linger; + public int linger; // Minimum interval between attempts to reconnect, in milliseconds. // Default 100ms - int reconnectIvl; + public int reconnectIvl; // Maximum interval between attempts to reconnect, in milliseconds. // Default 0 (unused) - int reconnectIvlMax; - - // if the REQ socket has correlation enabled (= is sending request IDs) - int reqCorrelate; - - // if the REQ FSM is not strictly adhered to - int reqRelaxed; + public int reconnectIvlMax; // Maximum backlog for pending connections. - int backlog; + public int backlog; // Maximal size of message to handle. - long maxMsgSize; + public long maxMsgSize; // The timeout for send/recv operations for this socket. int recvTimeout; int sendTimeout; - // If 1, indicates the use of IPv4 sockets only, it will not be - // possible to communicate with IPv6-only hosts. If 0, the socket can - // connect to and accept connections from both IPv4 and IPv6 hosts. - int ipv4only; + // If true, IPv6 is enabled (as well as IPv4) + public boolean ipv6; - // If 1, connecting pipes are not attached immediately, meaning a send() + // If false, connecting pipes are not attached immediately, meaning a send() // on a socket with only connecting pipes would block - int delayAttachOnConnect; - - // If true, session reads all the pending messages from the pipe and - // sends them to the network when socket is closed. - boolean delayOnClose; - - // If true, socket reads all the messages from the pipe and delivers - // them to the user when the peer terminates. - boolean delayOnDisconnect; + public boolean immediate; // If 1, (X)SUB socket should filter the messages. If 0, it should not. - boolean filter; + public boolean filter; // If true, the identity message is forwarded to the socket. - boolean recvIdentity; + public boolean recvIdentity; + + // if true, router socket accepts non-zmq tcp connections + public boolean rawSocket; + + // Addres of SOCKS proxy + public String socksProxyAddress; // TCP keep-alive settings. // Defaults to -1 = do not change socket options - int tcpKeepAlive; - int tcpKeepAliveCnt; - int tcpKeepAliveIdle; - int tcpKeepAliveIntvl; + public int tcpKeepAlive; + public int tcpKeepAliveCnt; + public int tcpKeepAliveIdle; + public int tcpKeepAliveIntvl; // TCP accept() filters //typedef std::vector tcp_accept_filters_t; - final List tcpAcceptFilters; + public final List tcpAcceptFilters = new ArrayList(); + + // IPC accept() filters + final List ipcAcceptFilters = new ArrayList(); + + // Security mechanism for all connections on this socket + public Mechanisms mechanism = Mechanisms.NULL; + + // If peer is acting as server for PLAIN or CURVE mechanisms + public boolean asServer; + + // ZAP authentication domain + public String zapDomain = ""; + + // Security credentials for PLAIN mechanism + public String plainUsername; + public String plainPassword; + + // Security credentials for CURVE mechanism + // Normal base 256 key is 32 bytes + public static final int CURVE_KEYSIZE = 32; + // Key encoded using Z85 is 40 bytes + public static final int CURVE_KEYSIZE_Z85 = 40; + public byte[] curvePublicKey; + public byte[] curveSecretKey; + public byte[] curveServerKey; + + // Principals for GSSAPI mechanism + String gssPrincipal; + String gssServicePrincipal; + // If true, gss encryption will be disabled + boolean gssPlaintext; // ID of the socket. - int socketId; - Class decoder; - Class encoder; - MsgAllocator msgAllocator; + public int socketId; + + // If true, socket conflates outgoing/incoming messages. + // Applicable to dealer, push/pull, pub/sub socket types. + // Cannot receive multi-part messages. + // Ignores hwm + public boolean conflate; + + // If connection handshake is not done after this many milliseconds, + // close socket. Default is 30 secs. 0 means no handshake timeout. + public int handshakeIvl; + + public Class decoder; + public Class encoder; + + // threshold to allocate byte buffers on direct memory instead of heap. + // Set to <= 0 to disable this system. + public int allocationHeapThreshold; + + public final Errno errno = new Errno(); public Options() { @@ -114,96 +163,96 @@ public Options() multicastHops = 1; sndbuf = 0; rcvbuf = 0; + tos = 0; type = -1; linger = -1; reconnectIvl = 100; reconnectIvlMax = 0; - reqCorrelate = 0; - reqRelaxed = 0; backlog = 100; maxMsgSize = -1; recvTimeout = -1; sendTimeout = -1; - ipv4only = 1; - delayAttachOnConnect = 0; - delayOnClose = true; - delayOnDisconnect = true; + ipv6 = false; + immediate = true; filter = false; recvIdentity = false; + rawSocket = false; tcpKeepAlive = -1; tcpKeepAliveCnt = -1; tcpKeepAliveIdle = -1; tcpKeepAliveIntvl = -1; + mechanism = Mechanisms.NULL; + asServer = false; + gssPlaintext = false; socketId = 0; + conflate = false; + handshakeIvl = 30000; + identity = new byte[0]; + identitySize = (byte) identity.length; + + curvePublicKey = new byte[CURVE_KEYSIZE]; + curveSecretKey = new byte[CURVE_KEYSIZE]; + curveServerKey = new byte[CURVE_KEYSIZE]; - identity = null; - tcpAcceptFilters = new ArrayList(); - decoder = null; - encoder = null; - msgAllocator = null; + allocationHeapThreshold = Config.MSG_ALLOCATION_HEAP_THRESHOLD.getValue(); } - @SuppressWarnings("unchecked") - public void setSocketOpt(int option, Object optval) + @SuppressWarnings("deprecation") + public boolean setSocketOpt(int option, Object optval) { + final ValueReference result = new ValueReference<>(false); switch (option) { case ZMQ.ZMQ_SNDHWM: sendHwm = (Integer) optval; if (sendHwm < 0) { throw new IllegalArgumentException("sendHwm " + optval); } - return; + return true; case ZMQ.ZMQ_RCVHWM: recvHwm = (Integer) optval; if (recvHwm < 0) { throw new IllegalArgumentException("recvHwm " + optval); } - return; + return true; case ZMQ.ZMQ_AFFINITY: affinity = (Long) optval; - return; + return true; case ZMQ.ZMQ_IDENTITY: - byte[] val; - - if (optval instanceof String) { - val = ((String) optval).getBytes(ZMQ.CHARSET); - } - else if (optval instanceof byte[]) { - val = (byte[]) optval; - } - else { - throw new IllegalArgumentException("identity " + optval); - } + byte[] val = parseBytes(option, optval); if (val == null || val.length > 255) { throw new IllegalArgumentException("identity must not be null or less than 255 " + optval); } identity = Arrays.copyOf(val, val.length); identitySize = (byte) identity.length; - return; + return true; case ZMQ.ZMQ_RATE: rate = (Integer) optval; - return; + return true; case ZMQ.ZMQ_RECOVERY_IVL: recoveryIvl = (Integer) optval; - return; + return true; case ZMQ.ZMQ_SNDBUF: sndbuf = (Integer) optval; - return; + return true; case ZMQ.ZMQ_RCVBUF: rcvbuf = (Integer) optval; - return; + return true; + + case ZMQ.ZMQ_TOS: + tos = (Integer) optval; + return true; case ZMQ.ZMQ_LINGER: linger = (Integer) optval; - return; + return true; case ZMQ.ZMQ_RECONNECT_IVL: reconnectIvl = (Integer) optval; @@ -212,7 +261,7 @@ else if (optval instanceof byte[]) { throw new IllegalArgumentException("reconnectIvl " + optval); } - return; + return true; case ZMQ.ZMQ_RECONNECT_IVL_MAX: reconnectIvlMax = (Integer) optval; @@ -221,68 +270,64 @@ else if (optval instanceof byte[]) { throw new IllegalArgumentException("reconnectIvlMax " + optval); } - return; - - case ZMQ.ZMQ_REQ_CORRELATE: - reqCorrelate = (Integer) optval; - return; - - case ZMQ.ZMQ_REQ_RELAXED: - reqRelaxed = (Integer) optval; - return; + return true; case ZMQ.ZMQ_BACKLOG: backlog = (Integer) optval; - return; + return true; case ZMQ.ZMQ_MAXMSGSIZE: maxMsgSize = (Long) optval; - return; + return true; case ZMQ.ZMQ_MULTICAST_HOPS: multicastHops = (Integer) optval; - return; + return true; case ZMQ.ZMQ_RCVTIMEO: recvTimeout = (Integer) optval; - return; + return true; case ZMQ.ZMQ_SNDTIMEO: sendTimeout = (Integer) optval; - return; + return true; + /* Deprecated in favor of ZMQ_IPV6 */ case ZMQ.ZMQ_IPV4ONLY: + return setSocketOpt(ZMQ.ZMQ_IPV6, !parseBoolean(option, optval)); - ipv4only = (Integer) optval; - if (ipv4only != 0 && ipv4only != 1) { - throw new IllegalArgumentException("ipv4only only accepts 0 or 1 " + optval); - } - return; + /* To replace the somewhat surprising IPV4ONLY */ + case ZMQ.ZMQ_IPV6: + ipv6 = parseBoolean(option, optval); + return true; - case ZMQ.ZMQ_TCP_KEEPALIVE: + case ZMQ.ZMQ_SOCKS_PROXY: + socksProxyAddress = parseString(option, optval); + return true; - tcpKeepAlive = (Integer) optval; + case ZMQ.ZMQ_TCP_KEEPALIVE: + tcpKeepAlive = ((Number) optval).intValue(); if (tcpKeepAlive != -1 && tcpKeepAlive != 0 && tcpKeepAlive != 1) { throw new IllegalArgumentException("tcpKeepAlive only accepts one of -1,0,1 " + optval); } - return; - - case ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT: - - delayAttachOnConnect = (Integer) optval; - if (delayAttachOnConnect != 0 && delayAttachOnConnect != 1) { - throw new IllegalArgumentException("delayAttachOnConnect only accept 0 or 1 " + optval); - } - return; + return true; case ZMQ.ZMQ_TCP_KEEPALIVE_CNT: case ZMQ.ZMQ_TCP_KEEPALIVE_IDLE: case ZMQ.ZMQ_TCP_KEEPALIVE_INTVL: // not supported - return; + return false; + + case ZMQ.ZMQ_IMMEDIATE: + immediate = parseBoolean(option, optval); + return true; + + case ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT: + immediate = !parseBoolean(option, optval); + return true; case ZMQ.ZMQ_TCP_ACCEPT_FILTER: - String filterStr = (String) optval; + String filterStr = parseString(option, optval); if (filterStr == null) { tcpAcceptFilters.clear(); } @@ -290,87 +335,172 @@ else if (filterStr.length() == 0 || filterStr.length() > 255) { throw new IllegalArgumentException("tcp_accept_filter " + optval); } else { - TcpAddressMask filter = new TcpAddressMask(); - filter.resolve(filterStr, ipv4only == 1); + TcpAddressMask filter = new TcpAddressMask(filterStr, ipv6); tcpAcceptFilters.add(filter); } - return; - - case ZMQ.ZMQ_ENCODER: - if (optval instanceof String) { - try { - encoder = Class.forName((String) optval).asSubclass(EncoderBase.class); - } - catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } + return true; + + case ZMQ.ZMQ_PLAIN_SERVER: + asServer = parseBoolean(option, optval); + mechanism = (asServer ? Mechanisms.PLAIN : Mechanisms.NULL); + return true; + + case ZMQ.ZMQ_PLAIN_USERNAME: + if (optval == null) { + mechanism = Mechanisms.NULL; + asServer = false; + return true; } - else if (optval instanceof Class) { - encoder = (Class) optval; + plainUsername = parseString(option, optval); + asServer = false; + mechanism = Mechanisms.PLAIN; + return true; + + case ZMQ.ZMQ_PLAIN_PASSWORD: + if (optval == null) { + mechanism = Mechanisms.NULL; + asServer = false; + return true; } - else { - throw new IllegalArgumentException("encoder " + optval); + plainPassword = parseString(option, optval); + asServer = false; + mechanism = Mechanisms.PLAIN; + return true; + + case ZMQ.ZMQ_ZAP_DOMAIN: + String domain = parseString(option, optval); + if (domain != null && domain.length() < 256) { + zapDomain = domain; + return true; } - return; + throw new IllegalArgumentException("zap domain length shall be < 256 : " + optval); - case ZMQ.ZMQ_DECODER: - if (optval instanceof String) { - try { - decoder = Class.forName((String) optval).asSubclass(DecoderBase.class); - } - catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } + case ZMQ.ZMQ_CURVE_SERVER: + asServer = parseBoolean(option, optval); + mechanism = (asServer ? Mechanisms.CURVE : Mechanisms.NULL); + return true; + + case ZMQ.ZMQ_CURVE_PUBLICKEY: + curvePublicKey = setCurveKey(option, optval, result); + return result.get(); + + case ZMQ.ZMQ_CURVE_SECRETKEY: + curveSecretKey = setCurveKey(option, optval, result); + return result.get(); + + case ZMQ.ZMQ_CURVE_SERVERKEY: + curveServerKey = setCurveKey(option, optval, result); + if (curveServerKey == null) { + asServer = false; } - else if (optval instanceof Class) { - decoder = (Class) optval; + return result.get(); + + case ZMQ.ZMQ_CONFLATE: + conflate = parseBoolean(option, optval); + return true; + + case ZMQ.ZMQ_GSSAPI_SERVER: + asServer = parseBoolean(option, optval); + mechanism = Mechanisms.GSSAPI; + return true; + + case ZMQ.ZMQ_GSSAPI_PRINCIPAL: + gssPrincipal = parseString(option, optval); + mechanism = Mechanisms.GSSAPI; + return true; + + case ZMQ.ZMQ_GSSAPI_SERVICE_PRINCIPAL: + gssServicePrincipal = parseString(option, optval); + mechanism = Mechanisms.GSSAPI; + return true; + + case ZMQ.ZMQ_GSSAPI_PLAINTEXT: + gssPlaintext = parseBoolean(option, optval); + return true; + + case ZMQ.ZMQ_HANDSHAKE_IVL: + handshakeIvl = (Integer) optval; + if (handshakeIvl < 0) { + throw new IllegalArgumentException("handshakeIvl only accept positive values " + optval); } - else { - throw new IllegalArgumentException("decoder " + optval); + return true; + + case ZMQ.ZMQ_DECODER: + decoder = checkCustomClass(optval, IDecoder.class); + if (decoder == null) { + return false; + } + rawSocket = true; + return true; + + case ZMQ.ZMQ_ENCODER: + encoder = checkCustomClass(optval, IEncoder.class); + if (encoder == null) { + return false; } - return; - case ZMQ.ZMQ_MSG_ALLOCATOR: - if (optval instanceof String) { - try { - Class msgAllocatorClass = Class.forName((String) optval).asSubclass(MsgAllocator.class); - msgAllocator = msgAllocatorClass.newInstance(); - } - catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } - catch (InstantiationException e) { - throw new IllegalArgumentException(e); - } - catch (IllegalAccessException e) { - throw new IllegalArgumentException(e); - } - } - else if (optval instanceof Class) { - try { - Class msgAllocatorClass = (Class) optval; - msgAllocator = msgAllocatorClass.newInstance(); - } - catch (InstantiationException e) { - throw new IllegalArgumentException(e); - } - catch (IllegalAccessException e) { - throw new IllegalArgumentException(e); - } - } - else if (optval instanceof MsgAllocator) { - msgAllocator = (MsgAllocator) optval; - } - else { - throw new IllegalArgumentException("msgAllocator " + optval); - } - return; + rawSocket = true; + return true; + + case ZMQ.ZMQ_MSG_ALLOCATION_HEAP_THRESHOLD: + allocationHeapThreshold = (Integer) optval; + return true; default: throw new IllegalArgumentException("Unknown Option " + option); } } - public Object getsockopt(int option) + private Class checkCustomClass(Object optval, Class type) + { + Class clazz = (Class) optval; + assert (type.isAssignableFrom(clazz)); + Class custom = clazz.asSubclass(type); + try { + assert (custom.getConstructor(int.class, long.class) != null); + return custom; + } + catch (NoSuchMethodException | SecurityException e) { + System.out.println( + "Custom " + clazz + + " has no required constructor (int bufferSize, long maxMsgSize)"); + return null; + } + } + + private byte[] setCurveKey(int option, Object optval, ValueReference result) + { + if (optval == null) { + // TODO V4 setting a curve key as null does change the mechanism type ? + result.set(false); + return null; + } + else { + String val = parseString(option, optval); + byte[] key = null; + int length = val.length(); + if (length == CURVE_KEYSIZE_Z85) { + key = Z85.decode(val); + result.set(true); + errno.set(0); + } + else if (length == CURVE_KEYSIZE) { + key = val.getBytes(ZMQ.CHARSET); + result.set(true); + errno.set(0); + } + else { + result.set(false); + errno.set(ZError.EINVAL); + } + if (key != null) { + mechanism = Mechanisms.CURVE; + } + return key; + } + } + + @SuppressWarnings("deprecation") + public Object getSocketOpt(int option) { switch (option) { case ZMQ.ZMQ_SNDHWM: @@ -392,10 +522,13 @@ public Object getsockopt(int option) return recoveryIvl; case ZMQ.ZMQ_SNDBUF: - return sndbuf; + return sndbuf; case ZMQ.ZMQ_RCVBUF: - return rcvbuf; + return rcvbuf; + + case ZMQ.ZMQ_TOS: + return tos; case ZMQ.ZMQ_TYPE: return type; @@ -409,12 +542,6 @@ public Object getsockopt(int option) case ZMQ.ZMQ_RECONNECT_IVL_MAX: return reconnectIvlMax; - case ZMQ.ZMQ_REQ_CORRELATE: - return reqCorrelate; - - case ZMQ.ZMQ_REQ_RELAXED: - return reqRelaxed; - case ZMQ.ZMQ_BACKLOG: return backlog; @@ -431,13 +558,22 @@ public Object getsockopt(int option) return sendTimeout; case ZMQ.ZMQ_IPV4ONLY: - return ipv4only; + return !ipv6; + + case ZMQ.ZMQ_IPV6: + return ipv6; case ZMQ.ZMQ_TCP_KEEPALIVE: return tcpKeepAlive; + case ZMQ.ZMQ_IMMEDIATE: + return immediate; + case ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT: - return delayAttachOnConnect; + return !immediate; + + case ZMQ.ZMQ_SOCKS_PROXY: + return socksProxyAddress; case ZMQ.ZMQ_TCP_KEEPALIVE_CNT: case ZMQ.ZMQ_TCP_KEEPALIVE_IDLE: @@ -445,11 +581,92 @@ public Object getsockopt(int option) // not supported return 0; + case ZMQ.ZMQ_MECHANISM: + return mechanism; + + case ZMQ.ZMQ_PLAIN_SERVER: + return asServer && mechanism == Mechanisms.PLAIN; + + case ZMQ.ZMQ_PLAIN_USERNAME: + return plainUsername; + + case ZMQ.ZMQ_PLAIN_PASSWORD: + return plainPassword; + + case ZMQ.ZMQ_ZAP_DOMAIN: + return zapDomain; + case ZMQ.ZMQ_LAST_ENDPOINT: return lastEndpoint; + case ZMQ.ZMQ_CURVE_SERVER: + return asServer && mechanism == Mechanisms.CURVE; + + case ZMQ.ZMQ_CURVE_PUBLICKEY: + return curvePublicKey; + + case ZMQ.ZMQ_CURVE_SERVERKEY: + return curveServerKey; + + case ZMQ.ZMQ_CURVE_SECRETKEY: + return curveSecretKey; + + case ZMQ.ZMQ_CONFLATE: + return conflate; + + case ZMQ.ZMQ_GSSAPI_SERVER: + return asServer && mechanism == Mechanisms.GSSAPI; + + case ZMQ.ZMQ_GSSAPI_PRINCIPAL: + return gssPrincipal; + + case ZMQ.ZMQ_GSSAPI_SERVICE_PRINCIPAL: + return gssServicePrincipal; + + case ZMQ.ZMQ_GSSAPI_PLAINTEXT: + return gssPlaintext; + + case ZMQ.ZMQ_HANDSHAKE_IVL: + return handshakeIvl; + + case ZMQ.ZMQ_MSG_ALLOCATION_HEAP_THRESHOLD: + return allocationHeapThreshold; + default: throw new IllegalArgumentException("option=" + option); } } + + public static boolean parseBoolean(int option, Object optval) + { + if (optval instanceof Boolean) { + return (Boolean) optval; + } + else if (optval instanceof Integer) { + return (Integer) optval != 0 ? true : false; + } + throw new IllegalArgumentException(optval + " is neither an integer or a boolean for option " + option); + } + + public static String parseString(int option, Object optval) + { + if (optval instanceof String) { + return (String) optval; + } + else if (optval instanceof byte[]) { + return new String((byte[]) optval, ZMQ.CHARSET); + } + throw new IllegalArgumentException(optval + " is neither a string or an array of bytes for option " + option); + } + + public static byte[] parseBytes(int option, Object optval) + { + if (optval instanceof String) { + return ((String) optval).getBytes(ZMQ.CHARSET); + } + else if (optval instanceof byte[]) { + return (byte[]) optval; + } + throw new IllegalArgumentException(optval + " is neither a string or an array of bytes for option " + option); + } } diff --git a/src/main/java/zmq/Own.java b/src/main/java/zmq/Own.java index 2d4ec6fa5..4a2951a8c 100644 --- a/src/main/java/zmq/Own.java +++ b/src/main/java/zmq/Own.java @@ -4,9 +4,12 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicLong; +import zmq.io.IOThread; +import zmq.util.Errno; + // Base class for objects forming a part of ownership hierarchy. -// It handles initialisation and destruction of such objects. -abstract class Own extends ZObject +// It handles initialization and destruction of such objects. +public abstract class Own extends ZObject { protected final Options options; @@ -32,12 +35,14 @@ abstract class Own extends ZObject // Number of events we have to get before we can destroy the object. private int termAcks; + public final Errno errno; + // Note that the owner is unspecified in the constructor. // It'll be supplied later on when the object is plugged in. // The object is not living within an I/O thread. It has it's own // thread outside of 0MQ infrastructure. - public Own(Ctx parent, int tid) + protected Own(Ctx parent, int tid) { super(parent, tid); terminating = false; @@ -47,11 +52,12 @@ public Own(Ctx parent, int tid) termAcks = 0; options = new Options(); - owned = new HashSet(); + errno = options.errno; + owned = new HashSet<>(); } // The object is living within I/O thread. - public Own(IOThread ioThread, Options options) + protected Own(IOThread ioThread, Options options) { super(ioThread); this.options = options; @@ -60,11 +66,12 @@ public Own(IOThread ioThread, Options options) processedSeqnum = 0; owner = null; termAcks = 0; + errno = options.errno; - owned = new HashSet(); + owned = new HashSet<>(); } - public abstract void destroy(); + protected abstract void destroy(); // A place to hook in when phyicallal destruction of the object // is to be delayed. @@ -82,13 +89,14 @@ private void setOwner(Own owner) // When another owned object wants to send command to this object // it calls this function to let it know it should not shut down // before the command is delivered. - void incSeqnum() + protected void incSeqnum() { // This function may be called from a different thread! sendSeqnum.incrementAndGet(); } - protected void processSeqnum() + @Override + protected final void processSeqnum() { // Catch up with counter of processed commands. processedSeqnum++; @@ -98,7 +106,7 @@ protected void processSeqnum() } // Launch the supplied object and become its owner. - protected void launchChild(Own object) + protected final void launchChild(Own object) { // Specify the owner of the object. object.setOwner(this); @@ -111,13 +119,13 @@ protected void launchChild(Own object) } // Terminate owned object - protected void termChild(Own object) + protected final void termChild(Own object) { processTermReq(object); } @Override - protected void processTermReq(Own object) + protected final void processTermReq(Own object) { // When shutting down we can ignore termination requests from owned // objects. The termination request was already sent to the object. @@ -129,11 +137,10 @@ protected void processTermReq(Own object) // If not found, we assume that termination request was already sent to // the object so we can safely ignore the request. - if (!owned.contains(object)) { + if (!owned.remove(object)) { return; } - owned.remove(object); registerTermAcks(1); // Note that this object is the root of the (partial shutdown) thus, its @@ -141,7 +148,8 @@ protected void processTermReq(Own object) sendTerm(object, options.linger); } - protected void processOwn(Own object) + @Override + protected final void processOwn(Own object) { // If the object is already being shut down, new owned objects are // immediately asked to terminate. Note that linger is set to zero. @@ -158,7 +166,7 @@ protected void processOwn(Own object) // Ask owner object to terminate this object. It may take a while // while actual termination is started. This function should not be // called more than once. - protected void terminate() + protected final void terminate() { // If termination is already underway, there's no point // in starting it anew. @@ -178,7 +186,7 @@ protected void terminate() } // Returns true if the object is in process of termination. - protected boolean isTerminating() + protected final boolean isTerminating() { return terminating; } @@ -210,12 +218,12 @@ protected void processTerm(int linger) // register_tem_acks functions. When event occurs, call // remove_term_ack. When number of pending acks reaches zero // object will be deallocated. - public void registerTermAcks(int count) + final void registerTermAcks(int count) { termAcks += count; } - public void unregisterTermAck() + final void unregisterTermAck() { assert (termAcks > 0); termAcks--; @@ -225,15 +233,14 @@ public void unregisterTermAck() } @Override - protected void processTermAck() + protected final void processTermAck() { unregisterTermAck(); } private void checkTermAcks() { - if (terminating && processedSeqnum == sendSeqnum.get() && - termAcks == 0) { + if (terminating && processedSeqnum == sendSeqnum.get() && termAcks == 0) { // Sanity check. There should be no active children at this point. assert (owned.isEmpty()); diff --git a/src/main/java/zmq/Poller.java b/src/main/java/zmq/Poller.java deleted file mode 100644 index 64d77291e..000000000 --- a/src/main/java/zmq/Poller.java +++ /dev/null @@ -1,274 +0,0 @@ -package zmq; - -import java.io.IOException; -import java.nio.channels.CancelledKeyException; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.SelectableChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -public class Poller extends PollerBase implements Runnable -{ - private static class PollSet - { - protected IPollEvents handler; - protected SelectionKey key; - protected int ops; - protected boolean cancelled; - - protected PollSet(IPollEvents handler) - { - this.handler = handler; - key = null; - cancelled = false; - ops = 0; - } - } - // This table stores data for registered descriptors. - private final Map fdTable; - - // If true, there's at least one retired event source. - private final AtomicBoolean retired = new AtomicBoolean(false); - - // If true, thread is in the process of shutting down. - private volatile boolean stopping; - private volatile boolean stopped; - - private Thread worker; - private Selector selector; - private final String name; - - public Poller() - { - this("poller"); - } - - public Poller(String name) - { - this.name = name; - stopping = false; - stopped = false; - - fdTable = new HashMap(); - try { - selector = Selector.open(); - } - catch (IOException e) { - throw new ZError.IOException(e); - } - } - - public void destroy() - { - if (!stopped) { - try { - worker.join(); - } - catch (InterruptedException e) { - } - } - - try { - selector.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - - public final void addHandle(SelectableChannel fd, IPollEvents events) - { - fdTable.put(fd, new PollSet(events)); - - adjustLoad(1); - } - - public final void removeHandle(SelectableChannel handle) - { - fdTable.get(handle).cancelled = true; - retired.set(true); - - // Decrease the load metric of the thread. - adjustLoad(-1); - } - - public final void setPollIn(SelectableChannel handle) - { - register(handle, SelectionKey.OP_READ, false); - } - - public final void resetPollOn(SelectableChannel handle) - { - register(handle, SelectionKey.OP_READ, true); - } - - public final void setPollOut(SelectableChannel handle) - { - register(handle, SelectionKey.OP_WRITE, false); - } - - public final void resetPollOut(SelectableChannel handle) - { - register(handle, SelectionKey.OP_WRITE, true); - } - - public final void setPollConnect(SelectableChannel handle) - { - register(handle, SelectionKey.OP_CONNECT, false); - } - - public final void setPollAccept(SelectableChannel handle) - { - register(handle, SelectionKey.OP_ACCEPT, false); - } - - private final void register(SelectableChannel handle, int ops, boolean negate) - { - PollSet pollset = fdTable.get(handle); - - if (negate) { - pollset.ops = pollset.ops & ~ops; - } - else { - pollset.ops = pollset.ops | ops; - } - - if (pollset.key != null) { - pollset.key.interestOps(pollset.ops); - } - else { - retired.set(true); - } - } - - public void start() - { - worker = new Thread(this, name); - worker.setDaemon(true); - worker.start(); - } - - public void stop() - { - stopping = true; - selector.wakeup(); - } - - @Override - public void run() - { - int returnsImmediately = 0; - - while (!stopping) { - // Execute any due timers. - long timeout = executeTimers(); - - while (retired.compareAndSet(true, false)) { - Iterator> it = fdTable.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - SelectableChannel ch = entry.getKey(); - PollSet pollset = entry.getValue(); - if (pollset.key == null) { - try { - pollset.key = ch.register(selector, pollset.ops, pollset.handler); - } - catch (ClosedChannelException e) { - } - } - - if (pollset.cancelled || !ch.isOpen()) { - if (pollset.key != null) { - pollset.key.cancel(); - } - it.remove(); - } - } - } - - // Wait for events. - int rc; - long start = System.currentTimeMillis(); - try { - rc = selector.select(timeout); - } - catch (IOException e) { - throw new ZError.IOException(e); - } - - if (rc == 0) { - // Guess JDK epoll bug - if (timeout == 0 || - System.currentTimeMillis() - start < timeout / 2) { - returnsImmediately++; - } - else { - returnsImmediately = 0; - } - - if (returnsImmediately > 10) { - rebuildSelector(); - returnsImmediately = 0; - } - continue; - } - - Iterator it = selector.selectedKeys().iterator(); - while (it.hasNext()) { - SelectionKey key = it.next(); - IPollEvents evt = (IPollEvents) key.attachment(); - it.remove(); - - try { - if (key.isReadable()) { - evt.inEvent(); - } - else if (key.isAcceptable()) { - evt.acceptEvent(); - } - else if (key.isConnectable()) { - evt.connectEvent(); - } - if (key.isWritable()) { - evt.outEvent(); - } - } - catch (CancelledKeyException e) { - // channel might have been closed - } - } - - } - stopped = true; - } - - private void rebuildSelector() - { - Selector newSelector; - - try { - newSelector = Selector.open(); - } - catch (IOException e) { - throw new ZError.IOException(e); - } - - try { - selector.close(); - } - catch (IOException e) { - } - - selector = newSelector; - - for (PollSet pollSet : fdTable.values()) { - pollSet.key = null; - } - - retired.set(true); - } -} diff --git a/src/main/java/zmq/Proxy.java b/src/main/java/zmq/Proxy.java index 33b1585a3..b067cf45c 100644 --- a/src/main/java/zmq/Proxy.java +++ b/src/main/java/zmq/Proxy.java @@ -1,16 +1,36 @@ package zmq; -import java.io.IOException; import java.nio.channels.Selector; -public class Proxy +import zmq.poll.PollItem; + +class Proxy { + public static boolean proxy(SocketBase frontend, SocketBase backend, SocketBase capture) + { + return new Proxy().start(frontend, backend, capture, null); + } + + public static boolean proxy(SocketBase frontend, SocketBase backend, SocketBase capture, SocketBase control) + { + return new Proxy().start(frontend, backend, capture, control); + } + + public static enum State + { + ACTIVE, + PAUSED, + TERMINATED + } + + private State state; + private Proxy() { + state = State.ACTIVE; } - public static boolean proxy(SocketBase frontend, - SocketBase backend, SocketBase capture) + private boolean start(SocketBase frontend, SocketBase backend, SocketBase capture, SocketBase control) { // The algorithm below assumes ratio of requests and replies processed // under full load to be 1:1. @@ -20,103 +40,141 @@ public static boolean proxy(SocketBase frontend, boolean success = true; int rc; - long more; + int more; Msg msg; - PollItem[] items = new PollItem[2]; + int count = control == null ? 2 : 3; + + PollItem[] items = new PollItem[count]; items[0] = new PollItem(frontend, ZMQ.ZMQ_POLLIN); items[1] = new PollItem(backend, ZMQ.ZMQ_POLLIN); - - Selector selector; - try { - selector = Selector.open(); - } - catch (IOException e) { - throw new ZError.IOException(e); + if (control != null) { + items[2] = new PollItem(control, ZMQ.ZMQ_POLLIN); } + PollItem[] itemsout = new PollItem[2]; + + itemsout[0] = new PollItem(frontend, ZMQ.ZMQ_POLLOUT); + itemsout[1] = new PollItem(backend, ZMQ.ZMQ_POLLOUT); + + Selector selector = frontend.getCtx().createSelector(); try { - while (!Thread.currentThread().isInterrupted()) { + while (state != State.TERMINATED) { // Wait while there are either requests or replies to process. rc = ZMQ.poll(selector, items, -1); if (rc < 0) { return false; } + // Get the pollout separately because when combining this with pollin it makes the CPU + // because pollout shall most of the time return directly. + // POLLOUT is only checked when frontend and backend sockets are not the same. + if (frontend != backend) { + rc = ZMQ.poll(selector, itemsout, 0L); + if (rc < 0) { + return false; + } + } + + // Process a control command if any + if (control != null && items[2].isReadable()) { + msg = control.recv(0); + if (msg == null) { + return false; + } + more = control.getSocketOpt(ZMQ.ZMQ_RCVMORE); + + if (more < 0) { + return false; + } + + // Copy message to capture socket if any + success = capture(capture, msg, more); + if (!success) { + return false; + } + + String command = new String(msg.data(), ZMQ.CHARSET); + if ("PAUSE".equals(command)) { + state = State.PAUSED; + } + else if ("RESUME".equals(command)) { + state = State.ACTIVE; + } + else if ("TERMINATE".equals(command)) { + state = State.TERMINATED; + } + else { + // This is an API error, we should assert + System.out.println("E: invalid command sent to proxy '" + command + "'"); + assert false; + } + } // Process a request. - if (items[0].isReadable()) { - while (true) { - msg = frontend.recv(0); - if (msg == null) { - return false; - } - - more = frontend.getSocketOpt(ZMQ.ZMQ_RCVMORE); - - if (more < 0) { - return false; - } - - // Copy message to capture socket if any - if (capture != null) { - Msg ctrl = new Msg(msg); - success = capture.send(ctrl, more > 0 ? ZMQ.ZMQ_SNDMORE : 0); - if (!success) { - return false; - } - } - - success = backend.send(msg, more > 0 ? ZMQ.ZMQ_SNDMORE : 0); - if (!success) { - return false; - } - if (more == 0) { - break; - } + if (process(items[0], itemsout[1], frontend, backend)) { + if (!forward(frontend, backend, capture)) { + return false; } } // Process a reply. - if (items[1].isReadable()) { - while (true) { - msg = backend.recv(0); - if (msg == null) { - return false; - } - - more = backend.getSocketOpt(ZMQ.ZMQ_RCVMORE); - - if (more < 0) { - return false; - } - - // Copy message to capture socket if any - if (capture != null) { - Msg ctrl = new Msg(msg); - success = capture.send(ctrl, more > 0 ? ZMQ.ZMQ_SNDMORE : 0); - if (!success) { - return false; - } - } - - success = frontend.send(msg, more > 0 ? ZMQ.ZMQ_SNDMORE : 0); - if (!success) { - return false; - } - if (more == 0) { - break; - } + if (process(items[1], itemsout[0], frontend, backend)) { + if (!forward(backend, frontend, capture)) { + return false; } } } } finally { - try { - selector.close(); + frontend.getCtx().closeSelector(selector); + } + + return true; + } + + private boolean process(PollItem read, PollItem write, SocketBase frontend, SocketBase backend) + { + return state == State.ACTIVE && read.isReadable() && (frontend == backend || write.isWritable()); + } + + private boolean forward(SocketBase from, SocketBase to, SocketBase capture) + { + int more; + boolean success; + while (true) { + Msg msg = from.recv(0); + if (msg == null) { + return false; + } + more = from.getSocketOpt(ZMQ.ZMQ_RCVMORE); + if (more < 0) { + return false; + } + + // Copy message to capture socket if any + success = capture(capture, msg, more); + if (!success) { + return false; } - catch (Exception e) { + success = to.send(msg, more > 0 ? ZMQ.ZMQ_SNDMORE : 0); + if (!success) { + return false; + } + if (more == 0) { + break; } } + return true; + } + private boolean capture(SocketBase capture, Msg msg, int more) + { + if (capture != null) { + Msg ctrl = new Msg(msg); + boolean success = capture.send(ctrl, more > 0 ? ZMQ.ZMQ_SNDMORE : 0); + if (!success) { + return false; + } + } return true; } } diff --git a/src/main/java/zmq/Pub.java b/src/main/java/zmq/Pub.java deleted file mode 100644 index d2e6debe1..000000000 --- a/src/main/java/zmq/Pub.java +++ /dev/null @@ -1,32 +0,0 @@ -package zmq; - -public class Pub extends XPub -{ - public static class PubSession extends XPub.XPubSession - { - public PubSession(IOThread ioThread, boolean connect, - SocketBase socket, Options options, Address addr) - { - super(ioThread, connect, socket, options, addr); - } - } - - public Pub(Ctx parent, int tid, int sid) - { - super(parent, tid, sid); - options.type = ZMQ.ZMQ_PUB; - } - - @Override - protected Msg xrecv() - { - // Messages cannot be received from PUB socket. - throw new UnsupportedOperationException(); - } - - @Override - protected boolean xhasIn() - { - return false; - } -} diff --git a/src/main/java/zmq/Reaper.java b/src/main/java/zmq/Reaper.java index 49560aaaa..e76b1c253 100644 --- a/src/main/java/zmq/Reaper.java +++ b/src/main/java/zmq/Reaper.java @@ -5,13 +5,16 @@ import java.nio.channels.SelectableChannel; import java.util.concurrent.atomic.AtomicBoolean; -public class Reaper extends ZObject implements IPollEvents, Closeable +import zmq.poll.IPollEvents; +import zmq.poll.Poller; + +final class Reaper extends ZObject implements IPollEvents, Closeable { // Reaper thread accesses incoming commands via this mailbox. private final Mailbox mailbox; // Handle associated with mailbox' file descriptor. - private final SelectableChannel mailboxHandle; + private final Poller.Handle mailboxHandle; // I/O multiplexing is performed using a poller object. private final Poller poller; @@ -22,19 +25,19 @@ public class Reaper extends ZObject implements IPollEvents, Closeable // If true, we were already asked to terminate. private final AtomicBoolean terminating = new AtomicBoolean(); - private String name; + private final String name; - public Reaper(Ctx ctx, int tid) + Reaper(Ctx ctx, int tid) { super(ctx, tid); socketsReaping = 0; name = "reaper-" + tid; - poller = new Poller(name); + poller = new Poller(ctx, name); - mailbox = new Mailbox(name); + mailbox = new Mailbox(ctx, name, tid); - mailboxHandle = mailbox.getFd(); - poller.addHandle(mailboxHandle, this); + SelectableChannel fd = mailbox.getFd(); + mailboxHandle = poller.addHandle(fd, this); poller.setPollIn(mailboxHandle); } @@ -45,17 +48,17 @@ public void close() throws IOException mailbox.close(); } - public Mailbox getMailbox() + Mailbox getMailbox() { return mailbox; } - public void start() + void start() { poller.start(); } - public void stop() + void stop() { if (!terminating.get()) { sendStop(); @@ -73,7 +76,7 @@ public void inEvent() } // Process the command. - cmd.destination().processCommand(cmd); + cmd.process(); } } diff --git a/src/main/java/zmq/Req.java b/src/main/java/zmq/Req.java deleted file mode 100644 index c00be43da..000000000 --- a/src/main/java/zmq/Req.java +++ /dev/null @@ -1,219 +0,0 @@ -package zmq; - -import java.util.Arrays; -import java.util.Calendar; -import java.util.Random; - -public class Req extends Dealer -{ - static final int REQUEST_ID_LENGTH = 6; - // Random generator for request IDs. - public static final Random REQUEST_ID_GEN = new Random( - Calendar.getInstance().getTimeInMillis()); - - // If true, request was already sent and reply wasn't received yet or - // was raceived partially. - private boolean receivingReply; - - // If ZMQ_REQ_CORRELATE is enabled, this is set to the request ID of the most - // recent request. Any incoming message that does not have this ID will - // be discarded. - private byte[] currentRequestId; - - // If true, we are starting to send/recv a message. The first part - // of the message must be empty message part (backtrace stack bottom). - private boolean messageBegins; - - public Req(Ctx parent, int tid, int sid) - { - super(parent, tid, sid); - receivingReply = false; - messageBegins = true; - options.type = ZMQ.ZMQ_REQ; - currentRequestId = new byte[REQUEST_ID_LENGTH]; - } - - @Override - public boolean xsend(Msg msg) - { - // If we've sent a request and we still haven't got the reply, - // we can't send another request. - if (receivingReply && getSocketOpt(zmq.ZMQ.ZMQ_REQ_RELAXED) == 0) { - errno.set(ZError.EFSM); - return false; - } - - // First part of the request is the request identity. - if (messageBegins) { - // If CORRELATE is enabled, send request ID frame before the empty - // frame. - if (getSocketOpt(zmq.ZMQ.ZMQ_REQ_CORRELATE) > 0) { - REQUEST_ID_GEN.nextBytes(currentRequestId); - - Msg requestId = new Msg(currentRequestId); - requestId.setFlags(Msg.MORE); - - boolean rc = super.xsend(requestId); - if (!rc) { - return rc; - } - } - - Msg bottom = new Msg(); - - bottom.setFlags(Msg.MORE); - boolean rc = super.xsend(bottom); - if (!rc) { - return rc; - } - messageBegins = false; - } - - boolean more = msg.hasMore(); - - boolean rc = super.xsend(msg); - if (!rc) { - return rc; - } - - // If the request was fully sent, flip the FSM into reply-receiving state. - if (!more) { - receivingReply = true; - messageBegins = true; - } - - return true; - } - - @Override - protected Msg xrecv() - { - // If request wasn't send, we can't wait for reply. - // Thus, we don't look at the state of the ZMQ_REQ_RELAXED option. - if (!receivingReply) { - errno.set(ZError.EFSM); - return null; - } - - Msg msg = null; - // First part of the reply should be the original request ID, if - // ZMQ_REQ_CORRELATE is enabled. - if (messageBegins) { - msg = super.xrecv(); - if (msg == null) { - return null; - } - - boolean requestIdIsBad = false; - - // Check request ID - if (getSocketOpt(zmq.ZMQ.ZMQ_REQ_CORRELATE) > 0) { - requestIdIsBad = !Arrays.equals(msg.data(), currentRequestId); - - // Receive empty delimiter frame - msg = super.xrecv(); - if (msg == null) { - return null; - } - } - - // TODO: This should also close the connection with the peer! - if (!msg.hasMore() || msg.size() != 0 || requestIdIsBad) { - while (true) { - msg = super.xrecv(); - assert (msg != null); - if (!msg.hasMore()) { - break; - } - } - errno.set(ZError.EAGAIN); - return null; - } - - messageBegins = false; - } - - msg = super.xrecv(); - if (msg == null) { - return null; - } - - // If the reply is fully received, flip the FSM into request-sending state. - if (!msg.hasMore()) { - receivingReply = false; - messageBegins = true; - } - - return msg; - } - - @Override - public boolean xhasIn() - { - // TODO: Duplicates should be removed here. - - return receivingReply && super.xhasIn(); - } - - @Override - public boolean xhasOut() - { - return !receivingReply && super.xhasOut(); - } - - public static class ReqSession extends Dealer.DealerSession - { - enum State { - IDENTITY, - BOTTOM, - BODY - }; - - private State state; - - public ReqSession(IOThread ioThread, boolean connect, - SocketBase socket, final Options options, - final Address addr) - { - super(ioThread, connect, socket, options, addr); - - state = State.IDENTITY; - } - - @Override - public int pushMsg(Msg msg) - { - switch (state) { - case BOTTOM: - if (msg.hasMore() && msg.size() == 0) { - state = State.BODY; - return super.pushMsg(msg); - } - break; - case BODY: - if (msg.hasMore()) { - return super.pushMsg(msg); - } - if (msg.flags() == 0) { - state = State.BOTTOM; - return super.pushMsg(msg); - } - break; - case IDENTITY: - if (msg.flags() == 0) { - state = State.BOTTOM; - return super.pushMsg(msg); - } - break; - } - socket.errno.set(ZError.EFAULT); - return -1; - } - - public void reset() - { - super.reset(); - state = State.IDENTITY; - } - } -} diff --git a/src/main/java/zmq/SessionBase.java b/src/main/java/zmq/SessionBase.java deleted file mode 100644 index 03171aefc..000000000 --- a/src/main/java/zmq/SessionBase.java +++ /dev/null @@ -1,504 +0,0 @@ -package zmq; - -import java.util.HashSet; -import java.util.Set; - -class SessionBase extends Own implements - Pipe.IPipeEvents, IPollEvents, - IMsgSink, IMsgSource -{ - // If true, this session (re)connects to the peer. Otherwise, it's - // a transient session created by the listener. - private boolean connect; - - // Pipe connecting the session to its socket. - private Pipe pipe; - - // This set is added to with pipes we are disconnecting, but haven't yet completed - private final Set terminatingPipes; - - // This flag is true if the remainder of the message being processed - // is still in the in pipe. - private boolean incompleteIn; - - // True if termination have been suspended to push the pending - // messages to the network. - private boolean pending; - - // The protocol I/O engine connected to the session. - private IEngine engine; - - // The socket the session belongs to. - protected SocketBase socket; - - // I/O thread the session is living in. It will be used to plug in - // the engines into the same thread. - private IOThread ioThread; - - // ID of the linger timer - private static final int LINGER_TIMER_ID = 0x20; - - // True is linger timer is running. - private boolean hasLingerTimer; - - // If true, identity has been sent/received from the network. - private boolean identitySent; - private boolean identityReceived; - - // Protocol and address to use when connecting. - private final Address addr; - - private IOObject ioObject; - - public static SessionBase create(IOThread ioThread, boolean connect, - SocketBase socket, Options options, Address addr) - { - SessionBase s = null; - switch (options.type) { - case ZMQ.ZMQ_REQ: - s = new Req.ReqSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_DEALER: - s = new Dealer.DealerSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_REP: - s = new Rep.RepSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_ROUTER: - s = new Router.RouterSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_PUB: - s = new Pub.PubSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_XPUB: - s = new XPub.XPubSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_SUB: - s = new Sub.SubSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_XSUB: - s = new XSub.XSubSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_PUSH: - s = new Push.PushSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_PULL: - s = new Pull.PullSession(ioThread, connect, - socket, options, addr); - break; - case ZMQ.ZMQ_PAIR: - s = new Pair.PairSession(ioThread, connect, - socket, options, addr); - break; - default: - throw new IllegalArgumentException("type=" + options.type); - } - return s; - } - - public SessionBase(IOThread ioThread, boolean connect, - SocketBase socket, Options options, Address addr) - { - super(ioThread, options); - ioObject = new IOObject(ioThread); - - this.connect = connect; - pipe = null; - incompleteIn = false; - pending = false; - engine = null; - this.socket = socket; - this.ioThread = ioThread; - hasLingerTimer = false; - identitySent = false; - identityReceived = false; - this.addr = addr; - - terminatingPipes = new HashSet(); - } - - @Override - public void destroy() - { - assert (pipe == null); - - // If there's still a pending linger timer, remove it. - if (hasLingerTimer) { - ioObject.cancelTimer(LINGER_TIMER_ID); - hasLingerTimer = false; - } - - // Close the engine. - if (engine != null) { - engine.terminate(); - } - } - - // To be used once only, when creating the session. - public void attachPipe(Pipe pipe) - { - assert (!isTerminating()); - assert (this.pipe == null); - assert (pipe != null); - this.pipe = pipe; - this.pipe.setEventSink(this); - } - - public Msg pullMsg() - { - // First message to send is identity - if (!identitySent) { - Msg msg = new Msg(options.identitySize); - msg.put(options.identity, 0, options.identitySize); - identitySent = true; - incompleteIn = false; - - return msg; - } - - if (pipe == null) { - return null; - } - - Msg msg = pipe.read(); - if (msg == null) { - return null; - } - incompleteIn = msg.hasMore(); - - return msg; - - } - - @Override - public int pushMsg(Msg msg) - { - // First message to receive is identity (if required). - if (!identityReceived) { - msg.setFlags(Msg.IDENTITY); - identityReceived = true; - - if (!options.recvIdentity) { - return 0; - } - } - - if (pipe != null && pipe.write(msg)) { - return 0; - } - - return ZError.EAGAIN; - } - - protected void reset() - { - // Restore identity flags. - identitySent = false; - identityReceived = false; - } - - public void flush() - { - if (pipe != null) { - pipe.flush(); - } - } - - // Remove any half processed messages. Flush unflushed messages. - // Call this function when engine disconnect to get rid of leftovers. - private void cleanPipes() - { - if (pipe != null) { - // Get rid of half-processed messages in the out pipe. Flush any - // unflushed messages upstream. - pipe.rollback(); - pipe.flush(); - - // Remove any half-read message from the in pipe. - while (incompleteIn) { - Msg msg = pullMsg(); - if (msg == null) { - assert (!incompleteIn); - break; - } - // msg.close (); - } - } - } - - @Override - public void pipeTerminated(Pipe pipe) - { - // Drop the reference to the deallocated pipe. - assert (this.pipe == pipe || terminatingPipes.contains(pipe)); - - if (this.pipe == pipe) { - // If this is our current pipe, remove it - this.pipe = null; - if (hasLingerTimer) { - ioObject.cancelTimer(LINGER_TIMER_ID); - hasLingerTimer = false; - } - } - else { - // Remove the pipe from the detached pipes set - terminatingPipes.remove(pipe); - } - - // If we are waiting for pending messages to be sent, at this point - // we are sure that there will be no more messages and we can proceed - // with termination safely. - if (pending && this.pipe == null && terminatingPipes.isEmpty()) { - pending = false; - super.processTerm(0); - } - } - - @Override - public void readActivated(Pipe pipe) - { - // Skip activating if we're detaching this pipe - if (this.pipe != pipe) { - assert (terminatingPipes.contains(pipe)); - return; - } - - if (engine != null) { - engine.activateOut(); - } - else { - this.pipe.checkRead(); - } - } - - @Override - public void writeActivated(Pipe pipe) - { - // Skip activating if we're detaching this pipe - if (this.pipe != pipe) { - assert (terminatingPipes.contains(pipe)); - return; - } - - if (engine != null) { - engine.activateIn(); - } - } - - @Override - public void hiccuped(Pipe pipe) - { - // Hiccups are always sent from session to socket, not the other - // way round. - throw new UnsupportedOperationException("Must Override"); - - } - - public SocketBase getSocket() - { - return socket; - } - - @Override - protected void processPlug() - { - ioObject.setHandler(this); - if (connect) { - startConnecting(false); - } - } - - @Override - protected void processAttach(IEngine engine) - { - assert (engine != null); - - // Create the pipe if it does not exist yet. - if (pipe == null && !isTerminating()) { - ZObject[] parents = {this, socket}; - Pipe[] pipes = {null, null}; - int[] hwms = {options.recvHwm, options.sendHwm}; - boolean[] delays = {options.delayOnClose, options.delayOnDisconnect}; - Pipe.pipepair(parents, pipes, hwms, delays); - - // Plug the local end of the pipe. - pipes[0].setEventSink(this); - - // Remember the local end of the pipe. - assert (pipe == null); - pipe = pipes[0]; - - // Ask socket to plug into the remote end of the pipe. - sendBind(socket, pipes[1]); - } - - // Plug in the engine. - assert (this.engine == null); - this.engine = engine; - this.engine.plug(ioThread, this); - } - - public void detach() - { - // Engine is dead. Let's forget about it. - engine = null; - - // Remove any half-done messages from the pipes. - cleanPipes(); - - // Send the event to the derived class. - detached(); - - // Just in case there's only a delimiter in the pipe. - if (pipe != null) { - pipe.checkRead(); - } - } - - protected void processTerm(int linger) - { - assert (!pending); - - // If the termination of the pipe happens before the term command is - // delivered there's nothing much to do. We can proceed with the - // stadard termination immediately. - if (pipe == null && terminatingPipes.isEmpty()) { - super.processTerm(0); - return; - } - - pending = true; - - // If there's finite linger value, delay the termination. - // If linger is infinite (negative) we don't even have to set - // the timer. - if (linger > 0) { - assert (!hasLingerTimer); - ioObject.addTimer(linger, LINGER_TIMER_ID); - hasLingerTimer = true; - } - - // Start pipe termination process. Delay the termination till all messages - // are processed in case the linger time is non-zero. - if (pipe != null) { - pipe.terminate(linger != 0); - - // TODO: Should this go into pipe_t::terminate ? - // In case there's no engine and there's only delimiter in the - // pipe it wouldn't be ever read. Thus we check for it explicitly. - pipe.checkRead(); - } - } - - @Override - public void timerEvent(int id) - { - // Linger period expired. We can proceed with termination even though - // there are still pending messages to be sent. - assert (id == LINGER_TIMER_ID); - hasLingerTimer = false; - - // Ask pipe to terminate even though there may be pending messages in it. - assert (pipe != null); - pipe.terminate(false); - } - - private void detached() - { - // Transient session self-destructs after peer disconnects. - if (!connect) { - terminate(); - return; - } - - // For delayed connect situations, terminate the pipe - // and reestablish later on - if (pipe != null && options.delayAttachOnConnect == 1 - && !addr.protocol().equals("pgm") && !addr.protocol().equals("epgm")) { - pipe.hiccup(); - pipe.terminate(false); - terminatingPipes.add(pipe); - pipe = null; - } - - reset(); - - // Reconnect. - if (options.reconnectIvl != -1) { - startConnecting(true); - } - - // For subscriber sockets we hiccup the inbound pipe, which will cause - // the socket object to resend all the subscriptions. - if (pipe != null && (options.type == ZMQ.ZMQ_SUB || options.type == ZMQ.ZMQ_XSUB)) { - pipe.hiccup(); - } - } - - private void startConnecting(boolean wait) - { - assert (connect); - - // Choose I/O thread to run connecter in. Given that we are already - // running in an I/O thread, there must be at least one available. - IOThread ioThread = chooseIoThread(options.affinity); - assert (ioThread != null); - - // Create the connecter object. - - if (addr.protocol().equals("tcp")) { - TcpConnecter connecter = new TcpConnecter( - ioThread, this, options, addr, wait); - launchChild(connecter); - return; - } - - if (addr.protocol().equals("ipc")) { - IpcConnecter connecter = new IpcConnecter( - ioThread, this, options, addr, wait); - launchChild(connecter); - return; - } - - assert (false); - } - - @Override - public String toString() - { - return super.toString() + "[" + options.socketId + "]"; - } - - @Override - public void inEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void outEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void connectEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void acceptEvent() - { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/zmq/Signaler.java b/src/main/java/zmq/Signaler.java index c519dbbca..3df60bd91 100644 --- a/src/main/java/zmq/Signaler.java +++ b/src/main/java/zmq/Signaler.java @@ -3,61 +3,60 @@ import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ClosedSelectorException; import java.nio.channels.Pipe; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.concurrent.atomic.AtomicInteger; +import zmq.util.Errno; +import zmq.util.Utils; + // This is a cross-platform equivalent to signal_fd. However, as opposed // to signal_fd there can be at most one signal in the signaler at any // given moment. Attempt to send a signal before receiving the previous // one will result in undefined behaviour. - -public class Signaler - implements Closeable +final class Signaler implements Closeable { // Underlying write & read file descriptor. - private final Pipe.SinkChannel w; + private final Pipe.SinkChannel w; private final Pipe.SourceChannel r; - private final Selector selector; - private final ByteBuffer rdummy = ByteBuffer.allocate(1); + private final Selector selector; + private final ByteBuffer wdummy = ByteBuffer.allocate(1); + private final ByteBuffer rdummy = ByteBuffer.allocate(1); // Selector.selectNow at every sending message doesn't show enough performance private final AtomicInteger wcursor = new AtomicInteger(0); - private int rcursor = 0; + private int rcursor = 0; - public Signaler() + private final Errno errno; + private final int pid; + private final Ctx ctx; + + Signaler(Ctx ctx, int pid, Errno errno) { - // Create the socketpair for signaling. - Pipe pipe; + this.ctx = ctx; + this.pid = pid; + this.errno = errno; + // Create the socket pair for signaling. try { - pipe = Pipe.open(); - } - catch (IOException e) { - throw new ZError.IOException(e); - } - r = pipe.source(); - w = pipe.sink(); + Pipe pipe = Pipe.open(); - // Set both fds to non-blocking mode. - try { - Utils.unblockSocket(w); - Utils.unblockSocket(r); - } - catch (IOException e) { - throw new ZError.IOException(e); - } + r = pipe.source(); + w = pipe.sink(); - try { - selector = Selector.open(); + // Set both fds to non-blocking mode. + Utils.unblockSocket(w, r); + + selector = ctx.createSelector(); r.register(selector, SelectionKey.OP_READ); } catch (IOException e) { throw new ZError.IOException(e); } - } @Override @@ -68,41 +67,38 @@ public void close() throws IOException r.close(); } catch (IOException e) { + e.printStackTrace(); exception = e; } try { w.close(); } catch (IOException e) { + e.printStackTrace(); exception = e; } - try { - selector.close(); - } - catch (IOException e) { - exception = e; - } + ctx.closeSelector(selector); if (exception != null) { throw exception; } } - public SelectableChannel getFd() + SelectableChannel getFd() { return r; } - public void send() + void send() { int nbytes = 0; - ByteBuffer dummy = ByteBuffer.allocate(1); while (true) { try { - Thread.interrupted(); - nbytes = w.write(dummy); + wdummy.clear(); + nbytes = w.write(wdummy); } catch (IOException e) { + e.printStackTrace(); throw new ZError.IOException(e); } if (nbytes == 0) { @@ -114,16 +110,22 @@ public void send() } } - public boolean waitEvent(long timeout) + boolean waitEvent(long timeout) { int rc = 0; - + boolean brc = (rcursor < wcursor.get()); + if (brc) { + return true; + } try { if (timeout == 0) { // waitEvent(0) is called every read/send of SocketBase // instant readiness is not strictly required // On the other hand, we can save lots of system call and increase performance - return rcursor < wcursor.get(); + if (!brc) { + errno.set(ZError.EAGAIN); + } + return brc; } else if (timeout < 0) { @@ -133,11 +135,14 @@ else if (timeout < 0) { rc = selector.select(timeout); } } - catch (IOException e) { - throw new ZError.IOException(e); + catch (ClosedSelectorException | IOException e) { + e.printStackTrace(); + errno.set(ZError.EINTR); + return false; } if (rc == 0) { + errno.set(ZError.EAGAIN); return false; } @@ -146,19 +151,31 @@ else if (timeout < 0) { return true; } - public void recv() + void recv() { int nbytes = 0; + // On windows, there may be a need to try several times until it succeeds while (nbytes == 0) { try { + rdummy.clear(); nbytes = r.read(rdummy); - rdummy.rewind(); - // assert nbytes == 1; This was introduced in 0.3.5 and fails on windows causing tests to hang + } + catch (ClosedChannelException e) { + e.printStackTrace(); + errno.set(ZError.EINTR); + return; } catch (IOException e) { throw new ZError.IOException(e); } } + assert (nbytes == 1); rcursor++; } + + @Override + public String toString() + { + return "Signaler[" + pid + "]"; + } } diff --git a/src/main/java/zmq/SocketBase.java b/src/main/java/zmq/SocketBase.java index e14a6af9e..1b4ef8029 100644 --- a/src/main/java/zmq/SocketBase.java +++ b/src/main/java/zmq/SocketBase.java @@ -1,21 +1,50 @@ package zmq; import java.io.IOException; +import java.net.InetSocketAddress; import java.nio.channels.SelectableChannel; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -public abstract class SocketBase extends Own - implements IPollEvents, Pipe.IPipeEvents +import java.nio.channels.SocketChannel; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.io.net.NetProtocol; +import zmq.io.net.ipc.IpcListener; +import zmq.io.net.tcp.TcpAddress; +import zmq.io.net.tcp.TcpListener; +import zmq.io.net.tipc.TipcListener; +import zmq.pipe.Pipe; +import zmq.poll.IPollEvents; +import zmq.poll.Poller; +import zmq.socket.Sockets; +import zmq.util.Blob; +import zmq.util.Clock; +import zmq.util.MultiMap; + +public abstract class SocketBase extends Own implements IPollEvents, Pipe.IPipeEvents { + private class EndpointPipe + { + private final Own endpoint; + private final Pipe pipe; + + public EndpointPipe(Own endpoint, Pipe pipe) + { + super(); + this.endpoint = endpoint; + this.pipe = pipe; + } + } + // Map of open endpoints. - private final Map endpoints; + private final MultiMap endpoints; // Map of open inproc endpoints. - private final Map inprocs; + private final MultiMap inprocs; // Used to check whether the object is a socket. private int tag; @@ -31,12 +60,12 @@ public abstract class SocketBase extends Own // Socket's mailbox object. private final Mailbox mailbox; - // List of attached pipes. - private final List pipes; + // the attached pipes. + private final Set pipes; // Reaper's poller and handle of this socket within it. - private Poller poller; - private SelectableChannel handle; + private Poller poller; + private Poller.Handle handle; // Timestamp of when commands were processed the last time. private long lastTsc; @@ -47,13 +76,19 @@ public abstract class SocketBase extends Own // True if the last message received had MORE flag set. private boolean rcvmore; + // File descriptor if applicable + private SocketChannel fileDesc; + // Monitor socket private SocketBase monitorSocket; // Bitmask of events being monitored private int monitorEvents; - protected ValueReference errno; + // Next assigned name on a zmq_connect() call used by ROUTER and STREAM socket types + protected String connectRid; + + private final ReentrantLock monitorSync = new ReentrantLock(false); protected SocketBase(Ctx parent, int tid, int sid) { @@ -68,96 +103,56 @@ protected SocketBase(Ctx parent, int tid, int sid) monitorEvents = 0; options.socketId = sid; + options.ipv6 = parent.get(ZMQ.ZMQ_IPV6) != 0; options.linger = parent.get(ZMQ.ZMQ_BLOCKY) != 0 ? -1 : 0; - endpoints = new MultiMap(); - inprocs = new MultiMap(); - pipes = new ArrayList(); + endpoints = new MultiMap<>(); + inprocs = new MultiMap<>(); + pipes = new HashSet<>(); - mailbox = new Mailbox("socket-" + sid); - - errno = new ValueReference(0); + mailbox = new Mailbox(parent, "socket-" + sid, tid); } // Concrete algorithms for the x- methods are to be defined by // individual socket types. - protected abstract void xattachPipe(Pipe pipe, boolean icanhasall); + protected abstract void xattachPipe(Pipe pipe, boolean subscribe2all); + protected abstract void xpipeTerminated(Pipe pipe); // Returns false if object is not a socket. - public boolean checkTag() + final boolean checkTag() { return tag == 0xbaddecaf; } - // Create a socket of a specified type. - public static SocketBase create(int type, Ctx parent, int tid, int sid) - { - SocketBase s = null; - switch (type) { - case ZMQ.ZMQ_PAIR: - s = new Pair(parent, tid, sid); - break; - case ZMQ.ZMQ_PUB: - s = new Pub(parent, tid, sid); - break; - case ZMQ.ZMQ_SUB: - s = new Sub(parent, tid, sid); - break; - case ZMQ.ZMQ_REQ: - s = new Req(parent, tid, sid); - break; - case ZMQ.ZMQ_REP: - s = new Rep(parent, tid, sid); - break; - case ZMQ.ZMQ_DEALER: - s = new Dealer(parent, tid, sid); - break; - case ZMQ.ZMQ_ROUTER: - s = new Router(parent, tid, sid); - break; - case ZMQ.ZMQ_PULL: - s = new Pull(parent, tid, sid); - break; - case ZMQ.ZMQ_PUSH: - s = new Push(parent, tid, sid); - break; - - case ZMQ.ZMQ_XPUB: - s = new XPub(parent, tid, sid); - break; - - case ZMQ.ZMQ_XSUB: - s = new XSub(parent, tid, sid); - break; - - default: - throw new IllegalArgumentException("type=" + type); - } - return s; - } - - public void destroy() + @Override + protected void destroy() { try { - mailbox.close(); + monitorSync.lock(); + try { + mailbox.close(); + } + catch (IOException ignore) { + } + + stopMonitor(); + assert (destroyed); } - catch (IOException ignore) { + finally { + monitorSync.unlock(); } - - stopMonitor(); - assert (destroyed); } // Returns the mailbox associated with this socket. - public Mailbox getMailbox() + final Mailbox getMailbox() { return mailbox; } // Interrupt blocking call if the socket is stuck in one. // This function can be called from a different thread! - public void stop() + final void stop() { // Called by ctx when it is terminated (zmq_term). // 'stop' command is sent from the threads that called zmq_term to @@ -168,24 +163,24 @@ public void stop() // Check whether transport protocol, as specified in connect or // bind, is available and compatible with the socket type. - private void checkProtocol(String protocol) + private NetProtocol checkProtocol(String protocol) { // First check out whether the protcol is something we are aware of. - if (!protocol.equals("inproc") && !protocol.equals("ipc") && !protocol.equals("tcp") /*&& - !protocol.equals("pgm") && !protocol.equals("epgm")*/) { - throw new UnsupportedOperationException(protocol); + NetProtocol proto = NetProtocol.getProtocol(protocol); + if (proto == null || !proto.valid) { + errno.set(ZError.EPROTONOSUPPORT); + return proto; } // Check whether socket type and transport protocol match. // Specifically, multicast protocols can't be combined with // bi-directional messaging patterns (socket types). - if ((protocol.equals("pgm") || protocol.equals("epgm")) && - options.type != ZMQ.ZMQ_PUB && options.type != ZMQ.ZMQ_SUB && - options.type != ZMQ.ZMQ_XPUB && options.type != ZMQ.ZMQ_XSUB) { - throw new UnsupportedOperationException(protocol + ",type=" + options.type); + if (!proto.compatible(options.type)) { + errno.set(ZError.ENOCOMPATPROTO); + return null; } - // Protocol is available. + return proto; } // Register the pipe with this socket. @@ -194,15 +189,16 @@ private void attachPipe(Pipe pipe) attachPipe(pipe, false); } - private void attachPipe(Pipe pipe, boolean icanhasall) + private void attachPipe(Pipe pipe, boolean subscribe2all) { - // First, register the pipe so that we can terminate it later on. + assert (pipe != null); + // First, register the pipe so that we can terminate it later on. pipe.setEventSink(this); pipes.add(pipe); // Let the derived socket type know about new pipe. - xattachPipe(pipe, icanhasall); + xattachPipe(pipe, subscribe2all); // If the socket is already being closed, ask any new pipes to terminate // straight away. @@ -212,26 +208,33 @@ private void attachPipe(Pipe pipe, boolean icanhasall) } } - public void setSocketOpt(int option, Object optval) + public final boolean setSocketOpt(int option, Object optval) { - if (ctxTerminated && option != zmq.ZMQ.ZMQ_LINGER) { - throw new ZError.CtxTerminatedException(); + if (ctxTerminated) { + errno.set(ZError.ETERM); + return false; } // First, check whether specific socket type overloads the option. - if (xsetsockopt(option, optval)) { - return; + boolean rc = xsetsockopt(option, optval); + if (rc || errno.get() != ZError.EINVAL) { + return rc; } // If the socket type doesn't support the option, pass it to // the generic option parser. - options.setSocketOpt(option, optval); + rc = options.setSocketOpt(option, optval); + if (rc) { + errno.set(0); + } + return rc; } - public int getSocketOpt(int option) + public final int getSocketOpt(int option) { - if (option != ZMQ.ZMQ_EVENTS && ctxTerminated) { - throw new ZError.CtxTerminatedException(); + if (ctxTerminated) { + errno.set(ZError.ETERM); + return -1; } // fast track to avoid boxing @@ -240,7 +243,7 @@ public int getSocketOpt(int option) } if (option == ZMQ.ZMQ_EVENTS) { boolean rc = processCommands(0, false); - if (!rc && errno.get() == ZError.ETERM) { + if (!rc && (errno.get() == ZError.ETERM || errno.get() == ZError.EINTR)) { return -1; } assert (rc); @@ -253,14 +256,21 @@ public int getSocketOpt(int option) } return val; } - - return (Integer) getsockoptx(option); + Object val = options.getSocketOpt(option); + if (val instanceof Integer) { + return (Integer) val; + } + if (val instanceof Boolean) { + return (Boolean) val ? 1 : 0; + } + throw new IllegalArgumentException(val + " is neither an integer or a boolean for option " + option); } - public Object getsockoptx(int option) + public final Object getSocketOptx(int option) { if (ctxTerminated) { - throw new ZError.CtxTerminatedException(); + errno.set(ZError.ETERM); + return null; } if (option == ZMQ.ZMQ_RCVMORE) { @@ -273,7 +283,7 @@ public Object getsockoptx(int option) if (option == ZMQ.ZMQ_EVENTS) { boolean rc = processCommands(0, false); - if (!rc && errno.get() == ZError.ETERM) { + if (!rc && (errno.get() == ZError.ETERM || errno.get() == ZError.EINTR)) { return -1; } assert (rc); @@ -288,13 +298,14 @@ public Object getsockoptx(int option) } // If the socket type doesn't support the option, pass it to // the generic option parser. - return options.getsockopt(option); + return options.getSocketOpt(option); } - public boolean bind(final String addr) + public final boolean bind(final String addr) { if (ctxTerminated) { - throw new ZError.CtxTerminatedException(); + errno.set(ZError.ETERM); + return false; } // Process pending commands, if any. @@ -304,15 +315,19 @@ public boolean bind(final String addr) } SimpleURI uri = SimpleURI.create(addr); - String protocol = uri.getProtocol(); + String protocolName = uri.getProtocol(); String address = uri.getAddress(); - checkProtocol(protocol); + NetProtocol protocol = checkProtocol(protocolName); + if (protocol == null || !protocol.valid) { + return false; + } - if (protocol.equals("inproc")) { + if (NetProtocol.inproc.equals(protocol)) { Ctx.Endpoint endpoint = new Ctx.Endpoint(this, options); boolean rc = registerEndpoint(addr, endpoint); if (rc) { + connectPending(addr, this); // Save last endpoint URI options.lastEndpoint = addr; } @@ -321,60 +336,78 @@ public boolean bind(final String addr) } return rc; } - if (protocol.equals("pgm") || protocol.equals("epgm")) { - // For convenience's sake, bind can be used interchageable with - // connect for PGM and EPGM transports. + + if (NetProtocol.pgm.equals(protocol) || NetProtocol.epgm.equals(protocol) + || NetProtocol.norm.equals(protocol)) { + // For convenience's sake, bind can be used interchangeable with + // connect for PGM, EPGM and NORM transports. return connect(addr); } - // Remaining trasnports require to be run in an I/O thread, so at this + // Remaining transports require to be run in an I/O thread, so at this // point we'll choose one. IOThread ioThread = chooseIoThread(options.affinity); if (ioThread == null) { - throw new IllegalStateException("EMTHREAD"); + errno.set(ZError.EMTHREAD); + return false; } - if (protocol.equals("tcp")) { + if (NetProtocol.tcp.equals(protocol)) { TcpListener listener = new TcpListener(ioThread, this, options); - int rc = listener.setAddress(address); - if (rc != 0) { + boolean rc = listener.setAddress(address); + if (!rc) { listener.destroy(); - eventBindFailed(address, rc); - errno.set(rc); + eventBindFailed(address, errno.get()); return false; } // Save last endpoint URI options.lastEndpoint = listener.getAddress(); - addEndpoint(addr, listener); + addEndpoint(addr, listener, null); return true; } - if (protocol.equals("ipc")) { + if (NetProtocol.ipc.equals(protocol)) { IpcListener listener = new IpcListener(ioThread, this, options); - int rc = listener.setAddress(address); - if (rc != 0) { + boolean rc = listener.setAddress(address); + if (!rc) { listener.destroy(); - eventBindFailed(address, rc); - errno.set(rc); + eventBindFailed(address, errno.get()); return false; } // Save last endpoint URI options.lastEndpoint = listener.getAddress(); - addEndpoint(addr, listener); + addEndpoint(addr, listener, null); + return true; + } + + if (NetProtocol.tipc.equals(protocol)) { + TipcListener listener = new TipcListener(ioThread, this, options); + boolean rc = listener.setAddress(address); + if (!rc) { + listener.destroy(); + eventBindFailed(address, errno.get()); + return false; + } + + // Save last endpoint URI + options.lastEndpoint = listener.getAddress(); + + addEndpoint(addr, listener, null); return true; } throw new IllegalArgumentException(addr); } - public boolean connect(String addr) + public final boolean connect(String addr) { if (ctxTerminated) { - throw new ZError.CtxTerminatedException(); + errno.set(ZError.ETERM); + return false; } // Process pending commands, if any. @@ -384,110 +417,156 @@ public boolean connect(String addr) } SimpleURI uri = SimpleURI.create(addr); - String protocol = uri.getProtocol(); + String protocolName = uri.getProtocol(); String address = uri.getAddress(); - checkProtocol(protocol); + NetProtocol protocol = checkProtocol(protocolName); + if (protocol == null || !protocol.valid) { + return false; + } - if (protocol.equals("inproc")) { + if (NetProtocol.inproc.equals(protocol)) { // TODO: inproc connect is specific with respect to creating pipes // as there's no 'reconnect' functionality implemented. Once that // is in place we should follow generic pipe creation algorithm. // Find the peer endpoint. Ctx.Endpoint peer = findEndpoint(addr); - if (peer.socket == null) { - return false; - } // The total HWM for an inproc connection should be the sum of // the binder's HWM and the connector's HWM. int sndhwm = 0; - if (options.sendHwm != 0 && peer.options.recvHwm != 0) { + if (peer.socket == null) { + sndhwm = options.sendHwm; + } + else if (options.sendHwm != 0 && peer.options.recvHwm != 0) { sndhwm = options.sendHwm + peer.options.recvHwm; } int rcvhwm = 0; - if (options.recvHwm != 0 && peer.options.sendHwm != 0) { + if (peer.socket == null) { + rcvhwm = options.recvHwm; + } + else if (options.recvHwm != 0 && peer.options.sendHwm != 0) { rcvhwm = options.recvHwm + peer.options.sendHwm; } // Create a bi-directional pipe to connect the peers. - ZObject[] parents = {this, peer.socket}; - Pipe[] pipes = {null, null}; - int[] hwms = {sndhwm, rcvhwm}; - boolean[] delays = {options.delayOnDisconnect, options.delayOnClose}; - Pipe.pipepair(parents, pipes, hwms, delays); + ZObject[] parents = { this, peer.socket == null ? this : peer.socket }; + + boolean conflate = options.conflate && (options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_PULL + || options.type == ZMQ.ZMQ_PUSH || options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_SUB); + + int[] hwms = { conflate ? -1 : sndhwm, conflate ? -1 : rcvhwm }; + boolean[] conflates = { conflate, conflate }; + Pipe[] pipes = Pipe.pair(parents, hwms, conflates); // Attach local end of the pipe to this socket object. attachPipe(pipes[0]); - // If required, send the identity of the peer to the local socket. - if (peer.options.recvIdentity) { + if (peer.socket == null) { + // The peer doesn't exist yet so we don't know whether + // to send the identity message or not. To resolve this, + // we always send our identity and drop it later if + // the peer doesn't expect it. Msg id = new Msg(options.identitySize); - id.put(options.identity, 0 , options.identitySize); + id.put(options.identity, 0, options.identitySize); id.setFlags(Msg.IDENTITY); boolean written = pipes[0].write(id); assert (written); pipes[0].flush(); - } - // If required, send the identity of the local socket to the peer. - if (options.recvIdentity) { - Msg id = new Msg(peer.options.identitySize); - id.put(peer.options.identity, 0 , peer.options.identitySize); - id.setFlags(Msg.IDENTITY); - boolean written = pipes[1].write(id); - assert (written); - pipes[1].flush(); + pendConnection(addr, new Ctx.Endpoint(this, options), pipes); } + else { + // If required, send the identity of the peer to the local socket. + if (peer.options.recvIdentity) { + Msg id = new Msg(options.identitySize); + id.put(options.identity, 0, options.identitySize); + id.setFlags(Msg.IDENTITY); + boolean written = pipes[0].write(id); + assert (written); + pipes[0].flush(); + } - // Attach remote end of the pipe to the peer socket. Note that peer's - // seqnum was incremented in findEndpoint function. We don't need it - // increased here. - sendBind(peer.socket, pipes[1], false); + // If required, send the identity of the local socket to the peer. + if (options.recvIdentity) { + Msg id = new Msg(peer.options.identitySize); + id.put(peer.options.identity, 0, peer.options.identitySize); + id.setFlags(Msg.IDENTITY); + boolean written = pipes[1].write(id); + assert (written); + pipes[1].flush(); + } + + // Attach remote end of the pipe to the peer socket. Note that peer's + // seqnum was incremented in findEndpoint function. We don't need it + // increased here. + sendBind(peer.socket, pipes[1], false); + } // Save last endpoint URI options.lastEndpoint = addr; // remember inproc connections for disconnect - inprocs.put(addr, pipes[0]); + inprocs.insert(addr, pipes[0]); return true; } + boolean isSingleConnect = options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_SUB + || options.type == ZMQ.ZMQ_REQ; + + if (isSingleConnect) { + boolean endpointpipe = endpoints.hasValues(addr); + if (endpointpipe) { + // There is no valid use for multiple connects for SUB-PUB nor + // DEALER-ROUTER nor REQ-REP. Multiple connects produces + // nonsensical results. + return true; + } + } + // Choose the I/O thread to run the session in. IOThread ioThread = chooseIoThread(options.affinity); if (ioThread == null) { - throw new IllegalStateException("Empty IO Thread"); + errno.set(ZError.EMTHREAD); + return false; } - boolean ipv4only = options.ipv4only != 0; - Address paddr = new Address(protocol, address, ipv4only); + Address paddr = new Address(protocolName, address); // Resolve address (if needed by the protocol) - paddr.resolve(); + if (NetProtocol.tcp.equals(protocol) || NetProtocol.ipc.equals(protocol) || NetProtocol.tipc.equals(protocol)) { + paddr.resolve(options.ipv6); + } + // TODO - Should we check address for ZMQ_HAVE_NORM??? + + if (NetProtocol.pgm.equals(protocol) || NetProtocol.epgm.equals(protocol)) { + // TODO V4 init address for pgm & epgm + } // Create session. - SessionBase session = SessionBase.create(ioThread, true, this, - options, paddr); + SessionBase session = Sockets.createSession(ioThread, true, this, options, paddr); assert (session != null); // PGM does not support subscription forwarding; ask for all data to be - // sent to this pipe. - boolean icanhasall = false; - if (protocol.equals("pgm") || protocol.equals("epgm")) { - icanhasall = true; - } + // sent to this pipe. (same for NORM, currently?) + boolean subscribe2all = NetProtocol.pgm.equals(protocol) || NetProtocol.epgm.equals(protocol) + || NetProtocol.norm.equals(protocol); - if (options.delayAttachOnConnect != 1 || icanhasall) { + Pipe newpipe = null; + + if (options.immediate || subscribe2all) { // Create a bi-directional pipe. - ZObject[] parents = {this, session}; - Pipe[] pipes = {null, null}; - int[] hwms = {options.sendHwm, options.recvHwm}; - boolean[] delays = {options.delayOnDisconnect, options.delayOnClose}; - Pipe.pipepair(parents, pipes, hwms, delays); + ZObject[] parents = { this, session }; + boolean conflate = options.conflate && (options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_PULL + || options.type == ZMQ.ZMQ_PUSH || options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_SUB); - // Attach local end of the pipe to the socket object. - attachPipe(pipes[0], icanhasall); + int[] hwms = { conflate ? -1 : options.sendHwm, conflate ? -1 : options.recvHwm }; + boolean[] conflates = { conflate, conflate }; + Pipe[] pipes = Pipe.pair(parents, hwms, conflates); + // Attach local end of the pipe to the socket object. + attachPipe(pipes[0], subscribe2all); + newpipe = pipes[0]; // Attach remote end of the pipe to the session object later on. session.attachPipe(pipes[1]); } @@ -495,27 +574,30 @@ public boolean connect(String addr) // Save last endpoint URI options.lastEndpoint = paddr.toString(); - addEndpoint(addr, session); + addEndpoint(addr, session, newpipe); return true; } // Creates new endpoint ID and adds the endpoint to the map. - private void addEndpoint(String addr, Own endpoint) + private void addEndpoint(String addr, Own endpoint, Pipe pipe) { // Activate the session. Make it a child of this socket. launchChild(endpoint); - endpoints.put(addr, endpoint); + endpoints.insert(addr, new EndpointPipe(endpoint, pipe)); } - public boolean termEndpoint(String addr) + public final boolean termEndpoint(String addr) { + // Check whether the library haven't been shut down yet. if (ctxTerminated) { - throw new ZError.CtxTerminatedException(); + errno.set(ZError.ETERM); + return false; } // Check whether endpoint address passed to the function is valid. if (addr == null) { - throw new IllegalArgumentException(); + errno.set(ZError.EINVAL); + return false; } // Process pending commands, if any, since there could be pending unprocessed processOwn()'s @@ -526,50 +608,83 @@ public boolean termEndpoint(String addr) } SimpleURI uri = SimpleURI.create(addr); - String protocol = uri.getProtocol(); - + NetProtocol protocol = checkProtocol(uri.getProtocol()); + if (protocol == null || !protocol.valid) { + return false; + } // Disconnect an inproc socket - if (protocol.equals("inproc")) { - if (!inprocs.containsKey(addr)) { - return false; + if (NetProtocol.inproc.equals(protocol)) { + if (unregisterEndpoint(addr, this)) { + return true; } - Iterator> it = inprocs.entrySet().iterator(); - while (it.hasNext()) { - it.next().getValue().terminate(true); - it.remove(); + Collection olds = inprocs.remove(addr); + if (olds == null || olds.isEmpty()) { + errno.set(ZError.ENOENT); + return false; + } + else { + for (Pipe old : olds) { + old.terminate(true); + } } return true; } - if (!endpoints.containsKey(addr)) { - return false; + String resolvedAddress = addr; + + // The resolved last_endpoint is used as a key in the endpoints map. + // The address passed by the user might not match in the TCP case due to + // IPv4-in-IPv6 mapping (EG: tcp://[::ffff:127.0.0.1]:9999), so try to + // resolve before giving up. Given at this stage we don't know whether a + // socket is connected or bound, try with both. + if (NetProtocol.tcp.equals(protocol)) { + boolean endpoint = endpoints.hasValues(resolvedAddress); + if (!endpoint) { + // TODO V4 resolve TCP address when unbinding + TcpAddress address = new TcpAddress(uri.getAddress(), options.ipv6); + resolvedAddress = address.address().toString(); + endpoint = endpoints.hasValues(resolvedAddress); + if (!endpoint) { + // no luck, try with local resolution + InetSocketAddress socketAddress = address.resolve(uri.getAddress(), options.ipv6, true); + resolvedAddress = socketAddress.toString(); + } + } } + // Find the endpoints range (if any) corresponding to the addr_ string. - Iterator> it = endpoints.entrySet().iterator(); + Collection eps = endpoints.remove(resolvedAddress); - while (it.hasNext()) { - Entry e = it.next(); - if (!e.getKey().equals(addr)) { - continue; + if (eps == null || eps.isEmpty()) { + errno.set(ZError.ENOENT); + return false; + } + else { + // If we have an associated pipe, terminate it. + for (EndpointPipe ep : eps) { + if (ep.pipe != null) { + ep.pipe.terminate(true); + } + termChild(ep.endpoint); } - termChild(e.getValue()); - it.remove(); } return true; } - public boolean send(Msg msg, int flags) + public final boolean send(Msg msg, int flags) { + // Check whether the library haven't been shut down yet. if (ctxTerminated) { errno.set(ZError.ETERM); return false; } // Check whether message passed to the function is valid. - if (msg == null) { - throw new IllegalArgumentException(); + if (msg == null || !msg.check()) { + errno.set(ZError.EFAULT); + return false; } // Process pending commands, if any. @@ -586,6 +701,8 @@ public boolean send(Msg msg, int flags) msg.setFlags(Msg.MORE); } + msg.resetMetadata(); + // Try to send the message. boolean rc = xsend(msg); @@ -604,7 +721,7 @@ public boolean send(Msg msg, int flags) } // Compute the time when the timeout should occur. - // If the timeout is infite, don't care. + // If the timeout is infinite, don't care. int timeout = options.sendTimeout; long end = timeout < 0 ? 0 : (Clock.nowMS() + timeout); @@ -636,13 +753,16 @@ public boolean send(Msg msg, int flags) return true; } - public Msg recv(int flags) + public final Msg recv(int flags) { + // Check whether the library haven't been shut down yet. if (ctxTerminated) { errno.set(ZError.ETERM); return null; } + // Check whether message passed to the function is valid.: NOT APPLICABLE + // Once every inbound_poll_rate messages check for signals and process // incoming commands. This happens only if we are not polling altogether // because there are messages available all the time. If poll occurs, @@ -666,13 +786,16 @@ public Msg recv(int flags) // If we have the message, return immediately. if (msg != null) { + if (fileDesc != null) { + msg.setFd(fileDesc); + } extractFlags(msg); return msg; } // If the message cannot be fetched immediately, there are two scenarios. // For non-blocking recv, commands are processed in case there's an - // activate_reader command already waiting int a command pipe. + // activate_reader command already waiting in a command pipe. // If it's not, return EAGAIN. if ((flags & ZMQ.ZMQ_DONTWAIT) > 0 || options.recvTimeout == 0) { if (!processCommands(0, false)) { @@ -689,7 +812,7 @@ public Msg recv(int flags) } // Compute the time when the timeout should occur. - // If the timeout is infite, don't care. + // If the timeout is infinite, don't care. int timeout = options.recvTimeout; long end = timeout < 0 ? 0 : (Clock.nowMS() + timeout); @@ -726,7 +849,7 @@ public Msg recv(int flags) } - public void close() + public final void close() { // Mark the socket as dead tag = 0xdeadbeef; @@ -739,27 +862,27 @@ public void close() // These functions are used by the polling mechanism to determine // which events are to be reported from this socket. - boolean hasIn() + final boolean hasIn() { return xhasIn(); } - boolean hasOut() + final boolean hasOut() { return xhasOut(); } // Using this function reaper thread ask the socket to register with // its poller. - public void startReaping(Poller poller) + final void startReaping(Poller poller) { // Plug the socket to the reaper thread. this.poller = poller; - handle = mailbox.getFd(); - this.poller.addHandle(handle, this); + SelectableChannel fd = mailbox.getFd(); + handle = this.poller.addHandle(fd, this); this.poller.setPollIn(handle); - // Initialise the termination and check whether it can be deallocated + // Initialize the termination and check whether it can be deallocated // immediately. terminate(); checkDestroy(); @@ -781,13 +904,13 @@ private boolean processCommands(int timeout, boolean throttle) // commands recently, so that we can throttle the new commands. // Get the CPU's tick counter. If 0, the counter is not available. - long tsc = 0; // save cpu Clock.rdtsc (); + long tsc = 0; // Clock.rdtsc(); - // Optimised version of command processing - it doesn't have to check + // Optimized version of command processing - it doesn't have to check // for incoming commands each time. It does so only if certain time // elapsed since last command processing. Command delay varies // depending on CPU speed: It's ~1ms on 3GHz CPU, ~2ms on 1.5GHz CPU - // etc. The optimisation makes sense only on platforms where getting + // etc. The optimization makes sense only on platforms where getting // a timestamp is a very cheap operation (tens of nanoseconds). if (tsc != 0 && throttle) { // Check whether TSC haven't jumped backwards (in case of migration @@ -804,14 +927,17 @@ private boolean processCommands(int timeout, boolean throttle) } // Process all the commands available at the moment. - while (true) { - if (cmd == null) { - break; - } - - cmd.destination().processCommand(cmd); + while (cmd != null) { + cmd.process(); cmd = mailbox.recv(0); } + + if (errno.get() == ZError.EINTR) { + return false; + } + + assert (errno.get() == ZError.EAGAIN); + if (ctxTerminated) { errno.set(ZError.ETERM); // Do not raise exception at the blocked operation return false; @@ -821,25 +947,31 @@ private boolean processCommands(int timeout, boolean throttle) } @Override - protected void processStop() + protected final void processStop() { // Here, someone have called zmq_term while the socket was still alive. // We'll remember the fact so that any blocking call is interrupted and any // further attempt to use the socket will return ETERM. The user is still // responsible for calling zmq_close on the socket though! - stopMonitor(); - ctxTerminated = true; + try { + monitorSync.lock(); + stopMonitor(); + ctxTerminated = true; + } + finally { + monitorSync.unlock(); + } } @Override - protected void processBind(Pipe pipe) + protected final void processBind(Pipe pipe) { attachPipe(pipe); } @Override - protected void processTerm(int linger) + protected final void processTerm(int linger) { // Unregister all inproc endpoints associated with this socket. // Doing this we make sure that no new pipes from other sockets (inproc) @@ -847,8 +979,8 @@ protected void processTerm(int linger) unregisterEndpoints(this); // Ask all attached pipes to terminate. - for (int i = 0; i != pipes.size(); ++i) { - pipes.get(i).terminate(false); + for (Pipe pipe : pipes) { + pipe.terminate(false); } registerTermAcks(pipes.size()); @@ -858,7 +990,7 @@ protected void processTerm(int linger) // Delay actual destruction of the socket. @Override - protected void processDestroy() + protected final void processDestroy() { destroyed = true; } @@ -868,6 +1000,7 @@ protected void processDestroy() // method. protected boolean xsetsockopt(int option, Object optval) { + errno.set(ZError.EINVAL); return false; } @@ -891,6 +1024,11 @@ protected Msg xrecv() throw new UnsupportedOperationException("Must Override"); } + protected Blob getCredential() + { + throw new UnsupportedOperationException("Must Override"); + } + protected void xreadActivated(Pipe pipe) { throw new UnsupportedOperationException("Must Override"); @@ -907,41 +1045,24 @@ protected void xhiccuped(Pipe pipe) } @Override - public void inEvent() + public final void inEvent() { // This function is invoked only once the socket is running in the context // of the reaper thread. Process any commands from other threads/sockets // that may be available at the moment. Ultimately, the socket will // be destroyed. - try { - processCommands(0, false); - } - catch (ZError.CtxTerminatedException e) { - } - + processCommands(0, false); checkDestroy(); } @Override - public void outEvent() + public final void outEvent() { throw new UnsupportedOperationException(); } @Override - public void connectEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void acceptEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void timerEvent(int id) + public final void timerEvent(int id) { throw new UnsupportedOperationException(); } @@ -954,6 +1075,7 @@ private void checkDestroy() if (destroyed) { // Remove the socket from the reaper's poller. poller.removeHandle(handle); + // Remove the socket from the context. destroySocket(this); @@ -966,21 +1088,21 @@ private void checkDestroy() } @Override - public void readActivated(Pipe pipe) + public final void readActivated(Pipe pipe) { xreadActivated(pipe); } @Override - public void writeActivated(Pipe pipe) + public final void writeActivated(Pipe pipe) { xwriteActivated(pipe); } @Override - public void hiccuped(Pipe pipe) + public final void hiccuped(Pipe pipe) { - if (options.delayAttachOnConnect == 1) { + if (!options.immediate) { pipe.terminate(false); } else { @@ -990,19 +1112,13 @@ public void hiccuped(Pipe pipe) } @Override - public void pipeTerminated(Pipe pipe) + public final void pipeTerminated(Pipe pipe) { // Notify the specific socket type about the pipe termination. xpipeTerminated(pipe); // Remove pipe from inproc pipes - Iterator> it = inprocs.entrySet().iterator(); - while (it.hasNext()) { - if (it.next().getValue() == pipe) { - it.remove(); - break; - } - } + inprocs.remove(pipe); // Remove the pipe from the list of attached pipes and confirm its // termination if we are already shutting down. @@ -1017,7 +1133,7 @@ public void pipeTerminated(Pipe pipe) private void extractFlags(Msg msg) { // Test whether IDENTITY flag is valid for this socket type. - if ((msg.flags() & Msg.IDENTITY) > 0) { + if (msg.isIdentity()) { assert (options.recvIdentity); } @@ -1025,147 +1141,159 @@ private void extractFlags(Msg msg) rcvmore = msg.hasMore(); } - public boolean monitor(final String addr, int events) + public final boolean monitor(final String addr, int events) { - boolean rc; - if (ctxTerminated) { - throw new ZError.CtxTerminatedException(); - } + try { + monitorSync.lock(); - // Support deregistering monitoring endpoints as well - if (addr == null) { - stopMonitor(); - return true; - } + boolean rc; + if (ctxTerminated) { + errno.set(ZError.ETERM); + return false; + } - SimpleURI uri = SimpleURI.create(addr); - String protocol = uri.getProtocol(); + // Support deregistering monitoring endpoints as well + if (addr == null) { + stopMonitor(); + return true; + } - checkProtocol(protocol); + SimpleURI uri = SimpleURI.create(addr); - // Event notification only supported over inproc:// - if (!protocol.equals("inproc")) { - stopMonitor(); - throw new IllegalArgumentException("inproc socket required"); - } + NetProtocol protocol = checkProtocol(uri.getProtocol()); + if (protocol == null || !protocol.valid) { + return false; + } - // Register events to monitor - monitorEvents = events; + // Event notification only supported over inproc:// + if (!NetProtocol.inproc.equals(protocol)) { + errno.set(ZError.EPROTONOSUPPORT); + return false; + } - monitorSocket = getCtx().createSocket(ZMQ.ZMQ_PAIR); - if (monitorSocket == null) { - return false; - } + // Register events to monitor + monitorEvents = events; - // Never block context termination on pending event messages - int linger = 0; - try { - monitorSocket.setSocketOpt(ZMQ.ZMQ_LINGER, linger); + monitorSocket = getCtx().createSocket(ZMQ.ZMQ_PAIR); + if (monitorSocket == null) { + return false; + } + + // Never block context termination on pending event messages + int linger = 0; + try { + monitorSocket.setSocketOpt(ZMQ.ZMQ_LINGER, linger); + } + catch (IllegalArgumentException e) { + stopMonitor(); + throw e; + } + + // Spawn the monitor socket endpoint + rc = monitorSocket.bind(addr); + if (!rc) { + stopMonitor(); + } + return rc; } - catch (IllegalArgumentException e) { - stopMonitor(); - throw e; + finally { + monitorSync.unlock(); } - // Spawn the monitor socket endpoint - rc = monitorSocket.bind(addr); - if (!rc) { - stopMonitor(); - } - return rc; } - public void eventConnected(String addr, SelectableChannel ch) + public final void eventConnected(String addr, SelectableChannel ch) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_CONNECTED) == 0) { - return; - } - - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_CONNECTED, addr, ch)); + event(addr, ch, ZMQ.ZMQ_EVENT_CONNECTED); } - public void eventConnectDelayed(String addr, int errno) + public final void eventConnectDelayed(String addr, int errno) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_CONNECT_DELAYED) == 0) { - return; - } - - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_CONNECT_DELAYED, addr, errno)); + event(addr, errno, ZMQ.ZMQ_EVENT_CONNECT_DELAYED); } - public void eventConnectRetried(String addr, int interval) + public final void eventConnectRetried(String addr, int interval) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_CONNECT_RETRIED) == 0) { - return; - } + try { + monitorSync.lock(); + if ((monitorEvents & ZMQ.ZMQ_EVENT_CONNECT_RETRIED) == 0) { + return; + } - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_CONNECT_RETRIED, addr, interval)); + monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_CONNECT_RETRIED, addr, interval)); + } + finally { + monitorSync.unlock(); + } } - public void eventListening(String addr, SelectableChannel ch) + public final void eventListening(String addr, SelectableChannel ch) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_LISTENING) == 0) { - return; - } - - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_LISTENING, addr, ch)); + event(addr, ch, ZMQ.ZMQ_EVENT_LISTENING); } - public void eventBindFailed(String addr, int errno) + public final void eventBindFailed(String addr, int errno) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_BIND_FAILED) == 0) { - return; - } - - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_BIND_FAILED, addr, errno)); + event(addr, errno, ZMQ.ZMQ_EVENT_BIND_FAILED); } - public void eventAccepted(String addr, SelectableChannel ch) + public final void eventAccepted(String addr, SelectableChannel ch) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_ACCEPTED) == 0) { - return; - } - - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_ACCEPTED, addr, ch)); + event(addr, ch, ZMQ.ZMQ_EVENT_ACCEPTED); } - public void eventAcceptFailed(String addr, int errno) + public final void eventAcceptFailed(String addr, int errno) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_ACCEPT_FAILED) == 0) { - return; - } + event(addr, errno, ZMQ.ZMQ_EVENT_ACCEPT_FAILED); + } - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_ACCEPT_FAILED, addr, errno)); + public final void eventClosed(String addr, SelectableChannel ch) + { + event(addr, ch, ZMQ.ZMQ_EVENT_CLOSED); } - public void eventClosed(String addr, SelectableChannel ch) + public final void eventCloseFailed(String addr, int errno) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_CLOSED) == 0) { - return; - } + event(addr, errno, ZMQ.ZMQ_EVENT_CLOSE_FAILED); + } - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_CLOSED, addr, ch)); + public final void eventDisconnected(String addr, SelectableChannel ch) + { + event(addr, ch, ZMQ.ZMQ_EVENT_DISCONNECTED); } - public void eventCloseFailed(String addr, int errno) + private void event(String addr, SelectableChannel ch, int event) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_CLOSE_FAILED) == 0) { - return; - } + try { + monitorSync.lock(); + if ((monitorEvents & event) == 0) { + return; + } - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_CLOSE_FAILED, addr, errno)); + monitorEvent(new ZMQ.Event(event, addr, ch)); + } + finally { + monitorSync.unlock(); + } } - public void eventDisconnected(String addr, SelectableChannel ch) + private void event(String addr, int errno, int event) { - if ((monitorEvents & ZMQ.ZMQ_EVENT_DISCONNECTED) == 0) { - return; - } + try { + monitorSync.lock(); + if ((monitorEvents & event) == 0) { + return; + } - monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_DISCONNECTED, addr, ch)); + monitorEvent(new ZMQ.Event(event, addr, errno)); + } + finally { + monitorSync.unlock(); + } } - protected void monitorEvent(ZMQ.Event event) + // Send a monitor event + protected final void monitorEvent(ZMQ.Event event) { if (monitorSocket == null) { return; @@ -1174,8 +1302,11 @@ protected void monitorEvent(ZMQ.Event event) event.write(monitorSocket); } - protected void stopMonitor() + private void stopMonitor() { + // this is a private method which is only called from + // contexts where the mutex has been locked before + if (monitorSocket != null) { if ((monitorEvents & ZMQ.ZMQ_EVENT_MONITOR_STOPPED) != 0) { monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_MONITOR_STOPPED, "", 0)); @@ -1189,41 +1320,20 @@ protected void stopMonitor() @Override public String toString() { - return super.toString() + "[" + options.socketId + "]"; + return getClass().getSimpleName() + "[" + options.socketId + "]"; } - public SelectableChannel getFD() + public final SelectableChannel getFD() { return mailbox.getFd(); } public String typeString() { - switch (options.type) { - case ZMQ.ZMQ_PAIR: - return "PAIR"; - case ZMQ.ZMQ_PUB: - return "PUB"; - case ZMQ.ZMQ_SUB: - return "SUB"; - case ZMQ.ZMQ_REQ: - return "REQ"; - case ZMQ.ZMQ_REP: - return "REP"; - case ZMQ.ZMQ_DEALER: - return "DEALER"; - case ZMQ.ZMQ_ROUTER: - return "ROUTER"; - case ZMQ.ZMQ_PULL: - return "PULL"; - case ZMQ.ZMQ_PUSH: - return "PUSH"; - default: - return "UNKOWN"; - } - } - - public int errno() + return Sockets.name(options.type); + } + + public final int errno() { return errno.get(); } @@ -1264,4 +1374,16 @@ public String getAddress() return address; } } + + @Override + public final void connectEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public final void acceptEvent() + { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/zmq/StreamEngine.java b/src/main/java/zmq/StreamEngine.java deleted file mode 100644 index 18f21a242..000000000 --- a/src/main/java/zmq/StreamEngine.java +++ /dev/null @@ -1,621 +0,0 @@ -package zmq; - -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.SocketChannel; - -public class StreamEngine implements IEngine, IPollEvents, IMsgSink -{ - // Size of the greeting message: - // Preamble (10 bytes) + version (1 byte) + socket type (1 byte). - private static final int GREETING_SIZE = 12; - - // True iff we are registered with an I/O poller. - private boolean ioEnabled; - - //final private IOObject ioObject; - private SocketChannel handle; - - private ByteBuffer inbuf; - private int insize; - private DecoderBase decoder; - - private Transfer outbuf; - private int outsize; - private EncoderBase encoder; - - // When true, we are still trying to determine whether - // the peer is using versioned protocol, and if so, which - // version. When false, normal message flow has started. - private boolean handshaking; - - // The receive buffer holding the greeting message - // that we are receiving from the peer. - private final ByteBuffer greeting; - - // The send buffer holding the greeting message - // that we are sending to the peer. - private final ByteBuffer greetingOutputBuffer; - - // The session this engine is attached to. - private SessionBase session; - - // Detached transient session. - //private SessionBase leftover_session; - - private Options options; - - // String representation of endpoint - private String endpoint; - - private boolean plugged; - - // Socket - private SocketBase socket; - - private IOObject ioObject; - - public StreamEngine(SocketChannel handle, final Options options, final String endpoint) - { - this.handle = handle; - inbuf = null; - insize = 0; - ioEnabled = false; - outbuf = null; - outsize = 0; - handshaking = true; - session = null; - this.options = options; - plugged = false; - this.endpoint = endpoint; - socket = null; - greeting = ByteBuffer.allocate(GREETING_SIZE).order(ByteOrder.BIG_ENDIAN); - greetingOutputBuffer = ByteBuffer.allocate(GREETING_SIZE).order(ByteOrder.BIG_ENDIAN); - encoder = null; - decoder = null; - - // Put the socket into non-blocking mode. - try { - Utils.unblockSocket(this.handle); - - // Set the socket buffer limits for the underlying socket. - if (this.options.sndbuf != 0) { - this.handle.socket().setSendBufferSize(this.options.sndbuf); - } - if (this.options.rcvbuf != 0) { - this.handle.socket().setReceiveBufferSize(this.options.rcvbuf); - } - } - catch (IOException e) { - throw new ZError.IOException(e); - } - } - - private DecoderBase newDecoder(int size, long max, SessionBase session, int version) - { - DecoderBase decoder; - if (options.decoder == null) { - if (version == V1Protocol.VERSION) { - decoder = new V1Decoder(size, max, session); - } - else { - decoder = new Decoder(size, max); - } - } - else { - try { - Constructor dcon; - - if (version == 0) { - dcon = options.decoder.getConstructor(int.class, long.class); - decoder = dcon.newInstance(size, max); - } - else { - dcon = options.decoder.getConstructor(int.class, long.class, IMsgSink.class, int.class); - decoder = dcon.newInstance(size, max, session, version); - } - } - catch (SecurityException e) { - throw new ZError.InstantiationException(e); - } - catch (NoSuchMethodException e) { - throw new ZError.InstantiationException(e); - } - catch (InvocationTargetException e) { - throw new ZError.InstantiationException(e); - } - catch (IllegalAccessException e) { - throw new ZError.InstantiationException(e); - } - catch (InstantiationException e) { - throw new ZError.InstantiationException(e); - } - } - - if (options.msgAllocator != null) { - decoder.setMsgAllocator(options.msgAllocator); - } - return decoder; - } - - private EncoderBase newEncoder(int size, SessionBase session, int version) - { - if (options.encoder == null) { - if (version == V1Protocol.VERSION) { - return new V1Encoder(size, session); - } - return new Encoder(size); - } - - try { - Constructor econ; - - if (version == 0) { - econ = options.encoder.getConstructor(int.class); - return econ.newInstance(size); - } - else { - econ = options.encoder.getConstructor(int.class, IMsgSource.class, int.class); - return econ.newInstance(size, session, version); - } - } - catch (SecurityException e) { - throw new ZError.InstantiationException(e); - } - catch (NoSuchMethodException e) { - throw new ZError.InstantiationException(e); - } - catch (InvocationTargetException e) { - throw new ZError.InstantiationException(e); - } - catch (IllegalAccessException e) { - throw new ZError.InstantiationException(e); - } - catch (InstantiationException e) { - throw new ZError.InstantiationException(e); - } - } - - public void destroy() - { - assert (!plugged); - - if (handle != null) { - try { - handle.close(); - } - catch (IOException e) { - } - handle = null; - } - } - - public void plug(IOThread ioThread, SessionBase session) - { - assert (!plugged); - plugged = true; - - // Connect to session object. - assert (this.session == null); - assert (session != null); - this.session = session; - socket = this.session.getSocket(); - - ioObject = new IOObject(null); - ioObject.setHandler(this); - // Connect to I/O threads poller object. - ioObject.plug(ioThread); - ioObject.addHandle(handle); - ioEnabled = true; - - // Send the 'length' and 'flags' fields of the identity message. - // The 'length' field is encoded in the long format. - greetingOutputBuffer.put((byte) 0xff); - greetingOutputBuffer.putLong(options.identitySize + 1); - greetingOutputBuffer.put((byte) 0x7f); - - ioObject.setPollIn(handle); - // When there's a raw custom encoder, we don't send 10 bytes frame - boolean custom = false; - try { - custom = options.encoder != null && options.encoder.getDeclaredField("RAW_ENCODER") != null; - } - catch (SecurityException e) { - } - catch (NoSuchFieldException e) { - } - - if (!custom) { - outsize = greetingOutputBuffer.position(); - greetingOutputBuffer.flip(); - outbuf = new Transfer.ByteBufferTransfer(greetingOutputBuffer); - ioObject.setPollOut(handle); - } - - // Flush all the data that may have been already received downstream. - inEvent(); - } - - private void unplug() - { - assert (plugged); - plugged = false; - - // Cancel all fd subscriptions. - if (ioEnabled) { - ioObject.removeHandle(handle); - ioEnabled = false; - } - - // Disconnect from I/O threads poller object. - ioObject.unplug(); - - // Disconnect from session object. - if (encoder != null) { - encoder.setMsgSource(null); - } - if (decoder != null) { - decoder.setMsgSink(null); - } - session = null; - } - - @Override - public void terminate() - { - unplug(); - destroy(); - } - - @Override - public void inEvent() - { - // If still handshaking, receive and process the greeting message. - if (handshaking) { - if (!handshake()) { - return; - } - } - - assert (decoder != null); - boolean disconnection = false; - - // If there's no data to process in the buffer... - if (insize == 0) { - // Retrieve the buffer and read as much data as possible. - // Note that buffer can be arbitrarily large. However, we assume - // the underlying TCP layer has fixed buffer size and thus the - // number of bytes read will be always limited. - inbuf = decoder.getBuffer(); - insize = read(inbuf); - inbuf.flip(); - - // Check whether the peer has closed the connection. - if (insize == -1) { - insize = 0; - disconnection = true; - } - } - - // Push the data to the decoder. - int processed = decoder.processBuffer(inbuf, insize); - - if (processed == -1) { - disconnection = true; - } - else { - // Stop polling for input if we got stuck. - if (processed < insize) { - ioObject.resetPollIn(handle); - } - - // Adjust the buffer. - insize -= processed; - } - - // Flush all messages the decoder may have produced. - session.flush(); - - // An input error has occurred. If the last decoded message - // has already been accepted, we terminate the engine immediately. - // Otherwise, we stop waiting for socket events and postpone - // the termination until after the message is accepted. - if (disconnection) { - if (decoder.stalled()) { - ioObject.removeHandle(handle); - ioEnabled = false; - } - else { - error(); - } - } - } - - @Override - public void outEvent() - { - // If write buffer is empty, try to read new data from the encoder. - if (outsize == 0) { - // Even when we stop polling as soon as there is no - // data to send, the poller may invoke outEvent one - // more time due to 'speculative write' optimisation. - if (encoder == null) { - assert (handshaking); - return; - } - - outbuf = encoder.getData(null); - outsize = outbuf.remaining(); - // If there is no data to send, stop polling for output. - if (outbuf.remaining() == 0) { - ioObject.resetPollOut(handle); - - // when we use custom encoder, we might want to close - if (encoder.isError()) { - error(); - } - - return; - } - } - - // If there are any data to write in write buffer, write as much as - // possible to the socket. Note that amount of data to write can be - // arbitratily large. However, we assume that underlying TCP layer has - // limited transmission buffer and thus the actual number of bytes - // written should be reasonably modest. - int nbytes = write(outbuf); - - // IO error has occurred. We stop waiting for output events. - // The engine is not terminated until we detect input error; - // this is necessary to prevent losing incomming messages. - if (nbytes == -1) { - ioObject.resetPollOut(handle); - return; - } - - outsize -= nbytes; - - // If we are still handshaking and there are no data - // to send, stop polling for output. - if (handshaking) { - if (outsize == 0) { - ioObject.resetPollOut(handle); - } - } - - // when we use custom encoder, we might want to close after sending a response - if (outsize == 0) { - if (encoder != null && encoder.isError()) { - error(); - } - } - } - - @Override - public void connectEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void acceptEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void timerEvent(int id) - { - throw new UnsupportedOperationException(); - } - - @Override - public void activateOut() - { - ioObject.setPollOut(handle); - - // Speculative write: The assumption is that at the moment new message - // was sent by the user the socket is probably available for writing. - // Thus we try to write the data to socket avoiding polling for POLLOUT. - // Consequently, the latency should be better in request/reply scenarios. - outEvent(); - } - - @Override - public void activateIn() - { - if (!ioEnabled) { - // There was an input error but the engine could not - // be terminated (due to the stalled decoder). - // Flush the pending message and terminate the engine now. - decoder.processBuffer(inbuf, 0); - assert (!decoder.stalled()); - session.flush(); - error(); - return; - } - - ioObject.setPollIn(handle); - - // Speculative read. - ioObject.inEvent(); - } - - private boolean handshake() - { - assert (handshaking); - - // Receive the greeting. - while (greeting.position() < GREETING_SIZE) { - final int n = read(greeting); - if (n == -1) { - error(); - return false; - } - - if (n == 0) { - return false; - } - - // We have received at least one byte from the peer. - // If the first byte is not 0xff, we know that the - // peer is using unversioned protocol. - if ((greeting.get(0) & 0xff) != 0xff) { - break; - } - - if (greeting.position() < 10) { - continue; - } - - // Inspect the right-most bit of the 10th byte (which coincides - // with the 'flags' field if a regular message was sent). - // Zero indicates this is a header of identity message - // (i.e. the peer is using the unversioned protocol). - if ((greeting.get(9) & 0x01) == 0) { - break; - } - - // The peer is using versioned protocol. - // Send the rest of the greeting, if necessary. - if (greetingOutputBuffer.limit() < GREETING_SIZE) { - if (outsize == 0) { - ioObject.setPollOut(handle); - } - int pos = greetingOutputBuffer.position(); - greetingOutputBuffer.position(10).limit(GREETING_SIZE); - greetingOutputBuffer.put((byte) 1); // Protocol version - greetingOutputBuffer.put((byte) options.type); // Socket type - greetingOutputBuffer.position(pos); - outsize += 2; - } - } - - // Position of the version field in the greeting. - final int versionPos = 10; - - // Is the peer using the unversioned protocol? - // If so, we send and receive rests of identity - // messages. - if ((greeting.get(0) & 0xff) != 0xff || (greeting.get(9) & 0x01) == 0) { - encoder = newEncoder(Config.OUT_BATCH_SIZE.getValue(), null, 0); - encoder.setMsgSource(session); - - decoder = newDecoder(Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize, null, 0); - decoder.setMsgSink(session); - - // We have already sent the message header. - // Since there is no way to tell the encoder to - // skip the message header, we simply throw that - // header data away. - final int headerSize = options.identitySize + 1 >= 255 ? 10 : 2; - ByteBuffer tmp = ByteBuffer.allocate(headerSize); - encoder.getData(tmp); - if (tmp.remaining() != headerSize) { - return false; - } - - // Make sure the decoder sees the data we have already received. - inbuf = greeting; - greeting.flip(); - insize = greeting.remaining(); - - // To allow for interoperability with peers that do not forward - // their subscriptions, we inject a phony subsription - // message into the incomming message stream. To put this - // message right after the identity message, we temporarily - // divert the message stream from session to ourselves. - if (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB) { - decoder.setMsgSink(this); - } - } - else - if (greeting.get(versionPos) == 0) { - // ZMTP/1.0 framing. - encoder = newEncoder(Config.OUT_BATCH_SIZE.getValue(), null, 0); - encoder.setMsgSource(session); - - decoder = newDecoder(Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize, null, 0); - decoder.setMsgSink(session); - } - else { - // v1 framing protocol. - encoder = newEncoder(Config.OUT_BATCH_SIZE.getValue(), session, V1Protocol.VERSION); - - decoder = newDecoder(Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize, session, V1Protocol.VERSION); - } - // Start polling for output if necessary. - if (outsize == 0) { - ioObject.setPollOut(handle); - } - - // Handshaking was successful. - // Switch into the normal message flow. - handshaking = false; - - return true; - } - - @Override - public int pushMsg(Msg msg) - { - assert (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB); - - // The first message is identity. - // Let the session process it. - int rc = session.pushMsg(msg); - assert (rc == 0); - - // Inject the subscription message so that the ZMQ 2.x peer - // receives our messages. - msg = new Msg(new byte[] { 1 }); - rc = session.pushMsg(msg); - session.flush(); - - // Once we have injected the subscription message, we can - // Divert the message flow back to the session. - assert (decoder != null); - decoder.setMsgSink(session); - - return rc; - } - - private void error() - { - assert (session != null); - socket.eventDisconnected(endpoint, handle); - session.detach(); - unplug(); - destroy(); - } - - private int write(Transfer buf) - { - int nbytes; - try { - nbytes = buf.transferTo(handle); - } - catch (IOException e) { - return -1; - } - - return nbytes; - } - - private int read(ByteBuffer buf) - { - int nbytes; - try { - nbytes = handle.read(buf); - } - catch (IOException e) { - return -1; - } - - return nbytes; - } -} diff --git a/src/main/java/zmq/TcpListener.java b/src/main/java/zmq/TcpListener.java deleted file mode 100644 index f762a234d..000000000 --- a/src/main/java/zmq/TcpListener.java +++ /dev/null @@ -1,198 +0,0 @@ -package zmq; - -import java.io.IOException; -import java.net.Socket; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; - -public class TcpListener extends Own implements IPollEvents -{ - private static boolean isWindows; - static - { - String os = System.getProperty("os.name").toLowerCase(); - isWindows = os.indexOf("win") >= 0; - } - - // Address to listen on. - private final TcpAddress address; - - // Underlying socket. - private ServerSocketChannel handle; - - // Socket the listerner belongs to. - private SocketBase socket; - - // String representation of endpoint to bind to - private String endpoint; - - private final IOObject ioObject; - - public TcpListener(IOThread ioThread, SocketBase socket, final Options options) - { - super(ioThread, options); - - ioObject = new IOObject(ioThread); - address = new TcpAddress(); - handle = null; - this.socket = socket; - } - - @Override - public void destroy() - { - assert (handle == null); - } - - @Override - protected void processPlug() - { - // Start polling for incoming connections. - ioObject.setHandler(this); - ioObject.addHandle(handle); - ioObject.setPollAccept(handle); - } - - @Override - protected void processTerm(int linger) - { - ioObject.setHandler(this); - ioObject.removeHandle(handle); - close(); - super.processTerm(linger); - } - - @Override - public void acceptEvent() - { - SocketChannel fd = null; - - try { - fd = accept(); - Utils.tuneTcpSocket(fd); - Utils.tuneTcpKeepalives(fd, options.tcpKeepAlive, options.tcpKeepAliveCnt, options.tcpKeepAliveIdle, options.tcpKeepAliveIntvl); - } - catch (IOException e) { - // If connection was reset by the peer in the meantime, just ignore it. - // TODO: Handle specific errors like ENFILE/EMFILE etc. - socket.eventAcceptFailed(endpoint, ZError.exccode(e)); - return; - } - - // Create the engine object for this connection. - StreamEngine engine = null; - try { - engine = new StreamEngine(fd, options, endpoint); - } - catch (ZError.InstantiationException e) { - socket.eventAcceptFailed(endpoint, ZError.EINVAL); - return; - } - // Choose I/O thread to run connecter in. Given that we are already - // running in an I/O thread, there must be at least one available. - IOThread ioThread = chooseIoThread(options.affinity); - - // Create and launch a session object. - SessionBase session = SessionBase.create(ioThread, false, socket, - options, new Address(fd.socket().getRemoteSocketAddress())); - session.incSeqnum(); - launchChild(session); - sendAttach(session, engine, false); - socket.eventAccepted(endpoint, fd); - } - - // Close the listening socket. - private void close() - { - if (handle == null) { - return; - } - - try { - handle.close(); - socket.eventClosed(endpoint, handle); - } - catch (IOException e) { - socket.eventCloseFailed(endpoint, ZError.exccode(e)); - } - handle = null; - } - - public String getAddress() - { - return address.toString(); - } - - // Set address to listen on. - public int setAddress(final String addr) - { - address.resolve(addr, options.ipv4only > 0); - try { - handle = ServerSocketChannel.open(); - handle.configureBlocking(false); - if (!isWindows) { - handle.socket().setReuseAddress(true); - } - handle.socket().bind(address.address(), options.backlog); - } - catch (IOException e) { - close(); - return ZError.EADDRINUSE; - } - endpoint = address.toString(); - socket.eventListening(endpoint, handle); - return 0; - } - - // Accept the new connection. Returns the file descriptor of the - // newly created connection. The function may throw IOException - // if the connection was dropped while waiting in the listen backlog - // or was denied because of accept filters. - private SocketChannel accept() throws IOException - { - Socket sock = handle.socket().accept(); - - if (!options.tcpAcceptFilters.isEmpty()) { - boolean matched = false; - for (TcpAddress.TcpAddressMask am : options.tcpAcceptFilters) { - if (am.matchAddress(address.address())) { - matched = true; - break; - } - } - if (!matched) { - try { - sock.close(); - } - catch (IOException e) { - } - throw new IOException("address does not match accept filter"); - } - } - return sock.getChannel(); - } - - @Override - public void inEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void outEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void connectEvent() - { - throw new UnsupportedOperationException(); - } - - @Override - public void timerEvent(int id) - { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/zmq/Transfer.java b/src/main/java/zmq/Transfer.java deleted file mode 100644 index b05adcacc..000000000 --- a/src/main/java/zmq/Transfer.java +++ /dev/null @@ -1,82 +0,0 @@ -package zmq; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; - -public interface Transfer -{ - public int transferTo(WritableByteChannel s) throws IOException; - public int remaining(); - - public static class ByteBufferTransfer implements Transfer - { - private ByteBuffer buf; - - public ByteBufferTransfer(ByteBuffer buf) - { - this.buf = buf; - } - - @Override - public final int transferTo(WritableByteChannel s) throws IOException - { - return s.write(buf); - } - - @Override - public final int remaining() - { - return buf.remaining(); - } - } - - public static class FileChannelTransfer implements Transfer - { - private Transfer parent; - private FileChannel channel; - private long position; - private long count; - private int remaining; - - public FileChannelTransfer(ByteBuffer buf, FileChannel channel, long position, long count) - { - parent = new ByteBufferTransfer(buf); - this.channel = channel; - this.position = position; - this.count = count; - remaining = parent.remaining() + (int) this.count; - } - - @Override - public final int transferTo(WritableByteChannel s) throws IOException - { - int sent = 0; - if (parent.remaining() > 0) { - sent = parent.transferTo(s); - } - - if (parent.remaining() == 0) { - long fileSent = channel.transferTo(position, count, s); - position += fileSent; - count -= fileSent; - sent += fileSent; - } - - remaining -= sent; - - if (remaining == 0) { - channel.close(); - } - - return sent; - } - - @Override - public final int remaining() - { - return remaining; - } - } -} diff --git a/src/main/java/zmq/Utils.java b/src/main/java/zmq/Utils.java index e09150939..289060f11 100644 --- a/src/main/java/zmq/Utils.java +++ b/src/main/java/zmq/Utils.java @@ -2,138 +2,62 @@ import java.io.File; import java.io.IOException; -import java.lang.reflect.Array; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.SelectableChannel; import java.nio.channels.SocketChannel; -import java.security.SecureRandom; +import zmq.io.net.Address; +import zmq.io.net.tcp.TcpUtils; + +@Deprecated public class Utils { private Utils() { } - private static SecureRandom random = new SecureRandom(); - - public static int generateRandom() - { - return random.nextInt(); - } - - public static int findOpenPort() throws IOException - { - ServerSocket tmpSocket = new ServerSocket(0); - int portNumber = tmpSocket.getLocalPort(); - tmpSocket.close(); - return portNumber; - } - - public static void tuneTcpSocket(SocketChannel ch) throws SocketException + public static int randomInt() { - tuneTcpSocket(ch.socket()); + return zmq.util.Utils.randomInt(); } - public static void tuneTcpSocket(Socket fd) throws SocketException + public static byte[] randomBytes(int length) { - // Disable Nagle's algorithm. We are doing data batching on 0MQ level, - // so using Nagle wouldn't improve throughput in anyway, but it would - // hurt latency. - try { - fd.setTcpNoDelay(true); - } - catch (SocketException e) { - } + return zmq.util.Utils.randomBytes(length); } - public static void tuneTcpKeepalives(SocketChannel ch, int tcpKeepalive, - int tcpKeepaliveCnt, int tcpKeepaliveIdle, - int tcpKeepaliveIntvl) throws SocketException - { - tuneTcpKeepalives(ch.socket(), tcpKeepalive, tcpKeepaliveCnt, - tcpKeepaliveIdle, tcpKeepaliveIntvl); - } - - public static void tuneTcpKeepalives(Socket fd, int tcpKeepalive, - int tcpKeepaliveCnt, int tcpKeepaliveIdle, - int tcpKeepaliveIntvl) throws SocketException + public static int findOpenPort() throws IOException { - if (tcpKeepalive == 1) { - fd.setKeepAlive(true); - } - else if (tcpKeepalive == 0) { - fd.setKeepAlive(false); - } + return zmq.util.Utils.findOpenPort(); } - public static void unblockSocket(SelectableChannel s) throws IOException + public static void unblockSocket(SelectableChannel... channels) throws IOException { - s.configureBlocking(false); + TcpUtils.unblockSocket(channels); } - @SuppressWarnings("unchecked") public static T[] realloc(Class klass, T[] src, int size, boolean ended) { - T[] dest; - - if (size > src.length) { - dest = (T[]) Array.newInstance(klass, size); - if (ended) { - System.arraycopy(src, 0, dest, 0, src.length); - } - else { - System.arraycopy(src, 0, dest, size - src.length, src.length); - } - } - else if (size < src.length) { - dest = (T[]) Array.newInstance(klass, size); - if (ended) { - System.arraycopy(src, src.length - size, dest, 0, size); - } - else { - System.arraycopy(src, 0, dest, 0, size); - } - } - else { - dest = src; - } - return dest; + return zmq.util.Utils.realloc(klass, src, size, ended); } public static byte[] bytes(ByteBuffer buf) { - byte[] d = new byte[buf.limit()]; - buf.get(d); - return d; + return zmq.util.Utils.bytes(buf); } public static byte[] realloc(byte[] src, int size) { - byte[] dest = new byte[size]; - if (src != null) { - System.arraycopy(src, 0, dest, 0, src.length); - } - - return dest; + return zmq.util.Utils.realloc(src, size); } public static boolean delete(File path) { - if (!path.exists()) { - return false; - } - boolean ret = true; - if (path.isDirectory()) { - File[] files = path.listFiles(); - if (files != null) { - for (File f : files) { - ret = ret && delete(f); - } - } - } - return ret && path.delete(); + return zmq.util.Utils.delete(path); + } + + public static Address getPeerIpAddress(SocketChannel fd) + { + return zmq.util.Utils.getPeerIpAddress(fd); } } diff --git a/src/main/java/zmq/V1Decoder.java b/src/main/java/zmq/V1Decoder.java deleted file mode 100644 index cde882bb8..000000000 --- a/src/main/java/zmq/V1Decoder.java +++ /dev/null @@ -1,167 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -public class V1Decoder extends DecoderBase -{ - private static final int ONE_BYTE_SIZE_READY = 0; - private static final int EIGHT_BYTE_SIZE_READY = 1; - private static final int FLAGS_READY = 2; - private static final int MESSAGE_READY = 3; - - private final byte[] tmpbuf; - private final ByteBuffer tmpbufWrap; - private Msg inProgress; - private IMsgSink msgSink; - private final long maxmsgsize; - private int msgFlags; - - public V1Decoder(int bufsize, long maxmsgsize, IMsgSink session) - { - super(bufsize); - - this.maxmsgsize = maxmsgsize; - msgSink = session; - - tmpbuf = new byte[8]; - tmpbufWrap = ByteBuffer.wrap(tmpbuf); - tmpbufWrap.limit(1); - - // At the beginning, read one byte and go to ONE_BYTE_SIZE_READY state. - nextStep(tmpbufWrap, FLAGS_READY); - } - - // Set the receiver of decoded messages. - @Override - public void setMsgSink(IMsgSink msgSink) - { - this.msgSink = msgSink; - } - - @Override - protected boolean next() - { - switch(state()) { - case ONE_BYTE_SIZE_READY: - return oneByteSizeReady(); - case EIGHT_BYTE_SIZE_READY: - return eightByteSizeReady(); - case FLAGS_READY: - return flagsReady(); - case MESSAGE_READY: - return messageReady(); - default: - return false; - } - } - - private boolean oneByteSizeReady() - { - int size = tmpbuf[0]; - if (size < 0) { - size = (0xff) & size; - } - - // Message size must not exceed the maximum allowed size. - if (maxmsgsize >= 0) { - if (size > maxmsgsize) { - decodingError(); - return false; - } - } - - // inProgress is initialised at this point so in theory we should - // close it before calling msgInitWithSize, however, it's a 0-byte - // message and thus we can treat it as uninitialised... - inProgress = getMsgAllocator().allocate(size); - - inProgress.setFlags(msgFlags); - nextStep(inProgress, - MESSAGE_READY); - - return true; - } - - private boolean eightByteSizeReady() - { - // The payload size is encoded as 64-bit unsigned integer. - // The most significant byte comes first. - tmpbufWrap.position(0); - tmpbufWrap.limit(8); - final long msgSize = tmpbufWrap.getLong(0); - - // Message size must not exceed the maximum allowed size. - if (maxmsgsize >= 0) { - if (msgSize > maxmsgsize) { - decodingError(); - return false; - } - } - - // Message size must fit within range of size_t data type. - if (msgSize > Integer.MAX_VALUE) { - decodingError(); - return false; - } - - // inProgress is initialised at this point so in theory we should - // close it before calling init_size, however, it's a 0-byte - // message and thus we can treat it as uninitialised. - inProgress = getMsgAllocator().allocate((int) msgSize); - - inProgress.setFlags(msgFlags); - nextStep(inProgress, - MESSAGE_READY); - - return true; - } - - private boolean flagsReady() - { - // Store the flags from the wire into the message structure. - msgFlags = 0; - int first = tmpbuf[0]; - if ((first & V1Protocol.MORE_FLAG) > 0) { - msgFlags |= Msg.MORE; - } - - // The payload length is either one or eight bytes, - // depending on whether the 'large' bit is set. - tmpbufWrap.position(0); - if ((first & V1Protocol.LARGE_FLAG) > 0) { - tmpbufWrap.limit(8); - nextStep(tmpbufWrap, EIGHT_BYTE_SIZE_READY); - } - else { - tmpbufWrap.limit(1); - nextStep(tmpbufWrap, ONE_BYTE_SIZE_READY); - } - - return true; - } - - private boolean messageReady() - { - // Message is completely read. Push it further and start reading - // new message. (inProgress is a 0-byte message after this point.) - - if (msgSink == null) { - return false; - } - - int rc = msgSink.pushMsg(inProgress); - if (rc != 0) { - if (rc != ZError.EAGAIN) { - decodingError(); - } - - return false; - } - - tmpbufWrap.position(0); - tmpbufWrap.limit(1); - nextStep(tmpbufWrap, FLAGS_READY); - - return true; - } -} diff --git a/src/main/java/zmq/V1Encoder.java b/src/main/java/zmq/V1Encoder.java deleted file mode 100644 index 57bebaa79..000000000 --- a/src/main/java/zmq/V1Encoder.java +++ /dev/null @@ -1,96 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -// Encoder for 0MQ framing protocol. Converts messages into data stream. - -public class V1Encoder extends EncoderBase -{ - private static final int SIZE_READY = 0; - private static final int MESSAGE_READY = 1; - - private Msg inProgress; - private final byte[] tmpbuf; - private final ByteBuffer tmpbufWrap; - private IMsgSource msgSource; - - public V1Encoder(int bufsize, IMsgSource session) - { - super(bufsize); - tmpbuf = new byte[9]; - tmpbufWrap = ByteBuffer.wrap(tmpbuf); - msgSource = session; - - // Write 0 bytes to the batch and go to messageReady state. - nextStep((byte[]) null, 0, MESSAGE_READY, true); - } - - @Override - public void setMsgSource(IMsgSource msgSource) - { - this.msgSource = msgSource; - } - - @Override - protected boolean next() - { - switch(state()) { - case SIZE_READY: - return sizeReady(); - case MESSAGE_READY: - return messageReady(); - default: - return false; - } - } - - private boolean sizeReady() - { - // Write message body into the buffer. - nextStep(inProgress.buf(), MESSAGE_READY, !inProgress.hasMore()); - return true; - } - - private boolean messageReady() - { - // Read new message. If there is none, return false. - // Note that new state is set only if write is successful. That way - // unsuccessful write will cause retry on the next state machine - // invocation. - - if (msgSource == null) { - return false; - } - - inProgress = msgSource.pullMsg(); - if (inProgress == null) { - return false; - } - - int protocolFlags = 0; - if (inProgress.hasMore()) { - protocolFlags |= V1Protocol.MORE_FLAG; - } - if (inProgress.size() > 255) { - protocolFlags |= V1Protocol.LARGE_FLAG; - } - tmpbuf[0] = (byte) protocolFlags; - - // Encode the message length. For messages less then 256 bytes, - // the length is encoded as 8-bit unsigned integer. For larger - // messages, 64-bit unsigned integer in network byte order is used. - final int size = inProgress.size(); - tmpbufWrap.position(0); - if (size > 255) { - tmpbufWrap.limit(9); - tmpbufWrap.putLong(1, size); - nextStep(tmpbufWrap, SIZE_READY, false); - } - else { - tmpbufWrap.limit(2); - tmpbuf[1] = (byte) (size); - nextStep(tmpbufWrap, SIZE_READY, false); - } - return true; - } -} diff --git a/src/main/java/zmq/V1Protocol.java b/src/main/java/zmq/V1Protocol.java deleted file mode 100644 index b85c232a1..000000000 --- a/src/main/java/zmq/V1Protocol.java +++ /dev/null @@ -1,12 +0,0 @@ -package zmq; - -class V1Protocol -{ - private V1Protocol() - { - } - - public static final int VERSION = 1; - public static final int MORE_FLAG = 1; - public static final int LARGE_FLAG = 2; -} diff --git a/src/main/java/zmq/ZError.java b/src/main/java/zmq/ZError.java index 3bdccf774..0223c5735 100644 --- a/src/main/java/zmq/ZError.java +++ b/src/main/java/zmq/ZError.java @@ -39,36 +39,48 @@ public IOException(java.io.IOException e) } } - public static final int EINTR = 4; - public static final int EACCESS = 13; - public static final int EFAULT = 14; - public static final int EINVAL = 22; - public static final int EAGAIN = 35; - public static final int EINPROGRESS = 36; + public static final int ENOENT = 2; + public static final int EINTR = 4; + public static final int EACCESS = 13; + public static final int EFAULT = 14; + public static final int EINVAL = 22; + public static final int EAGAIN = 35; + public static final int EINPROGRESS = 36; public static final int EPROTONOSUPPORT = 43; - public static final int ENOTSUP = 45; - public static final int EADDRINUSE = 48; - public static final int EADDRNOTAVAIL = 49; - public static final int ENETDOWN = 50; - public static final int ENOBUFS = 55; - public static final int EISCONN = 56; - public static final int ENOTCONN = 57; - public static final int ECONNREFUSED = 61; - public static final int EHOSTUNREACH = 65; + public static final int ENOTSUP = 45; + public static final int EADDRINUSE = 48; + public static final int EADDRNOTAVAIL = 49; + public static final int ENETDOWN = 50; + public static final int ENOBUFS = 55; + public static final int EISCONN = 56; + public static final int ENOTCONN = 57; + public static final int ECONNREFUSED = 61; + public static final int EHOSTUNREACH = 65; private static final int ZMQ_HAUSNUMERO = 156384712; - public static final int ENOTSOCK = ZMQ_HAUSNUMERO + 5; - public static final int EFSM = ZMQ_HAUSNUMERO + 51; + public static final int ENOTSOCK = ZMQ_HAUSNUMERO + 5; + public static final int EMSGSIZE = ZMQ_HAUSNUMERO + 10; + public static final int EAFNOSUPPORT = ZMQ_HAUSNUMERO + 11; + public static final int ENETUNREACH = ZMQ_HAUSNUMERO + 12; + + public static final int ECONNABORTED = ZMQ_HAUSNUMERO + 13; + public static final int ECONNRESET = ZMQ_HAUSNUMERO + 14; + public static final int ETIMEDOUT = ZMQ_HAUSNUMERO + 16; + public static final int ENETRESET = ZMQ_HAUSNUMERO + 18; + + public static final int EFSM = ZMQ_HAUSNUMERO + 51; public static final int ENOCOMPATPROTO = ZMQ_HAUSNUMERO + 52; - public static final int ETERM = ZMQ_HAUSNUMERO + 53; - public static final int EMTHREAD = ZMQ_HAUSNUMERO + 54; + public static final int ETERM = ZMQ_HAUSNUMERO + 53; + public static final int EMTHREAD = ZMQ_HAUSNUMERO + 54; - public static final int EIOEXC = ZMQ_HAUSNUMERO + 105; + public static final int EIOEXC = ZMQ_HAUSNUMERO + 105; public static final int ESOCKET = ZMQ_HAUSNUMERO + 106; - public static final int EMFILE = ZMQ_HAUSNUMERO + 107; + public static final int EMFILE = ZMQ_HAUSNUMERO + 107; + + public static final int EPROTO = ZMQ_HAUSNUMERO + 108; - static int exccode(java.io.IOException e) + public static int exccode(java.io.IOException e) { if (e instanceof SocketException) { return ESOCKET; diff --git a/src/main/java/zmq/ZMQ.java b/src/main/java/zmq/ZMQ.java index 20ca57a40..994b2a01f 100644 --- a/src/main/java/zmq/ZMQ.java +++ b/src/main/java/zmq/ZMQ.java @@ -9,6 +9,12 @@ import java.nio.channels.Selector; import java.nio.charset.Charset; import java.util.HashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import zmq.io.Metadata; +import zmq.poll.PollItem; +import zmq.util.Clock; public class ZMQ { @@ -17,16 +23,16 @@ public class ZMQ /******************************************************************************/ /* Version macros for compile-time API version detection */ - public static final int ZMQ_VERSION_MAJOR = 3; - public static final int ZMQ_VERSION_MINOR = 2; - public static final int ZMQ_VERSION_PATCH = 5; + public static final int ZMQ_VERSION_MAJOR = 4; + public static final int ZMQ_VERSION_MINOR = 1; + public static final int ZMQ_VERSION_PATCH = 7; /* Context options */ - public static final int ZMQ_IO_THREADS = 1; + public static final int ZMQ_IO_THREADS = 1; public static final int ZMQ_MAX_SOCKETS = 2; /* Default for new contexts */ - public static final int ZMQ_IO_THREADS_DFLT = 1; + public static final int ZMQ_IO_THREADS_DFLT = 1; public static final int ZMQ_MAX_SOCKETS_DFLT = 1024; /******************************************************************************/ @@ -34,17 +40,18 @@ public class ZMQ /******************************************************************************/ /* Socket types. */ - public static final int ZMQ_PAIR = 0; - public static final int ZMQ_PUB = 1; - public static final int ZMQ_SUB = 2; - public static final int ZMQ_REQ = 3; - public static final int ZMQ_REP = 4; + public static final int ZMQ_PAIR = 0; + public static final int ZMQ_PUB = 1; + public static final int ZMQ_SUB = 2; + public static final int ZMQ_REQ = 3; + public static final int ZMQ_REP = 4; public static final int ZMQ_DEALER = 5; public static final int ZMQ_ROUTER = 6; - public static final int ZMQ_PULL = 7; - public static final int ZMQ_PUSH = 8; - public static final int ZMQ_XPUB = 9; - public static final int ZMQ_XSUB = 10; + public static final int ZMQ_PULL = 7; + public static final int ZMQ_PUSH = 8; + public static final int ZMQ_XPUB = 9; + public static final int ZMQ_XSUB = 10; + public static final int ZMQ_STREAM = 11; /* Deprecated aliases */ @Deprecated @@ -52,97 +59,127 @@ public class ZMQ @Deprecated public static final int ZMQ_XREP = ZMQ_ROUTER; + private static final int ZMQ_CUSTOM_OPTION = 1000; + /* Socket options. */ - public static final int ZMQ_AFFINITY = 4; - public static final int ZMQ_IDENTITY = 5; - public static final int ZMQ_SUBSCRIBE = 6; - public static final int ZMQ_UNSUBSCRIBE = 7; - public static final int ZMQ_RATE = 8; - public static final int ZMQ_RECOVERY_IVL = 9; - public static final int ZMQ_SNDBUF = 11; - public static final int ZMQ_RCVBUF = 12; - public static final int ZMQ_RCVMORE = 13; - public static final int ZMQ_FD = 14; - public static final int ZMQ_EVENTS = 15; - public static final int ZMQ_TYPE = 16; - public static final int ZMQ_LINGER = 17; - public static final int ZMQ_RECONNECT_IVL = 18; - public static final int ZMQ_BACKLOG = 19; - public static final int ZMQ_RECONNECT_IVL_MAX = 21; - public static final int ZMQ_MAXMSGSIZE = 22; - public static final int ZMQ_SNDHWM = 23; - public static final int ZMQ_RCVHWM = 24; - public static final int ZMQ_MULTICAST_HOPS = 25; - public static final int ZMQ_RCVTIMEO = 27; - public static final int ZMQ_SNDTIMEO = 28; - public static final int ZMQ_IPV4ONLY = 31; - public static final int ZMQ_LAST_ENDPOINT = 32; - public static final int ZMQ_ROUTER_MANDATORY = 33; - public static final int ZMQ_TCP_KEEPALIVE = 34; - public static final int ZMQ_TCP_KEEPALIVE_CNT = 35; - public static final int ZMQ_TCP_KEEPALIVE_IDLE = 36; + public static final int ZMQ_AFFINITY = 4; + public static final int ZMQ_IDENTITY = 5; + public static final int ZMQ_SUBSCRIBE = 6; + public static final int ZMQ_UNSUBSCRIBE = 7; + public static final int ZMQ_RATE = 8; + public static final int ZMQ_RECOVERY_IVL = 9; + public static final int ZMQ_SNDBUF = 11; + public static final int ZMQ_RCVBUF = 12; + public static final int ZMQ_RCVMORE = 13; + public static final int ZMQ_FD = 14; + public static final int ZMQ_EVENTS = 15; + public static final int ZMQ_TYPE = 16; + public static final int ZMQ_LINGER = 17; + public static final int ZMQ_RECONNECT_IVL = 18; + public static final int ZMQ_BACKLOG = 19; + public static final int ZMQ_RECONNECT_IVL_MAX = 21; + public static final int ZMQ_MAXMSGSIZE = 22; + public static final int ZMQ_SNDHWM = 23; + public static final int ZMQ_RCVHWM = 24; + public static final int ZMQ_MULTICAST_HOPS = 25; + public static final int ZMQ_RCVTIMEO = 27; + public static final int ZMQ_SNDTIMEO = 28; + public static final int ZMQ_LAST_ENDPOINT = 32; + public static final int ZMQ_ROUTER_MANDATORY = 33; + public static final int ZMQ_TCP_KEEPALIVE = 34; + public static final int ZMQ_TCP_KEEPALIVE_CNT = 35; + public static final int ZMQ_TCP_KEEPALIVE_IDLE = 36; public static final int ZMQ_TCP_KEEPALIVE_INTVL = 37; - public static final int ZMQ_TCP_ACCEPT_FILTER = 38; - public static final int ZMQ_DELAY_ATTACH_ON_CONNECT = 39; - public static final int ZMQ_XPUB_VERBOSE = 40; - public static final int ZMQ_REQ_CORRELATE = 52; - public static final int ZMQ_REQ_RELAXED = 53; + public static final int ZMQ_IMMEDIATE = 39 + ZMQ_CUSTOM_OPTION; // for compatibility with ZMQ_DELAY_ATTACH_ON_CONNECT + public static final int ZMQ_XPUB_VERBOSE = 40; + public static final int ZMQ_ROUTER_RAW = 41; + public static final int ZMQ_IPV6 = 42; + public static final int ZMQ_MECHANISM = 43; + public static final int ZMQ_PLAIN_SERVER = 44; + public static final int ZMQ_PLAIN_USERNAME = 45; + public static final int ZMQ_PLAIN_PASSWORD = 46; + public static final int ZMQ_CURVE_SERVER = 47; + public static final int ZMQ_CURVE_PUBLICKEY = 48; + public static final int ZMQ_CURVE_SECRETKEY = 49; + public static final int ZMQ_CURVE_SERVERKEY = 50; + public static final int ZMQ_PROBE_ROUTER = 51; + public static final int ZMQ_REQ_CORRELATE = 52; + public static final int ZMQ_REQ_RELAXED = 53; + public static final int ZMQ_CONFLATE = 54; + public static final int ZMQ_ZAP_DOMAIN = 55; // TODO: more constants - public static final int ZMQ_ROUTER_HANDOVER = 56; - public static final int ZMQ_XPUB_NODROP = 69; - public static final int ZMQ_BLOCKY = 70; + public static final int ZMQ_ROUTER_HANDOVER = 56; + public static final int ZMQ_TOS = 57; + public static final int ZMQ_CONNECT_RID = 61; + public static final int ZMQ_GSSAPI_SERVER = 62; + public static final int ZMQ_GSSAPI_PRINCIPAL = 63; + public static final int ZMQ_GSSAPI_SERVICE_PRINCIPAL = 64; + public static final int ZMQ_GSSAPI_PLAINTEXT = 65; + public static final int ZMQ_HANDSHAKE_IVL = 66; + public static final int ZMQ_SOCKS_PROXY = 67; + public static final int ZMQ_XPUB_NODROP = 69; + public static final int ZMQ_BLOCKY = 70; + @Deprecated public static final int ZMQ_XPUB_VERBOSE_UNSUBSCRIBE = 78; /* Custom options */ - public static final int ZMQ_ENCODER = 1001; - public static final int ZMQ_DECODER = 1002; - public static final int ZMQ_MSG_ALLOCATOR = 1003; + @Deprecated + public static final int ZMQ_ENCODER = ZMQ_CUSTOM_OPTION + 1; + @Deprecated + public static final int ZMQ_DECODER = ZMQ_CUSTOM_OPTION + 2; + @Deprecated + public static final int ZMQ_MSG_ALLOCATOR = ZMQ_CUSTOM_OPTION + 3; + public static final int ZMQ_MSG_ALLOCATION_HEAP_THRESHOLD = ZMQ_CUSTOM_OPTION + 4; /* Message options */ public static final int ZMQ_MORE = 1; /* Send/recv options. */ public static final int ZMQ_DONTWAIT = 1; - public static final int ZMQ_SNDMORE = 2; + public static final int ZMQ_SNDMORE = 2; /* Deprecated aliases */ - public static final int ZMQ_NOBLOCK = ZMQ_DONTWAIT; - public static final int ZMQ_FAIL_UNROUTABLE = ZMQ_ROUTER_MANDATORY; - public static final int ZMQ_ROUTER_BEHAVIOR = ZMQ_ROUTER_MANDATORY; + @Deprecated + public static final int ZMQ_TCP_ACCEPT_FILTER = 38; + @Deprecated + public static final int ZMQ_IPV4ONLY = 31; + @Deprecated + public static final int ZMQ_DELAY_ATTACH_ON_CONNECT = 39; + @Deprecated + public static final int ZMQ_NOBLOCK = ZMQ_DONTWAIT; + @Deprecated + public static final int ZMQ_FAIL_UNROUTABLE = ZMQ_ROUTER_MANDATORY; + @Deprecated + public static final int ZMQ_ROUTER_BEHAVIOR = ZMQ_ROUTER_MANDATORY; /******************************************************************************/ /* 0MQ socket events and monitoring */ /******************************************************************************/ /* Socket transport events (tcp and ipc only) */ - public static final int ZMQ_EVENT_CONNECTED = 1; + public static final int ZMQ_EVENT_CONNECTED = 1; public static final int ZMQ_EVENT_CONNECT_DELAYED = 2; public static final int ZMQ_EVENT_CONNECT_RETRIED = 4; - - public static final int ZMQ_EVENT_LISTENING = 8; - public static final int ZMQ_EVENT_BIND_FAILED = 16; - - public static final int ZMQ_EVENT_ACCEPTED = 32; - public static final int ZMQ_EVENT_ACCEPT_FAILED = 64; - - public static final int ZMQ_EVENT_CLOSED = 128; - public static final int ZMQ_EVENT_CLOSE_FAILED = 256; - public static final int ZMQ_EVENT_DISCONNECTED = 512; + public static final int ZMQ_EVENT_LISTENING = 8; + public static final int ZMQ_EVENT_BIND_FAILED = 16; + public static final int ZMQ_EVENT_ACCEPTED = 32; + public static final int ZMQ_EVENT_ACCEPT_FAILED = 64; + public static final int ZMQ_EVENT_CLOSED = 128; + public static final int ZMQ_EVENT_CLOSE_FAILED = 256; + public static final int ZMQ_EVENT_DISCONNECTED = 512; public static final int ZMQ_EVENT_MONITOR_STOPPED = 1024; + public static final int ZMQ_EVENT_ALL = 0xffff; - public static final int ZMQ_EVENT_ALL = ZMQ_EVENT_CONNECTED | ZMQ_EVENT_CONNECT_DELAYED | - ZMQ_EVENT_CONNECT_RETRIED | ZMQ_EVENT_LISTENING | - ZMQ_EVENT_BIND_FAILED | ZMQ_EVENT_ACCEPTED | - ZMQ_EVENT_ACCEPT_FAILED | ZMQ_EVENT_CLOSED | - ZMQ_EVENT_CLOSE_FAILED | ZMQ_EVENT_DISCONNECTED | ZMQ_EVENT_MONITOR_STOPPED; - - public static final int ZMQ_POLLIN = 1; + public static final int ZMQ_POLLIN = 1; public static final int ZMQ_POLLOUT = 2; public static final int ZMQ_POLLERR = 4; - public static final int ZMQ_STREAMER = 1; + @Deprecated + public static final int ZMQ_STREAMER = 1; + @Deprecated public static final int ZMQ_FORWARDER = 2; - public static final int ZMQ_QUEUE = 3; + @Deprecated + public static final int ZMQ_QUEUE = 3; public static final byte[] MESSAGE_SEPARATOR = new byte[0]; @@ -155,10 +192,10 @@ public static class Event private static final int VALUE_INTEGER = 1; private static final int VALUE_CHANNEL = 2; - public final int event; + public final int event; public final String addr; public final Object arg; - private final int flag; + private final int flag; public Event(int event, String addr, Object arg) { @@ -208,7 +245,7 @@ public static Event read(SocketBase s, int flags) int event = buffer.getInt(); int len = buffer.get(); - byte [] addr = new byte [len]; + byte[] addr = new byte[len]; buffer.get(addr); int flag = buffer.get(); Object arg = null; @@ -234,28 +271,34 @@ public static Ctx createContext() return ctx; } - private static void destroyContext(Ctx ctx) + private static void checkContext(Ctx ctx) { if (ctx == null || !ctx.checkTag()) { throw new IllegalStateException(); } + } + private static void destroyContext(Ctx ctx) + { + checkContext(ctx); ctx.terminate(); } + private static void shutdownContext(Ctx ctx) + { + checkContext(ctx); + ctx.shutdown(); + } + public static void setContextOption(Ctx ctx, int option, int optval) { - if (ctx == null || !ctx.checkTag()) { - throw new IllegalStateException(); - } + checkContext(ctx); ctx.set(option, optval); } public static int getContextOption(Ctx ctx, int option) { - if (ctx == null || !ctx.checkTag()) { - throw new IllegalStateException(); - } + checkContext(ctx); return ctx.get(option); } @@ -267,7 +310,7 @@ public static Ctx init(int ioThreads) setContextOption(ctx, ZMQ_IO_THREADS, ioThreads); return ctx; } - throw new IllegalArgumentException("io_threds must not be negative"); + throw new IllegalArgumentException("io_threads must not be negative"); } public static void term(Ctx ctx) @@ -278,38 +321,41 @@ public static void term(Ctx ctx) // Sockets public static SocketBase socket(Ctx ctx, int type) { - if (ctx == null || !ctx.checkTag()) { - throw new IllegalStateException(); - } + checkContext(ctx); SocketBase s = ctx.createSocket(type); return s; } - public static void close(SocketBase s) + private static void checkSocket(SocketBase s) { if (s == null || !s.checkTag()) { throw new IllegalStateException(); } - s.close(); } - public static void setSocketOption(SocketBase s, int option, Object optval) + public static void closeZeroLinger(SocketBase s) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); + s.setSocketOpt(ZMQ.ZMQ_LINGER, 0); + s.close(); + } - s.setSocketOpt(option, optval); + public static void close(SocketBase s) + { + checkSocket(s); + s.close(); + } + public static boolean setSocketOption(SocketBase s, int option, Object optval) + { + checkSocket(s); + return s.setSocketOpt(option, optval); } public static Object getSocketOptionExt(SocketBase s, int option) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } - - return s.getsockoptx(option); + checkSocket(s); + return s.getSocketOptx(option); } public static int getSocketOption(SocketBase s, int opt) @@ -319,50 +365,40 @@ public static int getSocketOption(SocketBase s, int opt) public static boolean monitorSocket(SocketBase s, final String addr, int events) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); return s.monitor(addr, events); } public static boolean bind(SocketBase s, final String addr) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); return s.bind(addr); } public static boolean connect(SocketBase s, String addr) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); return s.connect(addr); } public static boolean unbind(SocketBase s, String addr) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); return s.termEndpoint(addr); } public static boolean disconnect(SocketBase s, String addr) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); return s.termEndpoint(addr); } // Sending functions. public static int send(SocketBase s, String str, int flags) { - byte [] data = str.getBytes(CHARSET); + byte[] data = str.getBytes(CHARSET); return send(s, data, data.length, flags); } @@ -378,9 +414,7 @@ public static int send(SocketBase s, Msg msg, int flags) public static int send(SocketBase s, byte[] buf, int len, int flags) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); Msg msg = new Msg(len); msg.put(buf, 0, len); @@ -401,9 +435,7 @@ public static int send(SocketBase s, byte[] buf, int len, int flags) // public int sendiov(SocketBase s, byte[][] a, int count, int flags) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); int rc = 0; Msg msg; @@ -414,14 +446,30 @@ public int sendiov(SocketBase s, byte[][] a, int count, int flags) } rc = sendMsg(s, msg, flags); if (rc < 0) { - rc = -1; - break; + rc = -1; + break; } } return rc; } + public static boolean sendMsg(SocketBase socket, byte[]... data) + { + int rc = 0; + if (data.length == 0) { + return false; + } + for (int idx = 0; idx < data.length - 1; ++idx) { + rc = send(socket, new Msg(data[idx]), ZMQ_MORE); + if (rc < 0) { + return false; + } + } + rc = send(socket, new Msg(data[data.length - 1]), 0); + return rc >= 0; + } + public static int sendMsg(SocketBase s, Msg msg, int flags) { int sz = msgSize(msg); @@ -435,9 +483,7 @@ public static int sendMsg(SocketBase s, Msg msg, int flags) // Receiving functions. public static Msg recv(SocketBase s, int flags) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); Msg msg = recvMsg(s, flags); if (msg == null) { return null; @@ -450,31 +496,29 @@ public static Msg recv(SocketBase s, int flags) return msg; } - // Receive a multi-part message - // - // Receives up to *count_ parts of a multi-part message. - // Sets *count_ to the actual number of parts read. - // ZMQ_RCVMORE is set to indicate if a complete multi-part message was read. - // Returns number of message parts read, or -1 on error. - // - // Note: even if -1 is returned, some parts of the message - // may have been read. Therefore the client must consult - // *count_ to retrieve message parts successfully read, - // even if -1 is returned. - // - // The iov_base* buffers of each iovec *a_ filled in by this - // function may be freed using free(). - // - // Implementation note: We assume zmq::msg_t buffer allocated - // by zmq::recvmsg can be freed by free(). - // We assume it is safe to steal these buffers by simply - // not closing the zmq::msg_t. - // + // Receive a multi-part message + // + // Receives up to *count_ parts of a multi-part message. + // Sets *count_ to the actual number of parts read. + // ZMQ_RCVMORE is set to indicate if a complete multi-part message was read. + // Returns number of message parts read, or -1 on error. + // + // Note: even if -1 is returned, some parts of the message + // may have been read. Therefore the client must consult + // *count_ to retrieve message parts successfully read, + // even if -1 is returned. + // + // The iov_base* buffers of each iovec *a_ filled in by this + // function may be freed using free(). + // + // Implementation note: We assume zmq::msg_t buffer allocated + // by zmq::recvmsg can be freed by free(). + // We assume it is safe to steal these buffers by simply + // not closing the zmq::msg_t. + // public int recviov(SocketBase s, byte[][] a, int count, int flags) { - if (s == null || !s.checkTag()) { - throw new IllegalStateException(); - } + checkSocket(s); int nread = 0; boolean recvmore = true; @@ -520,39 +564,32 @@ public static int msgSize(Msg msg) public static int getMessageOption(Msg msg, int option) { switch (option) { - case ZMQ_MORE: - return msg.hasMore() ? 1 : 0; - default: - throw new IllegalArgumentException(); + case ZMQ_MORE: + return msg.hasMore() ? 1 : 0; + default: + throw new IllegalArgumentException(); } } - public static void sleep(int s) + // Get message metadata string + public static String getMessageMetadata(Msg msg, String property) { - try { - Thread.sleep(s * (1000L)); - } - catch (InterruptedException e) { + String data = null; + Metadata metadata = msg.getMetadata(); + if (metadata != null) { + data = metadata.get(property); } + return data; } - // The proxy functionality - public static boolean proxy(SocketBase frontend, SocketBase backend, SocketBase control) + public static void sleep(int seconds) { - if (frontend == null || backend == null) { - throw new IllegalArgumentException(); - } - return Proxy.proxy( - frontend, - backend, - control); + LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(seconds, TimeUnit.SECONDS)); } - @Deprecated - public static boolean device(int device, SocketBase insocket, - SocketBase outsocket) + public static void msleep(int milliseconds) { - return Proxy.proxy(insocket, outsocket, null); + LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(milliseconds, TimeUnit.MILLISECONDS)); } /** @@ -568,6 +605,7 @@ public static int poll(Selector selector, PollItem[] items, long timeout) { return poll(selector, items, items.length, timeout); } + /** * Polling on items with given selector * CAUTION: This could be affected by jdk epoll bug @@ -587,11 +625,7 @@ public static int poll(Selector selector, PollItem[] items, int count, long time if (timeout <= 0) { return 0; } - try { - Thread.sleep(timeout); - } - catch (InterruptedException e) { - } + LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS)); return 0; } long now = 0L; @@ -723,6 +757,31 @@ else if (waitMillis == 0) { return nevents; } + // The proxy functionality + public static boolean proxy(SocketBase frontend, SocketBase backend, SocketBase capture) + { + if (frontend == null || backend == null) { + throw new IllegalArgumentException(); + } + return Proxy.proxy(frontend, backend, capture, null); + } + + public static boolean proxy(SocketBase frontend, SocketBase backend, SocketBase capture, SocketBase control) + { + if (frontend == null || backend == null) { + throw new IllegalArgumentException(); + } + return Proxy.proxy(frontend, backend, capture, control); + } + + public static boolean device(int device, SocketBase frontend, SocketBase backend) + { + if (frontend == null || backend == null) { + throw new IllegalArgumentException(); + } + return Proxy.proxy(frontend, backend, null, null); + } + public static long startStopwatch() { return System.nanoTime(); diff --git a/src/main/java/zmq/ZObject.java b/src/main/java/zmq/ZObject.java index 5b20751c1..98b028030 100644 --- a/src/main/java/zmq/ZObject.java +++ b/src/main/java/zmq/ZObject.java @@ -1,5 +1,11 @@ package zmq; +import zmq.io.IEngine; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.pipe.Pipe; +import zmq.pipe.YPipeBase; + // Base class for all objects that participate in inter-thread // communication. public abstract class ZObject @@ -8,7 +14,7 @@ public abstract class ZObject private final Ctx ctx; // Thread ID of the thread the object belongs to. - private final int tid; + private int tid; protected ZObject(Ctx ctx, int tid) { @@ -21,19 +27,26 @@ protected ZObject(ZObject parent) this(parent.ctx, parent.tid); } - protected int getTid() + public final int getTid() { return tid; } - protected Ctx getCtx() + protected final void setTid(int tid) + { + this.tid = tid; + } + + protected final Ctx getCtx() { return ctx; } - protected void processCommand(Command cmd) + @SuppressWarnings("unchecked") + final void processCommand(Command cmd) { - switch (cmd.type()) { + // System.out.println(Thread.currentThread().getName() + ": Processing command " + cmd); + switch (cmd.type) { case ACTIVATE_READ: processActivateRead(); break; @@ -67,7 +80,7 @@ protected void processCommand(Command cmd) break; case HICCUP: - processHiccup(cmd.arg); + processHiccup((YPipeBase) cmd.arg); break; case PIPE_TERM: @@ -98,38 +111,58 @@ protected void processCommand(Command cmd) processReaped(); break; + case INPROC_CONNECTED: + processSeqnum(); + break; + + case DONE: default: throw new IllegalArgumentException(); } } - protected boolean registerEndpoint(String addr, Ctx.Endpoint endpoint) + protected final boolean registerEndpoint(String addr, Ctx.Endpoint endpoint) { return ctx.registerEndpoint(addr, endpoint); } - protected void unregisterEndpoints(SocketBase socket) + protected final boolean unregisterEndpoint(String addr, SocketBase socket) + { + return ctx.unregisterEndpoint(addr, socket); + } + + protected final void unregisterEndpoints(SocketBase socket) { ctx.unregisterEndpoints(socket); } - protected Ctx.Endpoint findEndpoint(String addr) + protected final Ctx.Endpoint findEndpoint(String addr) { return ctx.findEndpoint(addr); } - protected void destroySocket(SocketBase socket) + protected final void pendConnection(String addr, Ctx.Endpoint endpoint, Pipe[] pipes) + { + ctx.pendConnection(addr, endpoint, pipes); + } + + protected final void connectPending(String addr, SocketBase bindSocket) + { + ctx.connectPending(addr, bindSocket); + } + + protected final void destroySocket(SocketBase socket) { ctx.destroySocket(socket); } // Chooses least loaded I/O thread. - protected IOThread chooseIoThread(long affinity) + protected final IOThread chooseIoThread(long affinity) { return ctx.chooseIoThread(affinity); } - protected void sendStop() + protected final void sendStop() { // 'stop' command goes always from administrative thread to // the current object. @@ -137,12 +170,12 @@ protected void sendStop() ctx.sendCommand(tid, cmd); } - protected void sendPlug(Own destination) + protected final void sendPlug(Own destination) { sendPlug(destination, true); } - protected void sendPlug(Own destination, boolean incSeqnum) + protected final void sendPlug(Own destination, boolean incSeqnum) { if (incSeqnum) { destination.incSeqnum(); @@ -152,19 +185,19 @@ protected void sendPlug(Own destination, boolean incSeqnum) sendCommand(cmd); } - protected void sendOwn(Own destination, Own object) + protected final void sendOwn(Own destination, Own object) { destination.incSeqnum(); Command cmd = new Command(destination, Command.Type.OWN, object); sendCommand(cmd); } - protected void sendAttach(SessionBase destination, IEngine engine) + protected final void sendAttach(SessionBase destination, IEngine engine) { sendAttach(destination, engine, true); } - protected void sendAttach(SessionBase destination, IEngine engine, boolean incSeqnum) + protected final void sendAttach(SessionBase destination, IEngine engine, boolean incSeqnum) { if (incSeqnum) { destination.incSeqnum(); @@ -174,12 +207,12 @@ protected void sendAttach(SessionBase destination, IEngine engine, boolean incSe sendCommand(cmd); } - protected void sendBind(Own destination, Pipe pipe) + protected final void sendBind(Own destination, Pipe pipe) { sendBind(destination, pipe, true); } - protected void sendBind(Own destination, Pipe pipe, boolean incSeqnum) + protected final void sendBind(Own destination, Pipe pipe, boolean incSeqnum) { if (incSeqnum) { destination.incSeqnum(); @@ -189,67 +222,73 @@ protected void sendBind(Own destination, Pipe pipe, boolean incSeqnum) sendCommand(cmd); } - protected void sendActivateRead(Pipe destination) + protected final void sendActivateRead(Pipe destination) { Command cmd = new Command(destination, Command.Type.ACTIVATE_READ); sendCommand(cmd); } - protected void sendActivateWrite(Pipe destination, long msgsRead) + protected final void sendActivateWrite(Pipe destination, long msgsRead) { Command cmd = new Command(destination, Command.Type.ACTIVATE_WRITE, msgsRead); sendCommand(cmd); } - protected void sendHiccup(Pipe destination, Object pipe) + protected final void sendHiccup(Pipe destination, YPipeBase pipe) { Command cmd = new Command(destination, Command.Type.HICCUP, pipe); sendCommand(cmd); } - protected void sendPipeTerm(Pipe destination) + protected final void sendPipeTerm(Pipe destination) { Command cmd = new Command(destination, Command.Type.PIPE_TERM); sendCommand(cmd); } - protected void sendPipeTermAck(Pipe destination) + protected final void sendPipeTermAck(Pipe destination) { Command cmd = new Command(destination, Command.Type.PIPE_TERM_ACK); sendCommand(cmd); } - protected void sendTermReq(Own destination, Own object) + protected final void sendTermReq(Own destination, Own object) { Command cmd = new Command(destination, Command.Type.TERM_REQ, object); sendCommand(cmd); } - protected void sendTerm(Own destination, int linger) + protected final void sendTerm(Own destination, int linger) { Command cmd = new Command(destination, Command.Type.TERM, linger); sendCommand(cmd); } - protected void sendTermAck(Own destination) + protected final void sendTermAck(Own destination) { Command cmd = new Command(destination, Command.Type.TERM_ACK); sendCommand(cmd); } - protected void sendReap(SocketBase socket) + protected final void sendReap(SocketBase socket) { Command cmd = new Command(ctx.getReaper(), Command.Type.REAP, socket); sendCommand(cmd); } - protected void sendReaped() + protected final void sendReaped() { Command cmd = new Command(ctx.getReaper(), Command.Type.REAPED); sendCommand(cmd); } - protected void sendDone() + protected final void sendInprocConnected(SocketBase socket) + { + Command cmd = new Command(socket, Command.Type.INPROC_CONNECTED); + sendCommand(cmd); + } + + protected final void sendDone() { Command cmd = new Command(null, Command.Type.DONE); ctx.sendCommand(Ctx.TERM_TID, cmd); @@ -290,7 +329,7 @@ protected void processActivateWrite(long msgsRead) throw new UnsupportedOperationException(); } - protected void processHiccup(Object hiccupPipe) + protected void processHiccup(YPipeBase hiccupPipe) { throw new UnsupportedOperationException(); } @@ -340,6 +379,6 @@ protected void processSeqnum() private void sendCommand(Command cmd) { - ctx.sendCommand(cmd.destination().getTid(), cmd); + ctx.sendCommand(cmd.destination.getTid(), cmd); } } diff --git a/src/main/java/zmq/IEngine.java b/src/main/java/zmq/io/IEngine.java similarity index 64% rename from src/main/java/zmq/IEngine.java rename to src/main/java/zmq/io/IEngine.java index c4949c4cf..ab4dad36a 100644 --- a/src/main/java/zmq/IEngine.java +++ b/src/main/java/zmq/io/IEngine.java @@ -1,4 +1,4 @@ -package zmq; +package zmq.io; // Abstract interface to be implemented by various engines. public interface IEngine @@ -10,11 +10,13 @@ public interface IEngine // events are not fired on termination. void terminate(); - // This method is called by the session to signalise that more + // This method is called by the session to signal that more // messages can be written to the pipe. - void activateIn(); + void restartInput(); - // This method is called by the session to signalise that there + // This method is called by the session to signal that there // are messages to send available. - void activateOut(); + void restartOutput(); + + void zapMsgAvailable(); } diff --git a/src/main/java/zmq/IOObject.java b/src/main/java/zmq/io/IOObject.java similarity index 57% rename from src/main/java/zmq/IOObject.java rename to src/main/java/zmq/io/IOObject.java index e358269ee..c113bd76e 100644 --- a/src/main/java/zmq/IOObject.java +++ b/src/main/java/zmq/io/IOObject.java @@ -1,81 +1,79 @@ -package zmq; +package zmq.io; import java.nio.channels.SelectableChannel; +import zmq.poll.IPollEvents; +import zmq.poll.Poller; +import zmq.poll.Poller.Handle; + // Simple base class for objects that live in I/O threads. // It makes communication with the poller object easier and // makes defining unneeded event handlers unnecessary. - public class IOObject implements IPollEvents { - private Poller poller; - private IPollEvents handler; - - public IOObject(IOThread ioThread) - { - if (ioThread != null) { - plug(ioThread); - } - } + private final Poller poller; + private final IPollEvents handler; - // When migrating an object from one I/O thread to another, first - // unplug it, then migrate it, then plug it to the new thread. + private boolean alive; - public void plug(IOThread ioThread) + public IOObject(IOThread ioThread, IPollEvents handler) { assert (ioThread != null); - assert (poller == null); + assert (handler != null); + this.handler = handler; // Retrieve the poller from the thread we are running in. poller = ioThread.getPoller(); } - public void unplug() + // When migrating an object from one I/O thread to another, first + // unplug it, then migrate it, then plug it to the new thread. + public final void plug() { - assert (poller != null); + alive = true; + } - // Forget about old poller in preparation to be migrated - // to a different I/O thread. - poller = null; - handler = null; + public final void unplug() + { + alive = false; } - public final void addHandle(SelectableChannel handle) + public final Handle addFd(SelectableChannel fd) { - poller.addHandle(handle, this); + return poller.addHandle(fd, this); } - public final void removeHandle(SelectableChannel handle) + public final void removeHandle(Handle handle) { poller.removeHandle(handle); } - public final void setPollIn(SelectableChannel handle) + public final void setPollIn(Handle handle) { poller.setPollIn(handle); } - public final void setPollOut(SelectableChannel handle) + public final void setPollOut(Handle handle) { poller.setPollOut(handle); } - public final void setPollConnect(SelectableChannel handle) + public final void setPollConnect(Handle handle) { poller.setPollConnect(handle); } - public final void setPollAccept(SelectableChannel handle) + public final void setPollAccept(Handle handle) { poller.setPollAccept(handle); } - public final void resetPollIn(SelectableChannel handle) + public final void resetPollIn(Handle handle) { - poller.resetPollOn(handle); + poller.resetPollIn(handle); } - public final void resetPollOut(SelectableChannel handle) + public final void resetPollOut(Handle handle) { poller.resetPollOut(handle); } @@ -83,45 +81,53 @@ public final void resetPollOut(SelectableChannel handle) @Override public final void inEvent() { + assert (alive); handler.inEvent(); } @Override public final void outEvent() { + assert (alive); handler.outEvent(); } @Override public final void connectEvent() { + assert (alive); handler.connectEvent(); } @Override public final void acceptEvent() { + assert (alive); handler.acceptEvent(); } @Override public final void timerEvent(int id) { + assert (alive); handler.timerEvent(id); } public final void addTimer(long timeout, int id) { + assert (alive); poller.addTimer(timeout, this, id); } - public final void setHandler(IPollEvents handler) + public final void cancelTimer(int id) { - this.handler = handler; + assert (alive); + poller.cancelTimer(this, id); } - public void cancelTimer(int id) + @Override + public String toString() { - poller.cancelTimer(this, id); + return "" + handler; } } diff --git a/src/main/java/zmq/IOThread.java b/src/main/java/zmq/io/IOThread.java similarity index 80% rename from src/main/java/zmq/IOThread.java rename to src/main/java/zmq/io/IOThread.java index 8c3052fb8..ecbc39cd1 100644 --- a/src/main/java/zmq/IOThread.java +++ b/src/main/java/zmq/io/IOThread.java @@ -1,31 +1,38 @@ -package zmq; +package zmq.io; import java.io.Closeable; import java.io.IOException; import java.nio.channels.SelectableChannel; +import zmq.Command; +import zmq.Ctx; +import zmq.Mailbox; +import zmq.ZObject; +import zmq.poll.IPollEvents; +import zmq.poll.Poller; + public class IOThread extends ZObject implements IPollEvents, Closeable { // I/O thread accesses incoming commands via this mailbox. private final Mailbox mailbox; // Handle associated with mailbox' file descriptor. - private final SelectableChannel mailboxHandle; + private final Poller.Handle mailboxHandle; // I/O multiplexing is performed using a poller object. private final Poller poller; - final String name; + private final String name; public IOThread(Ctx ctx, int tid) { super(ctx, tid); name = "iothread-" + tid; - poller = new Poller(name); + poller = new Poller(ctx, name); - mailbox = new Mailbox(name); - mailboxHandle = mailbox.getFd(); - poller.addHandle(mailboxHandle, this); + mailbox = new Mailbox(ctx, name, tid); + SelectableChannel fd = mailbox.getFd(); + mailboxHandle = poller.addHandle(fd, this); poller.setPollIn(mailboxHandle); } @@ -70,8 +77,7 @@ public void inEvent() } // Process the command. - - cmd.destination().processCommand(cmd); + cmd.process(); } } @@ -99,12 +105,13 @@ public void timerEvent(int id) throw new UnsupportedOperationException(); } - public Poller getPoller() + Poller getPoller() { assert (poller != null); return poller; } + @Override protected void processStop() { poller.removeHandle(mailboxHandle); diff --git a/src/main/java/zmq/io/Metadata.java b/src/main/java/zmq/io/Metadata.java new file mode 100644 index 000000000..09d3f669b --- /dev/null +++ b/src/main/java/zmq/io/Metadata.java @@ -0,0 +1,64 @@ +package zmq.io; + +import java.util.Objects; +import java.util.Properties; + +public class Metadata +{ + // Dictionary holding metadata. + private final Properties dictionary = new Properties(); + + public Metadata() + { + super(); + } + + public Metadata(Properties dictionary) + { + this.dictionary.putAll(dictionary); + } + + // Returns property value or NULL if + // property is not found. + public final String get(String key) + { + return dictionary.getProperty(key); + } + + public final void set(String key, String value) + { + dictionary.setProperty(key, value); + } + + @Override + public int hashCode() + { + return Objects.hashCode(dictionary); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof Metadata) { + Metadata other = (Metadata) obj; + return dictionary.equals(other.dictionary); + } + return false; + } + + public final void set(Metadata zapProperties) + { + dictionary.putAll(zapProperties.dictionary); + } + + public final boolean isEmpty() + { + return dictionary.isEmpty(); + } + + @Override + public String toString() + { + return "Metadata=" + dictionary; + } +} diff --git a/src/main/java/zmq/io/SessionBase.java b/src/main/java/zmq/io/SessionBase.java new file mode 100644 index 000000000..3c45af99f --- /dev/null +++ b/src/main/java/zmq/io/SessionBase.java @@ -0,0 +1,634 @@ +package zmq.io; + +import java.util.HashSet; +import java.util.Set; + +import zmq.Ctx; +import zmq.Msg; +import zmq.Options; +import zmq.Own; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.ZObject; +import zmq.io.StreamEngine.ErrorReason; +import zmq.io.mechanism.Mechanisms; +import zmq.io.net.Address; +import zmq.io.net.NetProtocol; +import zmq.io.net.ipc.IpcConnecter; +import zmq.io.net.norm.NormEngine; +import zmq.io.net.pgm.PgmReceiver; +import zmq.io.net.pgm.PgmSender; +import zmq.io.net.tcp.SocksConnecter; +import zmq.io.net.tcp.TcpConnecter; +import zmq.io.net.tipc.TipcConnecter; +import zmq.pipe.Pipe; +import zmq.poll.IPollEvents; + +public class SessionBase extends Own implements Pipe.IPipeEvents, IPollEvents +{ + // If true, this session (re)connects to the peer. Otherwise, it's + // a transient session created by the listener. + private boolean active; + + // Pipe connecting the session to its socket. + private Pipe pipe; + + // Pipe used to exchange messages with ZAP socket. + private Pipe zapPipe; + + // This set is added to with pipes we are disconnecting, but haven't yet completed + private final Set terminatingPipes; + + // This flag is true if the remainder of the message being processed + // is still in the in pipe. + private boolean incompleteIn; + + // True if termination have been suspended to push the pending + // messages to the network. + private boolean pending; + + // The protocol I/O engine connected to the session. + private IEngine engine; + + // The socket the session belongs to. + protected final SocketBase socket; + + // I/O thread the session is living in. It will be used to plug in + // the engines into the same thread. + private final IOThread ioThread; + + // ID of the linger timer + private static final int LINGER_TIMER_ID = 0x20; + + // True is linger timer is running. + private boolean hasLingerTimer; + + // Protocol and address to use when connecting. + private final Address addr; + + private final IOObject ioObject; + + public SessionBase(IOThread ioThread, boolean connect, SocketBase socket, Options options, Address addr) + { + super(ioThread, options); + ioObject = new IOObject(ioThread, this); + + this.active = connect; + pipe = null; + zapPipe = null; + incompleteIn = false; + pending = false; + engine = null; + this.socket = socket; + this.ioThread = ioThread; + hasLingerTimer = false; + this.addr = addr; + + terminatingPipes = new HashSet<>(); + } + + @Override + public void destroy() + { + assert (pipe == null); + assert (zapPipe == null); + + // If there's still a pending linger timer, remove it. + if (hasLingerTimer) { + ioObject.cancelTimer(LINGER_TIMER_ID); + hasLingerTimer = false; + } + + // Close the engine. + if (engine != null) { + engine.terminate(); + } + ioObject.unplug(); + } + + // To be used once only, when creating the session. + public void attachPipe(Pipe pipe) + { + assert (!isTerminating()); + assert (this.pipe == null); + assert (pipe != null); + this.pipe = pipe; + this.pipe.setEventSink(this); + } + + protected Msg pullMsg() + { + if (pipe == null) { + return null; + } + + Msg msg = pipe.read(); + if (msg == null) { + return null; + } + incompleteIn = msg.hasMore(); + + return msg; + + } + + protected boolean pushMsg(Msg msg) + { + if (pipe != null && pipe.write(msg)) { + return true; + } + errno.set(ZError.EAGAIN); + return false; + } + + public Msg readZapMsg() + { + if (zapPipe == null) { + errno.set(ZError.ENOTCONN); + return null; + } + Msg msg = zapPipe.read(); + if (msg == null) { + errno.set(ZError.EAGAIN); + } + return msg; + } + + public boolean writeZapMsg(Msg msg) + { + if (zapPipe == null) { + errno.set(ZError.ENOTCONN); + return false; + } + boolean rc = zapPipe.write(msg); + assert (rc); + if ((msg.flags() & ZMQ.ZMQ_SNDMORE) == 0) { + zapPipe.flush(); + } + return true; + } + + protected void reset() + { + } + + public void flush() + { + if (pipe != null) { + pipe.flush(); + } + } + + // Remove any half processed messages. Flush unflushed messages. + // Call this function when engine disconnect to get rid of leftovers. + private void cleanPipes() + { + assert (pipe != null); + + // Get rid of half-processed messages in the out pipe. Flush any + // unflushed messages upstream. + pipe.rollback(); + pipe.flush(); + // Remove any half-read message from the in pipe. + while (incompleteIn) { + Msg msg = pullMsg(); + if (msg == null) { + assert (!incompleteIn); + break; + } + // msg.close (); + } + } + + @Override + public void pipeTerminated(Pipe pipe) + { + // Drop the reference to the deallocated pipe. + assert (this.pipe == pipe || this.zapPipe == pipe || terminatingPipes.contains(pipe)); + + if (this.pipe == pipe) { + // If this is our current pipe, remove it + this.pipe = null; + if (hasLingerTimer) { + ioObject.cancelTimer(LINGER_TIMER_ID); + hasLingerTimer = false; + } + } + else if (zapPipe == pipe) { + zapPipe = null; + } + else { + // Remove the pipe from the detached pipes set + terminatingPipes.remove(pipe); + } + + if (!isTerminating() && options.rawSocket) { + if (engine != null) { + engine.terminate(); + engine = null; + } + } + + // If we are waiting for pending messages to be sent, at this point + // we are sure that there will be no more messages and we can proceed + // with termination safely. + if (pending && this.pipe == null && this.zapPipe == null && terminatingPipes.isEmpty()) { + pending = false; + super.processTerm(0); + } + } + + @Override + public void readActivated(Pipe pipe) + { + // Skip activating if we're detaching this pipe + if (this.pipe != pipe && this.zapPipe != pipe) { + assert (terminatingPipes.contains(pipe)); + return; + } + + if (engine == null) { + this.pipe.checkRead(); + return; + } + if (this.pipe == pipe) { + engine.restartOutput(); + } + else { + engine.zapMsgAvailable(); + } + } + + @Override + public void writeActivated(Pipe pipe) + { + // Skip activating if we're detaching this pipe + if (this.pipe != pipe) { + assert (terminatingPipes.contains(pipe)); + return; + } + + if (engine != null) { + engine.restartInput(); + } + } + + @Override + public void hiccuped(Pipe pipe) + { + // Hiccups are always sent from session to socket, not the other + // way round. + throw new UnsupportedOperationException("Must Override"); + + } + + public SocketBase getSocket() + { + return socket; + } + + @Override + protected void processPlug() + { + ioObject.plug(); + if (active) { + startConnecting(false); + } + } + + public int zapConnect() + { + assert (zapPipe == null); + + Ctx.Endpoint peer = findEndpoint("inproc://zeromq.zap.01"); + if (peer.socket == null) { + errno.set(ZError.ECONNREFUSED); + return ZError.ECONNREFUSED; + } + if (peer.options.type != ZMQ.ZMQ_REP && peer.options.type != ZMQ.ZMQ_ROUTER) { + errno.set(ZError.ECONNREFUSED); + return ZError.ECONNREFUSED; + } + + // Create a bi-directional pipe that will connect + // session with zap socket. + ZObject[] parents = { this, peer.socket }; + int[] hwms = { 0, 0 }; + boolean[] conflates = { false, false }; + Pipe[] pipes = Pipe.pair(parents, hwms, conflates); + + // Attach local end of the pipe to this socket object. + zapPipe = pipes[0]; + zapPipe.setNoDelay(); + zapPipe.setEventSink(this); + + sendBind(peer.socket, pipes[1], false); + + // Send empty identity if required by the peer. + if (peer.options.recvIdentity) { + Msg id = new Msg(); + id.setFlags(Msg.IDENTITY); + zapPipe.write(id); + zapPipe.flush(); + } + return 0; + } + + protected boolean zapEnabled() + { + return options.mechanism != Mechanisms.NULL + || (options.mechanism == Mechanisms.NULL && options.zapDomain != null && !options.zapDomain.isEmpty()); + } + + @Override + protected void processAttach(IEngine engine) + { + assert (engine != null); + + // Create the pipe if it does not exist yet. + if (pipe == null && !isTerminating()) { + ZObject[] parents = { this, socket }; + boolean conflate = options.conflate && (options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_PULL + || options.type == ZMQ.ZMQ_PUSH || options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_SUB); + + int[] hwms = { conflate ? -1 : options.recvHwm, conflate ? -1 : options.sendHwm }; + boolean[] conflates = { conflate, conflate }; + Pipe[] pipes = Pipe.pair(parents, hwms, conflates); + + // Plug the local end of the pipe. + pipes[0].setEventSink(this); + + // Remember the local end of the pipe. + assert (pipe == null); + pipe = pipes[0]; + + // Ask socket to plug into the remote end of the pipe. + sendBind(socket, pipes[1]); + } + + // Plug in the engine. + assert (this.engine == null); + this.engine = engine; + this.engine.plug(ioThread, this); + } + + public void engineError(ErrorReason reason) + { + // Engine is dead. Let's forget about it. + engine = null; + + // Remove any half-done messages from the pipes. + if (pipe != null) { + cleanPipes(); + } + + assert (reason == ErrorReason.CONNECTION || reason == ErrorReason.TIMEOUT || reason == ErrorReason.PROTOCOL); + + switch (reason) { + case TIMEOUT: + case CONNECTION: + if (active) { + reconnect(); + } + else { + terminate(); + } + break; + case PROTOCOL: + terminate(); + break; + default: + break; + } + + // Just in case there's only a delimiter in the pipe. + if (pipe != null) { + pipe.checkRead(); + } + if (zapPipe != null) { + zapPipe.checkRead(); + } + } + + @Override + protected void processTerm(int linger) + { + assert (!pending); + + // If the termination of the pipe happens before the term command is + // delivered there's nothing much to do. We can proceed with the + // standard termination immediately. + if (pipe == null && zapPipe == null && terminatingPipes.isEmpty()) { + super.processTerm(0); + return; + } + + pending = true; + + if (pipe != null) { + // If there's finite linger value, delay the termination. + // If linger is infinite (negative) we don't even have to set + // the timer. + if (linger > 0) { + assert (!hasLingerTimer); + ioObject.addTimer(linger, LINGER_TIMER_ID); + hasLingerTimer = true; + } + + // Start pipe termination process. Delay the termination till all messages + // are processed in case the linger time is non-zero. + pipe.terminate(linger != 0); + + // TODO: Should this go into pipe_t::terminate ? + // In case there's no engine and there's only delimiter in the + // pipe it wouldn't be ever read. Thus we check for it explicitly. + if (engine == null) { + pipe.checkRead(); + } + } + + if (zapPipe != null) { + zapPipe.terminate(false); + } + } + + @Override + public void timerEvent(int id) + { + // Linger period expired. We can proceed with termination even though + // there are still pending messages to be sent. + assert (id == LINGER_TIMER_ID); + hasLingerTimer = false; + + // Ask pipe to terminate even though there may be pending messages in it. + assert (pipe != null); + pipe.terminate(false); + } + + private void reconnect() + { + // TODO V4 - // Transient session self-destructs after peer disconnects. ? + + // For delayed connect situations, terminate the pipe + // and reestablish later on + if (pipe != null && !options.immediate && !NetProtocol.pgm.equals(addr.protocol()) + && !NetProtocol.epgm.equals(addr.protocol()) && !NetProtocol.norm.equals(addr.protocol())) { + pipe.hiccup(); + pipe.terminate(false); + terminatingPipes.add(pipe); + pipe = null; + } + + reset(); + + // Reconnect. + if (options.reconnectIvl != -1) { + startConnecting(true); + } + + // For subscriber sockets we hiccup the inbound pipe, which will cause + // the socket object to resend all the subscriptions. + if (pipe != null && (options.type == ZMQ.ZMQ_SUB || options.type == ZMQ.ZMQ_XSUB)) { + pipe.hiccup(); + } + } + + private void startConnecting(boolean wait) + { + assert (active); + + // Choose I/O thread to run connecter in. Given that we are already + // running in an I/O thread, there must be at least one available. + IOThread ioThread = chooseIoThread(options.affinity); + assert (ioThread != null); + + // Create the connecter object. + + NetProtocol protocol = addr.protocol(); + if (protocol == null) { + errno.set(ZError.EPROTONOSUPPORT); + return; + } + switch (protocol) { + case tcp: + if (options.socksProxyAddress != null) { + Address proxyAddress = new Address(NetProtocol.tcp.name(), options.socksProxyAddress); + SocksConnecter connecter = new SocksConnecter(ioThread, this, options, addr, proxyAddress, wait); + launchChild(connecter); + } + else { + TcpConnecter connecter = new TcpConnecter(ioThread, this, options, addr, wait); + launchChild(connecter); + } + break; + case ipc: { + IpcConnecter connecter = new IpcConnecter(ioThread, this, options, addr, wait); + launchChild(connecter); + } + break; + + case tipc: { + TipcConnecter connecter = new TipcConnecter(ioThread, this, options, addr, wait); + launchChild(connecter); + } + break; + + case pgm: + case epgm: { + assert (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB || options.type == ZMQ.ZMQ_SUB + || options.type == ZMQ.ZMQ_XSUB); + + // For EPGM transport with UDP encapsulation of PGM is used. + boolean udpEncapsulation = protocol == NetProtocol.epgm; + + // At this point we'll create message pipes to the session straight + // away. There's no point in delaying it as no concept of 'connect' + // exists with PGM anyway. + if (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB) { + // PGM sender. + PgmSender pgmSender = new PgmSender(ioThread, options); + boolean rc = pgmSender.init(udpEncapsulation, addr); + assert (rc); + sendAttach(this, pgmSender); + } + else { + // PGM receiver. + PgmReceiver pgmReceiver = new PgmReceiver(ioThread, options); + boolean rc = pgmReceiver.init(udpEncapsulation, addr); + assert (rc); + sendAttach(this, pgmReceiver); + + } + } + break; + + case norm: { + // At this point we'll create message pipes to the session straight + // away. There's no point in delaying it as no concept of 'connect' + // exists with NORM anyway. + if (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB) { + // NORM sender. + NormEngine normSender = new NormEngine(ioThread, options); + boolean rc = normSender.init(addr, true, false); + assert (rc); + sendAttach(this, normSender); + } + else { + // NORM receiver. + NormEngine normReceiver = new NormEngine(ioThread, options); + boolean rc = normReceiver.init(addr, false, true); + assert (rc); + sendAttach(this, normReceiver); + + } + } + break; + + default: + assert (false); + break; + } + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "-" + socket; + } + + @Override + public void inEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void outEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void connectEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void acceptEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public final void incSeqnum() + { + super.incSeqnum(); + } + + public void errno(int error) + { + socket.errno.set(error); + } +} diff --git a/src/main/java/zmq/io/StreamEngine.java b/src/main/java/zmq/io/StreamEngine.java new file mode 100644 index 000000000..c552f67b7 --- /dev/null +++ b/src/main/java/zmq/io/StreamEngine.java @@ -0,0 +1,1215 @@ +package zmq.io; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import zmq.Config; +import zmq.Msg; +import zmq.Options; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.coder.IDecoder; +import zmq.io.coder.IDecoder.Step; +import zmq.io.coder.IEncoder; +import zmq.io.coder.raw.RawDecoder; +import zmq.io.coder.raw.RawEncoder; +import zmq.io.coder.v1.V1Decoder; +import zmq.io.coder.v1.V1Encoder; +import zmq.io.coder.v2.V2Decoder; +import zmq.io.coder.v2.V2Encoder; +import zmq.io.mechanism.Mechanism; +import zmq.io.mechanism.Mechanisms; +import zmq.io.net.Address; +import zmq.poll.IPollEvents; +import zmq.poll.Poller; +import zmq.util.Blob; +import zmq.util.Errno; +import zmq.util.Utils; +import zmq.util.ValueReference; +import zmq.util.Wire; + +// This engine handles any socket with SOCK_STREAM semantics, +// e.g. TCP socket or an UNIX domain socket. +public class StreamEngine implements IEngine, IPollEvents +{ + private final class HandshakeCommand extends MessageProcessor.Adapter + { + @Override + public Msg nextMsg() + { + return nextHandshakeCommand(); + } + + @Override + public boolean processMsg(Msg msg) + { + return processHandshakeCommand(msg); + } + } + + private final class PushMsgToSession extends MessageProcessor.Adapter + { + @Override + public Msg nextMsg() + { + return pullMsgFromSession(); + } + + @Override + public boolean processMsg(Msg msg) + { + return pushMsgToSession(msg); + } + } + + private final class PushRawMsgToSession extends MessageProcessor.Adapter + { + @Override + public boolean processMsg(Msg msg) + { + return pushRawMsgToSession(msg); + } + } + + private final class WriteCredential extends MessageProcessor.Adapter + { + @Override + public boolean processMsg(Msg msg) + { + return writeCredential(msg); + } + } + + private final class DecodeAndPush extends MessageProcessor.Adapter + { + @Override + public boolean processMsg(Msg msg) + { + assert (mechanism != null); + + msg = mechanism.decode(msg); + if (msg == null) { + return false; + } + if (metadata != null) { + msg.setMetadata(metadata); + } + boolean rc = session.pushMsg(msg); + if (!rc) { + if (errno.is(ZError.EAGAIN)) { + processMsg = pushOneThenDecodeAndPush; + } + return false; + } + return true; + } + } + + private final class PullAndEncode extends MessageProcessor.Adapter + { + @Override + public Msg nextMsg() + { + assert (mechanism != null); + + Msg msg = session.pullMsg(); + if (msg == null) { + return null; + + } + msg = mechanism.encode(msg); + return msg; + } + } + + private final class PushOneThenDecodeAndPush extends MessageProcessor.Adapter + { + @Override + public boolean processMsg(Msg msg) + { + boolean rc = session.pushMsg(msg); + if (rc) { + processMsg = decodeAndPush; + } + return rc; + } + } + + private final class Identity implements MessageProcessor + { + @Override + public Msg nextMsg() + { + return identityMsg(); + } + + @Override + public boolean processMsg(Msg msg) + { + return processIdentityMsg(msg); + } + } + + // used to implement FSM actions + private static interface MessageProcessor + { + Msg nextMsg(); + + boolean processMsg(Msg msg); + + public static class Adapter implements MessageProcessor + { + @Override + public Msg nextMsg() + { + throw new UnsupportedOperationException("nextMsg is not implemented and should not be used here"); + } + + @Override + public boolean processMsg(Msg msg) + { + throw new UnsupportedOperationException("processMsg is not implemented and should not be used here"); + } + } + } + + // Protocol revisions + private static enum Protocol + { + V0(0), + V1(1), + V2(2), + V3(3); + + private final byte revision; + + Protocol(int revision) + { + this.revision = (byte) revision; + } + } + + public static enum ErrorReason + { + PROTOCOL, + CONNECTION, + TIMEOUT, + } + + private IOObject ioObject; + + // Underlying socket. + private SocketChannel fd; + + // True if this is server's engine. + // private boolean asServer; + + // private Msg txMsg; + + private Poller.Handle handle; + + private ByteBuffer inpos; + private int insize; + private IDecoder decoder; + + private final ValueReference outpos; + private int outsize; + private IEncoder encoder; + + private Metadata metadata; + + // When true, we are still trying to determine whether + // the peer is using versioned protocol, and if so, which + // version. When false, normal message flow has started. + private boolean handshaking; + + private static final int SIGNATURE_SIZE = 10; + // Size of ZMTP/1.0 and ZMTP/2.0 greeting message + private static final int V2_GREETING_SIZE = 12; + // Size of ZMTP/3.0 greeting message + private static final int V3_GREETING_SIZE = 64; + + // Expected greeting size. + private int greetingSize; + + // Greeting received from, and sent to peer + private final ByteBuffer greetingRecv; + private final ByteBuffer greetingSend; + + // handy reminder of the used ZMTP protocol version + private Protocol zmtpVersion; + + // The session this engine is attached to. + private SessionBase session; + + private Options options; + + // String representation of endpoint + private String endpoint; + + private boolean plugged; + + private MessageProcessor nextMsg; + private MessageProcessor processMsg; + + private boolean ioError; + + // Indicates whether the engine is to inject a phantom + // subscription message into the incoming stream. + // Needed to support old peers. + private boolean subscriptionRequired; + + private Mechanism mechanism; + + // True if the engine couldn't consume the last decoded message. + private boolean inputStopped; + + // True if the engine doesn't have any message to encode. + private boolean outputStopped; + + // ID of the handshake timer + private static final int HANDSHAKE_TIMER_ID = 0x40; + + // True is linger timer is running. + private boolean hasHandshakeTimer; + + // Socket + private SocketBase socket; + + private Address peerAddress; + + private final Errno errno; + + public StreamEngine(SocketChannel fd, final Options options, final String endpoint) + { + this.errno = options.errno; + this.fd = fd; + this.handshaking = true; + greetingSize = V2_GREETING_SIZE; + this.options = options; + this.endpoint = endpoint; + nextMsg = identity; + processMsg = identity; + + outpos = new ValueReference<>(); + + greetingRecv = ByteBuffer.allocate(V3_GREETING_SIZE); + greetingSend = ByteBuffer.allocate(V3_GREETING_SIZE); + + // Put the socket into non-blocking mode. + try { + Utils.unblockSocket(this.fd); + } + catch (IOException e) { + throw new ZError.IOException(e); + } + + peerAddress = Utils.getPeerIpAddress(fd); + } + + public void destroy() + { + assert (!plugged); + + if (fd != null) { + try { + fd.close(); + } + catch (IOException e) { + assert (false); + } + fd = null; + } + if (encoder != null) { + encoder.destroy(); + } + if (decoder != null) { + decoder.destroy(); + } + if (mechanism != null) { + mechanism.destroy(); + } + } + + @Override + public void plug(IOThread ioThread, SessionBase session) + { + assert (!plugged); + plugged = true; + + // Connect to session object. + assert (this.session == null); + assert (session != null); + this.session = session; + socket = session.getSocket(); + + // Connect to I/O threads poller object. + ioObject = new IOObject(ioThread, this); + ioObject.plug(); + handle = ioObject.addFd(fd); + ioError = false; + + if (options.rawSocket) { + decoder = (IDecoder) instantiate(options.decoder, Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize); + if (decoder == null) { + decoder = new RawDecoder(Config.IN_BATCH_SIZE.getValue()); + } + encoder = (IEncoder) instantiate(options.encoder, Config.OUT_BATCH_SIZE.getValue(), options.maxMsgSize); + if (encoder == null) { + encoder = new RawEncoder(errno, Config.OUT_BATCH_SIZE.getValue()); + } + + // disable handshaking for raw socket + handshaking = false; + + nextMsg = pullMsgFromSession; + processMsg = pushRawMsgToSession; + + if (peerAddress != null && !peerAddress.address().isEmpty()) { + assert (metadata == null); + // Compile metadata + metadata = new Metadata(); + metadata.set("Peer-Address", peerAddress.address()); + } + + // For raw sockets, send an initial 0-length message to the + // application so that it knows a peer has connected. + Msg connector = new Msg(); + pushRawMsgToSession(connector); + session.flush(); + } + else { + // start optional timer, to prevent handshake hanging on no input + setHandshakeTimer(); + + // Send the 'length' and 'flags' fields of the identity message. + // The 'length' field is encoded in the long format. + greetingSend.put((byte) 0xff); + Wire.putUInt64(greetingSend, options.identitySize + 1); + greetingSend.put((byte) 0x7f); + + outpos.set(greetingSend); + outsize = greetingSend.position(); + greetingSend.flip(); + } + + ioObject.setPollIn(handle); + ioObject.setPollOut(handle); + + // Flush all the data that may have been already received downstream. + inEvent(); + } + + private Object instantiate(Class decoder, int size, long max) + { + if (decoder == null) { + return null; + } + try { + return decoder.getConstructor(int.class, long.class).newInstance(size, max); + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void unplug() + { + assert (plugged); + plugged = false; + + // Cancel all timers. + if (hasHandshakeTimer) { + ioObject.cancelTimer(HANDSHAKE_TIMER_ID); + hasHandshakeTimer = false; + } + + if (!ioError) { + // Cancel all fd subscriptions. + ioObject.removeHandle(handle); + handle = null; + } + + // Disconnect from I/O threads poller object. + ioObject.unplug(); + + session = null; + } + + @Override + public void terminate() + { + unplug(); + destroy(); + } + + @Override + public void inEvent() + { + assert (!ioError); + + // If still handshaking, receive and process the greeting message. + if (handshaking) { + if (!handshake()) { + return; + } + } + + assert (decoder != null); + + // If there has been an I/O error, stop polling. + if (inputStopped) { + ioObject.removeHandle(handle); + handle = null; + ioError = true; + return; + } + + // If there's no data to process in the buffer... + if (insize == 0) { + // Retrieve the buffer and read as much data as possible. + // Note that buffer can be arbitrarily large. However, we assume + // the underlying TCP layer has fixed buffer size and thus the + // number of bytes read will be always limited. + + inpos = decoder.getBuffer(); + int rc = read(inpos); + + if (rc == 0) { + error(ErrorReason.CONNECTION); + } + + if (rc == -1) { + if (!errno.is(ZError.EAGAIN)) { + error(ErrorReason.CONNECTION); + } + return; + } + // Adjust input size + inpos.flip(); + insize = rc; + } + + boolean rc = false; + ValueReference processed = new ValueReference<>(0); + + while (insize > 0) { + // Push the data to the decoder. + Step.Result result = decoder.decode(inpos, insize, processed); + assert (processed.get() <= insize); + insize -= processed.get(); + + if (result == Step.Result.MORE_DATA) { + rc = true; + break; + } + if (result == Step.Result.ERROR) { + rc = false; + break; + } + + Msg msg = decoder.msg(); + rc = processMsg.processMsg(msg); + if (!rc) { + break; + } + } + + // Tear down the connection if we have failed to decode input data + // or the session has rejected the message. + if (!rc) { + if (!errno.is(ZError.EAGAIN)) { + error(ErrorReason.PROTOCOL); + return; + } + + inputStopped = true; + ioObject.resetPollIn(handle); + } + + // Flush all messages the decoder may have produced. + session.flush(); + } + + @Override + public void outEvent() + { + assert (!ioError); + + // If write buffer is empty, try to read new data from the encoder. + if (outsize == 0) { + // Even when we stop polling as soon as there is no + // data to send, the poller may invoke outEvent one + // more time due to 'speculative write' optimization. + if (encoder == null) { + assert (handshaking); + return; + } + outpos.set(null); + outsize = encoder.encode(outpos, 0); + + while (outsize < Config.OUT_BATCH_SIZE.getValue()) { + Msg msg = nextMsg.nextMsg(); + if (msg == null) { + break; + } + encoder.loadMsg(msg); + int n = encoder.encode(outpos, Config.OUT_BATCH_SIZE.getValue() - outsize); + assert (n > 0); + outsize += n; + } + + // If there is no data to send, stop polling for output. + if (outsize == 0) { + outputStopped = true; + ioObject.resetPollOut(handle); + + return; + } + + ByteBuffer buf = outpos.get(); + assert (buf != null); + + if (outsize <= Config.OUT_BATCH_SIZE.getValue()) { + buf.flip(); + } + } + + // If there are any data to write in write buffer, write as much as + // possible to the socket. Note that amount of data to write can be + // arbitrarily large. However, we assume that underlying TCP layer has + // limited transmission buffer and thus the actual number of bytes + // written should be reasonably modest. + int nbytes = write(outpos.get()); + + // IO error has occurred. We stop waiting for output events. + // The engine is not terminated until we detect input error; + // this is necessary to prevent losing incoming messages. + if (nbytes == -1) { + ioObject.resetPollOut(handle); + return; + } + + outsize -= nbytes; + + // If we are still handshaking and there are no data + // to send, stop polling for output. + if (handshaking) { + if (outsize == 0) { + ioObject.resetPollOut(handle); + } + } + } + + @Override + public void restartOutput() + { + if (ioError) { + return; + } + + if (outputStopped) { + ioObject.setPollOut(handle); + outputStopped = false; + } + + // Speculative write: The assumption is that at the moment new message + // was sent by the user the socket is probably available for writing. + // Thus we try to write the data to socket avoiding polling for POLLOUT. + // Consequently, the latency should be better in request/reply scenarios. + outEvent(); + } + + @Override + public void restartInput() + { + assert (inputStopped); + assert (session != null); + assert (decoder != null); + + boolean rc = false; + Msg msg = decoder.msg(); + rc = processMsg.processMsg(msg); + if (!rc) { + if (errno.is(ZError.EAGAIN)) { + session.flush(); + } + else { + error(ErrorReason.PROTOCOL); + } + return; + } + + while (insize > 0) { + ValueReference processed = new ValueReference<>(0); + Step.Result result = decoder.decode(inpos, insize, processed); + assert (processed.get() <= insize); + insize -= processed.get(); + if (result == Step.Result.MORE_DATA) { + rc = true; + break; + } + if (result == Step.Result.ERROR) { + rc = false; + break; + } + msg = decoder.msg(); + rc = processMsg.processMsg(msg); + if (!rc) { + break; + } + } + if (!rc && errno.is(ZError.EAGAIN)) { + session.flush(); + } + else if (ioError) { + error(ErrorReason.CONNECTION); + } + else if (!rc) { + error(ErrorReason.PROTOCOL); + } + else { + inputStopped = false; + ioObject.setPollIn(handle); + session.flush(); + + // Speculative read. + inEvent(); + } + } + + // Detects the protocol used by the peer. + private boolean handshake() + { + assert (handshaking); + assert (greetingRecv.position() < greetingSize); + + // Position of the version field in the greeting. + final int revisionPos = SIGNATURE_SIZE; + + // Receive the greeting. + while (greetingRecv.position() < greetingSize) { + final int n = read(greetingRecv); + if (n == 0) { + error(ErrorReason.CONNECTION); + return false; + } + if (n == -1) { + if (!errno.is(ZError.EAGAIN)) { + error(ErrorReason.CONNECTION); + } + return false; + } + + // We have received at least one byte from the peer. + // If the first byte is not 0xff, we know that the + // peer is using unversioned protocol. + if ((greetingRecv.get(0) & 0xff) != 0xff) { + // If this first byte is not %FF, + // then the other peer is using ZMTP 1.0. + break; + } + + if (greetingRecv.position() < SIGNATURE_SIZE) { + continue; + } + + // Inspect the right-most bit of the 10th byte (which coincides + // with the 'flags' field if a regular message was sent). + // Zero indicates this is a header of identity message + // (i.e. the peer is using the unversioned protocol). + if ((greetingRecv.get(9) & 0x01) != 0x01) { + break; + } + + // If the least significant bit is 1, the peer is using ZMTP 2.0 or later + // and has sent us the ZMTP signature. + + int outpos = greetingSend.position(); + // The peer is using versioned protocol. + // Send the major version number. + if (greetingSend.limit() == SIGNATURE_SIZE) { + if (outsize == 0) { + ioObject.setPollOut(handle); + } + greetingSend.limit(SIGNATURE_SIZE + 1); + greetingSend.put(revisionPos, Protocol.V3.revision); // Major version number + outsize += 1; + continue; + } + + if (greetingRecv.position() > SIGNATURE_SIZE) { + if (greetingSend.limit() == SIGNATURE_SIZE + 1) { + if (outsize == 0) { + ioObject.setPollOut(handle); + } + // We read a further byte, which indicates the ZMTP version. + byte protocol = greetingRecv.get(revisionPos); + if (protocol == Protocol.V0.revision) { + // TODO V4: we should never lead here... + break; + } + else if (protocol == Protocol.V1.revision || protocol == Protocol.V2.revision) { + // If this is 1 or 2, we have a ZMTP 2.0 peer. + greetingSend.limit(V2_GREETING_SIZE); + greetingSend.position(SIGNATURE_SIZE + 1); + greetingSend.put((byte) options.type); // Socket type + outsize += 1; + } + else { + // If this is 3 or greater, we have a ZMTP 3.0 peer. + greetingSend.limit(V3_GREETING_SIZE); + greetingSend.position(SIGNATURE_SIZE + 1); + greetingSend.put((byte) 0); // Minor version number + outsize += 1; + greetingSend.mark(); + greetingSend.put(new byte[20]); + + assert (options.mechanism == Mechanisms.NULL || options.mechanism == Mechanisms.PLAIN + || options.mechanism == Mechanisms.CURVE || options.mechanism == Mechanisms.GSSAPI); + greetingSend.reset(); + greetingSend.put(options.mechanism.name().getBytes(ZMQ.CHARSET)); + greetingSend.reset(); + greetingSend.position(greetingSend.position() + 20); + outsize += 20; + greetingSend.put(new byte[32]); + outsize += 32; + + greetingSize = V3_GREETING_SIZE; + } + } + } + greetingSend.position(outpos); + } + + // Is the peer using the unversioned protocol? + // If so, we send and receive rest of identity + // messages. + if ((greetingRecv.get(0) & 0xff) != 0xff || (greetingRecv.get(9) & 0x01) == 0) { + // If this first byte is %FF, then we read nine further bytes, + // and inspect the last byte (the 10th in total). + // If the least significant bit is 0, then the other peer is using ZMTP 1.0. + if (session.zapEnabled()) { + // reject ZMTP 1.0 connections if ZAP is enabled + error(ErrorReason.PROTOCOL); + return false; + } + + zmtpVersion = Protocol.V0; + + encoder = new V1Encoder(errno, Config.OUT_BATCH_SIZE.getValue()); + decoder = new V1Decoder(errno, Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize, + options.allocationHeapThreshold); + + // We have already sent the message header. + // Since there is no way to tell the encoder to + // skip the message header, we simply throw that + // header data away. + final int headerSize = options.identitySize + 1 >= 255 ? 10 : 2; + ByteBuffer tmp = ByteBuffer.allocate(headerSize); + + // Prepare the identity message and load it into encoder. + // Then consume bytes we have already sent to the peer. + Msg txMsg = new Msg(options.identitySize); + txMsg.put(options.identity, 0, options.identitySize); + encoder.loadMsg(txMsg); + + ValueReference bufferp = new ValueReference<>(tmp); + int bufferSize = encoder.encode(bufferp, headerSize); + assert (bufferSize == headerSize); + + // Make sure the decoder sees the data we have already received. + greetingRecv.flip(); + inpos = greetingRecv; + insize = greetingRecv.limit(); + + // To allow for interoperability with peers that do not forward + // their subscriptions, we inject a phantom subscription message + // message into the incoming message stream. + if (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB) { + subscriptionRequired = true; + } + + // We are sending our identity now and the next message + // will come from the socket. + nextMsg = pullMsgFromSession; + // We are expecting identity message. + processMsg = identity; + + } + else if (greetingRecv.get(revisionPos) == Protocol.V1.revision) { + // ZMTP/1.0 framing. + + zmtpVersion = Protocol.V1; + + if (session.zapEnabled()) { + // reject ZMTP 1.0 connections if ZAP is enabled + error(ErrorReason.PROTOCOL); + return false; + } + encoder = new V1Encoder(errno, Config.OUT_BATCH_SIZE.getValue()); + decoder = new V1Decoder(errno, Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize, + options.allocationHeapThreshold); + } + else if (greetingRecv.get(revisionPos) == Protocol.V2.revision) { + // ZMTP/2.0 framing. + + zmtpVersion = Protocol.V2; + + if (session.zapEnabled()) { + // reject ZMTP 2.0 connections if ZAP is enabled + error(ErrorReason.PROTOCOL); + return false; + } + encoder = new V2Encoder(errno, Config.OUT_BATCH_SIZE.getValue()); + decoder = new V2Decoder(errno, Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize, + options.allocationHeapThreshold); + } + else { + zmtpVersion = Protocol.V3; + + encoder = new V2Encoder(errno, Config.OUT_BATCH_SIZE.getValue()); + decoder = new V2Decoder(errno, Config.IN_BATCH_SIZE.getValue(), options.maxMsgSize, + options.allocationHeapThreshold); + + greetingRecv.position(V2_GREETING_SIZE); + if (options.mechanism == null) { + error(ErrorReason.PROTOCOL); + return false; + } + else { + if (options.mechanism.isMechanism(greetingRecv)) { + mechanism = options.mechanism.create(session, peerAddress, options); + } + else { + error(ErrorReason.PROTOCOL); + return false; + } + } + + nextMsg = nextHandshakeCommand; + processMsg = processHandshakeCommand; + } + + // Start polling for output if necessary. + if (outsize == 0) { + ioObject.setPollOut(handle); + } + + // Handshaking was successful. + // Switch into the normal message flow. + handshaking = false; + + if (hasHandshakeTimer) { + ioObject.cancelTimer(HANDSHAKE_TIMER_ID); + hasHandshakeTimer = false; + } + return true; + } + + private Msg identityMsg() + { + Msg msg = new Msg(options.identitySize); + if (options.identitySize > 0) { + msg.put(options.identity, 0, options.identitySize); + } + nextMsg = pullMsgFromSession; + return msg; + } + + private boolean processIdentityMsg(Msg msg) + { + if (options.recvIdentity) { + msg.setFlags(Msg.IDENTITY); + boolean rc = session.pushMsg(msg); + assert (rc); + } + + if (subscriptionRequired) { + // Inject the subscription message, so that also + // ZMQ 2.x peers receive published messages. + Msg subscription = new Msg(1); + subscription.put((byte) 1); + boolean rc = session.pushMsg(subscription); + assert (rc); + } + + processMsg = pushMsgToSession; + + return true; + } + + private final MessageProcessor identity = new Identity(); + + private Msg nextHandshakeCommand() + { + assert (mechanism != null); + + if (mechanism.status() == Mechanism.Status.READY) { + mechanismReady(); + + return pullAndEncode.nextMsg(); + } + else if (mechanism.status() == Mechanism.Status.ERROR) { + errno.set(ZError.EPROTO); + // error(ErrorReason.PROTOCOL); + return null; + } + else { + Msg.Builder msg = new Msg.Builder(); + int rc = mechanism.nextHandshakeCommand(msg); + if (rc == 0) { + msg.setFlags(Msg.COMMAND); + return msg.build(); + } + else { + errno.set(rc); + return null; + } + } + } + + private boolean processHandshakeCommand(Msg msg) + { + assert (mechanism != null); + + int rc = mechanism.processHandshakeCommand(msg); + if (rc == 0) { + if (mechanism.status() == Mechanism.Status.READY) { + mechanismReady(); + } + else if (mechanism.status() == Mechanism.Status.ERROR) { + errno.set(ZError.EPROTO); + return false; + } + if (outputStopped) { + restartOutput(); + } + } + else { + errno.set(rc); + } + return rc == 0; + } + + private final MessageProcessor processHandshakeCommand = new HandshakeCommand(); + private final MessageProcessor nextHandshakeCommand = processHandshakeCommand; + + @Override + public void zapMsgAvailable() + { + assert (mechanism != null); + + int rc = mechanism.zapMsgAvailable(); + if (rc == -1) { + error(ErrorReason.PROTOCOL); + return; + } + if (inputStopped) { + restartInput(); + } + if (outputStopped) { + restartOutput(); + } + } + + private void mechanismReady() + { + if (options.recvIdentity) { + Msg identity = mechanism.peerIdentity(); + boolean rc = session.pushMsg(identity); + if (!rc && errno.is(ZError.EAGAIN)) { + // If the write is failing at this stage with + // an EAGAIN the pipe must be being shut down, + // so we can just bail out of the identity set. + return; + } + assert (rc); + session.flush(); + } + + nextMsg = pullAndEncode; + processMsg = writeCredential; + + // Compile metadata. + assert (metadata == null); + + metadata = new Metadata(); + + // If we have a peer_address, add it to metadata + if (peerAddress != null && !peerAddress.address().isEmpty()) { + metadata.set("Peer-Address", peerAddress.address()); + } + // Add ZAP properties. + metadata.set(mechanism.zapProperties); + + // Add ZMTP properties. + metadata.set(mechanism.zmtpProperties); + + if (metadata.isEmpty()) { + metadata = null; + } + + } + + private Msg pullMsgFromSession() + { + return session.pullMsg(); + } + + private boolean pushMsgToSession(Msg msg) + { + return session.pushMsg(msg); + } + + private final MessageProcessor pushMsgToSession = new PushMsgToSession(); + private final MessageProcessor pullMsgFromSession = pushMsgToSession; + + private boolean pushRawMsgToSession(Msg msg) + { + if (metadata != null && !metadata.equals(msg.getMetadata())) { + msg.setMetadata(metadata); + } + return pushMsgToSession(msg); + } + + private final MessageProcessor pushRawMsgToSession = new PushRawMsgToSession(); + + private boolean writeCredential(Msg msg) + { + assert (mechanism != null); + assert (session != null); + + Blob credential = mechanism.getUserId(); + if (credential != null && credential.size() > 0) { + Msg cred = new Msg(credential.size()); + cred.put(credential.data(), 0, credential.size()); + cred.setFlags(Msg.CREDENTIAL); + + boolean rc = session.pushMsg(cred); + if (!rc) { + return false; + } + } + processMsg = decodeAndPush; + return decodeAndPush.processMsg(msg); + } + + private final MessageProcessor writeCredential = new WriteCredential(); + + private final MessageProcessor pullAndEncode = new PullAndEncode(); + + private final MessageProcessor decodeAndPush = new DecodeAndPush(); + + private final MessageProcessor pushOneThenDecodeAndPush = new PushOneThenDecodeAndPush(); + + // Function to handle network disconnections. + private void error(ErrorReason error) + { + if (options.rawSocket) { + // For raw sockets, send a final 0-length message to the application + // so that it knows the peer has been disconnected. + Msg terminator = new Msg(); + processMsg.processMsg(terminator); + } + assert (session != null); + socket.eventDisconnected(endpoint, fd); + session.flush(); + session.engineError(error); + unplug(); + destroy(); + } + + private void setHandshakeTimer() + { + assert (!hasHandshakeTimer); + + if (!options.rawSocket && options.handshakeIvl > 0) { + ioObject.addTimer(options.handshakeIvl, HANDSHAKE_TIMER_ID); + hasHandshakeTimer = true; + } + } + + @Override + public void timerEvent(int id) + { + assert (id == HANDSHAKE_TIMER_ID); + hasHandshakeTimer = false; + + // handshake timer expired before handshake completed, so engine fails + error(ErrorReason.TIMEOUT); + } + + // Writes data to the socket. Returns the number of bytes actually + // written (even zero is to be considered to be a success). In case + // of error or orderly shutdown by the other peer -1 is returned. + private int write(ByteBuffer outbuf) + { + int nbytes; + try { + nbytes = fd.write(outbuf); + if (nbytes == 0) { + errno.set(ZError.EAGAIN); + } + } + catch (IOException e) { + errno.set(ZError.ENOTCONN); + nbytes = -1; + } + + return nbytes; + } + + // Reads data from the socket (up to 'size' bytes). + // Returns the number of bytes actually read or -1 on error. + // Zero indicates the peer has closed the connection. + private int read(ByteBuffer buf) + { + int nbytes; + try { + nbytes = fd.read(buf); + if (nbytes == -1) { + errno.set(ZError.ENOTCONN); + } + else if (nbytes == 0) { + if (!fd.isBlocking()) { + // If not a single byte can be read from the socket in non-blocking mode + // we'll get an error (this may happen during the speculative read). + + // Several errors are OK. When speculative read is being done we may not + // be able to read a single byte from the socket. Also, SIGSTOP issued + // by a debugging tool can result in EINTR error. + errno.set(ZError.EAGAIN); + nbytes = -1; + } + } + } + catch (IOException e) { + errno.set(ZError.ENOTCONN); + nbytes = -1; + } + + return nbytes; + } + + @Override + public void connectEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void acceptEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() + { + return getClass().getSimpleName() + socket + "-" + zmtpVersion; + } +} diff --git a/src/main/java/zmq/io/coder/Decoder.java b/src/main/java/zmq/io/coder/Decoder.java new file mode 100644 index 000000000..d91875544 --- /dev/null +++ b/src/main/java/zmq/io/coder/Decoder.java @@ -0,0 +1,148 @@ +package zmq.io.coder; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.ZError; +import zmq.util.Errno; + +// Helper base class for decoders that know the amount of data to read +// in advance at any moment. Knowing the amount in advance is a property +// of the protocol used. 0MQ framing protocol is based size-prefixed +// paradigm, which qualifies it to be parsed by this class. +// On the other hand, XML-based transports (like XMPP or SOAP) don't allow +// for knowing the size of data to read in advance and should use different +// decoding algorithms. +// +// This class implements the state machine that parses the incoming buffer. +// Derived class should implement individual state machine actions. + +public abstract class Decoder extends DecoderBase +{ + private final class MessageReady implements Step + { + @Override + public Step.Result apply() + { + return messageReady(); + } + } + + private final class FlagsReady implements Step + { + @Override + public Step.Result apply() + { + return flagsReady(); + } + } + + private final class EightByteSizeReady implements Step + { + @Override + public Step.Result apply() + { + return eightByteSizeReady(); + } + } + + private final class OneByteSizeReady implements Step + { + @Override + public Step.Result apply() + { + return oneByteSizeReady(); + } + } + + protected final long maxmsgsize; + + // the message decoded so far + protected Msg inProgress; + protected int msgFlags; + + protected final Step oneByteSizeReady = new OneByteSizeReady(); + protected final Step eightByteSizeReady = new EightByteSizeReady(); + protected final Step flagsReady = new FlagsReady(); + protected final Step messageReady = new MessageReady(); + + private final int allocationHeapThreshold; + + public Decoder(Errno errno, int bufsize, long maxmsgsize, int allocationHeapThreshold) + { + super(errno, bufsize); + this.maxmsgsize = maxmsgsize; + this.allocationHeapThreshold = allocationHeapThreshold; + } + + protected final Step.Result sizeReady(final long size) + { + // Message size must not exceed the maximum allowed size. + if (maxmsgsize >= 0) { + if (size > maxmsgsize) { + errno(ZError.EMSGSIZE); + return Step.Result.ERROR; + } + } + + // Message size must fit within range of size_t data type. + if (size > Integer.MAX_VALUE) { + errno(ZError.EMSGSIZE); + return Step.Result.ERROR; + } + + // inProgress is initialized at this point so in theory we should + // close it before calling init_size, however, it's a 0-byte + // message and thus we can treat it as uninitialized. + inProgress = allocate(size); + + return Step.Result.MORE_DATA; + } + + protected final Msg allocate(final long size) + { + Msg msg; + if (allocationHeapThreshold > 0 && size > allocationHeapThreshold) { + msg = new Msg(ByteBuffer.allocateDirect((int) size)); + } + else { + msg = new Msg((int) size); + } + msg.setFlags(msgFlags); + + return msg; + } + + protected Step.Result oneByteSizeReady() + { + throw new UnsupportedOperationException("Have you forgot to implement oneByteSizeReady ?"); + } + + protected Step.Result eightByteSizeReady() + { + throw new UnsupportedOperationException("Have you forgot to implement eightByteSizeReady ?"); + } + + protected Step.Result flagsReady() + { + throw new UnsupportedOperationException("Have you forgot to implement flagsReady ?"); + } + + protected Step.Result messageReady() + { + throw new UnsupportedOperationException("Have you forgot to implement messageReady ?"); + } + + protected Step.Result messageIncomplete() + { + return Step.Result.MORE_DATA; + } + + @Override + public Msg msg() + { + return inProgress; + } + + protected abstract long binarySize(Msg msg); +} diff --git a/src/main/java/zmq/io/coder/DecoderBase.java b/src/main/java/zmq/io/coder/DecoderBase.java new file mode 100644 index 000000000..dc697d82c --- /dev/null +++ b/src/main/java/zmq/io/coder/DecoderBase.java @@ -0,0 +1,158 @@ +package zmq.io.coder; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.util.Errno; +import zmq.util.ValueReference; + +// Helper base class for decoders that know the amount of data to read +// in advance at any moment. Knowing the amount in advance is a property +// of the protocol used. 0MQ framing protocol is based size-prefixed +// paradigm, which qualifies it to be parsed by this class. +// On the other hand, XML-based transports (like XMPP or SOAP) don't allow +// for knowing the size of data to read in advance and should use different +// decoding algorithms. +// +// This class implements the state machine that parses the incoming buffer. +// Derived class should implement individual state machine actions. + +public abstract class DecoderBase implements IDecoder +{ + // Where to store the read data. + private ByteBuffer readPos; + + // TODO V4 remove zeroCopy boolean + private boolean zeroCopy; + // How much data to read before taking next step. + private int toRead; + + // The buffer for data to decode. + private int bufsize; + + private ByteBuffer buf; + + private Step next; + + private final Errno errno; + + public DecoderBase(Errno errno, int bufsize) + { + next = null; + readPos = null; + toRead = 0; + this.bufsize = bufsize; + assert (bufsize > 0); + buf = ByteBuffer.allocateDirect(bufsize); + this.errno = errno; + } + + // Returns a buffer to be filled with binary data. + @Override + public ByteBuffer getBuffer() + { + // If we are expected to read large message, we'll opt for zero- + // copy, i.e. we'll ask caller to fill the data directly to the + // message. Note that subsequent read(s) are non-blocking, thus + // each single read reads at most SO_RCVBUF bytes at once not + // depending on how large is the chunk returned from here. + // As a consequence, large messages being received won't block + // other engines running in the same I/O thread for excessive + // amounts of time. + if (toRead >= bufsize) { + zeroCopy = true; + return readPos.duplicate(); + } + else { + zeroCopy = false; + buf.clear(); + return buf; + } + } + + // Processes the data in the buffer previously allocated using + // get_buffer function. size_ argument specifies number of bytes + // actually filled into the buffer. Function returns number of + // bytes actually processed. + @Override + public Step.Result decode(ByteBuffer data, int size, ValueReference processed) + { + processed.set(0); + + // In case of zero-copy simply adjust the pointers, no copying + // is required. Also, run the state machine in case all the data + // were processed. + if (zeroCopy) { + assert (size <= toRead); + readPos.position(readPos.position() + size); + toRead -= size; + processed.set(size); + + while (readPos.remaining() == 0) { + Step.Result result = next.apply(); + if (result != Step.Result.MORE_DATA) { + return result; + } + } + return Step.Result.MORE_DATA; + } + + while (processed.get() < size) { + // Copy the data from buffer to the message. + int toCopy = Math.min(toRead, size - processed.get()); + int limit = buf.limit(); + buf.limit(buf.position() + toCopy); + readPos.put(buf); + buf.limit(limit); + toRead -= toCopy; + processed.set(processed.get() + toCopy); + + // Try to get more space in the message to fill in. + // If none is available, return. + while (readPos.remaining() == 0) { + Step.Result result = next.apply(); + if (result != Step.Result.MORE_DATA) { + return result; + } + } + } + + return Step.Result.MORE_DATA; + } + + protected void nextStep(Msg msg, Step next) + { + nextStep(msg.buf(), next); + } + + @Deprecated + protected void nextStep(byte[] buf, int toRead, Step next) + { + readPos = ByteBuffer.wrap(buf); + readPos.limit(toRead); + this.toRead = toRead; + this.next = next; + } + + protected void nextStep(ByteBuffer buf, Step next) + { + readPos = buf; + this.toRead = buf.remaining(); + this.next = next; + } + + protected void errno(int err) + { + this.errno.set(err); + } + + public int errno() + { + return errno.get(); + } + + @Override + public void destroy() + { + } +} diff --git a/src/main/java/zmq/io/coder/Encoder.java b/src/main/java/zmq/io/coder/Encoder.java new file mode 100644 index 000000000..d9f757de0 --- /dev/null +++ b/src/main/java/zmq/io/coder/Encoder.java @@ -0,0 +1,35 @@ +package zmq.io.coder; + +import zmq.util.Errno; + +public abstract class Encoder extends EncoderBase +{ + protected final Runnable sizeReady = new Runnable() + { + @Override + public void run() + { + sizeReady(); + } + + }; + + protected final Runnable messageReady = new Runnable() + { + @Override + public void run() + { + messageReady(); + } + + }; + + protected Encoder(Errno errno, int bufsize) + { + super(errno, bufsize); + } + + protected abstract void sizeReady(); + + protected abstract void messageReady(); +} diff --git a/src/main/java/zmq/io/coder/EncoderBase.java b/src/main/java/zmq/io/coder/EncoderBase.java new file mode 100644 index 000000000..49055b77a --- /dev/null +++ b/src/main/java/zmq/io/coder/EncoderBase.java @@ -0,0 +1,199 @@ +package zmq.io.coder; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.util.Errno; +import zmq.util.ValueReference; + +public abstract class EncoderBase implements IEncoder +{ + // Where to get the data to write from. + private ByteBuffer writeBuf; + + // Next step. If set to null, it means that associated data stream + // is dead. + private Runnable next; + + // If true, first byte of the message is being written. + private boolean newMsgFlag; + + // How much data to write before next step should be executed. + private int toWrite; + + // The buffer for encoded data. + private final ByteBuffer buffer; + + private final int bufferSize; + + private boolean error; + + protected Msg inProgress; + + private final Errno errno; + + protected EncoderBase(Errno errno, int bufferSize) + { + this.errno = errno; + this.bufferSize = bufferSize; + buffer = ByteBuffer.allocateDirect(bufferSize); + error = false; + } + + // Load a new message into encoder. + @Override + public final void loadMsg(Msg msg) + { + assert (inProgress == null); + inProgress = msg; + next(); + } + + // The function returns a batch of binary data. The data + // are filled to a supplied buffer. If no buffer is supplied (data + // is NULL) encoder will provide buffer of its own. + @Override + public final int encode(ValueReference data, int size) + { + int bufferSize = size; + ByteBuffer buf = data.get(); + if (buf == null) { + buf = this.buffer; + bufferSize = this.bufferSize; + buffer.clear(); + } + + if (inProgress == null) { + return 0; + } + + int pos = 0; + + buf.limit(buf.capacity()); + while (pos < bufferSize) { + // If there are no more data to return, run the state machine. + // If there are still no data, return what we already have + // in the buffer. + if (toWrite == 0) { + if (newMsgFlag) { + inProgress = null; + break; + } + next(); + } + + // If there are no data in the buffer yet and we are able to + // fill whole buffer in a single go, let's use zero-copy. + // There's no disadvantage to it as we cannot stuck multiple + // messages into the buffer anyway. Note that subsequent + // write(s) are non-blocking, thus each single write writes + // at most SO_SNDBUF bytes at once not depending on how large + // is the chunk returned from here. + // As a consequence, large messages being sent won't block + // other engines running in the same I/O thread for excessive + // amounts of time. + if (pos == 0 && data.get() == null && toWrite >= bufferSize) { + writeBuf.limit(writeBuf.capacity()); + data.set(writeBuf); + pos = toWrite; + writeBuf = null; + toWrite = 0; + return pos; + } + + // Copy data to the buffer. If the buffer is full, return. + int toCopy = Math.min(toWrite, bufferSize - pos); + int limit = writeBuf.limit(); + writeBuf.limit(Math.min(writeBuf.capacity(), writeBuf.position() + toCopy)); + int current = buf.position(); + buf.put(writeBuf); + toCopy = buf.position() - current; + writeBuf.limit(limit); + pos += toCopy; + toWrite -= toCopy; + } + + data.set(buf); + + return pos; + } + + protected void encodingError() + { + error = true; + } + + public final boolean isError() + { + return error; + } + + protected void next() + { + if (next != null) { + next.run(); + } + } + + protected void nextStep(Msg msg, Runnable state, boolean beginning) + { + if (msg == null) { + nextStep((byte[]) null, 0, state, beginning); + } + else { + nextStep(msg.buf(), state, beginning); + } + } + + // This function should be called from derived class to write the data + // to the buffer and schedule next state machine action. + private void nextStep(byte[] buf, int toWrite, Runnable next, boolean newMsgFlag) + { + if (buf != null) { + writeBuf = ByteBuffer.wrap(buf); + writeBuf.limit(toWrite); + } + else { + writeBuf = null; + } + this.toWrite = toWrite; + this.next = next; + this.newMsgFlag = newMsgFlag; + } + + protected void initStep(Runnable next, boolean newMsgFlag) + { + nextStep((byte[]) null, 0, next, newMsgFlag); + } + + private void nextStep(ByteBuffer buf, Runnable next, boolean newMsgFlag) + { + nextStep(buf, buf.limit(), next, newMsgFlag); + } + + protected void nextStep(ByteBuffer buf, int toWrite, Runnable next, boolean newMsgFlag) + { + buf.limit(toWrite); + buf.position(toWrite); + buf.flip(); + writeBuf = buf; + this.toWrite = toWrite; + this.next = next; + this.newMsgFlag = newMsgFlag; + } + + public int errno() + { + return errno.get(); + } + + public void errno(int err) + { + this.errno.set(err); + } + + @Override + public void destroy() + { + } +} diff --git a/src/main/java/zmq/io/coder/IDecoder.java b/src/main/java/zmq/io/coder/IDecoder.java new file mode 100644 index 000000000..b1d2fc875 --- /dev/null +++ b/src/main/java/zmq/io/coder/IDecoder.java @@ -0,0 +1,38 @@ +package zmq.io.coder; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.util.ValueReference; + +public interface IDecoder +{ + public static interface Step + { + public static enum Result + { + MORE_DATA(0), + DECODED(1), + ERROR(-1); + + @SuppressWarnings("unused") + // reminder for c++ equivalent + private final int code; + + private Result(int code) + { + this.code = code; + } + } + + Result apply(); + } + + ByteBuffer getBuffer(); + + Step.Result decode(ByteBuffer buffer, int size, ValueReference processed); + + Msg msg(); + + void destroy(); +} diff --git a/src/main/java/zmq/io/coder/IEncoder.java b/src/main/java/zmq/io/coder/IEncoder.java new file mode 100644 index 000000000..b044a5a25 --- /dev/null +++ b/src/main/java/zmq/io/coder/IEncoder.java @@ -0,0 +1,19 @@ +package zmq.io.coder; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.util.ValueReference; + +public interface IEncoder +{ + // Load a new message into encoder. + void loadMsg(Msg msg); + + // The function returns a batch of binary data. The data + // are filled to a supplied buffer. If no buffer is supplied (data_ + // points to NULL) decoder object will provide buffer of its own. + int encode(ValueReference data, int size); + + void destroy(); +} diff --git a/src/main/java/zmq/io/coder/raw/RawDecoder.java b/src/main/java/zmq/io/coder/raw/RawDecoder.java new file mode 100644 index 000000000..e3ec506c9 --- /dev/null +++ b/src/main/java/zmq/io/coder/raw/RawDecoder.java @@ -0,0 +1,49 @@ +package zmq.io.coder.raw; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.io.coder.IDecoder; +import zmq.util.ValueReference; + +public class RawDecoder implements IDecoder +{ + // The buffer for data to decode. + private final ByteBuffer buffer; + + protected Msg inProgress; + + public RawDecoder(int bufsize) + { + buffer = ByteBuffer.allocateDirect(bufsize); + inProgress = new Msg(); + } + + @Override + public ByteBuffer getBuffer() + { + buffer.clear(); + return buffer; + } + + @Override + public Step.Result decode(ByteBuffer buffer, int size, ValueReference processed) + { + processed.set(size); + inProgress = new Msg(size); + inProgress.put(buffer); + + return Step.Result.DECODED; + } + + @Override + public Msg msg() + { + return inProgress; + } + + @Override + public void destroy() + { + } +} diff --git a/src/main/java/zmq/io/coder/raw/RawEncoder.java b/src/main/java/zmq/io/coder/raw/RawEncoder.java new file mode 100644 index 000000000..91531f363 --- /dev/null +++ b/src/main/java/zmq/io/coder/raw/RawEncoder.java @@ -0,0 +1,28 @@ +package zmq.io.coder.raw; + +import zmq.io.coder.Encoder; +import zmq.util.Errno; + +// Encoder for 0MQ framing protocol. Converts messages into data batches. + +public class RawEncoder extends Encoder +{ + public RawEncoder(Errno errno, int bufsize) + { + super(errno, bufsize); + // Write 0 bytes to the batch and go to messageReady state. + initStep(messageReady, true); + } + + @Override + protected void sizeReady() + { + throw new UnsupportedOperationException(); + } + + @Override + protected void messageReady() + { + nextStep(inProgress.buf(), inProgress.size(), messageReady, true); + } +} diff --git a/src/main/java/zmq/io/coder/v1/V1Decoder.java b/src/main/java/zmq/io/coder/v1/V1Decoder.java new file mode 100644 index 000000000..d5568bc37 --- /dev/null +++ b/src/main/java/zmq/io/coder/v1/V1Decoder.java @@ -0,0 +1,111 @@ +package zmq.io.coder.v1; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.ZError; +import zmq.io.coder.Decoder; +import zmq.util.Errno; +import zmq.util.Wire; + +public class V1Decoder extends Decoder +{ + private final ByteBuffer tmpbuf; + + public V1Decoder(Errno errno, int bufsize, long maxmsgsize, int allocationHeapThreshold) + { + super(errno, bufsize, maxmsgsize, allocationHeapThreshold); + + tmpbuf = ByteBuffer.allocate(8); + tmpbuf.limit(1); + + // At the beginning, read one byte and go to ONE_BYTE_SIZE_READY state. + nextStep(tmpbuf, oneByteSizeReady); + } + + @Override + protected Step.Result oneByteSizeReady() + { + // First byte of size is read. If it is 0xff read 8-byte size. + // Otherwise allocate the buffer for message data and read the + // message data into it. + int size = tmpbuf.get(0) & 0xff; + if (size == 0xff) { + tmpbuf.position(0); + tmpbuf.limit(8); + nextStep(tmpbuf, eightByteSizeReady); + } + else { + // There has to be at least one byte (the flags) in the message). + if (size <= 0) { + errno(ZError.EPROTO); + return Step.Result.ERROR; + } + tmpbuf.position(0); + tmpbuf.limit(1); + Step.Result rc = sizeReady(size - 1); + if (rc != Step.Result.ERROR) { + nextStep(tmpbuf, flagsReady); + } + + return rc; + } + return Step.Result.MORE_DATA; + } + + @Override + protected Step.Result eightByteSizeReady() + { + // 8-byte payload length is read. Allocate the buffer + // for message body and read the message data into it. + tmpbuf.position(0); + tmpbuf.limit(8); + final long payloadLength = Wire.getUInt64(tmpbuf, 0); + + if (payloadLength <= 0) { + errno(ZError.EPROTO); + return Step.Result.ERROR; + } + tmpbuf.limit(1); + Step.Result rc = sizeReady(payloadLength - 1); + if (rc != Step.Result.ERROR) { + nextStep(tmpbuf, flagsReady); + } + return rc; + } + + @Override + protected Step.Result flagsReady() + { + // Store the flags from the wire into the message structure. + msgFlags = 0; + int first = tmpbuf.get(0) & 0xff; + if ((first & V1Protocol.MORE_FLAG) > 0) { + msgFlags |= Msg.MORE; + inProgress.setFlags(Msg.MORE); + } + + nextStep(inProgress, messageReady); + + return Step.Result.MORE_DATA; + } + + @Override + protected Step.Result messageReady() + { + // Message is completely read. Push it further and start reading + // new message. (inProgress is a 0-byte message after this point.) + + tmpbuf.position(0); + tmpbuf.limit(1); + nextStep(tmpbuf, oneByteSizeReady); + + return Step.Result.DECODED; + } + + @Override + protected long binarySize(Msg msg) + { + return msg.size() + (msg.size() > 255 ? 1 : 0) + tmpbuf.limit() + 1; + } +} diff --git a/src/main/java/zmq/io/coder/v1/V1Encoder.java b/src/main/java/zmq/io/coder/v1/V1Encoder.java new file mode 100644 index 000000000..0c1e6639d --- /dev/null +++ b/src/main/java/zmq/io/coder/v1/V1Encoder.java @@ -0,0 +1,58 @@ +package zmq.io.coder.v1; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.io.coder.Encoder; +import zmq.util.Errno; +import zmq.util.Wire; + +// Encoder for 0MQ framing protocol. Converts messages into data stream. +public class V1Encoder extends Encoder +{ + private final ByteBuffer tmpbufWrap; + + public V1Encoder(Errno errno, int bufsize) + { + super(errno, bufsize); + tmpbufWrap = ByteBuffer.allocate(10); + + // Write 0 bytes to the batch and go to messageReady state. + initStep(messageReady, true); + } + + @Override + protected void sizeReady() + { + // Write message body into the buffer. + nextStep(inProgress.buf(), inProgress.size(), messageReady, true); + } + + @Override + protected void messageReady() + { + // Get the message size. + int size = inProgress.size(); + + // Account for the 'flags' byte. + size++; + + // For messages less than 255 bytes long, write one byte of message size. + // For longer messages write 0xff escape character followed by 8-byte + // message size. In both cases 'flags' field follows. + + tmpbufWrap.position(0); + if (size < 255) { + tmpbufWrap.limit(2); + tmpbufWrap.put((byte) size); + } + else { + tmpbufWrap.limit(10); + tmpbufWrap.put((byte) 0xff); + Wire.putUInt64(tmpbufWrap, size); + } + + tmpbufWrap.put((byte) (inProgress.flags() & Msg.MORE)); + nextStep(tmpbufWrap, tmpbufWrap.limit(), sizeReady, false); + } +} diff --git a/src/main/java/zmq/io/coder/v1/V1Protocol.java b/src/main/java/zmq/io/coder/v1/V1Protocol.java new file mode 100644 index 000000000..a1ebff4e0 --- /dev/null +++ b/src/main/java/zmq/io/coder/v1/V1Protocol.java @@ -0,0 +1,12 @@ +package zmq.io.coder.v1; + +public interface V1Protocol +{ + int VERSION = 1; + + int MORE_FLAG = 1; + + // make checkstyle not block the release + @Override + String toString(); +} diff --git a/src/main/java/zmq/io/coder/v2/V2Decoder.java b/src/main/java/zmq/io/coder/v2/V2Decoder.java new file mode 100644 index 000000000..9c232bf80 --- /dev/null +++ b/src/main/java/zmq/io/coder/v2/V2Decoder.java @@ -0,0 +1,97 @@ +package zmq.io.coder.v2; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.io.coder.Decoder; +import zmq.util.Errno; +import zmq.util.Wire; + +public class V2Decoder extends Decoder +{ + private final ByteBuffer tmpbuf; + + public V2Decoder(Errno errno, int bufsize, long maxmsgsize, int allocationHeapThreshold) + { + super(errno, bufsize, maxmsgsize, allocationHeapThreshold); + + tmpbuf = ByteBuffer.allocate(8); + tmpbuf.limit(1); + + // At the beginning, read one byte and go to ONE_BYTE_SIZE_READY state. + nextStep(tmpbuf, flagsReady); + } + + @Override + protected Step.Result oneByteSizeReady() + { + int size = tmpbuf.get(0) & 0xff; + Step.Result rc = sizeReady(size); + if (rc != Step.Result.ERROR) { + nextStep(inProgress, messageReady); + } + return rc; + } + + @Override + protected Step.Result eightByteSizeReady() + { + // The payload size is encoded as 64-bit unsigned integer. + // The most significant byte comes first. + tmpbuf.position(0); + tmpbuf.limit(8); + final long size = Wire.getUInt64(tmpbuf, 0); + + Step.Result rc = sizeReady(size); + if (rc != Step.Result.ERROR) { + nextStep(inProgress, messageReady); + } + return rc; + } + + @Override + protected Step.Result flagsReady() + { + // Store the flags from the wire into the message structure. + this.msgFlags = 0; + int first = tmpbuf.get(0) & 0xff; + if ((first & V2Protocol.MORE_FLAG) > 0) { + this.msgFlags |= Msg.MORE; + } + if ((first & V2Protocol.COMMAND_FLAG) > 0) { + this.msgFlags |= Msg.COMMAND; + } + + // The payload length is either one or eight bytes, + // depending on whether the 'large' bit is set. + tmpbuf.position(0); + if ((first & V2Protocol.LARGE_FLAG) > 0) { + tmpbuf.limit(8); + nextStep(tmpbuf, eightByteSizeReady); + } + else { + tmpbuf.limit(1); + nextStep(tmpbuf, oneByteSizeReady); + } + + return Step.Result.MORE_DATA; + } + + @Override + protected Step.Result messageReady() + { + // Message is completely read. Signal this to the caller + // and prepare to decode next message. + tmpbuf.position(0); + tmpbuf.limit(1); + nextStep(tmpbuf, flagsReady); + + return Step.Result.DECODED; + } + + @Override + protected long binarySize(Msg msg) + { + return msg.size() + tmpbuf.limit() + 1; + } +} diff --git a/src/main/java/zmq/io/coder/v2/V2Encoder.java b/src/main/java/zmq/io/coder/v2/V2Encoder.java new file mode 100644 index 000000000..26869130b --- /dev/null +++ b/src/main/java/zmq/io/coder/v2/V2Encoder.java @@ -0,0 +1,64 @@ +package zmq.io.coder.v2; + +import java.nio.ByteBuffer; + +import zmq.io.coder.Encoder; +import zmq.util.Errno; +import zmq.util.Wire; + +// Encoder for 0MQ framing protocol. Converts messages into data stream. +public class V2Encoder extends Encoder +{ + private final ByteBuffer tmpbufWrap; + + public V2Encoder(Errno errno, int bufsize) + { + super(errno, bufsize); + tmpbufWrap = ByteBuffer.allocate(9); + + // Write 0 bytes to the batch and go to messageReady state. + initStep(messageReady, true); + } + + @Override + protected void messageReady() + { + // Encode flags. + + byte protocolFlags = 0; + if (inProgress.hasMore()) { + protocolFlags |= V2Protocol.MORE_FLAG; + } + if (inProgress.size() > 255) { + protocolFlags |= V2Protocol.LARGE_FLAG; + } + if (inProgress.isCommand()) { + protocolFlags |= V2Protocol.COMMAND_FLAG; + } + + // Encode the message length. For messages less then 256 bytes, + // the length is encoded as 8-bit unsigned integer. For larger + // messages, 64-bit unsigned integer in network byte order is used. + + final int size = inProgress.size(); + tmpbufWrap.position(0); + tmpbufWrap.put(protocolFlags); + + if (size > 255) { + tmpbufWrap.limit(9); + Wire.putUInt64(tmpbufWrap, size); + } + else { + tmpbufWrap.limit(2); + tmpbufWrap.put((byte) size); + } + nextStep(tmpbufWrap, tmpbufWrap.limit(), sizeReady, false); + } + + @Override + protected void sizeReady() + { + // Write message body into the buffer. + nextStep(inProgress.buf(), inProgress.size(), messageReady, true); + } +} diff --git a/src/main/java/zmq/io/coder/v2/V2Protocol.java b/src/main/java/zmq/io/coder/v2/V2Protocol.java new file mode 100644 index 000000000..3f22548c4 --- /dev/null +++ b/src/main/java/zmq/io/coder/v2/V2Protocol.java @@ -0,0 +1,15 @@ +package zmq.io.coder.v2; + +import zmq.io.coder.v1.V1Protocol; + +public interface V2Protocol extends V1Protocol +{ + int VERSION = 2; + + int LARGE_FLAG = 2; + int COMMAND_FLAG = 4; + + // make checkstyle not block the release + @Override + String toString(); +} diff --git a/src/main/java/zmq/io/mechanism/Mechanism.java b/src/main/java/zmq/io/mechanism/Mechanism.java new file mode 100644 index 000000000..fc7aee387 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/Mechanism.java @@ -0,0 +1,379 @@ +package zmq.io.mechanism; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import zmq.Msg; +import zmq.Options; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.Metadata; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.socket.Sockets; +import zmq.util.Blob; +import zmq.util.Wire; + +// Abstract class representing security mechanism. +// Different mechanism extends this class. +public abstract class Mechanism +{ + protected static final String IDENTITY = "Identity"; + + protected static final String SOCKET_TYPE = "Socket-Type"; + + public static enum Status + { + HANDSHAKING, + READY, + ERROR + } + + protected final Options options; + + private Blob identity; + private Blob userId; + + // Properties received from ZAP server. + public final Metadata zapProperties = new Metadata(); + + // Properties received from ZMTP peer. + public final Metadata zmtpProperties = new Metadata(); + + protected final SessionBase session; + private final Address peerAddress; + + protected String statusCode; + + protected Mechanism(SessionBase session, Address peerAddress, Options options) + { + this.session = session; + this.options = options; + this.peerAddress = peerAddress; + } + + public abstract Status status(); + + void setPeerIdentity(String data) + { + identity = Blob.createBlob(data.getBytes(ZMQ.CHARSET)); + } + + public Msg peerIdentity() + { + Msg msg = new Msg(identity == null ? 0 : identity.size()); + msg.put(identity.data(), 0, identity.size()); + msg.setFlags(Msg.IDENTITY); + + return msg; + } + + void setUserId(byte[] data) + { + userId = Blob.createBlob(data); + zapProperties.set("User-Id", new String(data, ZMQ.CHARSET)); + } + + public Blob getUserId() + { + return userId; + } + + protected byte[] addProperty(String name, String value) + { + return addProperty(name, value.getBytes(ZMQ.CHARSET)); + } + + protected byte[] addProperty(String name, byte[] value) + { + byte[] nameB = name.getBytes(ZMQ.CHARSET); + int nameLength = nameB.length; + assert (nameLength <= 255); + + int valueLength = value == null ? 0 : value.length; + assert (valueLength <= Integer.MAX_VALUE); + + ByteBuffer buf = ByteBuffer.allocate(nameLength + valueLength + 1 + 4); + + buf.put((byte) nameLength); + buf.put(nameB); + + Wire.putUInt32(buf, valueLength); + if (value != null) { + buf.put(value); + } + + return buf.array(); + } + + protected int parseMetadata(Msg msg, int offset, boolean zapFlag) + { + byte[] dest = new byte[msg.size() - offset]; + System.arraycopy(msg.data(), offset, dest, 0, dest.length); + return parseMetadata(dest, zapFlag); + } + + protected int parseMetadata(ByteBuffer msg, int offset, boolean zapFlag) + { + byte[] dest = new byte[msg.limit() - offset]; + System.arraycopy(msg.array(), offset, dest, 0, dest.length); + return parseMetadata(dest, zapFlag); + } + + int parseMetadata(byte[] data, boolean zapFlag) + { + int bytesLeft = data.length; + int index = 0; + + while (bytesLeft > 1) { + byte nameLength = data[index]; + index++; + bytesLeft -= 1; + + if (bytesLeft < nameLength) { + break; + } + + String name = new String(data, index, nameLength); + index += nameLength; + bytesLeft -= nameLength; + + if (bytesLeft < 4) { + break; + } + + int valueLength = Wire.getUInt32(data, index); + index += 4; + bytesLeft -= 4; + + if (bytesLeft < valueLength) { + break; + } + + String value = new String(data, index, valueLength); + index += valueLength; + bytesLeft -= valueLength; + + if (IDENTITY.equals(name) && options.recvIdentity) { + setPeerIdentity(value); + } + else if (SOCKET_TYPE.equals(name)) { + if (!Sockets.compatible(options.type, value)) { + return ZError.EINVAL; + } + } + else { + int rc = property(name, value); + if (rc == -1) { + return -1; + } + } + if (zapFlag) { + zapProperties.set(name, value); + } + else { + zmtpProperties.set(name, value); + } + } + if (bytesLeft > 0) { + return ZError.EPROTO; + } + return 0; + } + + protected int property(String name, String value) + { + // Default implementation does not check + // property values and returns 0 to signal success. + return 0; + } + + protected String socketType(int socketType) + { + return Sockets.name(options.type); + } + + protected boolean compare(Msg msg, String data, boolean includeLength) + { + int start = includeLength ? 1 : 0; + if (msg.size() < data.length() + start) { + return false; + } + boolean comparison = includeLength ? msg.get(0) == data.length() : true; + if (comparison) { + for (int idx = start; idx < data.length(); ++idx) { + comparison &= (msg.get(idx) == data.charAt(idx - start)); + if (!comparison) { + break; + } + } + } + return comparison; + } + + protected boolean compare(ByteBuffer a1, byte[] b, int offset, int length) + { + if (length > b.length) { + return false; + } + boolean comparison = true; + for (int idx = 0; idx < length; ++idx) { + comparison |= a1.get(idx + offset) == b[idx]; + if (!comparison) { + break; + } + } + return comparison; + } + + public Msg decode(Msg msg) + { + return msg; + } + + public Msg encode(Msg msg) + { + return msg; + } + + public abstract int zapMsgAvailable(); + + public abstract int processHandshakeCommand(Msg msg); + + public abstract int nextHandshakeCommand(Msg msg); + + protected void sendZapRequest(Mechanisms mechanism, boolean more) + { + assert (session != null); + assert (peerAddress != null); + assert (mechanism != null); + + Msg msg = new Msg(); + + // Address delimiter frame + msg.setFlags(Msg.MORE); + boolean rc = session.writeZapMsg(msg); + assert (rc); + + // Version frame + msg = new Msg(3); + msg.setFlags(Msg.MORE); + msg.put("1.0".getBytes(ZMQ.CHARSET)); + rc = session.writeZapMsg(msg); + assert (rc); + + // Request id frame + msg = new Msg(1); + msg.setFlags(Msg.MORE); + msg.put("1".getBytes(ZMQ.CHARSET)); + rc = session.writeZapMsg(msg); + assert (rc); + + // Domain frame + msg = new Msg(options.zapDomain.length()); + msg.setFlags(Msg.MORE); + msg.put(options.zapDomain.getBytes(ZMQ.CHARSET)); + rc = session.writeZapMsg(msg); + assert (rc); + + // Address frame + msg = new Msg(peerAddress.address().length()); + msg.setFlags(Msg.MORE); + msg.put(peerAddress.address().getBytes(ZMQ.CHARSET)); + rc = session.writeZapMsg(msg); + assert (rc); + + // Identity frame + msg = new Msg(options.identitySize); + msg.setFlags(Msg.MORE); + msg.put(options.identity, 0, options.identitySize); + rc = session.writeZapMsg(msg); + assert (rc); + + // Mechanism frame + msg = new Msg(mechanism.name().length()); + msg.put(mechanism.name().getBytes(ZMQ.CHARSET)); + if (more) { + msg.setFlags(Msg.MORE); + } + rc = session.writeZapMsg(msg); + assert (rc); + } + + protected int receiveAndProcessZapReply() + { + assert (session != null); + + List msgs = new ArrayList<>(7); // ZAP reply consists of 7 frames + + // Initialize all reply frames + for (int idx = 0; idx < 7; ++idx) { + Msg msg = session.readZapMsg(); + if (msg == null) { + return session.errno.get(); + } + if ((msg.flags() & Msg.MORE) == (idx < 6 ? 0 : Msg.MORE)) { + // Temporary support for security debugging + puts("NULL I: ZAP handler sent incomplete reply message " + msg); + return ZError.EPROTO; + } + msgs.add(msg); + } + + // Address delimiter frame + if (msgs.get(0).size() > 0) { + // Temporary support for security debugging + puts("NULL I: ZAP handler sent malformed reply message in address delimiter frame " + msgs.get(0)); + return ZError.EPROTO; + } + + // Version frame + if (msgs.get(1).size() != 3 || !compare(msgs.get(1), "1.0", false)) { + // Temporary support for security debugging + puts("NULL I: ZAP handler sent bad version number " + msgs.get(1)); + return ZError.EPROTO; + } + + // Request id frame + if (msgs.get(2).size() != 1 || !compare(msgs.get(2), "1", false)) { + // Temporary support for security debugging + puts("NULL I: ZAP handler sent bad request ID " + msgs.get(2)); + return ZError.EPROTO; + } + + // Status code frame + if (msgs.get(3).size() != 3) { + // Temporary support for security debugging + puts("NULL I: ZAP handler rejected client authentication " + msgs.get(3)); + return ZError.EPROTO; + } + + // Save status code + statusCode = new String(msgs.get(3).data(), ZMQ.CHARSET); + + // Save user id + setUserId(msgs.get(5).data()); + + // Process metadata frame + int rc = parseMetadata(msgs.get(6).data(), true); + + return rc; + } + + protected final void puts(String msg) + { + // Temporary support for security debugging + System.out.println(session + " " + msg); + } + + protected final void appendData(Msg msg, String data) + { + msg.put((byte) data.length()); + msg.put(data.getBytes(ZMQ.CHARSET)); + } + + public void destroy() + { + } +} diff --git a/src/main/java/zmq/io/mechanism/Mechanisms.java b/src/main/java/zmq/io/mechanism/Mechanisms.java new file mode 100644 index 000000000..f3eeddabb --- /dev/null +++ b/src/main/java/zmq/io/mechanism/Mechanisms.java @@ -0,0 +1,74 @@ +package zmq.io.mechanism; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import zmq.Options; +import zmq.ZMQ; +import zmq.io.SessionBase; +import zmq.io.mechanism.curve.CurveClientMechanism; +import zmq.io.mechanism.curve.CurveServerMechanism; +import zmq.io.mechanism.gssapi.GssapiClientMechanism; +import zmq.io.mechanism.gssapi.GssapiServerMechanism; +import zmq.io.mechanism.plain.PlainClientMechanism; +import zmq.io.mechanism.plain.PlainServerMechanism; +import zmq.io.net.Address; + +public enum Mechanisms +{ + NULL { + @Override + public Mechanism create(SessionBase session, Address peerAddress, Options options) + { + return new NullMechanism(session, peerAddress, options); + } + }, + PLAIN { + @Override + public Mechanism create(SessionBase session, Address peerAddress, Options options) + { + if (options.asServer) { + return new PlainServerMechanism(session, peerAddress, options); + } + else { + return new PlainClientMechanism(options); + } + } + }, + CURVE { + @Override + public Mechanism create(SessionBase session, Address peerAddress, Options options) + { + if (options.asServer) { + return new CurveServerMechanism(session, peerAddress, options); + } + else { + return new CurveClientMechanism(options); + } + } + }, + GSSAPI { + @Override + public Mechanism create(SessionBase session, Address peerAddress, Options options) + { + if (options.asServer) { + return new GssapiServerMechanism(session, peerAddress, options); + } + else { + return new GssapiClientMechanism(options); + } + } + }; + + public abstract Mechanism create(SessionBase session, Address peerAddress, Options options); + + public boolean isMechanism(ByteBuffer greetingRecv) + { + byte[] dst = new byte[20]; + greetingRecv.get(dst, 0, dst.length); + + byte[] name = name().getBytes(ZMQ.CHARSET); + byte[] comp = Arrays.copyOf(name, 20); + return Arrays.equals(dst, comp); + }; +} diff --git a/src/main/java/zmq/io/mechanism/NullMechanism.java b/src/main/java/zmq/io/mechanism/NullMechanism.java new file mode 100644 index 000000000..b41ac4646 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/NullMechanism.java @@ -0,0 +1,153 @@ +package zmq.io.mechanism; + +import zmq.Msg; +import zmq.Options; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.SessionBase; +import zmq.io.net.Address; + +class NullMechanism extends Mechanism +{ + private static final String OK = "200"; + private static final String READY = "READY"; + private static final String ERROR = "ERROR"; + + private boolean readyCommandSent; + private boolean errorCommandSent; + + private boolean readyCommandReceived; + private boolean errorCommandReceived; + + private boolean zapConnected; + private boolean zapRequestSent; + private boolean zapReplyReceived; + + NullMechanism(SessionBase session, Address peerAddress, Options options) + { + super(session, peerAddress, options); + + // NULL mechanism only uses ZAP if there's a domain defined + // This prevents ZAP requests on naive sockets + if (options.zapDomain != null && options.zapDomain.length() > 0 && session.zapConnect() == 0) { + zapConnected = true; + } + } + + @Override + public int nextHandshakeCommand(Msg msg) + { + if (readyCommandSent || errorCommandSent) { + return ZError.EAGAIN; + } + + if (zapConnected && !zapReplyReceived) { + if (zapRequestSent) { + return ZError.EAGAIN; + } + + sendZapRequest(Mechanisms.NULL, false); + zapRequestSent = true; + + int rc = receiveAndProcessZapReply(); + if (rc != 0) { + return rc; + } + zapReplyReceived = true; + } + + if (zapReplyReceived && !OK.equals(statusCode)) { + appendData(msg, ERROR); + appendData(msg, statusCode); + + errorCommandSent = true; + return 0; + } + + // Add mechanism string + appendData(msg, READY); + + // Add socket type property + String socketType = socketType(options.type); + msg.put(addProperty(SOCKET_TYPE, socketType)); + + // Add identity property + if (options.type == ZMQ.ZMQ_REQ || options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_ROUTER) { + msg.put(addProperty(IDENTITY, options.identity)); + } + readyCommandSent = true; + + return 0; + } + + @Override + public int processHandshakeCommand(Msg msg) + { + if (readyCommandReceived || errorCommandReceived) { + puts("NULL I: client sent invalid NULL handshake (duplicate READY)"); + return ZError.EPROTO; + } + int dataSize = msg.size(); + + int rc = 0; + if (dataSize >= 6 && compare(msg, READY, true)) { + rc = processReadyCommand(msg); + } + else if (dataSize >= 6 && compare(msg, ERROR, true)) { + rc = processErrorCommand(msg); + } + else { + puts("NULL I: client sent invalid NULL handshake (not READY) "); + return ZError.EPROTO; + } + return rc; + } + + private int processReadyCommand(Msg msg) + { + readyCommandReceived = true; + return parseMetadata(msg, 6, false); + } + + private int processErrorCommand(Msg msg) + { + if (msg.size() < 7) { + return ZError.EPROTO; + } + byte errorReasonLength = msg.get(6); + if (errorReasonLength > msg.size() - 7) { + return ZError.EPROTO; + } + errorCommandReceived = true; + return 0; + } + + @Override + public int zapMsgAvailable() + { + if (zapReplyReceived) { + return ZError.EFSM; + } + int rc = receiveAndProcessZapReply(); + if (rc == 0) { + zapReplyReceived = true; + } + + return rc; + } + + @Override + public Status status() + { + boolean commandSent = readyCommandSent || errorCommandSent; + boolean commandReceived = readyCommandReceived || errorCommandReceived; + + if (readyCommandSent && readyCommandReceived) { + return Status.READY; + } + if (commandSent && commandReceived) { + return Status.ERROR; + } + return Status.HANDSHAKING; + } +} diff --git a/src/main/java/zmq/io/mechanism/curve/Curve.java b/src/main/java/zmq/io/mechanism/curve/Curve.java new file mode 100644 index 000000000..6274fa616 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/curve/Curve.java @@ -0,0 +1,210 @@ +package zmq.io.mechanism.curve; + +import java.nio.ByteBuffer; + +import org.abstractj.kalium.NaCl; +import org.abstractj.kalium.NaCl.Sodium; + +import zmq.util.Utils; +import zmq.util.Z85; + +// wrapper around the wrapper of libsodium (ahem), for shorter names. +public class Curve +{ + static enum Size + { + NONCE { + @Override + public int bytes() + { + return NaCl.Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_NONCEBYTES; + } + }, + ZERO { + @Override + public int bytes() + { + return NaCl.Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_ZEROBYTES; + } + }, + BOXZERO { + @Override + public int bytes() + { + return NaCl.Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_BOXZEROBYTES; + } + }, + PUBLICKEY { + @Override + public int bytes() + { + return NaCl.Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_PUBLICKEYBYTES; + } + }, + SECRETKEY { + @Override + public int bytes() + { + return NaCl.Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_SECRETKEYBYTES; + } + }, + KEY { + @Override + public int bytes() + { + return NaCl.Sodium.CRYPTO_SECRETBOX_XSALSA20POLY1305_KEYBYTES; + } + }, + BEFORENM { + @Override + public int bytes() + { + return NaCl.Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_BEFORENMBYTES; + } + }; + + public abstract int bytes(); + } + + public static boolean installed() + { + try { + int init = NaCl.init(); + return init >= 0; + } + catch (UnsatisfiedLinkError e) { + return false; + } + } + + public Curve() + { + boolean installed = installed(); + if (!installed) { + throw new UnsupportedOperationException("libsodium does not look lie it is installed."); + } + } + + /** + * Generates a pair of Z85-encoded keys for use with this class. + * + * @return an array of 2 strings, holding Z85-encoded keys. + * The first element of the array is the public key, + * the second element is the private (or secret) key. + */ + public String[] keypairZ85() + { + String[] pair = new String[2]; + + byte[] publicKey = new byte[Size.PUBLICKEY.bytes()]; + byte[] secretKey = new byte[Size.SECRETKEY.bytes()]; + + int rc = salt().crypto_box_curve25519xsalsa20poly1305_keypair(publicKey, secretKey); + assert (rc == 0); + + pair[0] = Z85.encode(publicKey, Size.PUBLICKEY.bytes()); + pair[1] = Z85.encode(secretKey, Size.SECRETKEY.bytes()); + + return pair; + } + + /** + * Generates a pair of keys for use with this class. + * + * @return an array of 2 byte arrays, holding keys. + * The first element of the array is the public key, + * the second element is the private (or secret) key. + */ + public byte[][] keypair() + { + byte[][] pair = new byte[2][]; + + byte[] publicKey = new byte[Size.PUBLICKEY.bytes()]; + byte[] secretKey = new byte[Size.SECRETKEY.bytes()]; + + int rc = salt().crypto_box_curve25519xsalsa20poly1305_keypair(publicKey, secretKey); + assert (rc == 0); + + pair[0] = publicKey; + pair[1] = secretKey; + + return pair; + } + + int beforenm(byte[] outSharedKey, byte[] publicKey, byte[] secretKey) + { + return salt().crypto_box_curve25519xsalsa20poly1305_beforenm(outSharedKey, publicKey, secretKey); + } + + int afternm(ByteBuffer ciphered, ByteBuffer plaintext, int length, ByteBuffer nonce, byte[] precom) + { + return afternm(ciphered.array(), plaintext.array(), length, nonce.array(), precom); + } + + int afternm(byte[] ciphered, byte[] plaintext, int length, byte[] nonce, byte[] precomp) + { + return salt().crypto_box_curve25519xsalsa20poly1305_afternm(ciphered, plaintext, length, nonce, precomp); + } + + int openAfternm(ByteBuffer plaintext, ByteBuffer messagebox, int length, ByteBuffer nonce, byte[] precom) + { + return openAfternm(plaintext.array(), messagebox.array(), length, nonce.array(), precom); + } + + int openAfternm(byte[] plaintext, byte[] cipher, int length, byte[] nonce, byte[] precom) + { + return salt().crypto_box_curve25519xsalsa20poly1305_open_afternm(plaintext, cipher, length, nonce, precom); + } + + private Sodium salt() + { + return NaCl.sodium(); + } + + int open(ByteBuffer plaintext, ByteBuffer messagebox, int length, ByteBuffer nonce, byte[] precom, byte[] secretKey) + { + return open(plaintext.array(), messagebox.array(), length, nonce.array(), precom, secretKey); + } + + int open(byte[] plaintext, byte[] messagebox, int length, byte[] nonce, byte[] publicKey, byte[] secretKey) + { + return salt() + .crypto_box_curve25519xsalsa20poly1305_open(plaintext, messagebox, length, nonce, publicKey, secretKey); + } + + int secretbox(ByteBuffer ciphertext, ByteBuffer plaintext, int length, ByteBuffer nonce, byte[] key) + { + return secretbox(ciphertext.array(), plaintext.array(), length, nonce.array(), key); + } + + int secretbox(byte[] ciphertext, byte[] plaintext, int length, byte[] nonce, byte[] key) + { + return salt().crypto_secretbox_xsalsa20poly1305(ciphertext, plaintext, length, nonce, key); + } + + int secretboxOpen(ByteBuffer plaintext, ByteBuffer box, int length, ByteBuffer nonce, byte[] key) + { + return secretboxOpen(plaintext.array(), box.array(), length, nonce.array(), key); + } + + int secretboxOpen(byte[] plaintext, byte[] box, int length, byte[] nonce, byte[] key) + { + return salt().crypto_secretbox_xsalsa20poly1305_open(plaintext, box, length, nonce, key); + } + + byte[] random(int length) + { + return Utils.randomBytes(length); + } + + public int box(ByteBuffer ciphertext, ByteBuffer plaintext, int length, ByteBuffer nonce, byte[] publicKey, + byte[] secretKey) + { + return box(ciphertext.array(), plaintext.array(), length, nonce.array(), publicKey, secretKey); + } + + public int box(byte[] ciphertext, byte[] plaintext, int length, byte[] nonce, byte[] publicKey, byte[] secretKey) + { + return salt().crypto_box_curve25519xsalsa20poly1305(ciphertext, plaintext, length, nonce, publicKey, secretKey); + } +} diff --git a/src/main/java/zmq/io/mechanism/curve/CurveClientMechanism.java b/src/main/java/zmq/io/mechanism/curve/CurveClientMechanism.java new file mode 100644 index 000000000..5a4b0647e --- /dev/null +++ b/src/main/java/zmq/io/mechanism/curve/CurveClientMechanism.java @@ -0,0 +1,423 @@ +package zmq.io.mechanism.curve; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.Options; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.mechanism.Mechanism; +import zmq.util.Errno; +import zmq.util.Wire; + +public class CurveClientMechanism extends Mechanism +{ + private enum State + { + SEND_HELLO, + EXPECT_WELCOME, + SEND_INITIATE, + EXPECT_READY, + ERROR_RECEIVED, + CONNECTED + } + + private State state; + + // Our public key (C) + private final byte[] publicKey; + // Our secret key (c) + private final byte[] secretKey; + // Our short-term public key (C') + private final byte[] cnPublic; + // Our short-term secret key (c') + private final byte[] cnSecret; + // Server's public key (S) + private final byte[] serverKey; + // Server's short-term public key (S') + private byte[] cnServer = new byte[Curve.Size.PUBLICKEY.bytes()]; + // Cookie received from server + private byte[] cnCookie = new byte[16 + 80]; + // Intermediary buffer used to speed up boxing and unboxing. + private final byte[] cnPrecom = new byte[Curve.Size.BEFORENM.bytes()]; + + private long cnNonce; + private long cnPeerNonce; + + private final Curve cryptoBox; + + private final Errno errno; + + public CurveClientMechanism(Options options) + { + super(null, null, options); + this.state = State.SEND_HELLO; + cnNonce = 1; + cnPeerNonce = 1; + publicKey = options.curvePublicKey; + assert (publicKey != null && publicKey.length == Curve.Size.PUBLICKEY.bytes()); + secretKey = options.curveSecretKey; + assert (secretKey != null && secretKey.length == Curve.Size.SECRETKEY.bytes()); + serverKey = options.curveServerKey; + assert (serverKey != null && serverKey.length == Curve.Size.PUBLICKEY.bytes()); + + cryptoBox = new Curve(); + // Generate short-term key pair + byte[][] keys = cryptoBox.keypair(); + assert (keys != null && keys.length == 2); + cnPublic = keys[0]; + assert (cnPublic != null && cnPublic.length == Curve.Size.PUBLICKEY.bytes()); + cnSecret = keys[1]; + assert (cnSecret != null && cnSecret.length == Curve.Size.SECRETKEY.bytes()); + errno = options.errno; + } + + @Override + public int nextHandshakeCommand(Msg msg) + { + int rc = 0; + switch (state) { + case SEND_HELLO: + rc = produceHello(msg); + if (rc == 0) { + state = State.EXPECT_WELCOME; + } + break; + case SEND_INITIATE: + rc = produceInitiate(msg); + if (rc == 0) { + state = State.EXPECT_READY; + } + break; + default: + rc = ZError.EAGAIN; + break; + + } + return rc; + } + + @Override + public int processHandshakeCommand(Msg msg) + { + int rc = 0; + + int dataSize = msg.size(); + if (dataSize >= 8 && compare(msg, "WELCOME", true)) { + rc = processWelcome(msg); + } + else if (dataSize >= 6 && compare(msg, "READY", true)) { + rc = processReady(msg); + } + else if (dataSize >= 6 && compare(msg, "ERROR", true)) { + rc = processError(msg); + } + else { + rc = ZError.EPROTO; + } + return rc; + } + + @Override + public Msg encode(Msg msg) + { + assert (state == State.CONNECTED); + + byte flags = 0; + if ((msg.flags() & Msg.MORE) != 0) { + flags |= 0x01; + } + + ByteBuffer messageNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + + messageNonce.put("CurveZMQMESSAGEC".getBytes(ZMQ.CHARSET)); + Wire.putUInt64(messageNonce, cnNonce); + + int mlen = Curve.Size.ZERO.bytes() + 1 + msg.size(); + + ByteBuffer messagePlaintext = ByteBuffer.allocate(mlen); + messagePlaintext.put(Curve.Size.ZERO.bytes(), flags); + messagePlaintext.position(Curve.Size.ZERO.bytes() + 1); + msg.transfer(messagePlaintext, 0, msg.size()); + + ByteBuffer messageBox = ByteBuffer.allocate(mlen); + + int rc = cryptoBox.afternm(messageBox, messagePlaintext, mlen, messageNonce, cnPrecom); + assert (rc == 0); + + Msg encoded = new Msg(16 + mlen - Curve.Size.BOXZERO.bytes()); + appendData(encoded, "MESSAGE"); + encoded.put(messageNonce, 16, 8); + encoded.put(messageBox, Curve.Size.BOXZERO.bytes(), mlen - Curve.Size.BOXZERO.bytes()); + + cnNonce++; + return encoded; + } + + @Override + public Msg decode(Msg msg) + { + assert (state == State.CONNECTED); + + if (msg.size() < 33) { + // Temporary support for security debugging + puts("CURVE I: invalid CURVE server, sent malformed command"); + errno.set(ZError.EPROTO); + return null; + } + + if (!compare(msg, "MESSAGE", true)) { + // Temporary support for security debugging + puts("CURVE I: invalid CURVE server, did not send MESSAGE"); + errno.set(ZError.EPROTO); + return null; + } + + ByteBuffer messageNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + messageNonce.put("CurveZMQMESSAGES".getBytes(ZMQ.CHARSET)); + msg.transfer(messageNonce, 8, 8); + + long nonce = Wire.getUInt64(msg, 8); + + if (nonce <= cnPeerNonce) { + errno.set(ZError.EPROTO); + return null; + } + cnPeerNonce = nonce; + + int clen = Curve.Size.BOXZERO.bytes() + msg.size() - 16; + + ByteBuffer messagePlaintext = ByteBuffer.allocate(clen); + ByteBuffer messageBox = ByteBuffer.allocate(clen); + + messageBox.position(Curve.Size.BOXZERO.bytes()); + msg.transfer(messageBox, 16, msg.size() - 16); + + int rc = cryptoBox.openAfternm(messagePlaintext, messageBox, clen, messageNonce, cnPrecom); + if (rc == 0) { + Msg decoded = new Msg(clen - 1 - Curve.Size.ZERO.bytes()); + + byte flags = messagePlaintext.get(Curve.Size.ZERO.bytes()); + if ((flags & 0x01) != 0) { + decoded.setFlags(Msg.MORE); + } + + messagePlaintext.position(Curve.Size.ZERO.bytes() + 1); + decoded.put(messagePlaintext); + return decoded; + } + else { + // Temporary support for security debugging + puts("CURVE I: connection key used for MESSAGE is wrong"); + errno.set(ZError.EPROTO); + return null; + } + } + + @Override + public Status status() + { + if (state == State.CONNECTED) { + return Status.READY; + } + else if (state == State.ERROR_RECEIVED) { + return Status.ERROR; + } + else { + return Status.HANDSHAKING; + } + } + + @Override + public int zapMsgAvailable() + { + return 0; + } + + private int produceHello(Msg msg) + { + ByteBuffer helloNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer helloPlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 64); + ByteBuffer helloBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 80); + + // Prepare the full nonce + helloNonce.put("CurveZMQHELLO---".getBytes(ZMQ.CHARSET)); + Wire.putUInt64(helloNonce, cnNonce); + + // Create Box [64 * %x0](C'->S) + int rc = cryptoBox.box(helloBox, helloPlaintext, helloPlaintext.capacity(), helloNonce, serverKey, cnSecret); + if (rc != 0) { + return -1; + } + appendData(msg, "HELLO"); + // CurveZMQ major and minor version numbers + msg.put(1); + msg.put(0); + // Anti-amplification padding + msg.put(new byte[72]); + // Client public connection key + msg.put(cnPublic); + // Short nonce, prefixed by "CurveZMQHELLO---" + msg.put(helloNonce, 16, 8); + // Signature, Box [64 * %x0](C'->S) + msg.put(helloBox, Curve.Size.BOXZERO.bytes(), 80); + + assert (msg.size() == 200); + cnNonce++; + + return 0; + } + + private int processWelcome(Msg msg) + { + if (msg.size() != 168) { + // Temporary support for security debugging + puts("CURVE I: server HELLO is not correct size"); + return ZError.EPROTO; + } + + ByteBuffer welcomeNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer welcomePlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 128); + ByteBuffer welcomeBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 144); + + // Open Box [S' + cookie](C'->S) + welcomeBox.position(Curve.Size.BOXZERO.bytes()); + msg.transfer(welcomeBox, 24, 144); + + welcomeNonce.put("WELCOME-".getBytes(ZMQ.CHARSET)); + msg.transfer(welcomeNonce, 8, 16); + + int rc = cryptoBox.open(welcomePlaintext, welcomeBox, welcomeBox.capacity(), welcomeNonce, serverKey, cnSecret); + if (rc != 0) { + return ZError.EPROTO; + } + + welcomePlaintext.position(Curve.Size.ZERO.bytes()); + welcomePlaintext.get(cnServer); + welcomePlaintext.get(cnCookie); + + // Message independent precomputation + rc = cryptoBox.beforenm(cnPrecom, cnServer, cnSecret); + assert (rc == 0); + + state = State.SEND_INITIATE; + + return 0; + } + + private int produceInitiate(Msg msg) + { + ByteBuffer vouchNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer vouchPlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 64); + ByteBuffer vouchBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 80); + + // Create vouch = Box [C',S](C->S') + vouchPlaintext.position(Curve.Size.ZERO.bytes()); + vouchPlaintext.put(cnPublic); + vouchPlaintext.put(serverKey); + + vouchNonce.put("VOUCH---".getBytes(ZMQ.CHARSET)); + vouchNonce.put(cryptoBox.random(16)); + + int rc = cryptoBox.box(vouchBox, vouchPlaintext, vouchPlaintext.capacity(), vouchNonce, cnServer, secretKey); + if (rc == -1) { + return -1; + } + + // Assume here that metadata is limited to 256 bytes + ByteBuffer initiateNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer initiatePlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 128 + 256); + ByteBuffer initiateBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 144 + 256); + + // Create Box [C + vouch + metadata](C'->S') + initiatePlaintext.position(Curve.Size.ZERO.bytes()); + initiatePlaintext.put(publicKey); + vouchNonce.limit(16 + 8).position(8); + initiatePlaintext.put(vouchNonce); + vouchBox.limit(Curve.Size.BOXZERO.bytes() + 80).position(Curve.Size.BOXZERO.bytes()); + initiatePlaintext.put(vouchBox); + + // Metadata starts after vouch + + // Add socket type property + String socketType = socketType(options.type); + initiatePlaintext.put(addProperty(SOCKET_TYPE, socketType)); + + // Add identity property + if (options.type == ZMQ.ZMQ_REQ || options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_ROUTER) { + initiatePlaintext.put(addProperty(IDENTITY, options.identity)); + } + + int mlen = initiatePlaintext.position(); + + initiateNonce.put("CurveZMQINITIATE".getBytes(ZMQ.CHARSET)); + Wire.putUInt64(initiateNonce, cnNonce); + + rc = cryptoBox.box(initiateBox, initiatePlaintext, mlen, initiateNonce, cnServer, cnSecret); + if (rc == -1) { + return -1; + } + + appendData(msg, "INITIATE"); + // Cookie provided by the server in the WELCOME command + msg.put(cnCookie); + // Short nonce, prefixed by "CurveZMQINITIATE" + msg.put(initiateNonce, 16, 8); + // Box [C + vouch + metadata](C'->S') + msg.put(initiateBox, Curve.Size.BOXZERO.bytes(), mlen - Curve.Size.BOXZERO.bytes()); + + assert (msg.size() == 113 + mlen - Curve.Size.BOXZERO.bytes()); + cnNonce++; + + return 0; + } + + private int processReady(Msg msg) + { + if (msg.size() < 30) { + return ZError.EPROTO; + } + int clen = Curve.Size.BOXZERO.bytes() + msg.size() - 14; + + ByteBuffer readyNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer readyPlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 256); + ByteBuffer readyBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 16 + 256); + + readyBox.position(Curve.Size.BOXZERO.bytes()); + msg.transfer(readyBox, 14, clen - Curve.Size.BOXZERO.bytes()); + + readyNonce.put("CurveZMQREADY---".getBytes(ZMQ.CHARSET)); + msg.transfer(readyNonce, 6, 8); + cnPeerNonce = Wire.getUInt64(msg, 6); + + int rc = cryptoBox.openAfternm(readyPlaintext, readyBox, clen, readyNonce, cnPrecom); + if (rc != 0) { + return ZError.EPROTO; + } + + readyPlaintext.limit(clen); + rc = parseMetadata(readyPlaintext, Curve.Size.ZERO.bytes(), false); + if (rc == 0) { + state = State.CONNECTED; + } + + return rc; + } + + private int processError(Msg msg) + { + if (state != State.EXPECT_WELCOME && state != State.EXPECT_READY) { + return ZError.EPROTO; + } + if (msg.size() < 7) { + return ZError.EPROTO; + } + byte errorReasonLength = msg.get(6); + if (errorReasonLength > msg.size() - 7) { + return ZError.EPROTO; + } + state = State.ERROR_RECEIVED; + + return 0; + } +} diff --git a/src/main/java/zmq/io/mechanism/curve/CurveServerMechanism.java b/src/main/java/zmq/io/mechanism/curve/CurveServerMechanism.java new file mode 100644 index 000000000..2e0e446e5 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/curve/CurveServerMechanism.java @@ -0,0 +1,530 @@ +package zmq.io.mechanism.curve; + +import java.nio.ByteBuffer; + +import zmq.Msg; +import zmq.Options; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.SessionBase; +import zmq.io.mechanism.Mechanism; +import zmq.io.mechanism.Mechanisms; +import zmq.io.net.Address; +import zmq.util.Errno; +import zmq.util.Wire; + +public class CurveServerMechanism extends Mechanism +{ + private enum State + { + EXPECT_HELLO, + SEND_WELCOME, + EXPECT_INITIATE, + EXPECT_ZAP_REPLY, + SEND_READY, + SEND_ERROR, + ERROR_SENT, + CONNECTED + } + + private long cnNonce; + private long cnPeerNonce; + // Our secret key (s) + private final byte[] secretKey; + // Our short-term public key (S') + private final byte[] cnPublic; + // Our short-term secret key (s') + private final byte[] cnSecret; + // Client's short-term public key (C') + private byte[] cnClient = new byte[Curve.Size.PUBLICKEY.bytes()]; + // Key used to produce cookie + private byte[] cookieKey; + // Intermediary buffer used to speed up boxing and unboxing. + private final byte[] cnPrecom = new byte[Curve.Size.BEFORENM.bytes()]; + + private State state; + + private final Curve cryptoBox; + + private final Errno errno; + + public CurveServerMechanism(SessionBase session, Address peerAddress, Options options) + { + super(session, peerAddress, options); + this.state = State.EXPECT_HELLO; + cnNonce = 1; + cnPeerNonce = 1; + + secretKey = options.curveSecretKey; + assert (secretKey != null && secretKey.length == Curve.Size.SECRETKEY.bytes()); + cryptoBox = new Curve(); + // Generate short-term key pair + byte[][] keys = cryptoBox.keypair(); + assert (keys != null && keys.length == 2); + cnPublic = keys[0]; + assert (cnPublic != null && cnPublic.length == Curve.Size.PUBLICKEY.bytes()); + cnSecret = keys[1]; + assert (cnSecret != null && cnSecret.length == Curve.Size.SECRETKEY.bytes()); + + errno = options.errno; + } + + @Override + public int nextHandshakeCommand(Msg msg) + { + int rc = 0; + switch (state) { + case SEND_WELCOME: + rc = produceWelcome(msg); + if (rc == 0) { + state = State.EXPECT_INITIATE; + } + break; + case SEND_READY: + rc = produceReady(msg); + if (rc == 0) { + state = State.CONNECTED; + } + break; + case SEND_ERROR: + rc = produceError(msg); + if (rc == 0) { + state = State.ERROR_SENT; + } + break; + default: + rc = ZError.EAGAIN; + break; + + } + return rc; + } + + @Override + public int processHandshakeCommand(Msg msg) + { + int rc = 0; + switch (state) { + case EXPECT_HELLO: + rc = processHello(msg); + break; + case EXPECT_INITIATE: + rc = processInitiate(msg); + break; + default: + // Temporary support for security debugging + puts("CURVE I: invalid handshake command"); + rc = ZError.EPROTO; + break; + + } + return rc; + } + + @Override + public Msg encode(Msg msg) + { + assert (state == State.CONNECTED); + + byte flags = 0; + if ((msg.flags() & Msg.MORE) != 0) { + flags |= 0x01; + } + + ByteBuffer messageNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + messageNonce.put("CurveZMQMESSAGES".getBytes(ZMQ.CHARSET)); + Wire.putUInt64(messageNonce, cnNonce); + + int mlen = Curve.Size.ZERO.bytes() + 1 + msg.size(); + + ByteBuffer messagePlaintext = ByteBuffer.allocate(mlen); + messagePlaintext.put(Curve.Size.ZERO.bytes(), flags); + messagePlaintext.position(Curve.Size.ZERO.bytes() + 1); + msg.transfer(messagePlaintext, 0, msg.size()); + + ByteBuffer messageBox = ByteBuffer.allocate(mlen); + + int rc = cryptoBox.afternm(messageBox, messagePlaintext, mlen, messageNonce, cnPrecom); + assert (rc == 0); + + Msg encoded = new Msg(16 + mlen - Curve.Size.BOXZERO.bytes()); + appendData(encoded, "MESSAGE"); + encoded.put(messageNonce, 16, 8); + encoded.put(messageBox, Curve.Size.BOXZERO.bytes(), mlen - Curve.Size.BOXZERO.bytes()); + + cnNonce++; + return encoded; + } + + @Override + public Msg decode(Msg msg) + { + assert (state == State.CONNECTED); + + if (msg.size() < 33) { + // Temporary support for security debugging + puts("CURVE I: invalid CURVE client, sent malformed command"); + errno.set(ZError.EPROTO); + return null; + } + + if (!compare(msg, "MESSAGE", true)) { + // Temporary support for security debugging + puts("CURVE I: invalid CURVE client, did not send MESSAGE"); + errno.set(ZError.EPROTO); + return null; + } + + ByteBuffer messageNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + messageNonce.put("CurveZMQMESSAGEC".getBytes(ZMQ.CHARSET)); + msg.transfer(messageNonce, 8, 8); + + long nonce = Wire.getUInt64(msg, 8); + + if (nonce <= cnPeerNonce) { + errno.set(ZError.EPROTO); + return null; + } + cnPeerNonce = nonce; + + int clen = Curve.Size.BOXZERO.bytes() + msg.size() - 16; + + ByteBuffer messagePlaintext = ByteBuffer.allocate(clen); + ByteBuffer messageBox = ByteBuffer.allocate(clen); + + messageBox.position(Curve.Size.BOXZERO.bytes()); + msg.transfer(messageBox, 16, msg.size() - 16); + + int rc = cryptoBox.openAfternm(messagePlaintext, messageBox, clen, messageNonce, cnPrecom); + if (rc == 0) { + Msg decoded = new Msg(clen - 1 - Curve.Size.ZERO.bytes()); + + byte flags = messagePlaintext.get(Curve.Size.ZERO.bytes()); + if ((flags & 0x01) != 0) { + decoded.setFlags(Msg.MORE); + } + + messagePlaintext.position(Curve.Size.ZERO.bytes() + 1); + decoded.put(messagePlaintext); + return decoded; + } + else { + // Temporary support for security debugging + puts("CURVE I: connection key used for MESSAGE is wrong"); + errno.set(ZError.EPROTO); + return null; + } + } + + @Override + public int zapMsgAvailable() + { + if (state != State.EXPECT_ZAP_REPLY) { + return ZError.EFSM; + } + + int rc = receiveAndProcessZapReply(); + if (rc == 0) { + state = "200".equals(statusCode) ? State.SEND_READY : State.SEND_ERROR; + } + return rc; + } + + @Override + public Status status() + { + if (state == State.CONNECTED) { + return Status.READY; + } + else if (state == State.ERROR_SENT) { + return Status.ERROR; + } + else { + return Status.HANDSHAKING; + } + } + + private int processHello(Msg msg) + { + if (msg.size() != 200) { + // Temporary support for security debugging + puts("CURVE I: client HELLO is not correct size"); + return ZError.EPROTO; + } + + if (!compare(msg, "HELLO", true)) { + // Temporary support for security debugging + puts("CURVE I: client HELLO has invalid command name"); + return ZError.EPROTO; + } + + byte major = msg.get(6); + byte minor = msg.get(7); + + if (major != 1 || minor != 0) { + // Temporary support for security debugging + puts("CURVE I: client HELLO has unknown version number"); + return ZError.EPROTO; + } + + // Save client's short-term public key (C') + msg.getBytes(80, cnClient, 0, Curve.Size.PUBLICKEY.bytes()); + + ByteBuffer helloNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer helloPlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 64); + ByteBuffer helloBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 80); + + helloNonce.put("CurveZMQHELLO---".getBytes(ZMQ.CHARSET)); + msg.transfer(helloNonce, 112, 8); + cnPeerNonce = Wire.getUInt64(msg, 112); + + helloBox.position(Curve.Size.BOXZERO.bytes()); + msg.transfer(helloBox, 120, 80); + + // Open Box [64 * %x0](C'->S) + int rc = cryptoBox.open(helloPlaintext, helloBox, helloBox.capacity(), helloNonce, cnClient, secretKey); + if (rc != 0) { + // Temporary support for security debugging + puts("CURVE I: cannot open client HELLO -- wrong server key?"); + return ZError.EPROTO; + } + + state = State.SEND_WELCOME; + return 0; + } + + private int produceWelcome(Msg msg) + { + ByteBuffer cookieNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer cookiePlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 64); + ByteBuffer cookieCiphertext = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 80); + + // Create full nonce for encryption + // 8-byte prefix plus 16-byte random nonce + cookieNonce.put("COOKIE--".getBytes(ZMQ.CHARSET)); + cookieNonce.put(cryptoBox.random(16)); + + // Generate cookie = Box [C' + s'](t) + cookiePlaintext.position(Curve.Size.ZERO.bytes()); + cookiePlaintext.put(cnClient); + cookiePlaintext.put(cnSecret); + + // Generate fresh cookie key + cookieKey = cryptoBox.random(Curve.Size.KEY.bytes()); + + // Encrypt using symmetric cookie key + int rc = cryptoBox + .secretbox(cookieCiphertext, cookiePlaintext, cookiePlaintext.capacity(), cookieNonce, cookieKey); + assert (rc == 0); + + ByteBuffer welcomeNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer welcomePlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 128); + ByteBuffer welcomeCiphertext = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 144); + + // Create full nonce for encryption + // 8-byte prefix plus 16-byte random nonce + welcomeNonce.put("WELCOME-".getBytes(ZMQ.CHARSET)); + welcomeNonce.put(cryptoBox.random(Curve.Size.NONCE.bytes() - 8)); + + // Create 144-byte Box [S' + cookie](S->C') + welcomePlaintext.position(Curve.Size.ZERO.bytes()); + welcomePlaintext.put(cnPublic); + cookieNonce.limit(16 + 8).position(8); + welcomePlaintext.put(cookieNonce); + cookieCiphertext.limit(Curve.Size.BOXZERO.bytes() + 80).position(Curve.Size.BOXZERO.bytes()); + welcomePlaintext.put(cookieCiphertext); + + rc = cryptoBox.box( + welcomeCiphertext, + welcomePlaintext, + welcomePlaintext.capacity(), + welcomeNonce, + cnClient, + secretKey); + if (rc == -1) { + return -1; + } + appendData(msg, "WELCOME"); + msg.put(welcomeNonce, 8, 16); + msg.put(welcomeCiphertext, Curve.Size.BOXZERO.bytes(), 144); + + assert (msg.size() == 168); + return 0; + } + + private int processInitiate(Msg msg) + { + if (msg.size() < 257) { + // Temporary support for security debugging + puts("CURVE I: client INITIATE is not correct size"); + return ZError.EPROTO; + } + + if (!compare(msg, "INITIATE", true)) { + // Temporary support for security debugging + puts("CURVE I: client INITIATE has invalid command name"); + return ZError.EPROTO; + } + ByteBuffer cookieNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer cookiePlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 64); + ByteBuffer cookieBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 80); + + // Open Box [C' + s'](t) + cookieBox.position(Curve.Size.BOXZERO.bytes()); + msg.transfer(cookieBox, 25, 80); + + cookieNonce.put("COOKIE--".getBytes(ZMQ.CHARSET)); + msg.transfer(cookieNonce, 9, 16); + + int rc = cryptoBox.secretboxOpen(cookiePlaintext, cookieBox, cookieBox.capacity(), cookieNonce, cookieKey); + if (rc != 0) { + // Temporary support for security debugging + puts("CURVE I: cannot open client INITIATE cookie"); + return ZError.EPROTO; + } + + // Check cookie plain text is as expected [C' + s'] + if (!compare(cookiePlaintext, cnClient, Curve.Size.ZERO.bytes(), 32) + || !compare(cookiePlaintext, cnSecret, Curve.Size.ZERO.bytes() + 32, 32)) { + // Temporary support for security debugging + puts("CURVE I: client INITIATE cookie is not valid"); + return ZError.EPROTO; + } + + int clen = msg.size() - 113 + Curve.Size.BOXZERO.bytes(); + + ByteBuffer initiateNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer initiatePlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 128 + 256); + ByteBuffer initiateBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 144 + 256); + + // Open Box [C + vouch + metadata](C'->S') + initiateBox.position(Curve.Size.BOXZERO.bytes()); + msg.transfer(initiateBox, 113, clen - Curve.Size.BOXZERO.bytes()); + + initiateNonce.put("CurveZMQINITIATE".getBytes(ZMQ.CHARSET)); + msg.transfer(initiateNonce, 105, 8); + + cnPeerNonce = Wire.getUInt64(msg, 105); + + rc = cryptoBox.open(initiatePlaintext, initiateBox, clen, initiateNonce, cnClient, cnSecret); + if (rc != 0) { + // Temporary support for security debugging + puts("CURVE I: cannot open client INITIATE"); + return ZError.EPROTO; + } + + byte[] clientKey = new byte[128 + 256]; + initiatePlaintext.position(Curve.Size.ZERO.bytes()); + initiatePlaintext.get(clientKey); + + ByteBuffer vouchNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer vouchPlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 64); + ByteBuffer vouchBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 80); + + // Open Box Box [C',S](C->S') and check contents + vouchBox.position(Curve.Size.BOXZERO.bytes()); + initiatePlaintext.limit(Curve.Size.ZERO.bytes() + 48 + 80).position(Curve.Size.ZERO.bytes() + 48); + vouchBox.put(initiatePlaintext); + + vouchNonce.put("VOUCH---".getBytes(ZMQ.CHARSET)); + initiatePlaintext.limit(Curve.Size.ZERO.bytes() + 32 + 16).position(Curve.Size.ZERO.bytes() + 32); + vouchNonce.put(initiatePlaintext); + + rc = cryptoBox.open(vouchPlaintext, vouchBox, vouchBox.capacity(), vouchNonce, clientKey, cnSecret); + if (rc != 0) { + // Temporary support for security debugging + puts("CURVE I: cannot open client INITIATE vouch"); + return ZError.EPROTO; + } + + // What we decrypted must be the client's short-term public key + if (!compare(vouchPlaintext, cnClient, Curve.Size.ZERO.bytes(), 32)) { + // Temporary support for security debugging + puts("CURVE I: invalid handshake from client (public key)"); + return ZError.EPROTO; + } + + // Precompute connection secret from client key + rc = cryptoBox.beforenm(cnPrecom, cnClient, cnSecret); + assert (rc == 0); + + // Use ZAP protocol (RFC 27) to authenticate the user. + rc = session.zapConnect(); + if (rc == 0) { + sendZapRequest(clientKey); + rc = receiveAndProcessZapReply(); + if (rc == 0) { + state = "200".equals(statusCode) ? State.SEND_READY : State.SEND_ERROR; + } + else if (rc == ZError.EAGAIN) { + state = State.EXPECT_ZAP_REPLY; + } + else { + return -1; + } + } + else { + state = State.SEND_READY; + } + initiatePlaintext.position(0); + initiatePlaintext.limit(clen); + return parseMetadata(initiatePlaintext, Curve.Size.ZERO.bytes() + 128, false); + } + + private int produceReady(Msg msg) + { + ByteBuffer readyNonce = ByteBuffer.allocate(Curve.Size.NONCE.bytes()); + ByteBuffer readyPlaintext = ByteBuffer.allocate(Curve.Size.ZERO.bytes() + 256); + ByteBuffer readyBox = ByteBuffer.allocate(Curve.Size.BOXZERO.bytes() + 16 + 256); + + // Create Box [metadata](S'->C') + readyPlaintext.position(Curve.Size.ZERO.bytes()); + // Add socket type property + String socketType = socketType(options.type); + readyPlaintext.put(addProperty(SOCKET_TYPE, socketType)); + + // Add identity property + if (options.type == ZMQ.ZMQ_REQ || options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_ROUTER) { + readyPlaintext.put(addProperty(IDENTITY, options.identity)); + } + + int mlen = readyPlaintext.position(); + readyNonce.put("CurveZMQREADY---".getBytes(ZMQ.CHARSET)); + Wire.putUInt64(readyNonce, cnNonce); + + int rc = cryptoBox.afternm(readyBox, readyPlaintext, mlen, readyNonce, cnPrecom); + assert (rc == 0); + + appendData(msg, "READY"); + // Short nonce, prefixed by "CurveZMQREADY---" + msg.put(readyNonce, 16, 8); + // Box [metadata](S'->C') + msg.put(readyBox, Curve.Size.BOXZERO.bytes(), mlen - Curve.Size.BOXZERO.bytes()); + + assert (msg.size() == 14 + mlen - Curve.Size.BOXZERO.bytes()); + cnNonce++; + + return 0; + } + + private int produceError(Msg msg) + { + assert (statusCode != null && statusCode.length() == 3); + + appendData(msg, "ERROR"); + appendData(msg, statusCode); + + return 0; + } + + private void sendZapRequest(byte[] key) + { + sendZapRequest(Mechanisms.CURVE, true); + + // Credentials frame + Msg msg = new Msg(Curve.Size.PUBLICKEY.bytes()); + msg.put(key, 0, Curve.Size.PUBLICKEY.bytes()); + boolean rc = session.writeZapMsg(msg); + assert (rc); + } +} diff --git a/src/main/java/zmq/io/mechanism/gssapi/GssapiClientMechanism.java b/src/main/java/zmq/io/mechanism/gssapi/GssapiClientMechanism.java new file mode 100644 index 000000000..fdf7ed6f6 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/gssapi/GssapiClientMechanism.java @@ -0,0 +1,39 @@ +package zmq.io.mechanism.gssapi; + +import zmq.Msg; +import zmq.Options; +import zmq.io.mechanism.Mechanism; + +// TODO V4 implement GSSAPI +public class GssapiClientMechanism extends Mechanism +{ + public GssapiClientMechanism(Options options) + { + super(null, null, options); + throw new UnsupportedOperationException("GSSAPI mechanism is not yet implemented"); + } + + @Override + public Status status() + { + return null; + } + + @Override + public int zapMsgAvailable() + { + return 0; + } + + @Override + public int processHandshakeCommand(Msg msg) + { + return 0; + } + + @Override + public int nextHandshakeCommand(Msg msg) + { + return 0; + } +} diff --git a/src/main/java/zmq/io/mechanism/gssapi/GssapiServerMechanism.java b/src/main/java/zmq/io/mechanism/gssapi/GssapiServerMechanism.java new file mode 100644 index 000000000..227f0f912 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/gssapi/GssapiServerMechanism.java @@ -0,0 +1,41 @@ +package zmq.io.mechanism.gssapi; + +import zmq.Msg; +import zmq.Options; +import zmq.io.SessionBase; +import zmq.io.mechanism.Mechanism; +import zmq.io.net.Address; + +// TODO V4 implement GSSAPI +public class GssapiServerMechanism extends Mechanism +{ + public GssapiServerMechanism(SessionBase session, Address peerAddress, Options options) + { + super(session, peerAddress, options); + throw new UnsupportedOperationException("GSSAPI mechanism is not yet implemented"); + } + + @Override + public Status status() + { + return null; + } + + @Override + public int zapMsgAvailable() + { + return 0; + } + + @Override + public int processHandshakeCommand(Msg msg) + { + return 0; + } + + @Override + public int nextHandshakeCommand(Msg msg) + { + return 0; + } +} diff --git a/src/main/java/zmq/io/mechanism/plain/PlainClientMechanism.java b/src/main/java/zmq/io/mechanism/plain/PlainClientMechanism.java new file mode 100644 index 000000000..541ddf947 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/plain/PlainClientMechanism.java @@ -0,0 +1,170 @@ +package zmq.io.mechanism.plain; + +import zmq.Msg; +import zmq.Options; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.mechanism.Mechanism; + +public class PlainClientMechanism extends Mechanism +{ + private enum State + { + SENDING_HELLO, + WAITING_FOR_WELCOME, + SENDING_INITIATE, + WAITING_FOR_READY, + ERROR_COMMAND_RECEIVED, + READY + } + + private State state; + + public PlainClientMechanism(Options options) + { + super(null, null, options); + this.state = State.SENDING_HELLO; + } + + @Override + public int nextHandshakeCommand(Msg msg) + { + int rc = 0; + switch (state) { + case SENDING_HELLO: + rc = produceHello(msg); + if (rc == 0) { + state = State.WAITING_FOR_WELCOME; + } + break; + case SENDING_INITIATE: + rc = produceInitiate(msg); + if (rc == 0) { + state = State.WAITING_FOR_READY; + } + break; + default: + rc = ZError.EAGAIN; + break; + + } + return rc; + } + + @Override + public int processHandshakeCommand(Msg msg) + { + int rc = 0; + + int dataSize = msg.size(); + if (dataSize >= 8 && compare(msg, "WELCOME", true)) { + rc = processWelcome(msg); + } + else if (dataSize >= 6 && compare(msg, "READY", true)) { + rc = processReady(msg); + } + else if (dataSize >= 6 && compare(msg, "ERROR", true)) { + rc = processError(msg); + } + else { + // Temporary support for security debugging + System.out.println("PLAIN Client I: invalid handshake command"); + rc = ZError.EPROTO; + } + return rc; + } + + @Override + public Status status() + { + if (state == State.READY) { + return Status.READY; + } + else if (state == State.ERROR_COMMAND_RECEIVED) { + return Status.ERROR; + } + else { + return Status.HANDSHAKING; + } + } + + @Override + public int zapMsgAvailable() + { + return 0; + } + + private int produceHello(Msg msg) + { + String plainUsername = options.plainUsername; + assert (plainUsername.length() < 256); + + String plainPassword = options.plainPassword; + assert (plainPassword.length() < 256); + + appendData(msg, "HELLO"); + appendData(msg, plainUsername); + appendData(msg, plainPassword); + + return 0; + } + + private int processWelcome(Msg msg) + { + if (state != State.WAITING_FOR_WELCOME) { + return ZError.EPROTO; + } + if (msg.size() != 8) { + return ZError.EPROTO; + } + state = State.SENDING_INITIATE; + return 0; + } + + private int produceInitiate(Msg msg) + { + // Add mechanism string + appendData(msg, "INITIATE"); + + // Add socket type property + String socketType = socketType(options.type); + msg.put(addProperty(SOCKET_TYPE, socketType)); + + // Add identity property + if (options.type == ZMQ.ZMQ_REQ || options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_ROUTER) { + msg.put(addProperty(IDENTITY, options.identity)); + } + + return 0; + } + + private int processReady(Msg msg) + { + if (state != State.WAITING_FOR_READY) { + return ZError.EPROTO; + } + int rc = parseMetadata(msg, 6, false); + if (rc == 0) { + state = State.READY; + } + + return rc; + } + + private int processError(Msg msg) + { + if (state != State.WAITING_FOR_WELCOME && state != State.WAITING_FOR_READY) { + return ZError.EPROTO; + } + if (msg.size() < 7) { + return ZError.EPROTO; + } + byte errorReasonLength = msg.get(6); + if (errorReasonLength > msg.size() - 7) { + return ZError.EPROTO; + } + state = State.ERROR_COMMAND_RECEIVED; + + return 0; + } +} diff --git a/src/main/java/zmq/io/mechanism/plain/PlainServerMechanism.java b/src/main/java/zmq/io/mechanism/plain/PlainServerMechanism.java new file mode 100644 index 000000000..500918742 --- /dev/null +++ b/src/main/java/zmq/io/mechanism/plain/PlainServerMechanism.java @@ -0,0 +1,252 @@ +package zmq.io.mechanism.plain; + +import zmq.Msg; +import zmq.Options; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.SessionBase; +import zmq.io.mechanism.Mechanism; +import zmq.io.mechanism.Mechanisms; +import zmq.io.net.Address; + +public class PlainServerMechanism extends Mechanism +{ + private enum State + { + WAITING_FOR_HELLO, + SENDING_WELCOME, + WAITING_FOR_INITIATE, + SENDING_READY, + WAITING_FOR_ZAP_REPLY, + SENDING_ERROR, + ERROR_COMMAND_SENT, + READY + } + + private State state; + + public PlainServerMechanism(SessionBase session, Address peerAddress, Options options) + { + super(session, peerAddress, options); + this.state = State.WAITING_FOR_HELLO; + } + + @Override + public int nextHandshakeCommand(Msg msg) + { + int rc = 0; + switch (state) { + case SENDING_WELCOME: + rc = produceWelcome(msg); + if (rc == 0) { + state = State.WAITING_FOR_INITIATE; + } + break; + case SENDING_READY: + rc = produceReady(msg); + if (rc == 0) { + state = State.READY; + } + break; + case SENDING_ERROR: + rc = produceError(msg); + if (rc == 0) { + state = State.ERROR_COMMAND_SENT; + } + break; + default: + rc = ZError.EAGAIN; + break; + + } + return rc; + } + + @Override + public int processHandshakeCommand(Msg msg) + { + int rc = 0; + switch (state) { + case WAITING_FOR_HELLO: + rc = produceHello(msg); + break; + case WAITING_FOR_INITIATE: + rc = produceInitiate(msg); + break; + default: + // Temporary support for security debugging + puts("PLAIN Server I: invalid handshake command"); + rc = ZError.EPROTO; + break; + + } + return rc; + } + + @Override + public Status status() + { + if (state == State.READY) { + return Status.READY; + } + else if (state == State.ERROR_COMMAND_SENT) { + return Status.ERROR; + } + else { + return Status.HANDSHAKING; + } + } + + @Override + public int zapMsgAvailable() + { + if (state != State.WAITING_FOR_ZAP_REPLY) { + return ZError.EFSM; + } + + int rc = receiveAndProcessZapReply(); + if (rc == 0) { + state = "200".equals(statusCode) ? State.SENDING_WELCOME : State.SENDING_ERROR; + } + return rc; + } + + private int produceHello(Msg msg) + { + int bytesLeft = msg.size(); + int index = 0; + if (bytesLeft < 6 || !compare(msg, "HELLO", true)) { + // Temporary support for security debugging + puts("PLAIN I: invalid PLAIN client, did not send HELLO"); + return ZError.EPROTO; + } + bytesLeft -= 6; + index += 6; + if (bytesLeft < 1) { + // Temporary support for security debugging + puts("PLAIN I: invalid PLAIN client, did not send username"); + return ZError.EPROTO; + } + byte length = msg.get(index); + bytesLeft -= 1; + if (bytesLeft < length) { + // Temporary support for security debugging + puts("PLAIN I: invalid PLAIN client, sent malformed username"); + return ZError.EPROTO; + } + byte[] tmp = new byte[length]; + index += 1; + msg.getBytes(index, tmp, 0, length); + byte[] username = tmp; + bytesLeft -= length; + index += length; + + length = msg.get(index); + bytesLeft -= 1; + if (bytesLeft < length) { + // Temporary support for security debugging + puts("PLAIN I: invalid PLAIN client, sent malformed password"); + return ZError.EPROTO; + } + tmp = new byte[length]; + index += 1; + msg.getBytes(index, tmp, 0, length); + byte[] password = tmp; + bytesLeft -= length; + index += length; + + if (bytesLeft > 0) { + // Temporary support for security debugging + puts("PLAIN I: invalid PLAIN client, sent extraneous data"); + return ZError.EPROTO; + } + + // Use ZAP protocol (RFC 27) to authenticate the user. + int rc = session.zapConnect(); + if (rc == 0) { + sendZapRequest(username, password); + rc = receiveAndProcessZapReply(); + if (rc == 0) { + state = "200".equals(statusCode) ? State.SENDING_WELCOME : State.SENDING_ERROR; + } + else if (rc == ZError.EAGAIN) { + state = State.WAITING_FOR_ZAP_REPLY; + } + else { + return -1; + } + } + else { + state = State.SENDING_WELCOME; + } + + return 0; + } + + private int produceWelcome(Msg msg) + { + appendData(msg, "WELCOME"); + return 0; + } + + private int produceInitiate(Msg msg) + { + int bytesLeft = msg.size(); + if (bytesLeft < 9 || !compare(msg, "INITIATE", true)) { + // Temporary support for security debugging + puts("PLAIN I: invalid PLAIN client, did not send INITIATE"); + return ZError.EPROTO; + } + + int rc = parseMetadata(msg, 9, false); + if (rc == 0) { + state = State.SENDING_READY; + } + return rc; + } + + private int produceReady(Msg msg) + { + // Add command name + appendData(msg, "READY"); + + // Add socket type property + String socketType = socketType(options.type); + msg.put(addProperty(SOCKET_TYPE, socketType)); + + // Add identity property + if (options.type == ZMQ.ZMQ_REQ || options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_ROUTER) { + msg.put(addProperty(IDENTITY, options.identity)); + } + + return 0; + } + + private int produceError(Msg msg) + { + assert (statusCode != null && statusCode.length() == 3); + + appendData(msg, "ERROR"); + appendData(msg, statusCode); + + return 0; + } + + private void sendZapRequest(byte[] username, byte[] password) + { + sendZapRequest(Mechanisms.PLAIN, true); + + // Username frame + Msg msg = new Msg(username.length); + msg.setFlags(Msg.MORE); + msg.put(username); + boolean rc = session.writeZapMsg(msg); + assert (rc); + + // Password frame + msg = new Msg(password.length); + msg.put(password); + rc = session.writeZapMsg(msg); + assert (rc); + } +} diff --git a/src/main/java/zmq/io/net/Address.java b/src/main/java/zmq/io/net/Address.java new file mode 100644 index 000000000..84cafab88 --- /dev/null +++ b/src/main/java/zmq/io/net/Address.java @@ -0,0 +1,99 @@ +package zmq.io.net; + +import java.net.InetSocketAddress; +import java.net.ProtocolFamily; +import java.net.SocketAddress; + +import zmq.io.net.ipc.IpcAddress; +import zmq.io.net.tcp.TcpAddress; + +public class Address +{ + public interface IZAddress + { + ProtocolFamily family(); + + @Override + String toString(); + + InetSocketAddress resolve(String name, boolean ipv6, boolean local); + + SocketAddress address(); + + SocketAddress sourceAddress(); + }; + + private final NetProtocol protocol; + private final String address; + // private final boolean ipv4only; + + private IZAddress resolved; + + public Address(final String protocol, final String address) + { + this.protocol = NetProtocol.getProtocol(protocol); + this.address = address; + resolved = null; + } + + public Address(SocketAddress socketAddress) + { + InetSocketAddress sockAddr = (InetSocketAddress) socketAddress; + this.address = sockAddr.getAddress().getHostAddress() + ":" + sockAddr.getPort(); + protocol = NetProtocol.tcp; + resolved = null; + // ipv4only = !(sockAddr.getAddress() instanceof Inet6Address); + } + + @Override + public String toString() + { + if (NetProtocol.tcp == protocol && isResolved()) { + return resolved.toString(); + } + else if (NetProtocol.ipc == protocol && isResolved()) { + return resolved.toString(); + } + else if (protocol != null && !address.isEmpty()) { + return protocol.name() + "://" + address; + } + else { + return ""; + } + } + + public NetProtocol protocol() + { + return protocol; + } + + public String address() + { + return address; + } + + public IZAddress resolved() + { + return resolved; + } + + public boolean isResolved() + { + return resolved != null; + } + + public IZAddress resolve(boolean ipv6) + { + if (NetProtocol.tcp.equals(protocol)) { + resolved = new TcpAddress(address, ipv6); + return resolved; + } + else if (NetProtocol.ipc.equals(protocol)) { + resolved = new IpcAddress(address); + return resolved; + } + else { + return null; + } + } +} diff --git a/src/main/java/zmq/io/net/NetProtocol.java b/src/main/java/zmq/io/net/NetProtocol.java new file mode 100644 index 000000000..b9d38f9e5 --- /dev/null +++ b/src/main/java/zmq/io/net/NetProtocol.java @@ -0,0 +1,41 @@ +package zmq.io.net; + +import java.util.Arrays; +import java.util.List; + +import zmq.socket.Sockets; + +public enum NetProtocol +{ + inproc(true), + ipc(true), + tcp(true), + pgm(false, Sockets.PUB, Sockets.SUB, Sockets.XPUB, Sockets.XPUB), + epgm(false, Sockets.PUB, Sockets.SUB, Sockets.XPUB, Sockets.XPUB), + tipc(false), + norm(false); + + public final boolean valid; + private List compatibles; + + private NetProtocol(boolean implemented, Sockets... compatibles) + { + valid = implemented; + this.compatibles = Arrays.asList(compatibles); + } + + public static NetProtocol getProtocol(String protocol) + { + for (NetProtocol candidate : values()) { + if (candidate.name().equals(protocol)) { + return candidate; + } + } + return null; + } + + public final boolean compatible(int type) + { + return compatibles.isEmpty() || compatibles.contains(Sockets.fromType(type)); + } +} diff --git a/src/main/java/zmq/io/net/ipc/IpcAddress.java b/src/main/java/zmq/io/net/ipc/IpcAddress.java new file mode 100644 index 000000000..64937cf4a --- /dev/null +++ b/src/main/java/zmq/io/net/ipc/IpcAddress.java @@ -0,0 +1,92 @@ +package zmq.io.net.ipc; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ProtocolFamily; +import java.net.SocketAddress; +import java.net.StandardProtocolFamily; +import java.net.UnknownHostException; + +import zmq.io.net.Address; +import zmq.io.net.tcp.TcpAddress; + +public class IpcAddress implements Address.IZAddress +{ + public static class IpcAddressMask extends TcpAddress + { + public IpcAddressMask(String addr, boolean ipv6) + { + super(addr, ipv6); + } + + public boolean matchAddress(SocketAddress addr) + { + return address().equals(addr); + } + } + + private String name; + private final InetSocketAddress address; + private final SocketAddress sourceAddress; + + public IpcAddress(String addr) + { + String[] strings = addr.split(";"); + + address = resolve(strings[0], false, false); + if (strings.length == 2 && !"".equals(strings[1])) { + sourceAddress = resolve(strings[1], false, false); + } + else { + sourceAddress = null; + } + } + + @Override + public String toString() + { + if (name == null) { + return ""; + } + + return "ipc://" + name; + } + + @Override + public InetSocketAddress resolve(String name, boolean ipv6, boolean local) + { + this.name = name; + + int hash = name.hashCode(); + if (hash < 0) { + hash = -hash; + } + hash = hash % 55536; + hash += 10000; + + try { + return new InetSocketAddress(InetAddress.getByName(null), hash); + } + catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public SocketAddress address() + { + return address; + } + + @Override + public ProtocolFamily family() + { + return StandardProtocolFamily.INET; + } + + @Override + public SocketAddress sourceAddress() + { + return sourceAddress; + } +} diff --git a/src/main/java/zmq/io/net/ipc/IpcConnecter.java b/src/main/java/zmq/io/net/ipc/IpcConnecter.java new file mode 100644 index 000000000..717240314 --- /dev/null +++ b/src/main/java/zmq/io/net/ipc/IpcConnecter.java @@ -0,0 +1,15 @@ +package zmq.io.net.ipc; + +import zmq.Options; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.io.net.tcp.TcpConnecter; + +public class IpcConnecter extends TcpConnecter +{ + public IpcConnecter(IOThread ioThread, SessionBase session, final Options options, final Address addr, boolean wait) + { + super(ioThread, session, options, addr, wait); + } +} diff --git a/src/main/java/zmq/IpcListener.java b/src/main/java/zmq/io/net/ipc/IpcListener.java similarity index 69% rename from src/main/java/zmq/IpcListener.java rename to src/main/java/zmq/io/net/ipc/IpcListener.java index 6229a0181..b8cc1c56f 100644 --- a/src/main/java/zmq/IpcListener.java +++ b/src/main/java/zmq/io/net/ipc/IpcListener.java @@ -1,29 +1,35 @@ -package zmq; +package zmq.io.net.ipc; import java.net.InetSocketAddress; +import zmq.Options; +import zmq.SocketBase; +import zmq.io.IOThread; +import zmq.io.net.tcp.TcpListener; + // fake Unix domain socket public class IpcListener extends TcpListener { - private final IpcAddress address; + private IpcAddress address; public IpcListener(IOThread ioThread, SocketBase socket, final Options options) { super(ioThread, socket, options); - address = new IpcAddress(); } // Get the bound address for use with wildcards + @Override public String getAddress() { return address.toString(); } // Set address to listen on. - public int setAddress(String addr) + @Override + public boolean setAddress(String addr) { - address.resolve(addr, false); + address = new IpcAddress(addr); InetSocketAddress sock = (InetSocketAddress) address.address(); String fake = sock.getAddress().getHostAddress() + ":" + sock.getPort(); diff --git a/src/main/java/zmq/io/net/norm/NormEngine.java b/src/main/java/zmq/io/net/norm/NormEngine.java new file mode 100644 index 000000000..e9a2c0085 --- /dev/null +++ b/src/main/java/zmq/io/net/norm/NormEngine.java @@ -0,0 +1,46 @@ +package zmq.io.net.norm; + +import zmq.Options; +import zmq.io.IEngine; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; + +// TODO V4 implement NORM engine +public class NormEngine implements IEngine +{ + public NormEngine(IOThread ioThread, Options options) + { + throw new UnsupportedOperationException(); + } + + public boolean init(Address addr, boolean b, boolean c) + { + return false; + } + + @Override + public void plug(IOThread ioThread, SessionBase session) + { + } + + @Override + public void terminate() + { + } + + @Override + public void restartInput() + { + } + + @Override + public void restartOutput() + { + } + + @Override + public void zapMsgAvailable() + { + } +} diff --git a/src/main/java/zmq/io/net/pgm/PgmReceiver.java b/src/main/java/zmq/io/net/pgm/PgmReceiver.java new file mode 100644 index 000000000..258349872 --- /dev/null +++ b/src/main/java/zmq/io/net/pgm/PgmReceiver.java @@ -0,0 +1,46 @@ +package zmq.io.net.pgm; + +import zmq.Options; +import zmq.io.IEngine; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; + +//TODO V4 implement pgm receiver +public class PgmReceiver implements IEngine +{ + public PgmReceiver(IOThread ioThread, Options options) + { + throw new UnsupportedOperationException(); + } + + public boolean init(boolean udpEncapsulation, Address addr) + { + return false; + } + + @Override + public void plug(IOThread ioThread, SessionBase session) + { + } + + @Override + public void terminate() + { + } + + @Override + public void restartInput() + { + } + + @Override + public void restartOutput() + { + } + + @Override + public void zapMsgAvailable() + { + } +} diff --git a/src/main/java/zmq/io/net/pgm/PgmSender.java b/src/main/java/zmq/io/net/pgm/PgmSender.java new file mode 100644 index 000000000..937b68da9 --- /dev/null +++ b/src/main/java/zmq/io/net/pgm/PgmSender.java @@ -0,0 +1,46 @@ +package zmq.io.net.pgm; + +import zmq.Options; +import zmq.io.IEngine; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; + +// TODO V4 implement pgm sender +public class PgmSender implements IEngine +{ + public PgmSender(IOThread ioThread, Options options) + { + throw new UnsupportedOperationException(); + } + + public boolean init(boolean udpEncapsulation, Address addr) + { + return false; + } + + @Override + public void plug(IOThread ioThread, SessionBase session) + { + } + + @Override + public void terminate() + { + } + + @Override + public void restartInput() + { + } + + @Override + public void restartOutput() + { + } + + @Override + public void zapMsgAvailable() + { + } +} diff --git a/src/main/java/zmq/io/net/tcp/SocksConnecter.java b/src/main/java/zmq/io/net/tcp/SocksConnecter.java new file mode 100644 index 000000000..13e797ae1 --- /dev/null +++ b/src/main/java/zmq/io/net/tcp/SocksConnecter.java @@ -0,0 +1,149 @@ +package zmq.io.net.tcp; + +import zmq.Options; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.io.net.NetProtocol; + +// TODO continue socks connecter +public class SocksConnecter extends TcpConnecter +{ + // Method ID + private static final int SOCKS_NO_AUTH_REQUIRED = 0; + + private Address proxyAddress; + + private static enum Status + { + UNPLUGGED, + WAITING_FOR_RECONNECT_TIME, + WAITING_FOR_PROXY_CONNECTION, + SENDING_GREETING, + WAITING_FOR_CHOICE, + SENDING_REQUEST, + WAITING_FOR_RESPONSE + } + + Status status; + + // String representation of endpoint to connect to + String endpoint; + + public SocksConnecter(IOThread ioThread, SessionBase session, final Options options, final Address addr, + final Address proxyAddr, boolean delayedStart) + { + super(ioThread, session, options, addr, delayedStart); + assert (NetProtocol.tcp.equals(addr.protocol())); + this.proxyAddress = proxyAddr; + endpoint = proxyAddress.toString(); + this.status = Status.UNPLUGGED; + throw new UnsupportedOperationException("Socks connecter is not implemented"); + } + + @Override + protected void processPlug() + { + if (delayedStart) { + startTimer(); + } + else { + initiateConnect(); + } + } + + @Override + protected void processTerm(int linger) + { + switch (status) { + case UNPLUGGED: + break; + case WAITING_FOR_RECONNECT_TIME: + ioObject.cancelTimer(RECONNECT_TIMER_ID); + break; + case WAITING_FOR_PROXY_CONNECTION: + case SENDING_GREETING: + case WAITING_FOR_CHOICE: + case SENDING_REQUEST: + case WAITING_FOR_RESPONSE: + close(); + break; + + default: + break; + } + super.processTerm(linger); + } + + @Override + public void inEvent() + { + assert (status != Status.UNPLUGGED && status != Status.WAITING_FOR_RECONNECT_TIME); + + // if (status == Status.WAITING_FOR_CHOICE) { + // + // } + super.inEvent(); + } + + @Override + public void outEvent() + { + super.outEvent(); + } + + @Override + public void timerEvent(int id) + { + super.timerEvent(id); + } + + // Internal function to start the actual connection establishment. + void initiateConnect() + { + } + + int processServerResponse() + { + return -1; + } + + void parseAddress(String address, String hostname, int port) + { + } + + void connectToProxy() + { + } + + void error() + { + } + + // Internal function to start reconnect timer + void startTimer() + { + } + + // Internal function to return a reconnect backoff delay. + // Will modify the current_reconnect_ivl used for next call + // Returns the currently used interval + int getNewReconnectIvl() + { + return -1; + } + + // Open TCP connecting socket. Returns -1 in case of error, + // 0 if connect was successfull immediately. Returns -1 with + // EAGAIN errno if async connect was launched. + int open() + { + return -1; + } + + // Get the file descriptor of newly created connection. Returns + // retired_fd if the connection was unsuccessfull. + void checkProxyConnection() + { + } +} diff --git a/src/main/java/zmq/TcpAddress.java b/src/main/java/zmq/io/net/tcp/TcpAddress.java similarity index 62% rename from src/main/java/zmq/TcpAddress.java rename to src/main/java/zmq/io/net/tcp/TcpAddress.java index 54df3eb63..6b0ac5c4c 100644 --- a/src/main/java/zmq/TcpAddress.java +++ b/src/main/java/zmq/io/net/tcp/TcpAddress.java @@ -1,32 +1,56 @@ -package zmq; +package zmq.io.net.tcp; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.ProtocolFamily; import java.net.SocketAddress; +import java.net.StandardProtocolFamily; import java.net.UnknownHostException; +import zmq.io.net.Address; + public class TcpAddress implements Address.IZAddress { public static class TcpAddressMask extends TcpAddress { + public TcpAddressMask(String addr, boolean ipv6) + { + super(addr, ipv6); + } + public boolean matchAddress(SocketAddress addr) { - return address.equals(addr); + return address().equals(addr); } } - protected InetSocketAddress address; + private final InetSocketAddress address; + private final SocketAddress sourceAddress; - public TcpAddress(String addr) + public TcpAddress(String addr, boolean ipv6) { - resolve(addr, false); + String[] strings = addr.split(";"); + + address = resolve(strings[0], ipv6, false); + if (strings.length == 2 && !"".equals(strings[1])) { + sourceAddress = resolve(strings[1], ipv6, false); + } + else { + sourceAddress = null; + } } - public TcpAddress() + @Override + public ProtocolFamily family() { + if (address.getAddress() instanceof Inet6Address) { + return StandardProtocolFamily.INET6; + } + return StandardProtocolFamily.INET; } + // The opposite to resolve() @Override public String toString() { @@ -42,8 +66,11 @@ public String toString() } } + // This function enhances tcp_address_t::resolve() with ability to parse + // additional cidr-like(/xx) mask value at the end of the name string. + // Works only with remote hostnames. @Override - public void resolve(String name, boolean ipv4only) + public InetSocketAddress resolve(String name, boolean ipv6, boolean local) { // Find the ':' at end that separates address from the port number. int delimiter = name.lastIndexOf(':'); @@ -56,8 +83,7 @@ public void resolve(String name, boolean ipv4only) String portStr = name.substring(delimiter + 1); // Remove square brackets around the address, if any. - if (addrStr.length() >= 2 && addrStr.charAt(0) == '[' && - addrStr.charAt(addrStr.length() - 1) == ']') { + if (addrStr.length() >= 2 && addrStr.charAt(0) == '[' && addrStr.charAt(addrStr.length() - 1) == ']') { addrStr = addrStr.substring(1, addrStr.length() - 1); } @@ -82,7 +108,7 @@ public void resolve(String name, boolean ipv4only) } try { for (InetAddress ia : InetAddress.getAllByName(addrStr)) { - if (ipv4only && (ia instanceof Inet6Address)) { + if (ipv6 && !(ia instanceof Inet6Address)) { continue; } addrNet = ia; @@ -97,7 +123,7 @@ public void resolve(String name, boolean ipv4only) throw new IllegalArgumentException(name); } - address = new InetSocketAddress(addrNet, port); + return new InetSocketAddress(addrNet, port); } @Override @@ -105,4 +131,10 @@ public SocketAddress address() { return address; } + + @Override + public SocketAddress sourceAddress() + { + return sourceAddress; + } } diff --git a/src/main/java/zmq/TcpConnecter.java b/src/main/java/zmq/io/net/tcp/TcpConnecter.java similarity index 51% rename from src/main/java/zmq/TcpConnecter.java rename to src/main/java/zmq/io/net/tcp/TcpConnecter.java index 4c86f3d6c..1f2a913a7 100644 --- a/src/main/java/zmq/TcpConnecter.java +++ b/src/main/java/zmq/io/net/tcp/TcpConnecter.java @@ -1,35 +1,43 @@ -package zmq; +package zmq.io.net.tcp; import java.io.IOException; -import java.net.ConnectException; import java.net.SocketAddress; -import java.net.SocketException; -import java.net.SocketTimeoutException; +import java.net.StandardProtocolFamily; import java.nio.channels.SocketChannel; +import zmq.Options; +import zmq.Own; +import zmq.SocketBase; +import zmq.ZError; +import zmq.io.IOObject; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.StreamEngine; +import zmq.io.net.Address; +import zmq.poll.IPollEvents; +import zmq.poll.Poller; +import zmq.util.Utils; + // If 'delay' is true connecter first waits for a while, then starts // connection process. public class TcpConnecter extends Own implements IPollEvents { // ID of the timer used to delay the reconnection. - private static final int RECONNECT_TIMER_ID = 1; + protected static final int RECONNECT_TIMER_ID = 1; - private final IOObject ioObject; + protected final IOObject ioObject; // Address to connect to. Owned by session_base_t. private final Address addr; // Underlying socket. - private SocketChannel handle; - - // If true file descriptor is registered with the poller and 'handle' - // contains valid value. - private boolean handleValid; + private SocketChannel fd; + private Poller.Handle handle; // If true, connecter is waiting a while before trying to connect. - private final boolean delayedStart; + protected final boolean delayedStart; - // True iff a timer has been started. + // True if a timer has been started. private boolean timerStarted; // Reference to the session we belong to. @@ -39,43 +47,45 @@ public class TcpConnecter extends Own implements IPollEvents private int currentReconnectIvl; // String representation of endpoint to connect to - private final Address address; + private final String endpoint; // Socket private final SocketBase socket; - public TcpConnecter(IOThread ioThread, - SessionBase session, final Options options, - final Address addr, boolean delayedStart) + public TcpConnecter(IOThread ioThread, SessionBase session, final Options options, final Address addr, + boolean delayedStart) { super(ioThread, options); - ioObject = new IOObject(ioThread); + ioObject = new IOObject(ioThread, this); this.addr = addr; - handle = null; - handleValid = false; + fd = null; this.delayedStart = delayedStart; timerStarted = false; this.session = session; currentReconnectIvl = this.options.reconnectIvl; assert (this.addr != null); - address = this.addr; + // assert (NetProtocol.tcp.equals(this.addr.protocol())); // not always true, as ipc is emulated by tcp + + endpoint = this.addr.toString(); socket = session.getSocket(); } - public void destroy() + @Override + protected void destroy() { assert (!timerStarted); - assert (!handleValid); assert (handle == null); + assert (fd == null); + ioObject.unplug(); } @Override protected void processPlug() { - ioObject.setHandler(this); + ioObject.plug(); if (delayedStart) { - addreconnectTimer(); + addReconnectTimer(); } else { startConnecting(); @@ -90,86 +100,61 @@ protected void processTerm(int linger) timerStarted = false; } - if (handleValid) { + if (handle != null) { ioObject.removeHandle(handle); - handleValid = false; + handle = null; } - if (handle != null) { + if (fd != null) { close(); } super.processTerm(linger); } - @Override - public void inEvent() - { - // connected but attaching to stream engine is not completed. do nothing - } - - @Override - public void outEvent() - { - // connected but attaching to stream engine is not completed. do nothing - } - - @Override - public void acceptEvent() - { - throw new UnsupportedOperationException(); - } - @Override public void connectEvent() { - boolean err = false; - SocketChannel fd = null; - try { - fd = connect(); - } - catch (ConnectException e) { - err = true; - } - catch (SocketException e) { - err = true; - } - catch (SocketTimeoutException e) { - err = true; - } - catch (IOException e) { - throw new ZError.IOException(e); - } - ioObject.removeHandle(handle); - handleValid = false; + handle = null; - if (err) { + SocketChannel channel = connect(); + + if (channel == null) { // Handle the error condition by attempt to reconnect. close(); - addreconnectTimer(); + addReconnectTimer(); return; } - handle = null; - try { - Utils.tuneTcpSocket(fd); - Utils.tuneTcpKeepalives(fd, options.tcpKeepAlive, options.tcpKeepAliveCnt, options.tcpKeepAliveIdle, options.tcpKeepAliveIntvl); + TcpUtils.tuneTcpSocket(channel); + TcpUtils.tuneTcpKeepalives( + channel, + options.tcpKeepAlive, + options.tcpKeepAliveCnt, + options.tcpKeepAliveIdle, + options.tcpKeepAliveIntvl); } - catch (SocketException e) { - throw new RuntimeException(e); + catch (IOException e) { + throw new ZError.IOException(e); } + // remember our fd for ZMQ_SRCFD in messages + // socket.setFd(channel); + // Create the engine object for this connection. StreamEngine engine = null; try { - engine = new StreamEngine(fd, options, address.toString()); + engine = new StreamEngine(channel, options, addr.toString()); } catch (ZError.InstantiationException e) { - socket.eventConnectDelayed(address.toString(), -1); + // TODO V4 socket.eventConnectDelayed(addr.toString(), -1); return; } + assert (engine != null); + + this.fd = null; // Attach the engine to the corresponding session object. sendAttach(session, engine); @@ -177,12 +162,14 @@ public void connectEvent() // Shut the connecter down. terminate(); - socket.eventConnected(address.toString(), fd); + socket.eventConnected(addr.toString(), channel); } @Override public void timerEvent(int id) { + assert (id == RECONNECT_TIMER_ID); + timerStarted = false; startConnecting(); } @@ -191,36 +178,32 @@ public void timerEvent(int id) private void startConnecting() { // Open the connecting socket. - try { boolean rc = open(); // Connect may succeed in synchronous manner. if (rc) { - ioObject.addHandle(handle); - handleValid = true; - ioObject.connectEvent(); + handle = ioObject.addFd(fd); + connectEvent(); } - // Connection establishment may be delayed. Poll for its completion. else { - ioObject.addHandle(handle); - handleValid = true; + handle = ioObject.addFd(fd); ioObject.setPollConnect(handle); - socket.eventConnectDelayed(address.toString(), -1); + socket.eventConnectDelayed(addr.toString(), -1); } } - catch (IOException e) { + catch (RuntimeException | IOException e) { // Handle any other error condition by eventual reconnect. - if (handle != null) { + if (fd != null) { close(); } - addreconnectTimer(); + addReconnectTimer(); } } // Internal function to add a reconnect timer - private void addreconnectTimer() + private void addReconnectTimer() { int rcIvl = getNewReconnectIvl(); ioObject.addTimer(rcIvl, RECONNECT_TIMER_ID); @@ -228,7 +211,7 @@ private void addreconnectTimer() // resolve address again to take into account other addresses // besides the failing one (e.g. multiple dns entries). try { - address.resolve(); + addr.resolve(options.ipv6); } catch (Exception ignored) { // This will fail if the network goes away and the @@ -236,7 +219,7 @@ private void addreconnectTimer() // not to fail as the event loop will quit } - socket.eventConnectRetried(address.toString(), rcIvl); + socket.eventConnectRetried(addr.toString(), rcIvl); timerStarted = true; } @@ -246,40 +229,31 @@ private void addreconnectTimer() private int getNewReconnectIvl() { // The new interval is the current interval + random value. - int thisInterval = currentReconnectIvl + - (Utils.generateRandom() % options.reconnectIvl); + int interval = currentReconnectIvl + (Utils.randomInt() % options.reconnectIvl); // Only change the current reconnect interval if the maximum reconnect // interval was set and if it's larger than the reconnect interval. - if (options.reconnectIvlMax > 0 && - options.reconnectIvlMax > options.reconnectIvl) { + if (options.reconnectIvlMax > 0 && options.reconnectIvlMax > options.reconnectIvl) { // Calculate the next interval - currentReconnectIvl = currentReconnectIvl * 2; - if (currentReconnectIvl >= options.reconnectIvlMax) { - currentReconnectIvl = options.reconnectIvlMax; - } + currentReconnectIvl = Math.min(currentReconnectIvl * 2, options.reconnectIvlMax); } - return thisInterval; + return interval; } - // Open TCP connecting socket. Returns -1 in case of error, - // true if connect was successfull immediately. Returns false with - // if async connect was launched. + // Open TCP connecting socket. + // Returns true if connect was successful immediately. + // Returns false if async connect was launched. private boolean open() throws IOException { - assert (handle == null); - - // Create the socket. - handle = SocketChannel.open(); + assert (fd == null); - // Set the socket to non-blocking mode so that we get async connect(). - Utils.unblockSocket(handle); - - // Connect to the remote peer. + // Resolve the address if (addr == null) { throw new IOException("Null address"); } + addr.resolve(options.ipv6); + Address.IZAddress resolved = addr.resolved(); if (resolved == null) { throw new IOException("Address not resolved"); @@ -290,9 +264,64 @@ private boolean open() throws IOException throw new IOException("Socket address not resolved"); } + // Create the socket. + fd = SocketChannel.open(); + + // IPv6 address family not supported, try automatic downgrade to IPv4. + if (fd == null && resolved.family() == StandardProtocolFamily.INET6 && options.ipv6) { + resolved = addr.resolve(false); + if (resolved == null) { + return false; + } + // TODO V4 automatic downgrade to IPV4 + sa = resolved.address(); + fd = SocketChannel.open(); + + } + assert (fd != null); + + // On some systems, IPv4 mapping in IPv6 sockets is disabled by default. + // Switch it on in such cases. + if (resolved.family() == StandardProtocolFamily.INET6) { + TcpUtils.enableIpv4Mapping(fd); + } + + // Set the socket to non-blocking mode so that we get async connect(). + TcpUtils.unblockSocket(fd); + + // Set the socket buffer limits for the underlying socket. + if (options.sndbuf != 0) { + TcpUtils.setTcpSendBuffer(fd, options.sndbuf); + } + if (options.rcvbuf != 0) { + TcpUtils.setTcpReceiveBuffer(fd, options.rcvbuf); + } + + // Set the IP Type-Of-Service priority for this socket + if (options.tos != 0) { + TcpUtils.setIpTypeOfService(fd, options.tos); + } + + // TODO V4 Set a source address for conversations + if (resolved.sourceAddress() != null) { + // SocketChannel bind = channel.bind(resolved.sourceAddress()); + // if (bind == null) { + // return false; + // } + } + + // Connect to the remote peer. boolean rc = false; try { - rc = handle.connect(sa); + rc = fd.connect(sa); + if (rc) { + // Connect was successful immediately. + } + else { + // Translate error codes indicating asynchronous connect has been + // launched to a uniform EINPROGRESS. + errno.set(ZError.EINPROGRESS); + } } catch (IllegalArgumentException e) { // this will happen if sa is bad. Address validation is not documented but @@ -305,33 +334,55 @@ private boolean open() throws IOException } // Get the file descriptor of newly created connection. Returns - // retired_fd if the connection was unsuccessfull. - private SocketChannel connect() throws IOException + // null if the connection was unsuccessful. + private SocketChannel connect() { - boolean finished = handle.finishConnect(); - assert finished; - SocketChannel ret = handle; - - return ret; + try { + // Async connect has finished. Check whether an error occurred + boolean finished = fd.finishConnect(); + assert (finished); + return fd; + } + catch (IOException e) { + return null; + } } // Close the connecting socket. - private void close() + protected void close() { - assert (handle != null); + assert (fd != null); try { - handle.close(); - socket.eventClosed(address.toString(), handle); + fd.close(); + socket.eventClosed(addr.toString(), fd); } catch (IOException e) { - socket.eventCloseFailed(address.toString(), ZError.exccode(e)); + socket.eventCloseFailed(addr.toString(), ZError.exccode(e)); } - handle = null; + fd = null; + } + + @Override + public void acceptEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void inEvent() + { + // connected but attaching to stream engine is not completed. do nothing + } + + @Override + public void outEvent() + { + // connected but attaching to stream engine is not completed. do nothing } @Override public String toString() { - return super.toString() + "[" + options.socketId + "]"; + return getClass().getSimpleName() + "[" + options.socketId + "]"; } } diff --git a/src/main/java/zmq/io/net/tcp/TcpListener.java b/src/main/java/zmq/io/net/tcp/TcpListener.java new file mode 100644 index 000000000..1e33164bd --- /dev/null +++ b/src/main/java/zmq/io/net/tcp/TcpListener.java @@ -0,0 +1,280 @@ +package zmq.io.net.tcp; + +import java.io.IOException; +import java.net.StandardProtocolFamily; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import zmq.Options; +import zmq.Own; +import zmq.SocketBase; +import zmq.ZError; +import zmq.io.IOObject; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.StreamEngine; +import zmq.poll.IPollEvents; +import zmq.poll.Poller; +import zmq.socket.Sockets; + +public class TcpListener extends Own implements IPollEvents +{ + private static boolean isWindows; + static { + String os = System.getProperty("os.name").toLowerCase(); + isWindows = os.indexOf("win") >= 0; + } + + // Address to listen on. + private TcpAddress address; + + // Underlying socket. + private ServerSocketChannel fd; + private Poller.Handle handle; + + // Socket the listerner belongs to. + private SocketBase socket; + + // String representation of endpoint to bind to + private String endpoint; + + private final IOObject ioObject; + + public TcpListener(IOThread ioThread, SocketBase socket, final Options options) + { + super(ioThread, options); + + ioObject = new IOObject(ioThread, this); + fd = null; + this.socket = socket; + } + + @Override + public void destroy() + { + assert (fd == null); + assert (handle == null); + ioObject.unplug(); + } + + @Override + protected void processPlug() + { + // Start polling for incoming connections. + ioObject.plug(); + handle = ioObject.addFd(fd); + ioObject.setPollAccept(handle); + } + + @Override + protected void processTerm(int linger) + { + ioObject.removeHandle(handle); + handle = null; + close(); + super.processTerm(linger); + } + + @Override + public void acceptEvent() + { + SocketChannel channel = null; + + try { + channel = accept(); + + // If connection was reset by the peer in the meantime, just ignore it. + if (channel == null) { + socket.eventAcceptFailed(endpoint, ZError.EADDRNOTAVAIL); + return; + } + TcpUtils.tuneTcpSocket(channel); + TcpUtils.tuneTcpKeepalives( + channel, + options.tcpKeepAlive, + options.tcpKeepAliveCnt, + options.tcpKeepAliveIdle, + options.tcpKeepAliveIntvl); + } + catch (IOException e) { + // If connection was reset by the peer in the meantime, just ignore it. + // TODO: Handle specific errors like ENFILE/EMFILE etc. + socket.eventAcceptFailed(endpoint, ZError.exccode(e)); + return; + } + + // remember our fd for ZMQ_SRCFD in messages + // socket.setFd(channel); + + // Create the engine object for this connection. + StreamEngine engine = null; + try { + engine = new StreamEngine(channel, options, endpoint); + } + catch (ZError.InstantiationException e) { + socket.eventAcceptFailed(endpoint, ZError.EINVAL); + return; + } + + // Choose I/O thread to run connecter in. Given that we are already + // running in an I/O thread, there must be at least one available. + IOThread ioThread = chooseIoThread(options.affinity); + assert (ioThread != null); + + // Create and launch a session object. + SessionBase session = Sockets.createSession(ioThread, false, socket, options, null); + assert (session != null); + + session.incSeqnum(); + launchChild(session); + sendAttach(session, engine, false); + socket.eventAccepted(endpoint, channel); + } + + // Close the listening socket. + private void close() + { + assert (fd != null); + + try { + fd.close(); + socket.eventClosed(endpoint, fd); + } + catch (IOException e) { + socket.eventCloseFailed(endpoint, ZError.exccode(e)); + } + fd = null; + } + + public String getAddress() + { + return address.toString(); + } + + // Set address to listen on. + public boolean setAddress(final String addr) + { + address = new TcpAddress(addr, options.ipv6); + + // Create a listening socket. + try { + fd = ServerSocketChannel.open(); + + // IPv6 address family not supported, try automatic downgrade to IPv4. + if (fd == null && address.family() == StandardProtocolFamily.INET6 && options.ipv6) { + // TODO V4 automatic downgrade to IPV4 + } + assert (fd != null); + + // On some systems, IPv4 mapping in IPv6 sockets is disabled by default. + // Switch it on in such cases. + if (address.family() == StandardProtocolFamily.INET6) { + TcpUtils.enableIpv4Mapping(fd); + } + + TcpUtils.unblockSocket(fd); + + // Set the socket buffer limits for the underlying socket. + if (options.sndbuf != 0) { + TcpUtils.setTcpSendBuffer(fd, options.sndbuf); + } + if (options.rcvbuf != 0) { + TcpUtils.setTcpReceiveBuffer(fd, options.rcvbuf); + } + + if (!isWindows) { + TcpUtils.setReuseAddress(fd, true); + } + + // Bind the socket to the network interface and port. + fd.bind(address.address(), options.backlog); + } + catch (IOException e) { + close(); + errno.set(ZError.EADDRINUSE); + return false; + } + endpoint = address.toString(); + socket.eventListening(endpoint, fd); + return true; + } + + // Accept the new connection. Returns the file descriptor of the + // newly created connection. The function may throw IOException + // if the connection was dropped while waiting in the listen backlog + // or was denied because of accept filters. + private SocketChannel accept() throws IOException + { + // The situation where connection cannot be accepted due to insufficient + // resources is considered valid and treated by ignoring the connection. + // Accept one connection and deal with different failure modes. + assert (fd != null); + + SocketChannel sock = fd.accept(); + + if (!options.tcpAcceptFilters.isEmpty()) { + boolean matched = false; + for (TcpAddress.TcpAddressMask am : options.tcpAcceptFilters) { + if (am.matchAddress(address.address())) { + matched = true; + break; + } + } + if (!matched) { + try { + sock.close(); + } + catch (IOException e) { + } + return null; + } + } + // TODO V4 Set the IP Type-Of-Service priority for this client socket + if (options.tos != 0) { + TcpUtils.setIpTypeOfService(sock, options.tos); + } + // Set the socket buffer limits for the underlying socket. + if (options.sndbuf != 0) { + TcpUtils.setTcpSendBuffer(sock, options.sndbuf); + } + if (options.rcvbuf != 0) { + TcpUtils.setTcpReceiveBuffer(sock, options.rcvbuf); + } + + if (!isWindows) { + TcpUtils.setReuseAddress(sock, true); + } + + return sock; + } + + @Override + public void inEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void outEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void connectEvent() + { + throw new UnsupportedOperationException(); + } + + @Override + public void timerEvent(int id) + { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() + { + return getClass().getSimpleName() + "[" + options.socketId + "]"; + } +} diff --git a/src/main/java/zmq/io/net/tcp/TcpUtils.java b/src/main/java/zmq/io/net/tcp/TcpUtils.java new file mode 100644 index 000000000..a6c35ef4e --- /dev/null +++ b/src/main/java/zmq/io/net/tcp/TcpUtils.java @@ -0,0 +1,101 @@ +package zmq.io.net.tcp; + +import java.io.IOException; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketOption; +import java.net.StandardSocketOptions; +import java.nio.channels.NetworkChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SocketChannel; + +import zmq.ZError; +import zmq.io.net.Address; + +public class TcpUtils +{ + private TcpUtils() + { + } + + public static void tuneTcpSocket(SocketChannel channel) throws IOException + { + setOption(channel, StandardSocketOptions.TCP_NODELAY, true); + // Disable Nagle's algorithm. We are doing data batching on 0MQ level, + // so using Nagle wouldn't improve throughput in anyway, but it would + // hurt latency. + try { + channel.socket().setTcpNoDelay(true); + } + catch (SocketException e) { + } + } + + public static void tuneTcpKeepalives(SocketChannel channel, int tcpKeepAlive, int tcpKeepAliveCnt, + int tcpKeepAliveIdle, int tcpKeepAliveIntvl) + throws IOException + { + boolean keepAlive = tcpKeepAlive == 1; + setOption(channel, StandardSocketOptions.SO_KEEPALIVE, keepAlive); + try { + channel.socket().setKeepAlive(keepAlive); + } + catch (SocketException e) { + } + } + + public static boolean setTcpReceiveBuffer(NetworkChannel channel, int rcvbuf) + { + return setOption(channel, StandardSocketOptions.SO_RCVBUF, rcvbuf); + } + + public static boolean setTcpSendBuffer(NetworkChannel channel, int sndbuf) + { + return setOption(channel, StandardSocketOptions.SO_SNDBUF, sndbuf); + } + + public static boolean setIpTypeOfService(NetworkChannel channel, int tos) + { + return setOption(channel, StandardSocketOptions.IP_TOS, tos); + } + + public static boolean setReuseAddress(NetworkChannel channel, boolean reuse) + { + return setOption(channel, StandardSocketOptions.SO_REUSEADDR, reuse); + } + + private static boolean setOption(NetworkChannel channel, SocketOption option, T value) + { + try { + if (channel.supportedOptions().contains(option)) { + channel.setOption(option, value); + return true; + } + else { + return false; + } + } + catch (IOException e) { + throw new ZError.IOException(e); + } + } + + public static void unblockSocket(SelectableChannel... channels) throws IOException + { + for (SelectableChannel ch : channels) { + ch.configureBlocking(false); + } + } + + public static void enableIpv4Mapping(SelectableChannel channel) + { + // TODO V4 enable ipv4 mapping + } + + public static Address getPeerIpAddress(SocketChannel channel) + { + SocketAddress address = channel.socket().getRemoteSocketAddress(); + + return new Address(address); + } +} diff --git a/src/main/java/zmq/io/net/tipc/TipcConnecter.java b/src/main/java/zmq/io/net/tipc/TipcConnecter.java new file mode 100644 index 000000000..637dfd373 --- /dev/null +++ b/src/main/java/zmq/io/net/tipc/TipcConnecter.java @@ -0,0 +1,18 @@ +package zmq.io.net.tipc; + +import zmq.Options; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.io.net.tcp.TcpConnecter; + +public class TipcConnecter extends TcpConnecter +{ + public TipcConnecter(IOThread ioThread, SessionBase session, final Options options, final Address addr, + boolean wait) + { + super(ioThread, session, options, addr, wait); + // TODO V4 implement Tipc + throw new UnsupportedOperationException("TODO implement Tipc"); + } +} diff --git a/src/main/java/zmq/io/net/tipc/TipcListener.java b/src/main/java/zmq/io/net/tipc/TipcListener.java new file mode 100644 index 000000000..c24e22b3c --- /dev/null +++ b/src/main/java/zmq/io/net/tipc/TipcListener.java @@ -0,0 +1,19 @@ +package zmq.io.net.tipc; + +import zmq.Options; +import zmq.SocketBase; +import zmq.io.IOThread; +import zmq.io.net.ipc.IpcAddress; +import zmq.io.net.tcp.TcpListener; + +public class TipcListener extends TcpListener +{ + private IpcAddress address; + + public TipcListener(IOThread ioThread, SocketBase socket, final Options options) + { + super(ioThread, socket, options); + // TODO V4 implement tipc + throw new UnsupportedOperationException("TODO implement tipc"); + } +} diff --git a/src/main/java/zmq/pipe/DBuffer.java b/src/main/java/zmq/pipe/DBuffer.java new file mode 100644 index 000000000..2746e8651 --- /dev/null +++ b/src/main/java/zmq/pipe/DBuffer.java @@ -0,0 +1,81 @@ +package zmq.pipe; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import zmq.Msg; + +class DBuffer +{ + private T back; + private T front; + + private final Lock sync = new ReentrantLock(); + + private boolean hasMsg; + + public T back() + { + return back; + } + + public T front() + { + return front; + } + + void write(T msg) + { + assert (msg.check()); + sync.lock(); + try { + back = front; + front = msg; + hasMsg = true; + } + finally { + sync.unlock(); + } + } + + T read() + { + sync.lock(); + try { + if (!hasMsg) { + return null; + } + + assert (front.check()); + // TODO front->init (); // avoid double free + hasMsg = false; + + return front; + } + finally { + sync.unlock(); + } + } + + boolean checkRead() + { + sync.lock(); + try { + return hasMsg; + } + finally { + sync.unlock(); + } + } + + T probe() + { + sync.lock(); + try { + return front; + } + finally { + sync.unlock(); + } + } +} diff --git a/src/main/java/zmq/Pipe.java b/src/main/java/zmq/pipe/Pipe.java similarity index 65% rename from src/main/java/zmq/Pipe.java rename to src/main/java/zmq/pipe/Pipe.java index 635da1a5a..b00d884f7 100644 --- a/src/main/java/zmq/Pipe.java +++ b/src/main/java/zmq/pipe/Pipe.java @@ -1,21 +1,29 @@ -package zmq; +package zmq.pipe; + +import zmq.Config; +import zmq.Msg; +import zmq.ZObject; +import zmq.util.Blob; // Note that pipe can be stored in three different arrays. // The array of inbound pipes (1), the array of outbound pipes (2) and // the generic array of pipes to deallocate (3). -class Pipe extends ZObject +public class Pipe extends ZObject { - interface IPipeEvents + public interface IPipeEvents { void readActivated(Pipe pipe); + void writeActivated(Pipe pipe); + void hiccuped(Pipe pipe); + void pipeTerminated(Pipe pipe); } // Underlying pipes for both directions. - private YPipe inpipe; - private YPipe outpipe; + private YPipeBase inpipe; + private YPipeBase outpipe; // Can the pipe be read from / written to? private boolean inActive; @@ -41,23 +49,27 @@ interface IPipeEvents // Sink to send events to. private IPipeEvents sink; - // State of the pipe endpoint. Active is common state before any - // termination begins. Delimited means that delimiter was read from - // pipe before term command was received. Pending means that term - // command was already received from the peer but there are still - // pending messages to read. Terminating means that all pending - // messages were already read and all we are waiting for is ack from - // the peer. Terminated means that 'terminate' was explicitly called - // by the user. Double_terminated means that user called 'terminate' - // and then we've got term command from the peer as well. - enum State { + // States of the pipe endpoint: + // active: common state before any termination begins, + // delimiter_received: delimiter was read from pipe before + // term command was received, + // waiting_fo_delimiter: term command was already received + // from the peer but there are still pending messages to read, + // term_ack_sent: all pending messages were already read and + // all we are waiting for is ack from the peer, + // term_req_sent1: 'terminate' was explicitly called by the user, + // term_req_sent2: user called 'terminate' and then we've got + // term command from the peer as well. + enum State + { ACTIVE, - DELIMITED, - PENDING, - TERMINATING, - TERMINATED, - DOUBLE_TERMINATED + DELIMITER_RECEIVED, + WAITING_FOR_DELIMITER, + TERM_ACK_SENT, + TERM_REQ_SENT_1, + TERM_REQ_SENT_2 } + private State state; // If true, we receive all the pending inbound messages before @@ -68,13 +80,17 @@ enum State { // Identity of the writer. Used uniquely by the reader side. private Blob identity; + // Pipe's credential. + private Blob credential; + + private final boolean conflate; + // JeroMQ only - private ZObject parent; + private final ZObject parent; // Constructor is private. Pipe can only be created using // pipepair function. - private Pipe(ZObject parent, YPipe inpipe, YPipe outpipe, - int inhwm, int outhwm, boolean delay) + private Pipe(ZObject parent, YPipeBase inpipe, YPipeBase outpipe, int inhwm, int outhwm, boolean conflate) { super(parent); this.inpipe = inpipe; @@ -89,34 +105,37 @@ private Pipe(ZObject parent, YPipe inpipe, YPipe outpipe, peer = null; sink = null; state = State.ACTIVE; - this.delay = delay; + this.delay = true; + this.conflate = conflate; this.parent = parent; } + // This allows pipepair to create pipe objects. // Create a pipepair for bi-directional transfer of messages. // First HWM is for messages passed from first pipe to the second pipe. // Second HWM is for messages passed from second pipe to the first pipe. - // Delay specifies how the pipe behaves when the peer terminates. If true + // Conflate specifies how the pipe behaves when the peer terminates. If true // pipe receives all the pending messages before terminating, otherwise it // terminates straight away. - public static void pipepair(ZObject[] parents, Pipe[] pipes, int[] hwms, - boolean[] delays) + public static Pipe[] pair(ZObject[] parents, int[] hwms, boolean[] conflates) { + Pipe[] pipes = new Pipe[2]; // Creates two pipe objects. These objects are connected by two ypipes, // each to pass messages in one direction. - YPipe upipe1 = new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue()); - YPipe upipe2 = new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue()); + YPipeBase upipe1 = conflates[0] ? new YPipeConflate() + : new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue()); + YPipeBase upipe2 = conflates[1] ? new YPipeConflate() + : new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue()); - pipes[0] = new Pipe(parents[0], upipe1, upipe2, - hwms[1], hwms[0], delays[0]); - pipes[1] = new Pipe(parents[1], upipe2, upipe1, - hwms[0], hwms[1], delays[1]); + pipes[0] = new Pipe(parents[0], upipe1, upipe2, hwms[1], hwms[0], conflates[0]); + pipes[1] = new Pipe(parents[1], upipe2, upipe1, hwms[0], hwms[1], conflates[1]); pipes[0].setPeer(pipes[1]); pipes[1].setPeer(pipes[0]); + return pipes; } // Pipepair uses this function to let us know about @@ -124,6 +143,7 @@ public static void pipepair(ZObject[] parents, Pipe[] pipes, int[] hwms, private void setPeer(Pipe peer) { // Peer can be set once only. + assert (this.peer == null); assert (peer != null); this.peer = peer; } @@ -146,10 +166,19 @@ public Blob getIdentity() return identity; } + public Blob getCredential() + { + return credential; + } + // Returns true if there is at least one message to read in the pipe. public boolean checkRead() { - if (!inActive || (state != State.ACTIVE && state != State.PENDING)) { + if (!inActive) { + return false; + + } + if (state != State.ACTIVE && state != State.WAITING_FOR_DELIMITER) { return false; } @@ -164,7 +193,7 @@ public boolean checkRead() if (isDelimiter(inpipe.probe())) { Msg msg = inpipe.read(); assert (msg != null); - delimit(); + processDelimiter(); return false; } @@ -174,32 +203,42 @@ public boolean checkRead() // Reads a message to the underlying pipe. public Msg read() { - if (!inActive || (state != State.ACTIVE && state != State.PENDING)) { + if (!inActive) { return null; } - - Msg msg = inpipe.read(); - - if (msg == null) { - inActive = false; + if (state != State.ACTIVE && state != State.WAITING_FOR_DELIMITER) { return null; } - // If delimiter was read, start termination process of the pipe. - if (msg.isDelimiter()) { - delimit(); - return null; - } + while (true) { + Msg msg = inpipe.read(); - if (!msg.hasMore()) { - msgsRead++; - } + if (msg == null) { + inActive = false; + return null; + } - if (lwm > 0 && msgsRead % lwm == 0) { - sendActivateWrite(peer, msgsRead); - } + // If this is a credential, save a copy and receive next message. + if (msg.isCredential()) { + credential = Blob.createBlob(msg); + continue; + } + + // If delimiter was read, start termination process of the pipe. + if (msg.isDelimiter()) { + processDelimiter(); + return null; + } + + if (!msg.hasMore() && !msg.isIdentity()) { + msgsRead++; + } - return msg; + if (lwm > 0 && msgsRead % lwm == 0) { + sendActivateWrite(peer, msgsRead); + } + return msg; + } } // Checks whether messages can be written to the pipe. If writing @@ -210,7 +249,8 @@ public boolean checkWrite() return false; } - boolean full = hwm > 0 && msgsWritten - peersMsgsRead == (long) (hwm); + // TODO DIFF V4 small change, it is done like this in 4.2.2 + boolean full = !checkHwm(); if (full) { outActive = false; @@ -229,9 +269,10 @@ public boolean write(Msg msg) } boolean more = msg.hasMore(); + boolean identity = msg.isIdentity(); outpipe.write(msg, more); - if (!more) { + if (!more && !identity) { msgsWritten++; } @@ -245,16 +286,16 @@ public void rollback() Msg msg; if (outpipe != null) { while ((msg = outpipe.unwrite()) != null) { - assert ((msg.flags() & Msg.MORE) > 0); + assert (msg.hasMore()); } } } - // Flush the messages downsteam. + // Flush the messages downstream. public void flush() { // The peer does not exist anymore at this point. - if (state == State.TERMINATING) { + if (state == State.TERM_ACK_SENT) { return; } @@ -266,7 +307,7 @@ public void flush() @Override protected void processActivateRead() { - if (!inActive && (state == State.ACTIVE || state == State.PENDING)) { + if (!inActive && (state == State.ACTIVE || state == State.WAITING_FOR_DELIMITER)) { inActive = true; sink.readActivated(this); } @@ -284,21 +325,23 @@ protected void processActivateWrite(long msgsRead) } } - @SuppressWarnings("unchecked") @Override - protected void processHiccup(Object pipe) + protected void processHiccup(YPipeBase pipe) { // Destroy old outpipe. Note that the read end of the pipe was already // migrated to this thread. assert (outpipe != null); outpipe.flush(); - while (outpipe.read() != null) { - // do nothing + Msg msg = null; + while ((msg = outpipe.read()) != null) { + if (!msg.hasMore()) { + msgsWritten--; + } } // Plug in the new outpipe. assert (pipe != null); - outpipe = (YPipe) pipe; + outpipe = pipe; outActive = true; // If appropriate, notify the user about the hiccup. @@ -310,44 +353,40 @@ protected void processHiccup(Object pipe) @Override protected void processPipeTerm() { + assert (state == State.ACTIVE || state == State.DELIMITER_RECEIVED || state == State.TERM_REQ_SENT_1); + // This is the simple case of peer-induced termination. If there are no // more pending messages to read, or if the pipe was configured to drop - // pending messages, we can move directly to the terminating state. - // Otherwise we'll hang up in pending state till all the pending messages - // are sent. + // pending messages, we can move directly to the term_ack_sent state. + // Otherwise we'll hang up in waiting_for_delimiter state till all + // pending messages are read. if (state == State.ACTIVE) { - if (!delay) { - state = State.TERMINATING; - outpipe = null; - sendPipeTermAck(peer); + if (delay) { + state = State.WAITING_FOR_DELIMITER; } else { - state = State.PENDING; + state = State.TERM_ACK_SENT; + outpipe = null; + sendPipeTermAck(peer); } - return; } - + else // Delimiter happened to arrive before the term command. Now we have the - // term command as well, so we can move straight to terminating state. - if (state == State.DELIMITED) { - state = State.TERMINATING; + // term command as well, so we can move straight to term_ack_sent state. + if (state == State.DELIMITER_RECEIVED) { + state = State.TERM_ACK_SENT; outpipe = null; sendPipeTermAck(peer); - return; } - + else // This is the case where both ends of the pipe are closed in parallel. // We simply reply to the request by ack and continue waiting for our // own ack. - if (state == State.TERMINATED) { - state = State.DOUBLE_TERMINATED; + if (state == State.TERM_REQ_SENT_1) { + state = State.TERM_REQ_SENT_2; outpipe = null; sendPipeTermAck(peer); - return; } - - // pipe_term is invalid in other states. - assert (false); } @Override @@ -357,18 +396,19 @@ protected void processPipeTermAck() assert (sink != null); sink.pipeTerminated(this); - // In terminating and double_terminated states there's nothing to do. - // Simply deallocate the pipe. In terminated state we have to ack the - // peer before deallocating this side of the pipe. All the other states - // are invalid. - if (state == State.TERMINATED) { + // In term_ack_sent and term_req_sent2 states there's nothing to do. + // Simply deallocate the pipe. In term_req_sent1 state we have to ack + // the peer before deallocating this side of the pipe. + // All the other states are invalid. + if (state == State.TERM_REQ_SENT_1) { outpipe = null; sendPipeTermAck(peer); } else { - assert (state == State.TERMINATING || state == State.DOUBLE_TERMINATED); + assert (state == State.TERM_ACK_SENT || state == State.TERM_REQ_SENT_2); } + // TODO V4 not in zeromq, but no harm. Remove it? // If the inbound pipe has already been deallocated, then we're done. if (inpipe == null) { return; @@ -379,14 +419,21 @@ protected void processPipeTermAck() // First, delete all the unread messages in the pipe. We have to do it by // hand because msg_t doesn't have automatic destructor. Then deallocate // the ypipe itself. - while (inpipe.read() != null) { - // do nothing + if (!conflate) { + while (inpipe.read() != null) { + // do nothing + } } // Deallocate the pipe object inpipe = null; } + public void setNoDelay() + { + this.delay = false; + } + // Ask pipe to terminate. The termination will happen asynchronously // and user will be notified about actual deallocation by 'terminated' // event. If delay is true, the pending messages will be processed @@ -397,37 +444,37 @@ public void terminate(boolean delay) this.delay = delay; // If terminate was already called, we can ignore the duplicit invocation. - if (state == State.TERMINATED || state == State.DOUBLE_TERMINATED) { + if (state == State.TERM_REQ_SENT_1 || state == State.TERM_REQ_SENT_2) { return; } // If the pipe is in the final phase of async termination, it's going to // closed anyway. No need to do anything special here. - else if (state == State.TERMINATING) { + else if (state == State.TERM_ACK_SENT) { return; } // The simple sync termination case. Ask the peer to terminate and wait // for the ack. else if (state == State.ACTIVE) { sendPipeTerm(peer); - state = State.TERMINATED; + state = State.TERM_REQ_SENT_1; } // There are still pending messages available, but the user calls // 'terminate'. We can act as if all the pending messages were read. - else if (state == State.PENDING && !this.delay) { + else if (state == State.WAITING_FOR_DELIMITER && !this.delay) { outpipe = null; sendPipeTermAck(peer); - state = State.TERMINATING; + state = State.TERM_ACK_SENT; } // If there are pending messages still available, do nothing. - else if (state == State.PENDING) { + else if (state == State.WAITING_FOR_DELIMITER) { // do nothing } // We've already got delimiter, but not term command yet. We can ignore // the delimiter and ack synchronously terminate as if we were in // active state. - else if (state == State.DELIMITED) { + else if (state == State.DELIMITER_RECEIVED) { sendPipeTerm(peer); - state = State.TERMINATED; + state = State.TERM_REQ_SENT_1; } // There are no other states. else { @@ -480,31 +527,26 @@ private static int computeLwm(int hwm) // That done, we still we have to account for the cases where // HWM < max_wm_delta thus driving LWM to negative numbers. // Let's make LWM 1/2 of HWM in such cases. - - return (hwm > Config.MAX_WM_DELTA.getValue() * 2) ? - hwm - Config.MAX_WM_DELTA.getValue() : (hwm + 1) / 2; + // return (hwm +1) /2; + return (hwm + 1) / 2; } // Handler for delimiter read from the pipe. - private void delimit() + private void processDelimiter() { + assert (state == State.ACTIVE || state == State.WAITING_FOR_DELIMITER); + if (state == State.ACTIVE) { - state = State.DELIMITED; - return; + state = State.DELIMITER_RECEIVED; } - - if (state == State.PENDING) { + else { outpipe = null; sendPipeTermAck(peer); - state = State.TERMINATING; - return; + state = State.TERM_ACK_SENT; } - - // Delimiter in any other state is invalid. - assert (false); } - // Temporaraily disconnects the inbound message stream and drops + // Temporarily disconnects the inbound message stream and drops // all the messages on the fly. Causes 'hiccuped' event to be generated // in the peer. public void hiccup() @@ -519,22 +561,35 @@ public void hiccup() inpipe = null; // Create new inpipe. - inpipe = new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue()); + if (conflate) { + inpipe = new YPipeConflate(); + } + else { + inpipe = new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue()); + } inActive = true; // Notify the peer about the hiccup. sendHiccup(peer, inpipe); } + public void setHwms(int inhwm, int outhwm) + { + lwm = computeLwm(inhwm); + hwm = outhwm; + } + public boolean checkHwm() { - boolean full = hwm > 0 && (msgsWritten - peersMsgsRead) >= (hwm - 1); + // TODO DIFF V4 small change, it is done like this in 4.2.2 + boolean full = hwm > 0 && (msgsWritten - peersMsgsRead) >= hwm; return !full; } @Override public String toString() { - return super.toString() + "[" + parent + "]"; + return super.toString() + "(" + parent.getClass().getSimpleName() + "[" + parent.getTid() + "]->" + + peer.parent.getClass().getSimpleName() + "[" + peer.parent.getTid() + "])"; } } diff --git a/src/main/java/zmq/YPipe.java b/src/main/java/zmq/pipe/YPipe.java similarity index 89% rename from src/main/java/zmq/YPipe.java rename to src/main/java/zmq/pipe/YPipe.java index a06a00484..a2b7ef0a1 100644 --- a/src/main/java/zmq/YPipe.java +++ b/src/main/java/zmq/pipe/YPipe.java @@ -1,8 +1,8 @@ -package zmq; +package zmq.pipe; import java.util.concurrent.atomic.AtomicInteger; -public class YPipe +public class YPipe implements YPipeBase { // Allocation-efficient queue to store pipe items. // Front of the queue points to the first prefetched item, back of @@ -41,12 +41,13 @@ public YPipe(int qsize) // set to true the item is assumed to be continued by items // subsequently written to the pipe. Incomplete items are never // flushed down the stream. + @Override public void write(final T value, boolean incomplete) { // Place the value to the queue, add new terminator element. queue.push(value); - // Move the "flush up to here" poiter. + // Move the "flush up to here" pointer. if (!incomplete) { f = queue.backPos(); } @@ -54,6 +55,7 @@ public void write(final T value, boolean incomplete) // Pop an incomplete item from the pipe. Returns true is such // item exists, false otherwise. + @Override public T unwrite() { if (f == queue.backPos()) { @@ -66,6 +68,7 @@ public T unwrite() // Flush all the completed items into the pipe. Returns false if // the reader thread is sleeping. In that case, caller is obliged to // wake the reader up before using the pipe again. + @Override public boolean flush() { // If there are no un-flushed items, do nothing. @@ -75,7 +78,7 @@ public boolean flush() // Try to set 'c' to 'f'. if (!c.compareAndSet(w, f)) { - // Compare-and-swap was unseccessful because 'c' is NULL. + // Compare-and-swap was unsuccessful because 'c' is NULL. // This means that the reader is asleep. Therefore we don't // care about thread-safeness and update c in non-atomic // manner. We'll return false to let the caller know @@ -92,12 +95,13 @@ public boolean flush() } // Check whether item is available for reading. + @Override public boolean checkRead() { // Was the value prefetched already? If so, return. int h = queue.frontPos(); if (h != r) { - return true; + return true; } // There's no prefetched value, so let us prefetch more values. @@ -105,11 +109,11 @@ public boolean checkRead() // pointer from c in atomic fashion. If there are no // items to prefetch, set c to -1 (using compare-and-swap). if (c.compareAndSet(h, -1)) { - // nothing to read, h == r must be the same + // nothing to read, h == r must be the same } else { // something to have been written - r = c.get(); + r = c.get(); } // If there are no elements prefetched, exit. @@ -124,8 +128,9 @@ public boolean checkRead() return true; } - // Reads an item from the pipe. Returns false if there is no value. + // Reads an item from the pipe. Returns null if there is no value. // available. + @Override public T read() { // Try to prefetch a value. @@ -139,9 +144,9 @@ public T read() return queue.pop(); } - // Applies the function fn to the first elemenent in the pipe - // and returns the value returned by the fn. + // Returns the first element in the pipe without removing it. // The pipe mustn't be empty or the function crashes. + @Override public T probe() { boolean rc = checkRead(); diff --git a/src/main/java/zmq/pipe/YPipeBase.java b/src/main/java/zmq/pipe/YPipeBase.java new file mode 100644 index 000000000..9a3c410b6 --- /dev/null +++ b/src/main/java/zmq/pipe/YPipeBase.java @@ -0,0 +1,31 @@ +package zmq.pipe; + +public interface YPipeBase +{ + // Write an item to the pipe. Don't flush it yet. If incomplete is + // set to true the item is assumed to be continued by items + // subsequently written to the pipe. Incomplete items are never + // flushed down the stream. + void write(final T value, boolean incomplete); + + // Pop an incomplete item from the pipe. Returns true is such + // item exists, false otherwise. + T unwrite(); + + // Flush all the completed items into the pipe. Returns false if + // the reader thread is sleeping. In that case, caller is obliged to + // wake the reader up before using the pipe again. + boolean flush(); + + // Check whether item is available for reading. + boolean checkRead(); + + // Reads an item from the pipe. Returns false if there is no value. + // available. + T read(); + + // Applies the function fn to the first elemenent in the pipe + // and returns the value returned by the fn. + // The pipe mustn't be empty or the function crashes. + T probe(); +} diff --git a/src/main/java/zmq/pipe/YPipeConflate.java b/src/main/java/zmq/pipe/YPipeConflate.java new file mode 100644 index 000000000..7b5e06a2b --- /dev/null +++ b/src/main/java/zmq/pipe/YPipeConflate.java @@ -0,0 +1,79 @@ +package zmq.pipe; + +import zmq.Msg; + +// Adapter for dbuffer, to plug it in instead of a queue for the sake +// of implementing the conflate socket option, which, if set, makes +// the receiving side to discard all incoming messages but the last one. +// +// reader_awake flag is needed here to mimic ypipe delicate behaviour +// around the reader being asleep (see 'c' pointer being NULL in ypipe.hpp) + +public class YPipeConflate implements YPipeBase +{ + private boolean readerAwake; + + private final DBuffer dbuffer = new DBuffer<>(); + + // Following function (write) deliberately copies uninitialised data + // when used with zmq_msg. Initialising the VSM body for + // non-VSM messages won't be good for performance. + @Override + public void write(final T value, boolean incomplete) + { + dbuffer.write(value); + } + + // There are no incomplete items for conflate ypipe + @Override + public T unwrite() + { + return null; + } + + // Flush is no-op for conflate ypipe. Reader asleep behaviour + // is as of the usual ypipe. + // Returns false if the reader thread is sleeping. In that case, + // caller is obliged to wake the reader up before using the pipe again. + @Override + public boolean flush() + { + return readerAwake; + } + + // Check whether item is available for reading. + @Override + public boolean checkRead() + { + boolean rc = dbuffer.checkRead(); + if (!rc) { + readerAwake = false; + } + return rc; + } + + // Reads an item from the pipe. Returns false if there is no value. + // available. + @Override + public T read() + { + // Try to prefetch a value. + if (!checkRead()) { + return null; + } + + // There was at least one value prefetched. + // Return it to the caller. + + return dbuffer.read(); + } + + // Applies the function fn to the first elemenent in the pipe + // and returns the value returned by the fn. + // The pipe mustn't be empty or the function crashes. + @Override + public T probe() + { + return dbuffer.probe(); + } +} diff --git a/src/main/java/zmq/YQueue.java b/src/main/java/zmq/pipe/YQueue.java similarity index 88% rename from src/main/java/zmq/YQueue.java rename to src/main/java/zmq/pipe/YQueue.java index 937ec2605..dd56c1397 100644 --- a/src/main/java/zmq/YQueue.java +++ b/src/main/java/zmq/pipe/YQueue.java @@ -1,14 +1,14 @@ -package zmq; +package zmq.pipe; -public class YQueue +class YQueue { // Individual memory chunk to hold N elements. private static class Chunk { - final T[] values; + final T[] values; final int[] pos; - Chunk prev; - Chunk next; + Chunk prev; + Chunk next; @SuppressWarnings("unchecked") public Chunk(int size, int memoryPtr) @@ -26,14 +26,14 @@ public Chunk(int size, int memoryPtr) // while begin & end positions are always valid. Begin position is // accessed exclusively be queue reader (front/pop), while back and // end positions are accessed exclusively by queue writer (back/push). - private Chunk beginChunk; - private int beginPos; - private Chunk backChunk; - private int backPos; - private Chunk endChunk; - private int endPos; + private Chunk beginChunk; + private int beginPos; + private Chunk backChunk; + private int backPos; + private Chunk endChunk; + private int endPos; private volatile Chunk spareChunk; - private final int size; + private final int size; // People are likely to produce and consume at similar rates. In // this scenario holding onto the most recently freed chunk saves @@ -78,19 +78,6 @@ public T back() return backChunk.values[backPos]; } - public T pop() - { - T val = beginChunk.values[beginPos]; - beginChunk.values[beginPos] = null; - beginPos++; - if (beginPos == size) { - beginChunk = beginChunk.next; - beginChunk.prev = null; - beginPos = 0; - } - return val; - } - // Adds an element to the back end of the queue. public void push(T val) { @@ -98,8 +85,7 @@ public void push(T val) backChunk = endChunk; backPos = endPos; - endPos++; - if (endPos != size) { + if (++endPos != size) { return; } @@ -129,7 +115,7 @@ public void unpush() { // First, move 'back' one position backwards. if (backPos > 0) { - backPos--; + --backPos; } else { backPos = size - 1; @@ -141,7 +127,7 @@ public void unpush() // would require free and atomic operation per chunk deallocated // instead of a simple free. if (endPos > 0) { - endPos--; + --endPos; } else { endPos = size - 1; @@ -149,4 +135,18 @@ public void unpush() endChunk.next = null; } } + + // Removes an element from the front end of the queue. + public T pop() + { + T val = beginChunk.values[beginPos]; + beginChunk.values[beginPos] = null; + beginPos++; + if (beginPos == size) { + beginChunk = beginChunk.next; + beginChunk.prev = null; + beginPos = 0; + } + return val; + } } diff --git a/src/main/java/zmq/IPollEvents.java b/src/main/java/zmq/poll/IPollEvents.java similarity index 96% rename from src/main/java/zmq/IPollEvents.java rename to src/main/java/zmq/poll/IPollEvents.java index ffdb9febb..492763ffc 100644 --- a/src/main/java/zmq/IPollEvents.java +++ b/src/main/java/zmq/poll/IPollEvents.java @@ -1,4 +1,4 @@ -package zmq; +package zmq.poll; public interface IPollEvents { diff --git a/src/main/java/zmq/PollItem.java b/src/main/java/zmq/poll/PollItem.java similarity index 78% rename from src/main/java/zmq/PollItem.java rename to src/main/java/zmq/poll/PollItem.java index be731b214..082db4f63 100644 --- a/src/main/java/zmq/PollItem.java +++ b/src/main/java/zmq/poll/PollItem.java @@ -1,15 +1,18 @@ -package zmq; +package zmq.poll; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; +import zmq.SocketBase; +import zmq.ZMQ; + public class PollItem { - private SocketBase socket; + private SocketBase socket; private SelectableChannel channel; - private int zinterest; - private int interest; - private int ready; + private int zinterest; + private int interest; + private int ready; public PollItem(SocketBase socket) { @@ -77,12 +80,16 @@ public final SelectableChannel getRawSocket() return channel; } - protected final SelectableChannel getChannel() + public final SelectableChannel getChannel() { if (socket != null) { + // If the poll item is a 0MQ socket we are interested in input on the + // notification file descriptor retrieved by the ZMQ_FD socket option. return socket.getFD(); } else { + // Else, the poll item is a raw file descriptor. Convert the poll item + // events to the appropriate fd_sets. return channel; } } @@ -108,6 +115,8 @@ public final int readyOps(SelectionKey key, int nevents) ready = 0; if (socket != null) { + // The poll item is a 0MQ socket. Retrieve pending events + // using the ZMQ_EVENTS socket option. int events = socket.getSocketOpt(ZMQ.ZMQ_EVENTS); if (events < 0) { return -1; @@ -120,6 +129,8 @@ public final int readyOps(SelectionKey key, int nevents) ready |= ZMQ.ZMQ_POLLIN; } } + // Else, the poll item is a raw file descriptor, simply convert + // the events to zmq_pollitem_t-style format. else if (nevents > 0) { if (key.isReadable()) { ready |= ZMQ.ZMQ_POLLIN; diff --git a/src/main/java/zmq/poll/Poller.java b/src/main/java/zmq/poll/Poller.java new file mode 100644 index 000000000..4db81c0e6 --- /dev/null +++ b/src/main/java/zmq/poll/Poller.java @@ -0,0 +1,310 @@ +package zmq.poll; + +import java.io.IOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import zmq.Ctx; +import zmq.ZError; + +public final class Poller extends PollerBase implements Runnable +{ + // opaque class to mimic libzmq behaviour. + // extra interest is we do not need to look through the fdTable to perform common operations. + public static final class Handle + { + private final SelectableChannel fd; + private final IPollEvents handler; + + private int ops; + private boolean cancelled; + + public Handle(SelectableChannel fd, IPollEvents handler) + { + this.fd = fd; + this.handler = handler; + } + + @Override + public int hashCode() + { + return Objects.hash(fd, handler); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Handle)) { + return false; + } + Handle other = (Handle) obj; + return Objects.equals(fd, other.fd) && Objects.equals(handler, other.handler); + } + + @Override + public String toString() + { + return "Handle-" + fd; + } + } + + // Reference to ZMQ context. + private final Ctx ctx; + + // stores data for registered descriptors. + private final Set fdTable; + + // If true, there's at least one retired event source. + private boolean retired = false; + + // If true, thread is in the process of shutting down. + private final AtomicBoolean stopping = new AtomicBoolean(); + private final CountDownLatch stopped = new CountDownLatch(1); + + private Selector selector; + + public Poller(Ctx ctx, String name) + { + super(name); + this.ctx = ctx; + + fdTable = new HashSet<>(); + selector = ctx.createSelector(); + } + + public void destroy() + { + try { + stop(); + stopped.await(); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + finally { + ctx.closeSelector(selector); + } + } + + public Handle addHandle(SelectableChannel fd, IPollEvents events) + { + assert (Thread.currentThread() == worker || !worker.isAlive()); + + Handle handle = new Handle(fd, events); + fdTable.add(handle); + + // Increase the load metric of the thread. + adjustLoad(1); + return handle; + } + + public void removeHandle(Handle handle) + { + assert (Thread.currentThread() == worker || !worker.isAlive()); + + // Mark the fd as unused. + handle.cancelled = true; + retired = true; + + // Decrease the load metric of the thread. + adjustLoad(-1); + } + + public void setPollIn(Handle handle) + { + register(handle, SelectionKey.OP_READ, true); + } + + public void resetPollIn(Handle handle) + { + register(handle, SelectionKey.OP_READ, false); + } + + public void setPollOut(Handle handle) + { + register(handle, SelectionKey.OP_WRITE, true); + } + + public void resetPollOut(Handle handle) + { + register(handle, SelectionKey.OP_WRITE, false); + } + + public void setPollConnect(Handle handle) + { + register(handle, SelectionKey.OP_CONNECT, true); + } + + public void setPollAccept(Handle handle) + { + register(handle, SelectionKey.OP_ACCEPT, true); + } + + private void register(Handle handle, int ops, boolean add) + { + assert (Thread.currentThread() == worker || !worker.isAlive()); + + if (add) { + handle.ops |= ops; + } + else { + handle.ops &= ~ops; + } + retired = true; + } + + public void start() + { + worker.start(); + } + + public void stop() + { + stopping.set(true); + retired = false; + selector.wakeup(); + } + + @Override + public void run() + { + int returnsImmediately = 0; + + while (!stopping.get()) { + // Execute any due timers. + long timeout = executeTimers(); + + if (retired == true) { + retired = false; + Iterator iter = fdTable.iterator(); + while (iter.hasNext()) { + Handle handle = iter.next(); + SelectionKey key = handle.fd.keyFor(selector); + if (handle.cancelled || !handle.fd.isOpen()) { + if (key != null) { + key.cancel(); + } + iter.remove(); + continue; + } + if (key == null) { + if (handle.fd.isOpen()) { + try { + key = handle.fd.register(selector, handle.ops, handle); + assert (key != null); + } + catch (CancelledKeyException | ClosedSelectorException | ClosedChannelException e) { + e.printStackTrace(); + } + } + } + else if (key.isValid()) { + key.interestOps(handle.ops); + } + } + } + + // Wait for events. + int rc; + long start = System.currentTimeMillis(); + try { + rc = selector.select(timeout); + } + catch (ClosedSelectorException e) { + rebuildSelector(); + e.printStackTrace(); + ctx.errno().set(ZError.EINTR); + continue; + } + catch (IOException e) { + throw new ZError.IOException(e); + } + + // If there are no events (i.e. it's a timeout) there's no point + // in checking the keys. + if (rc == 0) { + returnsImmediately = maybeRebuildSelector(returnsImmediately, timeout, start); + continue; + } + + Iterator it = selector.selectedKeys().iterator(); + while (it.hasNext()) { + SelectionKey key = it.next(); + Handle pollset = (Handle) key.attachment(); + it.remove(); + if (pollset.cancelled) { + continue; + } + + try { + if (key.isValid() && key.isAcceptable()) { + pollset.handler.acceptEvent(); + } + if (key.isValid() && key.isConnectable()) { + pollset.handler.connectEvent(); + } + if (key.isValid() && key.isWritable()) { + pollset.handler.outEvent(); + } + if (key.isValid() && key.isReadable()) { + pollset.handler.inEvent(); + } + } + catch (CancelledKeyException e) { + // key may have been cancelled (?) + e.printStackTrace(); + } + catch (RuntimeException e) { + // avoid the thread death by continuing to iterate + e.printStackTrace(); + } + } + } + stopped.countDown(); + } + + private int maybeRebuildSelector(int returnsImmediately, long timeout, long start) + { + // Guess JDK epoll bug + if (timeout == 0 || System.currentTimeMillis() - start < timeout / 2) { + returnsImmediately++; + } + else { + returnsImmediately = 0; + } + + if (returnsImmediately > 10) { + rebuildSelector(); + returnsImmediately = 0; + } + return returnsImmediately; + } + + private void rebuildSelector() + { + System.out.println(this + " rebuilding selector"); + Selector newSelector = ctx.createSelector(); + Selector oldSelector = selector; + + selector = newSelector; + retired = true; + + ctx.closeSelector(oldSelector); + } +} diff --git a/src/main/java/zmq/PollerBase.java b/src/main/java/zmq/poll/PollerBase.java similarity index 53% rename from src/main/java/zmq/PollerBase.java rename to src/main/java/zmq/poll/PollerBase.java index 05164fdb6..99358443a 100644 --- a/src/main/java/zmq/PollerBase.java +++ b/src/main/java/zmq/poll/PollerBase.java @@ -1,20 +1,20 @@ -package zmq; +package zmq.poll; -import java.util.Iterator; -import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; -abstract class PollerBase -{ - // Load of the poller. Currently the number of file descriptors - // registered. - private final AtomicInteger load; +import zmq.util.Clock; +import zmq.util.MultiMap; - private final class TimerInfo +abstract class PollerBase implements Runnable +{ + private static final class TimerInfo { - IPollEvents sink; - int id; + private final IPollEvents sink; + private final int id; + + private boolean cancelled; public TimerInfo(IPollEvents sink, int id) { @@ -22,20 +22,45 @@ public TimerInfo(IPollEvents sink, int id) this.id = id; } + @Override + public int hashCode() + { + return Objects.hash(id, sink); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof TimerInfo) { + TimerInfo other = (TimerInfo) obj; + return this.id == other.id && Objects.equals(this.sink, other.sink); + } + return false; + } + @Override public String toString() { return "TimerInfo [id=" + id + ", sink=" + sink + "]"; } } - private final Map timers; - private final Map addingTimers; - protected PollerBase() + // Load of the poller. Currently the number of file descriptors + // registered. + private final AtomicInteger load; + + private final MultiMap timers; + + // the thread where all events will be dispatched. So, the actual IO or Reaper threads. + protected final Thread worker; + + protected PollerBase(String name) { + worker = new Thread(this, name); + worker.setDaemon(true); + load = new AtomicInteger(0); - timers = new MultiMap(); - addingTimers = new MultiMap(); + timers = new MultiMap<>(); } // Returns load of the poller. Note that this function can be @@ -56,43 +81,33 @@ protected void adjustLoad(int amount) // argument set to id_. public void addTimer(long timeout, IPollEvents sink, int id) { - long expiration = Clock.nowMS() + timeout; - TimerInfo info = new TimerInfo(sink, id); - addingTimers.put(expiration, info); + assert (Thread.currentThread() == worker); + final long expiration = Clock.nowMS() + timeout; + TimerInfo info = new TimerInfo(sink, id); + timers.insert(expiration, info); } // Cancel the timer created by sink_ object with ID equal to id_. public void cancelTimer(IPollEvents sink, int id) { - // Complexity of this operation is O(n). We assume it is rarely used. - - if (!addingTimers.isEmpty()) { - timers.putAll(addingTimers); - addingTimers.clear(); - } + assert (Thread.currentThread() == worker); - Iterator> it = timers.entrySet().iterator(); - while (it.hasNext()) { - TimerInfo v = it.next().getValue(); - if (v.sink == sink && v.id == id) { - it.remove(); - return; - } - } + TimerInfo copy = new TimerInfo(sink, id); + // Complexity of this operation is O(n). We assume it is rarely used. - // Timer not found. - assert (false); + TimerInfo timerInfo = timers.find(copy); + assert (timerInfo != null); + // let's defer the removal during the loop + timerInfo.cancelled = true; } // Executes any timers that are due. Returns number of milliseconds // to wait to match the next timer or 0 meaning "no timers". protected long executeTimers() { - if (!addingTimers.isEmpty()) { - timers.putAll(addingTimers); - addingTimers.clear(); - } + assert (Thread.currentThread() == worker); + // Fast track. if (timers.isEmpty()) { return 0L; @@ -102,26 +117,30 @@ protected long executeTimers() long current = Clock.nowMS(); // Execute the timers that are already due. - Iterator> it = timers.entrySet().iterator(); - while (it.hasNext()) { - Entry o = it.next(); + for (Entry entry : timers.entries()) { + final TimerInfo timerInfo = entry.getKey(); + if (timerInfo.cancelled) { + timers.remove(entry); + continue; + } // If we have to wait to execute the item, same will be true about // all the following items (multimap is sorted). Thus we can stop // checking the subsequent timers and return the time to wait for // the next timer (at least 1ms). - if (o.getKey() > current) { - return o.getKey() - current; + final Long key = entry.getValue(); + + if (key > current) { + return key - current; } // Trigger the timer. - o.getValue().sink.timerEvent(o.getValue().id); - // Remove it from the list of active timers. - it.remove(); - } + if (!timerInfo.cancelled) { + timerInfo.sink.timerEvent(timerInfo.id); + } - if (!addingTimers.isEmpty()) { - return executeTimers(); + // Remove it from the list of active timers. + timers.remove(entry); } // There are no more timers. diff --git a/src/main/java/zmq/FQ.java b/src/main/java/zmq/socket/FQ.java similarity index 74% rename from src/main/java/zmq/FQ.java rename to src/main/java/zmq/socket/FQ.java index fc3e9eeee..87031c079 100644 --- a/src/main/java/zmq/FQ.java +++ b/src/main/java/zmq/socket/FQ.java @@ -1,13 +1,20 @@ -package zmq; +package zmq.socket; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import zmq.Msg; +import zmq.ZError; +import zmq.pipe.Pipe; +import zmq.util.Blob; +import zmq.util.Errno; +import zmq.util.ValueReference; + // Class manages a set of inbound pipes. On receive it performs fair -// queueing so that senders gone berserk won't cause denial of +// queuing so that senders gone berserk won't cause denial of // service for decent senders. -class FQ +public class FQ { // Inbound pipes. private final List pipes; @@ -16,6 +23,11 @@ class FQ // beginning of the pipes array. private int active; + // the last pipe we received message from. + // NULL when no message has been received or the pipe + // has terminated. + private Pipe lastIn; + // Index of the next bound pipe to read a message from. private int current; @@ -23,6 +35,9 @@ class FQ // there are following parts still waiting in the current pipe. private boolean more; + // Holds credential after the last_acive_pipe has terminated. + private Blob savedCredential; + public FQ() { active = 0; @@ -53,6 +68,11 @@ public void terminated(Pipe pipe) } } pipes.remove(pipe); + + if (lastIn == pipe) { + savedCredential = lastIn.getCredential(); + lastIn = null; + } } public void activated(Pipe pipe) @@ -62,29 +82,31 @@ public void activated(Pipe pipe) active++; } - public Msg recv(ValueReference errno) + public Msg recv(Errno errno) { return recvPipe(errno, null); } - public Msg recvPipe(ValueReference errno, ValueReference pipe) + public Msg recvPipe(Errno errno, ValueReference pipe) { // Round-robin over the pipes to get the next message. while (active > 0) { // Try to fetch new message. If we've already read part of the message // subsequent part should be immediately available. - Msg msg = pipes.get(current).read(); - boolean fetched = msg != null; + final Pipe currentPipe = pipes.get(current); + final Msg msg = currentPipe.read(); + final boolean fetched = msg != null; // Note that when message is not fetched, current pipe is deactivated // and replaced by another active pipe. Thus we don't have to increase // the 'current' pointer. if (fetched) { if (pipe != null) { - pipe.set(pipes.get(current)); + pipe.set(currentPipe); } more = msg.hasMore(); if (!more) { + lastIn = currentPipe; current = (current + 1) % active; } return msg; @@ -102,7 +124,7 @@ public Msg recvPipe(ValueReference errno, ValueReference pipe) } } - // No message is available. Initialise the output parameter + // No message is available. Initialize the output parameter // to be a 0-byte message. errno.set(ZError.EAGAIN); return null; @@ -116,7 +138,7 @@ public boolean hasIn() } // Note that messing with current doesn't break the fairness of fair - // queueing algorithm. If there are no messages available current will + // queuing algorithm. If there are no messages available current will // get back to its original value. Otherwise it'll point to the first // pipe holding messages, skipping only pipes with no messages available. while (active > 0) { @@ -134,4 +156,9 @@ public boolean hasIn() return false; } + + public Blob getCredential() + { + return lastIn != null ? lastIn.getCredential() : savedCredential; + } } diff --git a/src/main/java/zmq/LB.java b/src/main/java/zmq/socket/LB.java similarity index 90% rename from src/main/java/zmq/LB.java rename to src/main/java/zmq/socket/LB.java index 610c39d96..e5b86d7cd 100644 --- a/src/main/java/zmq/LB.java +++ b/src/main/java/zmq/socket/LB.java @@ -1,9 +1,15 @@ -package zmq; +package zmq.socket; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import zmq.Msg; +import zmq.ZError; +import zmq.pipe.Pipe; +import zmq.util.Errno; +import zmq.util.ValueReference; + public class LB { // List of outbound pipes. @@ -40,7 +46,7 @@ public void attach(Pipe pipe) public void terminated(Pipe pipe) { - int index = pipes.indexOf(pipe); + final int index = pipes.indexOf(pipe); // If we are in the middle of multipart message and current pipe // have disconnected, we have to drop the remainder of the message. @@ -67,7 +73,7 @@ public void activated(Pipe pipe) active++; } - public boolean send(Msg msg, ValueReference errno) + public boolean sendpipe(Msg msg, Errno errno, ValueReference pipe) { // Drop the message if required. If we are at the end of the message // switch back to non-dropping mode. @@ -81,6 +87,9 @@ public boolean send(Msg msg, ValueReference errno) while (active > 0) { if (pipes.get(current).write(msg)) { + if (pipe != null) { + pipe.set(pipes.get(current)); + } break; } diff --git a/src/main/java/zmq/Pair.java b/src/main/java/zmq/socket/Pair.java similarity index 61% rename from src/main/java/zmq/Pair.java rename to src/main/java/zmq/socket/Pair.java index 0bb47c8b0..8b0bf0ea9 100644 --- a/src/main/java/zmq/Pair.java +++ b/src/main/java/zmq/socket/Pair.java @@ -1,18 +1,19 @@ -package zmq; +package zmq.socket; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.pipe.Pipe; +import zmq.util.Blob; public class Pair extends SocketBase { - public static class PairSession extends SessionBase - { - public PairSession(IOThread ioThread, boolean connect, - SocketBase socket, final Options options, - final Address addr) - { - super(ioThread, connect, socket, options, addr); - } - } - private Pipe pipe; + private Pipe lastIn; + + private Blob savedCredential; public Pair(Ctx parent, int tid, int sid) { @@ -21,7 +22,14 @@ public Pair(Ctx parent, int tid, int sid) } @Override - protected void xattachPipe(Pipe pipe, boolean icanhasall) + protected void destroy() + { + super.destroy(); + assert (pipe == null); + } + + @Override + protected void xattachPipe(Pipe pipe, boolean subscribe2all) { assert (pipe != null); @@ -39,6 +47,10 @@ protected void xattachPipe(Pipe pipe, boolean icanhasall) protected void xpipeTerminated(Pipe pipe) { if (this.pipe == pipe) { + if (lastIn == pipe) { + savedCredential = lastIn.getCredential(); + lastIn = null; + } this.pipe = null; } } @@ -65,7 +77,7 @@ protected boolean xsend(Msg msg) return false; } - if ((msg.flags() & ZMQ.ZMQ_SNDMORE) == 0) { + if (!msg.hasMore()) { pipe.flush(); } @@ -76,12 +88,21 @@ protected boolean xsend(Msg msg) protected Msg xrecv() { // Deallocate old content of the message. - Msg msg = pipe == null ? null : pipe.read(); - if (msg == null) { - // Initialise the output parameter to be a 0-byte message. + Msg msg = null; + if (pipe == null) { + // Initialize the output parameter to be a 0-byte message. errno.set(ZError.EAGAIN); return null; } + else { + msg = pipe.read(); + if (msg == null) { + // Initialize the output parameter to be a 0-byte message. + errno.set(ZError.EAGAIN); + return null; + } + } + lastIn = pipe; return msg; } @@ -96,4 +117,10 @@ protected boolean xhasOut() { return pipe != null && pipe.checkWrite(); } + + @Override + protected Blob getCredential() + { + return lastIn != null ? lastIn.getCredential() : savedCredential; + } } diff --git a/src/main/java/zmq/socket/Sockets.java b/src/main/java/zmq/socket/Sockets.java new file mode 100644 index 000000000..1b0b9aed9 --- /dev/null +++ b/src/main/java/zmq/socket/Sockets.java @@ -0,0 +1,156 @@ +package zmq.socket; + +import java.util.Arrays; +import java.util.List; + +import zmq.Ctx; +import zmq.Options; +import zmq.SocketBase; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.socket.pipeline.Pull; +import zmq.socket.pipeline.Push; +import zmq.socket.pubsub.Pub; +import zmq.socket.pubsub.Sub; +import zmq.socket.pubsub.XPub; +import zmq.socket.pubsub.XSub; +import zmq.socket.reqrep.Dealer; +import zmq.socket.reqrep.Rep; +import zmq.socket.reqrep.Req; +import zmq.socket.reqrep.Router; + +public enum Sockets +{ + PAIR("PAIR") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Pair(parent, tid, sid); + } + }, + PUB("SUB", "XSUB") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Pub(parent, tid, sid); + } + }, + SUB("PUB", "XPUB") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Sub(parent, tid, sid); + } + }, + REQ("REP", "ROUTER") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Req(parent, tid, sid); + } + + @Override + public SessionBase create(IOThread ioThread, boolean connect, SocketBase socket, Options options, Address addr) + { + return new Req.ReqSession(ioThread, connect, socket, options, addr); + } + }, + REP("REQ", "DEALER") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Rep(parent, tid, sid); + } + }, + DEALER("REP", "DEALER", "ROUTER") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Dealer(parent, tid, sid); + } + }, + ROUTER("REQ", "DEALER", "ROUTER") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Router(parent, tid, sid); + } + }, + PULL("PUSH") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Pull(parent, tid, sid); + } + }, + PUSH("PULL") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Push(parent, tid, sid); + } + }, + XPUB("SUB", "XSUB") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new XPub(parent, tid, sid); + } + }, + XSUB("PUB", "XPUB") { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new XSub(parent, tid, sid); + } + }, + STREAM { + @Override + SocketBase create(Ctx parent, int tid, int sid) + { + return new Stream(parent, tid, sid); + } + }; + + private final List compatible; + + Sockets(String... compatible) + { + this.compatible = Arrays.asList(compatible); + } + + // Create a socket of a specified type. + abstract SocketBase create(Ctx parent, int tid, int sid); + + public SessionBase create(IOThread ioThread, boolean connect, SocketBase socket, Options options, Address addr) + { + return new SessionBase(ioThread, connect, socket, options, addr); + } + + public static SessionBase createSession(IOThread ioThread, boolean connect, SocketBase socket, Options options, + Address addr) + { + return values()[options.type].create(ioThread, connect, socket, options, addr); + } + + public static SocketBase create(int socketType, Ctx parent, int tid, int sid) + { + return values()[socketType].create(parent, tid, sid); + } + + public static String name(int socketType) + { + return values()[socketType].name(); + } + + public static Sockets fromType(int socketType) + { + return values()[socketType]; + } + + public static boolean compatible(int self, String peer) + { + return values()[self].compatible.contains(peer); + } +} diff --git a/src/main/java/zmq/socket/Stream.java b/src/main/java/zmq/socket/Stream.java new file mode 100644 index 000000000..efaa1bc52 --- /dev/null +++ b/src/main/java/zmq/socket/Stream.java @@ -0,0 +1,316 @@ +package zmq.socket; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.Metadata; +import zmq.pipe.Pipe; +import zmq.util.Blob; +import zmq.util.Utils; +import zmq.util.ValueReference; +import zmq.util.Wire; + +public class Stream extends SocketBase +{ + // Fair queueing object for inbound pipes. + private final FQ fq; + + // True if there is a message held in the pre-fetch buffer. + private boolean prefetched; + + // If true, the receiver got the message part with + // the peer's identity. + private boolean identitySent; + + // Holds the prefetched identity. + private Msg prefetchedId; + + // Holds the prefetched message. + private Msg prefetchedMsg; + + private class Outpipe + { + private Pipe pipe; + private boolean active; + + public Outpipe(Pipe pipe, boolean active) + { + this.pipe = pipe; + this.active = active; + } + } + + // Outbound pipes indexed by the peer IDs. + private Map outpipes = new HashMap<>(); + + // The pipe we are currently writing to. + private Pipe currentOut; + + // If true, more outgoing message parts are expected. + private boolean moreOut; + + // Routing IDs are generated. It's a simple increment and wrap-over + // algorithm. This value is the next ID to use (if not used already). + private int nextRid; + + public Stream(Ctx parent, int tid, int sid) + { + super(parent, tid, sid); + prefetched = false; + identitySent = false; + currentOut = null; + moreOut = false; + nextRid = Utils.randomInt(); + options.type = ZMQ.ZMQ_STREAM; + options.rawSocket = true; + + fq = new FQ(); + prefetchedId = new Msg(); + prefetchedMsg = new Msg(); + } + + @Override + protected void xattachPipe(Pipe pipe, boolean icanhasall) + { + assert (pipe != null); + + identifyPeer(pipe); + fq.attach(pipe); + } + + @Override + protected void xpipeTerminated(Pipe pipe) + { + Outpipe outpipe = outpipes.remove(pipe.getIdentity()); + assert (outpipe != null); + fq.terminated(pipe); + if (pipe == currentOut) { + currentOut = null; + } + } + + @Override + protected void xreadActivated(Pipe pipe) + { + fq.activated(pipe); + } + + @Override + protected void xwriteActivated(Pipe pipe) + { + Outpipe out = null; + for (Outpipe outpipe : outpipes.values()) { + if (outpipe.pipe == pipe) { + out = outpipe; + break; + } + } + assert (out != null); + assert (!out.active); + out.active = true; + } + + @Override + protected boolean xsend(Msg msg) + { + // If this is the first part of the message it's the ID of the + // peer to send the message to. + if (!moreOut) { + assert (currentOut == null); + + // If we have malformed message (prefix with no subsequent message) + // then just silently ignore it. + // TODO: The connections should be killed instead. + if (msg.hasMore()) { + moreOut = true; + + // Find the pipe associated with the identity stored in the prefix. + // If there's no such pipe return an error + Blob identity = Blob.createBlob(msg); + Outpipe op = outpipes.get(identity); + + if (op != null) { + currentOut = op.pipe; + if (!currentOut.checkWrite()) { + op.active = false; + currentOut = null; + errno.set(ZError.EAGAIN); + return false; + } + } + else { + errno.set(ZError.EHOSTUNREACH); + return false; + } + } + // Expect one more message frame. + moreOut = true; + + return true; + } + + // Ignore the MORE flag + msg.resetFlags(Msg.MORE); + + // This is the last part of the message. + moreOut = false; + + // Push the message into the pipe. If there's no out pipe, just drop it. + if (currentOut != null) { + // Close the remote connection if user has asked to do so + // by sending zero length message. + // Pending messages in the pipe will be dropped (on receiving term- ack) + if (msg.size() == 0) { + currentOut.terminate(false); + currentOut = null; + return true; + } + boolean ok = currentOut.write(msg); + if (ok) { + currentOut.flush(); + } + currentOut = null; + } + + return true; + } + + @Override + protected boolean xsetsockopt(int option, Object optval) + { + switch (option) { + case ZMQ.ZMQ_CONNECT_RID: + connectRid = (String) optval; + return true; + default: + errno.set(ZError.EINVAL); + return false; + } + } + + @Override + public Msg xrecv() + { + Msg msg = null; + if (prefetched) { + if (!identitySent) { + msg = prefetchedId; + prefetchedId = null; + identitySent = true; + } + else { + msg = prefetchedMsg; + prefetchedMsg = null; + prefetched = false; + } + return msg; + } + + ValueReference pipe = new ValueReference(); + prefetchedMsg = fq.recvPipe(errno, pipe); + + // TODO DIFF V4 we sometimes need to process the commands to receive data, let's just return and give it another chance + if (prefetchedMsg == null) { + errno.set(ZError.EAGAIN); + return null; + } + assert (pipe.get() != null); + assert (!prefetchedMsg.hasMore()); + + // We have received a frame with TCP data. + // Rather than sending this frame, we keep it in prefetched + // buffer and send a frame with peer's ID. + Blob identity = pipe.get().getIdentity(); + + msg = new Msg(identity.data()); + + // forward metadata (if any) + Metadata metadata = prefetchedMsg.getMetadata(); + if (metadata != null) { + msg.setMetadata(metadata); + } + + msg.setFlags(Msg.MORE); + + prefetched = true; + identitySent = true; + + return msg; + } + + @Override + protected boolean xhasIn() + { + // We may already have a message pre-fetched. + if (prefetched) { + return true; + } + + // Try to read the next message. + // The message, if read, is kept in the pre-fetch buffer. + ValueReference pipe = new ValueReference(); + prefetchedMsg = fq.recvPipe(errno, pipe); + if (prefetchedMsg == null) { + return false; + } + + assert (pipe.get() != null); + assert (!prefetchedMsg.hasMore()); + + Blob identity = pipe.get().getIdentity(); + + prefetchedId = new Msg(identity.data()); + + // forward metadata (if any) + Metadata metadata = prefetchedMsg.getMetadata(); + if (metadata != null) { + prefetchedId.setMetadata(metadata); + } + + prefetchedId.setFlags(Msg.MORE); + + prefetched = true; + identitySent = false; + + return true; + } + + @Override + protected boolean xhasOut() + { + // In theory, STREAM socket is always ready for writing. Whether actual + // attempt to write succeeds depends on which pipe the message is going + // to be routed to. + return true; + } + + private void identifyPeer(Pipe pipe) + { + // Always assign identity for raw-socket + + Blob identity = null; + if (connectRid != null && !connectRid.isEmpty()) { + identity = Blob.createBlob(connectRid.getBytes(ZMQ.CHARSET)); + connectRid = null; + + Outpipe outpipe = outpipes.get(identity); + assert (outpipe == null); + } + else { + ByteBuffer buf = ByteBuffer.allocate(5); + buf.put((byte) 0); + Wire.putUInt32(buf, nextRid++); + identity = Blob.createBlob(buf.array()); + } + pipe.setIdentity(identity); + // Add the record into output pipes lookup table + Outpipe outpipe = new Outpipe(pipe, true); + outpipes.put(identity, outpipe); + } +} diff --git a/src/main/java/zmq/Pull.java b/src/main/java/zmq/socket/pipeline/Pull.java similarity index 63% rename from src/main/java/zmq/Pull.java rename to src/main/java/zmq/socket/pipeline/Pull.java index ea33ee75f..4f8fda5f2 100644 --- a/src/main/java/zmq/Pull.java +++ b/src/main/java/zmq/socket/pipeline/Pull.java @@ -1,17 +1,15 @@ -package zmq; +package zmq.socket.pipeline; -class Pull extends SocketBase -{ - public static class PullSession extends SessionBase - { - public PullSession(IOThread ioThread, boolean connect, - SocketBase socket, final Options options, - final Address addr) - { - super(ioThread, connect, socket, options, addr); - } - } +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.pipe.Pipe; +import zmq.socket.FQ; +import zmq.util.Blob; +public class Pull extends SocketBase +{ // Fair queueing object for inbound pipes. private final FQ fq; @@ -24,7 +22,7 @@ public Pull(Ctx parent, int tid, int sid) } @Override - protected void xattachPipe(Pipe pipe, boolean icanhasall) + protected void xattachPipe(Pipe pipe, boolean subscribe2all) { assert (pipe != null); fq.attach(pipe); @@ -53,4 +51,10 @@ protected boolean xhasIn() { return fq.hasIn(); } + + @Override + protected Blob getCredential() + { + return fq.getCredential(); + } } diff --git a/src/main/java/zmq/Push.java b/src/main/java/zmq/socket/pipeline/Push.java similarity index 63% rename from src/main/java/zmq/Push.java rename to src/main/java/zmq/socket/pipeline/Push.java index 3995d4e47..6d0886b9f 100644 --- a/src/main/java/zmq/Push.java +++ b/src/main/java/zmq/socket/pipeline/Push.java @@ -1,17 +1,14 @@ -package zmq; +package zmq.socket.pipeline; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.pipe.Pipe; +import zmq.socket.LB; public class Push extends SocketBase { - public static class PushSession extends SessionBase - { - public PushSession(IOThread ioThread, boolean connect, - SocketBase socket, final Options options, - final Address addr) - { - super(ioThread, connect, socket, options, addr); - } - } - // Load balancer managing the outbound pipes. private final LB lb; @@ -24,9 +21,14 @@ public Push(Ctx parent, int tid, int sid) } @Override - protected void xattachPipe(Pipe pipe, boolean icanhasall) + protected void xattachPipe(Pipe pipe, boolean subscribe2all) { assert (pipe != null); + + // Don't delay pipe termination as there is no one + // to receive the delimiter. + pipe.setNoDelay(); + lb.attach(pipe); } @@ -45,7 +47,7 @@ protected void xpipeTerminated(Pipe pipe) @Override public boolean xsend(Msg msg) { - return lb.send(msg, errno); + return lb.sendpipe(msg, errno, null); } @Override diff --git a/src/main/java/zmq/Dist.java b/src/main/java/zmq/socket/pubsub/Dist.java similarity index 92% rename from src/main/java/zmq/Dist.java rename to src/main/java/zmq/socket/pubsub/Dist.java index 936563157..e3ed6bf38 100644 --- a/src/main/java/zmq/Dist.java +++ b/src/main/java/zmq/socket/pubsub/Dist.java @@ -1,9 +1,12 @@ -package zmq; +package zmq.socket.pubsub; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import zmq.Msg; +import zmq.pipe.Pipe; + class Dist { // List of outbound pipes. @@ -45,13 +48,11 @@ public void attach(Pipe pipe) // of active pipes. if (more) { pipes.add(pipe); - //pipes.swap (eligible, pipes.size() - 1); Collections.swap(pipes, eligible, pipes.size() - 1); eligible++; } else { pipes.add(pipe); - //pipes.swap (active, pipes.size() - 1); Collections.swap(pipes, active, pipes.size() - 1); active++; eligible++; @@ -153,9 +154,12 @@ private void distribute(Msg msg) return; } - for (int i = 0; i < matching; ++i) { - if (!write(pipes.get(i), msg)) { - --i; // Retry last write because index will have been swapped + // TODO isVsm + + // Push copy of the message to each matching pipe. + for (int idx = 0; idx < matching; ++idx) { + if (!write(pipes.get(idx), msg)) { + --idx; // Retry last write because index will have been swapped } } } @@ -186,8 +190,8 @@ private boolean write(Pipe pipe, Msg msg) public boolean checkHwm() { - for (int i = 0; i < matching; ++i) { - if (!(pipes.get(i).checkHwm())) { + for (int idx = 0; idx < matching; ++idx) { + if (!pipes.get(idx).checkHwm()) { return false; } } diff --git a/src/main/java/zmq/Mtrie.java b/src/main/java/zmq/socket/pubsub/Mtrie.java similarity index 83% rename from src/main/java/zmq/Mtrie.java rename to src/main/java/zmq/socket/pubsub/Mtrie.java index 7705a003f..c381cd392 100644 --- a/src/main/java/zmq/Mtrie.java +++ b/src/main/java/zmq/socket/pubsub/Mtrie.java @@ -1,22 +1,25 @@ -package zmq; +package zmq.socket.pubsub; import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Set; +import zmq.pipe.Pipe; +import zmq.util.Utils; + //Multi-trie. Each node in the trie is a set of pointers to pipes. -public class Mtrie +class Mtrie { private Set pipes; - private int min; - private int count; - private int liveNodes; + private int min; + private int count; + private int liveNodes; private Mtrie[] next; public interface IMtrieHandler { - void invoke(Pipe pipe, byte[] data, int size, Object arg); + void invoke(Pipe pipe, byte[] data, int size, XPub arg); } public Mtrie() @@ -31,20 +34,20 @@ public Mtrie() public boolean add(byte[] prefix, Pipe pipe) { - return addHelper(prefix, 0, pipe); + return addHelper(prefix, 0, 0, pipe); } // Add key to the trie. Returns true if it's a new subscription // rather than a duplicate. - public boolean add(byte[] prefix, int start, Pipe pipe) + public boolean add(byte[] prefix, int size, Pipe pipe) { - return addHelper(prefix, start, pipe); + return addHelper(prefix, 1, size - 1, pipe); } - private boolean addHelper(byte[] prefix, int start, Pipe pipe) + private boolean addHelper(byte[] prefix, int start, int size, Pipe pipe) { // We are at the node corresponding to the prefix. We are done. - if (prefix == null || prefix.length == start) { + if (size == 0) { boolean result = pipes == null; if (pipes == null) { pipes = new HashSet(); @@ -91,7 +94,7 @@ else if (min < c) { ++liveNodes; //alloc_assert (next.node); } - return next[0].addHelper(prefix, start + 1, pipe); + return next[0].addHelper(prefix, start + 1, size - 1, pipe); } else { if (next[c - min] == null) { @@ -99,7 +102,7 @@ else if (min < c) { ++liveNodes; //alloc_assert (next.table [c - min]); } - return next[c - min].addHelper(prefix, start + 1, pipe); + return next[c - min].addHelper(prefix, start + 1, size - 1, pipe); } } @@ -111,21 +114,17 @@ private Mtrie[] realloc(Mtrie[] table, int size, boolean ended) // Remove all subscriptions for a specific peer from the trie. // If there are no subscriptions left on some topics, invoke the // supplied callback function. - public boolean rm(Pipe pipe, IMtrieHandler func, Object arg, boolean callOnUniq) + public boolean rm(Pipe pipe, IMtrieHandler func, XPub pub) { - return rmHelper(pipe, new byte[0], 0, 0, func, arg, callOnUniq); + return rmHelper(pipe, new byte[0], 0, 0, func, pub); } - private boolean rmHelper(Pipe pipe, byte[] buff, int buffsize, int maxBuffSize, - IMtrieHandler func, Object arg, boolean callOnUniq) + private boolean rmHelper(Pipe pipe, byte[] buff, int buffsize, int maxBuffSize, IMtrieHandler func, XPub pub) { // Remove the subscription from this node. if (pipes != null && pipes.remove(pipe)) { - if (!callOnUniq || pipes.isEmpty()) { - func.invoke(null, buff, buffsize, arg); - } - if (pipes.isEmpty()) { + func.invoke(null, buff, buffsize, pub); pipes = null; } } @@ -145,8 +144,7 @@ private boolean rmHelper(Pipe pipe, byte[] buff, int buffsize, int maxBuffSize, if (count == 1) { buff[buffsize] = (byte) min; buffsize++; - next[0].rmHelper(pipe, buff, buffsize, maxBuffSize, - func, arg, callOnUniq); + next[0].rmHelper(pipe, buff, buffsize, maxBuffSize, func, pub); // Prune the node if it was made redundant by the removal if (next[0].isRedundant()) { @@ -167,8 +165,7 @@ private boolean rmHelper(Pipe pipe, byte[] buff, int buffsize, int maxBuffSize, for (int c = 0; c != count; c++) { buff[buffsize] = (byte) (min + c); if (next[c] != null) { - next[c].rmHelper(pipe, buff, buffsize + 1, - maxBuffSize, func, arg, callOnUniq); + next[c].rmHelper(pipe, buff, buffsize + 1, maxBuffSize, func, pub); // Prune redundant nodes from the mtrie if (next[c].isRedundant()) { @@ -209,21 +206,21 @@ else if (liveNodes == 1) { // representation assert (newMin == newMax); assert (newMin >= min && newMin < min + count); - Mtrie node = next [newMin - min]; + Mtrie node = next[newMin - min]; assert (node != null); - next = null; - next = new Mtrie[]{node}; + next = new Mtrie[] { node }; count = 1; min = newMin; } else if (newMin > min || newMax < min + count - 1) { - assert (newMax - newMin + 1 > 1); + assert (newMax > newMin); Mtrie[] oldTable = next; assert (newMin > min || newMax < min + count - 1); assert (newMin >= min); assert (newMax <= min + count - 1); assert (newMax - newMin + 1 < count); + count = newMax - newMin + 1; next = new Mtrie[count]; @@ -236,14 +233,14 @@ else if (newMin > min || newMax < min + count - 1) { // Remove specific subscription from the trie. Return true is it was // actually removed rather than de-duplicated. - public boolean rm(byte[] prefix, int start, Pipe pipe) + public boolean rm(byte[] prefix, int size, Pipe pipe) { - return rmHelper(prefix, start, pipe); + return rmHelper(prefix, 1, size - 1, pipe); } - private boolean rmHelper(byte[] prefix, int start, Pipe pipe) + private boolean rmHelper(byte[] prefix, int start, int size, Pipe pipe) { - if (prefix == null || prefix.length == start) { + if (size == 0) { if (pipes != null) { boolean erased = pipes.remove(pipe); assert (erased); @@ -254,19 +251,18 @@ private boolean rmHelper(byte[] prefix, int start, Pipe pipe) return pipes == null; } - byte c = prefix[start]; + byte c = prefix[start + size]; if (count == 0 || c < min || c >= min + count) { return false; } - Mtrie nextNode = - count == 1 ? next[0] : next[c - min]; + Mtrie nextNode = count == 1 ? next[0] : next[c - min]; if (nextNode == null) { return false; } - boolean ret = nextNode.rmHelper(prefix, start + 1, pipe); + boolean ret = nextNode.rmHelper(prefix, start + 1, size - 1, pipe); if (nextNode.isRedundant()) { assert (count > 0); @@ -296,8 +292,8 @@ private boolean rmHelper(byte[] prefix, int start, Pipe pipe) assert (i < count); min += i; count = 1; - Mtrie old = next [i]; - next = new Mtrie [] { old }; + Mtrie old = next[i]; + next = new Mtrie[] { old }; } else if (c == min) { // We can compact the table "from the left" @@ -332,7 +328,7 @@ else if (c == min + count - 1) { } // Signal all the matching pipes. - public void match(ByteBuffer data, int size, IMtrieHandler func, Object arg) + public void match(ByteBuffer data, int size, IMtrieHandler func, XPub pub) { Mtrie current = this; int idx = 0; @@ -341,7 +337,7 @@ public void match(ByteBuffer data, int size, IMtrieHandler func, Object arg) // Signal the pipes attached to this node. if (current.pipes != null) { for (Pipe it : current.pipes) { - func.invoke(it, null, 0, arg); + func.invoke(it, null, 0, pub); } } @@ -368,8 +364,7 @@ public void match(ByteBuffer data, int size, IMtrieHandler func, Object arg) } // If there are multiple subnodes. - if (c < current.min || c >= - current.min + current.count) { + if (c < current.min || c >= current.min + current.count) { break; } if (current.next[c - current.min] == null) { diff --git a/src/main/java/zmq/socket/pubsub/Pub.java b/src/main/java/zmq/socket/pubsub/Pub.java new file mode 100644 index 000000000..f0a6c3814 --- /dev/null +++ b/src/main/java/zmq/socket/pubsub/Pub.java @@ -0,0 +1,42 @@ +package zmq.socket.pubsub; + +import zmq.Ctx; +import zmq.Msg; +import zmq.ZError; +import zmq.ZMQ; +import zmq.pipe.Pipe; + +public class Pub extends XPub +{ + public Pub(Ctx parent, int tid, int sid) + { + super(parent, tid, sid); + options.type = ZMQ.ZMQ_PUB; + } + + @Override + protected void xattachPipe(Pipe pipe, boolean subscribeToAll) + { + assert (pipe != null); + + // Don't delay pipe termination as there is no one + // to receive the delimiter. + pipe.setNoDelay(); + + super.xattachPipe(pipe, subscribeToAll); + } + + @Override + protected Msg xrecv() + { + errno.set(ZError.ENOTSUP); + // Messages cannot be received from PUB socket. + throw new UnsupportedOperationException(); + } + + @Override + protected boolean xhasIn() + { + return false; + } +} diff --git a/src/main/java/zmq/Sub.java b/src/main/java/zmq/socket/pubsub/Sub.java similarity index 69% rename from src/main/java/zmq/Sub.java rename to src/main/java/zmq/socket/pubsub/Sub.java index c0d44ff3f..8b46c3904 100644 --- a/src/main/java/zmq/Sub.java +++ b/src/main/java/zmq/socket/pubsub/Sub.java @@ -1,17 +1,13 @@ -package zmq; +package zmq.socket.pubsub; + +import zmq.Ctx; +import zmq.Msg; +import zmq.Options; +import zmq.ZError; +import zmq.ZMQ; public class Sub extends XSub { - public static class SubSession extends XSub.XSubSession - { - public SubSession(IOThread ioThread, boolean connect, - SocketBase socket, Options options, Address addr) - { - super(ioThread, connect, socket, options, addr); - } - - } - public Sub(Ctx parent, int tid, int sid) { super(parent, tid, sid); @@ -27,20 +23,15 @@ public Sub(Ctx parent, int tid, int sid) public boolean xsetsockopt(int option, Object optval) { if (option != ZMQ.ZMQ_SUBSCRIBE && option != ZMQ.ZMQ_UNSUBSCRIBE) { + errno.set(ZError.EINVAL); return false; } - byte[] val; - - if (optval instanceof String) { - val = ((String) optval).getBytes(ZMQ.CHARSET); - } - else if (optval instanceof byte[]) { - val = (byte[]) optval; - } - else { - throw new IllegalArgumentException(); + if (optval == null) { + errno.set(ZError.EINVAL); + return false; } + byte[] val = Options.parseBytes(option, optval); // Create the subscription message. Msg msg = new Msg(val.length + 1); @@ -52,9 +43,11 @@ else if (optval instanceof byte[]) { msg.put((byte) 0); } msg.put(val); + // Pass it further on in the stack. boolean rc = super.xsend(msg); if (!rc) { + errno.set(ZError.EINVAL); throw new IllegalStateException("Failed to send subscribe/unsubscribe message"); } @@ -64,6 +57,7 @@ else if (optval instanceof byte[]) { @Override protected boolean xsend(Msg msg) { + errno.set(ZError.ENOTSUP); // Overload the XSUB's send. throw new UnsupportedOperationException(); } diff --git a/src/main/java/zmq/Trie.java b/src/main/java/zmq/socket/pubsub/Trie.java similarity index 71% rename from src/main/java/zmq/Trie.java rename to src/main/java/zmq/socket/pubsub/Trie.java index 80046a7f5..0726de177 100644 --- a/src/main/java/zmq/Trie.java +++ b/src/main/java/zmq/socket/pubsub/Trie.java @@ -1,19 +1,23 @@ -package zmq; +package zmq.socket.pubsub; import java.nio.ByteBuffer; -public class Trie +import zmq.pipe.Pipe; +import zmq.util.Utils; + +class Trie { + public static interface ITrieHandler + { + void added(byte[] data, int size, Pipe arg); + } + private int refcnt; private byte min; - private int count; - private int liveNodes; + private int count; + private int liveNodes; - public interface ITrieHandler - { - void added(byte[] data, int size, Object arg); - } Trie[] next; public Trie() @@ -28,15 +32,10 @@ public Trie() // Add key to the trie. Returns true if this is a new item in the trie // rather than a duplicate. - public boolean add(byte[] prefix) - { - return add(prefix, 0); - } - - public boolean add(byte[] prefix, int start) + public boolean add(byte[] prefix, int start, int size) { // We are at the node corresponding to the prefix. We are done. - if (prefix == null || prefix.length == start) { + if (size == 0) { ++refcnt; return refcnt == 1; } @@ -44,7 +43,7 @@ public boolean add(byte[] prefix, int start) byte c = prefix[start]; if (c < min || c >= min + count) { // The character is out of range of currently handled - // charcters. We have to extend the table. + // characters. We have to extend the table. if (count == 0) { min = c; count = 1; @@ -76,18 +75,20 @@ else if (min < c) { if (next == null) { next = new Trie[1]; next[0] = new Trie(); + ++liveNodes; - //alloc_assert (next.node); + assert (liveNodes == 1); } - return next[0].add(prefix, start + 1); + return next[0].add(prefix, start + 1, size - 1); } else { if (next[c - min] == null) { next[c - min] = new Trie(); + ++liveNodes; - //alloc_assert (next.table [c - min]); + assert (liveNodes > 1); } - return next[c - min].add(prefix, start + 1); + return next[c - min].add(prefix, start + 1, size - 1); } } @@ -98,9 +99,11 @@ private Trie[] realloc(Trie[] table, int size, boolean ended) // Remove key from the trie. Returns true if the item is actually // removed from the trie. - public boolean rm(byte[] prefix, int start) + public boolean rm(byte[] prefix, int start, int size) { - if (prefix == null || prefix.length == start) { + // TODO: Shouldn't an error be reported if the key does not exist? + + if (size == 0) { if (refcnt == 0) { return false; } @@ -113,19 +116,21 @@ public boolean rm(byte[] prefix, int start) return false; } - Trie nextNode = - count == 1 ? next[0] : next[c - min]; + Trie nextNode = count == 1 ? next[0] : next[c - min]; if (nextNode == null) { return false; } - boolean ret = nextNode.rm(prefix , start + 1); + boolean ret = nextNode.rm(prefix, start + 1, size - 1); + + // Prune redundant nodes if (nextNode.isRedundant()) { //delete next_node; assert (count > 0); if (count == 1) { + // The just pruned node was the only live node next = null; count = 0; --liveNodes; @@ -138,26 +143,34 @@ public boolean rm(byte[] prefix, int start) // Compact the table if possible if (liveNodes == 1) { - // If there's only one live node in the table we can - // switch to using the more compact single-node - // representation + // We can switch to using the more compact single-node + // representation since the table only contains one live node + Trie node = null; - for (int i = 0; i < count; ++i) { - if (next[i] != null) { - node = next[i]; - min = (byte) (i + min); - break; - } + // Since we always compact the table the pruned node must + // either be the left-most or right-most ptr in the node + // table + if (c == min) { + // The pruned node is the left-most node ptr in the + // node table => keep the right-most node + node = next[count - 1]; + min += count - 1; + } + else if (c == min + count - 1) { + // The pruned node is the right-most node ptr in the + // node table => keep the left-most node + node = next[0]; } - assert (node != null); + //free (next.table); - next = null; - next = new Trie[]{node}; + next = new Trie[] { node }; count = 1; } else if (c == min) { - // We can compact the table "from the left" + // We can compact the table "from the left". + // Find the left-most non-null node ptr, which we'll use as + // our new min byte newMin = min; for (int i = 1; i < count; ++i) { if (next[i] != null) { @@ -176,7 +189,9 @@ else if (c == min) { min = newMin; } else if (c == min + count - 1) { - // We can compact the table "from the right" + // We can compact the table "from the right". + // Find the right-most non-null node ptr, which we'll use to + // determine the new table size int newCount = count; for (int i = 1; i < count; ++i) { if (next[count - 1 - i] != null) { @@ -198,6 +213,7 @@ else if (c == min + count - 1) { // Check whether particular key is in the trie. public boolean check(ByteBuffer data) { + int size = data.limit(); // This function is on critical path. It deliberately doesn't use // recursion to get a bit better performance. Trie current = this; @@ -209,7 +225,7 @@ public boolean check(ByteBuffer data) } // We've checked all the data and haven't found matching subscription. - if (data.remaining() == start) { + if (size == 0) { return false; } @@ -231,26 +247,26 @@ public boolean check(ByteBuffer data) } } start++; + size--; } } // Apply the function supplied to each subscription in the trie. - public void apply(ITrieHandler func, Object arg) + public void apply(ITrieHandler func, Pipe arg) { applyHelper(null, 0, 0, func, arg); } - private void applyHelper(byte[] buff, int buffsize, int maxBuffsize, ITrieHandler func, - Object arg) + private void applyHelper(byte[] buff, int buffsize, int maxBuffsize, ITrieHandler func, Pipe pipe) { // If this node is a subscription, apply the function. if (refcnt > 0) { - func.added(buff, buffsize, arg); + func.added(buff, buffsize, pipe); } // Adjust the buffer. - if (buffsize >= maxBuffsize) { - maxBuffsize = buffsize + 256; + if (buffsize >= maxBuffsize) { + maxBuffsize = buffsize + 256; buff = Utils.realloc(buff, maxBuffsize); assert (buff != null); } @@ -260,20 +276,19 @@ private void applyHelper(byte[] buff, int buffsize, int maxBuffsize, ITrieHandle return; } - // If there's one subnode (optimisation). + // If there's one subnode (optimization). if (count == 1) { - buff [buffsize] = min; + buff[buffsize] = min; buffsize++; - next[0].applyHelper(buff, buffsize, maxBuffsize, func, arg); + next[0].applyHelper(buff, buffsize, maxBuffsize, func, pipe); return; } // If there are multiple subnodes. for (int c = 0; c != count; c++) { - buff [buffsize] = (byte) (min + c); + buff[buffsize] = (byte) (min + c); if (next[c] != null) { - next[c].applyHelper(buff, buffsize + 1, maxBuffsize, - func, arg); + next[c].applyHelper(buff, buffsize + 1, maxBuffsize, func, pipe); } } } diff --git a/src/main/java/zmq/XPub.java b/src/main/java/zmq/socket/pubsub/XPub.java similarity index 62% rename from src/main/java/zmq/XPub.java rename to src/main/java/zmq/socket/pubsub/XPub.java index 7025d17b5..6a93108c0 100644 --- a/src/main/java/zmq/XPub.java +++ b/src/main/java/zmq/socket/pubsub/XPub.java @@ -1,18 +1,36 @@ -package zmq; +package zmq.socket.pubsub; import java.util.ArrayDeque; import java.util.Deque; -class XPub extends SocketBase +import zmq.Ctx; +import zmq.Msg; +import zmq.Options; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.pipe.Pipe; +import zmq.socket.pubsub.Mtrie.IMtrieHandler; +import zmq.util.Blob; + +public class XPub extends SocketBase { - public static class XPubSession extends SessionBase + private static final class SendUnsubscription implements IMtrieHandler { - public XPubSession(IOThread ioThread, boolean connect, - SocketBase socket, Options options, Address addr) + @Override + public void invoke(Pipe pipe, byte[] data, int size, XPub self) { - super(ioThread, connect, socket, options, addr); + self.sendUnsubscription(data, size); } + } + private static final class MarkAsMatching implements IMtrieHandler + { + @Override + public void invoke(Pipe pipe, byte[] data, int size, XPub self) + { + self.markAsMatching(pipe); + } } // List of all subscriptions mapped to corresponding pipes. @@ -23,11 +41,7 @@ public XPubSession(IOThread ioThread, boolean connect, // If true, send all subscription messages upstream, not just // unique ones - private boolean verboseSubs; - - // If true, send all unsubscription messages upstream, not just - // unique ones - private boolean verboseUnsubs; + private boolean verbose; // True if we are in the middle of sending a multi-part message. private boolean more; @@ -37,57 +51,25 @@ public XPubSession(IOThread ioThread, boolean connect, // List of pending (un)subscriptions, ie. those that were already // applied to the trie, but not yet received by the user. - private final Deque pendingData; + private final Deque pendingData; private final Deque pendingFlags; - private static Mtrie.IMtrieHandler markAsMatching; - private static Mtrie.IMtrieHandler sendUnsubscription; - - static { - markAsMatching = new Mtrie.IMtrieHandler() - { - @Override - public void invoke(Pipe pipe, byte[] data, int size, Object arg) - { - XPub self = (XPub) arg; - self.dist.match(pipe); - } - }; - - sendUnsubscription = new Mtrie.IMtrieHandler() - { - @Override - public void invoke(Pipe pipe, byte[] data, int size, Object arg) - { - XPub self = (XPub) arg; - - if (self.options.type != ZMQ.ZMQ_PUB) { - // Place the unsubscription to the queue of pending (un)sunscriptions - // to be retrived by the user later on. - byte[] unsub = new byte[size + 1]; - unsub[0] = 0; - System.arraycopy(data, 0, unsub, 1, size); - self.pendingData.add(Blob.createBlob(unsub, false)); - self.pendingFlags.add(0); - } - } - }; - } + private static final IMtrieHandler markAsMatching = new MarkAsMatching(); + private static final IMtrieHandler sendUnsubscription = new SendUnsubscription(); public XPub(Ctx parent, int tid, int sid) { super(parent, tid, sid); options.type = ZMQ.ZMQ_XPUB; - verboseSubs = false; - verboseUnsubs = false; + verbose = false; more = false; lossy = true; subscriptions = new Mtrie(); dist = new Dist(); - pendingData = new ArrayDeque(); - pendingFlags = new ArrayDeque(); + pendingData = new ArrayDeque<>(); + pendingFlags = new ArrayDeque<>(); } @Override @@ -96,7 +78,7 @@ protected void xattachPipe(Pipe pipe, boolean subscribeToAll) assert (pipe != null); dist.attach(pipe); - // If icanhasall_ is specified, the caller would like to subscribe + // If subscribe_to_all_ is specified, the caller would like to subscribe // to all data on this pipe, implicitly. if (subscribeToAll) { subscriptions.add(null, pipe); @@ -119,23 +101,22 @@ protected void xreadActivated(Pipe pipe) if (size > 0 && (data[0] == 0 || data[0] == 1)) { boolean unique; if (data[0] == 0) { - unique = subscriptions.rm(data, 1, pipe); + unique = subscriptions.rm(data, size, pipe); } else { - unique = subscriptions.add(data, 1, pipe); + unique = subscriptions.add(data, size, pipe); } // If the subscription is not a duplicate, store it so that it can be - // passed to used on next recv call. (Unsubscribe is not verboseSubs.) - if (options.type == ZMQ.ZMQ_XPUB && (unique || (data[0] == 1 && verboseSubs) || - (data[0] == 0 && verboseUnsubs))) { - pendingData.add(Blob.createBlob(data, true)); + // passed to used on next recv call. (Unsubscribe is not verbose.) + if (options.type == ZMQ.ZMQ_XPUB && (unique || (data[0] > 0 && verbose))) { + pendingData.add(Blob.createBlob(sub)); pendingFlags.add(0); } } else { // Process user message coming upstream from xsub socket - pendingData.add(Blob.createBlob(data, true)); + pendingData.add(Blob.createBlob(sub)); pendingFlags.add(sub.flags()); } } @@ -151,15 +132,13 @@ protected void xwriteActivated(Pipe pipe) public boolean xsetsockopt(int option, Object optval) { if (option == ZMQ.ZMQ_XPUB_VERBOSE) { - verboseSubs = (Integer) optval == 1; - } - else if (option == ZMQ.ZMQ_XPUB_VERBOSE_UNSUBSCRIBE) { - verboseUnsubs = (Integer) optval == 1; + verbose = Options.parseBoolean(option, optval); } else if (option == ZMQ.ZMQ_XPUB_NODROP) { - lossy = (Integer) optval == 0; + lossy = !Options.parseBoolean(option, optval); } else { + errno.set(ZError.EINVAL); return false; } @@ -173,11 +152,16 @@ protected void xpipeTerminated(Pipe pipe) // is interested in anymore, send corresponding unsubscriptions // upstream. - subscriptions.rm(pipe, sendUnsubscription, this, !verboseUnsubs); + subscriptions.rm(pipe, sendUnsubscription, this); dist.terminated(pipe); } + private void markAsMatching(Pipe pipe) + { + dist.match(pipe); + } + @Override protected boolean xsend(Msg msg) { @@ -185,8 +169,7 @@ protected boolean xsend(Msg msg) // For the first part of multi-part message, find the matching pipes. if (!more) { - subscriptions.match(msg.buf(), msg.size(), - markAsMatching, this); + subscriptions.match(msg.buf(), msg.size(), markAsMatching, this); } if (lossy || dist.checkHwm()) { @@ -199,7 +182,7 @@ protected boolean xsend(Msg msg) dist.unmatch(); } more = msgMore; - return true; + return true; // Yay, sent successfully } } else { @@ -236,4 +219,17 @@ protected boolean xhasIn() { return !pendingData.isEmpty(); } + + private void sendUnsubscription(byte[] data, int size) + { + if (options.type != ZMQ.ZMQ_PUB) { + // Place the unsubscription to the queue of pending (un)sunscriptions + // to be retrieved by the user later on. + byte[] unsub = new byte[size + 1]; + unsub[0] = 0; + System.arraycopy(data, 0, unsub, 1, size); + pendingData.add(Blob.createBlob(unsub)); + pendingFlags.add(0); + } + } } diff --git a/src/main/java/zmq/XSub.java b/src/main/java/zmq/socket/pubsub/XSub.java similarity index 63% rename from src/main/java/zmq/XSub.java rename to src/main/java/zmq/socket/pubsub/XSub.java index 6b3f75838..52c215f81 100644 --- a/src/main/java/zmq/XSub.java +++ b/src/main/java/zmq/socket/pubsub/XSub.java @@ -1,17 +1,27 @@ -package zmq; +package zmq.socket.pubsub; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.pipe.Pipe; +import zmq.socket.FQ; +import zmq.socket.pubsub.Trie.ITrieHandler; +import zmq.util.Blob; public class XSub extends SocketBase { - public static class XSubSession extends SessionBase + private final class SendSubscription implements ITrieHandler { - public XSubSession(IOThread ioThread, boolean connect, - SocketBase socket, Options options, Address addr) + @Override + public void added(byte[] data, int size, Pipe pipe) { - super(ioThread, connect, socket, options, addr); + sendSubscription(data, size, pipe); } } - // Fair queueing object for inbound pipes. + // Fair queuing object for inbound pipes. private final FQ fq; // Object for distributing the subscriptions upstream. @@ -22,55 +32,36 @@ public XSubSession(IOThread ioThread, boolean connect, // If true, 'message' contains a matching message to return on the // next recv call. - private boolean hashMessage; - private Msg message; + private boolean hasMessage; + private Msg message; // If true, part of a multipart message was already received, but // there are following parts still waiting. private boolean more; - private static Trie.ITrieHandler sendSubscription; - static { - sendSubscription = new Trie.ITrieHandler() - { - @Override - public void added(byte[] data, int size, Object arg) - { - Pipe pipe = (Pipe) arg; - - // Create the subsctription message. - Msg msg = new Msg(size + 1); - msg.put((byte) 1).put(data, 0, size); - - // Send it to the pipe. - boolean sent = pipe.write(msg); - // If we reached the SNDHWM, and thus cannot send the subscription, drop - // the subscription message instead. This matches the behaviour of - // setSocketOption(ZMQ_SUBSCRIBE, ...), which also drops subscriptions - // when the SNDHWM is reached. - // if (!sent) - // msg.close (); - - } - }; - } + private final ITrieHandler sendSubscription = new SendSubscription(); public XSub(Ctx parent, int tid, int sid) { super(parent, tid, sid); options.type = ZMQ.ZMQ_XSUB; - hashMessage = false; + hasMessage = false; more = false; + // When socket is being closed down we don't want to wait till pending + // subscription commands are sent to the wire. options.linger = 0; + fq = new FQ(); dist = new Dist(); subscriptions = new Trie(); + + message = new Msg(); } @Override - protected void xattachPipe(Pipe pipe, boolean icanhasall) + protected void xattachPipe(Pipe pipe, boolean subscribe2all) { assert (pipe != null); fq.attach(pipe); @@ -111,28 +102,28 @@ protected void xhiccuped(Pipe pipe) @Override protected boolean xsend(Msg msg) { - byte[] data = msg.data(); - // Malformed subscriptions. - if (data.length < 1 || (data[0] != 0 && data[0] != 1)) { - throw new IllegalArgumentException("subscription flag"); - } - - // Process the subscription. - if (data[0] == 1) { - // this used to filter out duplicate subscriptions, - // however this is alread done on the XPUB side and - // doing it here as well breaks ZMQ_XPUB_VERBOSE - // when there are forwarding devices involved - // - subscriptions.add(data , 1); + final int size = msg.size(); + final byte[] data = msg.data(); + + if (size > 0 && data[0] == 1) { + // Process subscribe message + // This used to filter out duplicate subscriptions, + // however this is already done on the XPUB side and + // doing it here as well breaks ZMQ_XPUB_VERBOSE + // when there are forwarding devices involved. + subscriptions.add(data, 1, size - 1); return dist.sendToAll(msg); } - else { - if (subscriptions.rm(data, 1)) { + else if (size > 0 && data[0] == 0) { + // Process unsubscribe message + if (subscriptions.rm(data, 1, size - 1)) { return dist.sendToAll(msg); } } - + else { + // User message sent upstream to XPUB socket + return dist.sendToAll(msg); + } return true; } @@ -150,9 +141,9 @@ protected Msg xrecv() // If there's already a message prepared by a previous call to zmq_poll, // return it straight ahead. - if (hashMessage) { + if (hasMessage) { msg = message; - hashMessage = false; + hasMessage = false; more = msg.hasMore(); return msg; } @@ -161,7 +152,7 @@ protected Msg xrecv() // stream of non-matching messages which breaks the non-blocking recv // semantics. while (true) { - // Get a message using fair queueing algorithm. + // Get a message using fair queuing algorithm. msg = fq.recv(errno); // If there's no message available, return immediately. @@ -196,25 +187,26 @@ protected boolean xhasIn() // If there's already a message prepared by a previous call to zmq_poll, // return straight ahead. - if (hashMessage) { + if (hasMessage) { return true; } // TODO: This can result in infinite loop in the case of continuous // stream of non-matching messages. while (true) { - // Get a message using fair queueing algorithm. + // Get a message using fair queuing algorithm. message = fq.recv(errno); // If there's no message available, return immediately. // The same when error occurs. if (message == null) { + assert (errno.is(ZError.EAGAIN)); return false; } // Check whether the message matches at least one subscription. if (!options.filter || match(message)) { - hashMessage = true; + hasMessage = true; return true; } @@ -227,8 +219,32 @@ protected boolean xhasIn() } } + @Override + protected Blob getCredential() + { + return fq.getCredential(); + } + private boolean match(Msg msg) { return subscriptions.check(msg.buf()); } + + private boolean sendSubscription(byte[] data, int size, Pipe pipe) + { + // Create the subscription message. + Msg msg = new Msg(size + 1); + msg.put((byte) 1).put(data, 0, size); + + // Send it to the pipe. + boolean sent = pipe.write(msg); + // If we reached the SNDHWM, and thus cannot send the subscription, drop + // the subscription message instead. This matches the behaviour of + // setSocketOption(ZMQ_SUBSCRIBE, ...), which also drops subscriptions + // when the SNDHWM is reached. + // if (!sent) + // msg.close (); + + return sent; + } } diff --git a/src/main/java/zmq/socket/reqrep/Dealer.java b/src/main/java/zmq/socket/reqrep/Dealer.java new file mode 100644 index 000000000..f2ab554bb --- /dev/null +++ b/src/main/java/zmq/socket/reqrep/Dealer.java @@ -0,0 +1,120 @@ +package zmq.socket.reqrep; + +import zmq.Ctx; +import zmq.Msg; +import zmq.Options; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.pipe.Pipe; +import zmq.socket.FQ; +import zmq.socket.LB; +import zmq.util.Blob; +import zmq.util.ValueReference; + +public class Dealer extends SocketBase +{ + // Messages are fair-queued from inbound pipes. And load-balanced to + // the outbound pipes. + private final FQ fq; + private final LB lb; + + // if true, send an empty message to every connected router peer + private boolean probeRouter; + + // Holds the prefetched message. + public Dealer(Ctx parent, int tid, int sid) + { + super(parent, tid, sid); + + options.type = ZMQ.ZMQ_DEALER; + + fq = new FQ(); + lb = new LB(); + } + + @Override + protected void xattachPipe(Pipe pipe, boolean subscribe2all) + { + assert (pipe != null); + + if (probeRouter) { + Msg probe = new Msg(); + pipe.write(probe); + // assert (rc == 0) is not applicable here, since it is not a bug. + pipe.flush(); + } + fq.attach(pipe); + lb.attach(pipe); + } + + @Override + protected boolean xsetsockopt(int option, Object optval) + { + if (option == ZMQ.ZMQ_PROBE_ROUTER) { + probeRouter = Options.parseBoolean(option, optval); + return true; + } + errno.set(ZError.EINVAL); + return false; + } + + @Override + protected boolean xsend(Msg msg) + { + return sendpipe(msg, null); + } + + @Override + protected Msg xrecv() + { + return recvpipe(null); + } + + @Override + protected boolean xhasIn() + { + return fq.hasIn(); + } + + @Override + protected boolean xhasOut() + { + return lb.hasOut(); + } + + @Override + protected Blob getCredential() + { + return fq.getCredential(); + } + + @Override + protected void xreadActivated(Pipe pipe) + { + fq.activated(pipe); + } + + @Override + protected void xwriteActivated(Pipe pipe) + { + lb.activated(pipe); + } + + @Override + protected void xpipeTerminated(Pipe pipe) + { + fq.terminated(pipe); + lb.terminated(pipe); + } + + protected final boolean sendpipe(Msg msg, ValueReference pipe) + { + return lb.sendpipe(msg, errno, pipe); + } + + protected final Msg recvpipe(ValueReference pipe) + { + return fq.recvPipe(errno, pipe); + } +} diff --git a/src/main/java/zmq/Rep.java b/src/main/java/zmq/socket/reqrep/Rep.java similarity index 85% rename from src/main/java/zmq/Rep.java rename to src/main/java/zmq/socket/reqrep/Rep.java index 1a004e8a4..bf865f3bb 100644 --- a/src/main/java/zmq/Rep.java +++ b/src/main/java/zmq/socket/reqrep/Rep.java @@ -1,16 +1,12 @@ -package zmq; +package zmq.socket.reqrep; + +import zmq.Ctx; +import zmq.Msg; +import zmq.ZError; +import zmq.ZMQ; public class Rep extends Router { - public static class RepSession extends Router.RouterSession - { - public RepSession(IOThread ioThread, boolean connect, - SocketBase socket, final Options options, - final Address addr) - { - super(ioThread, connect, socket, options, addr); - } - } // If true, we are in process of sending the reply. If false we are // in process of receiving a request. private boolean sendingReply; @@ -33,7 +29,8 @@ protected boolean xsend(Msg msg) { // If we are in the middle of receiving a request, we cannot send reply. if (!sendingReply) { - throw new IllegalStateException("Cannot send another reply"); + errno.set(ZError.EFSM); + return false; } boolean more = msg.hasMore(); @@ -57,7 +54,7 @@ protected Msg xrecv() { // If we are in middle of sending a reply, we cannot receive next request. if (sendingReply) { - throw new IllegalStateException("Cannot receive another request"); + errno.set(ZError.EFSM); } Msg msg = null; diff --git a/src/main/java/zmq/socket/reqrep/Req.java b/src/main/java/zmq/socket/reqrep/Req.java new file mode 100644 index 000000000..d5db203c4 --- /dev/null +++ b/src/main/java/zmq/socket/reqrep/Req.java @@ -0,0 +1,292 @@ +package zmq.socket.reqrep; + +import zmq.Ctx; +import zmq.Msg; +import zmq.Options; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.pipe.Pipe; +import zmq.util.Utils; +import zmq.util.ValueReference; +import zmq.util.Wire; + +public class Req extends Dealer +{ + // If true, request was already sent and reply wasn't received yet or + // was received partially. + private boolean receivingReply; + + // If true, we are starting to send/recv a message. The first part + // of the message must be empty message part (backtrace stack bottom). + private boolean messageBegins; + + // The pipe the request was sent to and where the reply is expected. + private final ValueReference replyPipe = new ValueReference<>(); + + // Whether request id frames shall be sent and expected. + private boolean requestIdFramesEnabled; + + // The current request id. It is incremented every time before a new + // request is sent. + private int requestId; + + // If false, send() will reset its internal state and terminate the + // reply_pipe's connection instead of failing if a previous request is + // still pending. + private boolean strict; + + public Req(Ctx parent, int tid, int sid) + { + super(parent, tid, sid); + receivingReply = false; + messageBegins = true; + options.type = ZMQ.ZMQ_REQ; + requestIdFramesEnabled = false; + requestId = Utils.randomInt(); + strict = true; + } + + @Override + public boolean xsend(final Msg msg) + { + // If we've sent a request and we still haven't got the reply, + // we can't send another request. + if (receivingReply) { + if (strict) { + errno.set(ZError.EFSM); + return false; + } + + if (replyPipe.get() != null) { + replyPipe.get().terminate(false); + } + receivingReply = false; + messageBegins = true; + } + + // First part of the request is the request identity. + if (messageBegins) { + replyPipe.set(null); + + if (requestIdFramesEnabled) { + requestId++; + + final Msg id = new Msg(4); + Wire.putUInt32(id.buf(), requestId); + id.setFlags(Msg.MORE); + boolean rc = super.sendpipe(id, replyPipe); + if (!rc) { + return false; + } + + } + Msg bottom = new Msg(); + bottom.setFlags(Msg.MORE); + boolean rc = super.sendpipe(bottom, replyPipe); + if (!rc) { + return false; + } + assert (replyPipe.get() != null); + + messageBegins = false; + + // Eat all currently available messages before the request is fully + // sent. This is done to avoid: + // REQ sends request to A, A replies, B replies too. + // A's reply was first and matches, that is used. + // An hour later REQ sends a request to B. B's old reply is used. + while (true) { + Msg drop = super.xrecv(); + if (drop == null) { + break; + } + } + } + + boolean more = msg.hasMore(); + + boolean rc = super.xsend(msg); + if (!rc) { + return rc; + } + + // If the request was fully sent, flip the FSM into reply-receiving state. + if (!more) { + receivingReply = true; + messageBegins = true; + } + + return true; + } + + @Override + protected Msg xrecv() + { + // If request wasn't send, we can't wait for reply. + // Thus, we don't look at the state of the ZMQ_REQ_RELAXED option. + if (!receivingReply) { + errno.set(ZError.EFSM); + return null; + } + + // Skip messages until one with the right first frames is found. + while (messageBegins) { + // If enabled, the first frame must have the correct request_id. + if (requestIdFramesEnabled) { + Msg msg = recvReplyPipe(); + if (msg == null) { + return null; + } + if (!msg.hasMore() || msg.size() != 4 || Wire.getUInt32(msg, 0) != requestId) { + // Skip the remaining frames and try the next message + while (msg.hasMore()) { + msg = recvReplyPipe(); + assert (msg != null); + } + continue; + } + } + + // The next frame must be 0. + // TODO: Failing this check should also close the connection with the peer! + Msg msg = recvReplyPipe(); + if (msg == null) { + return null; + } + if (!msg.hasMore() || msg.size() != 0) { + // Skip the remaining frames and try the next message + while (msg.hasMore()) { + msg = recvReplyPipe(); + assert (msg != null); + } + continue; + } + + messageBegins = false; + } + + Msg msg = recvReplyPipe(); + if (msg == null) { + return null; + } + + // If the reply is fully received, flip the FSM into request-sending state. + if (!msg.hasMore()) { + receivingReply = false; + messageBegins = true; + } + + return msg; + } + + @Override + public boolean xhasIn() + { + // TODO: Duplicates should be removed here. + + return receivingReply && super.xhasIn(); + } + + @Override + public boolean xhasOut() + { + return !receivingReply && super.xhasOut(); + } + + @Override + protected boolean xsetsockopt(int option, Object optval) + { + switch (option) { + case ZMQ.ZMQ_REQ_CORRELATE: + requestIdFramesEnabled = Options.parseBoolean(option, optval); + return true; + case ZMQ.ZMQ_REQ_RELAXED: + strict = !Options.parseBoolean(option, optval); + return true; + + default: + break; + } + return super.xsetsockopt(option, optval); + } + + @Override + protected void xpipeTerminated(Pipe pipe) + { + if (replyPipe.get() == pipe) { + replyPipe.set(null); + } + super.xpipeTerminated(pipe); + } + + private Msg recvReplyPipe() + { + while (true) { + ValueReference pipe = new ValueReference(); + Msg msg = super.recvpipe(pipe); + if (msg == null) { + return null; + } + + if (replyPipe.get() == null || replyPipe.get() == pipe.get()) { + return msg; + } + } + } + + public static class ReqSession extends SessionBase + { + enum State + { + BOTTOM, + BODY + }; + + private State state; + + public ReqSession(IOThread ioThread, boolean connect, SocketBase socket, final Options options, + final Address addr) + { + super(ioThread, connect, socket, options, addr); + + state = State.BOTTOM; + } + + @Override + public boolean pushMsg(Msg msg) + { + switch (state) { + case BOTTOM: + if (msg.hasMore() && msg.size() == 0) { + state = State.BODY; + return super.pushMsg(msg); + } + break; + case BODY: + if (msg.hasMore()) { + return super.pushMsg(msg); + } + if (msg.flags() == 0) { + state = State.BOTTOM; + return super.pushMsg(msg); + } + break; + default: + break; + } + errno(ZError.EFAULT); + return false; + } + + @Override + public void reset() + { + super.reset(); + state = State.BOTTOM; + } + } +} diff --git a/src/main/java/zmq/Router.java b/src/main/java/zmq/socket/reqrep/Router.java similarity index 61% rename from src/main/java/zmq/Router.java rename to src/main/java/zmq/socket/reqrep/Router.java index 74db238c4..4260a8f7f 100644 --- a/src/main/java/zmq/Router.java +++ b/src/main/java/zmq/socket/reqrep/Router.java @@ -1,4 +1,4 @@ -package zmq; +package zmq.socket.reqrep; import java.nio.ByteBuffer; import java.util.HashMap; @@ -6,23 +6,26 @@ import java.util.Map; import java.util.Set; +import zmq.Ctx; +import zmq.Msg; +import zmq.Options; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.pipe.Pipe; +import zmq.socket.FQ; +import zmq.util.Blob; +import zmq.util.Utils; +import zmq.util.ValueReference; +import zmq.util.Wire; + //TODO: This class uses O(n) scheduling. Rewrite it to use O(1) algorithm. public class Router extends SocketBase { - public static class RouterSession extends SessionBase - { - public RouterSession(IOThread ioThread, boolean connect, - SocketBase socket, final Options options, - final Address addr) - { - super(ioThread, connect, socket, options, addr); - } - } - - // Fair queueing object for inbound pipes. + // Fair queuing object for inbound pipes. private final FQ fq; - // True iff there is a message held in the pre-fetch buffer. + // True if there is a message held in the pre-fetch buffer. private boolean prefetched; // If true, the receiver got the message part with @@ -40,7 +43,7 @@ public RouterSession(IOThread ioThread, boolean connect, class Outpipe { - private Pipe pipe; + private Pipe pipe; private boolean active; public Outpipe(Pipe pipe, boolean active) @@ -62,14 +65,22 @@ public Outpipe(Pipe pipe, boolean active) // If true, more outgoing message parts are expected. private boolean moreOut; - // Peer ID are generated. It's a simple increment and wrap-over + // Routing IDs are generated. It's a simple increment and wrap-over // algorithm. This value is the next ID to use (if not used already). - private int nextPeerId; + private int nextRid; - // If true, report EHOSTUNREACH to the caller instead of silently dropping + // If true, report EAGAIN to the caller instead of silently dropping // the message targeting an unknown peer. private boolean mandatory; + private boolean rawSocket; + + // if true, send an empty message to every connected router peer + private boolean probeRouter; + + // If true, the router will reassign an identity upon encountering a + // name collision. The new pipe will take the identity, the old pipe + // will be terminated. private boolean handover; public Router(Ctx parent, int tid, int sid) @@ -80,33 +91,44 @@ public Router(Ctx parent, int tid, int sid) moreIn = false; currentOut = null; moreOut = false; - nextPeerId = Utils.generateRandom(); + nextRid = Utils.randomInt(); mandatory = false; + // raw_sock functionality in ROUTER is deprecated + rawSocket = false; handover = false; options.type = ZMQ.ZMQ_ROUTER; + options.recvIdentity = true; + options.rawSocket = false; fq = new FQ(); prefetchedId = new Msg(); prefetchedMsg = new Msg(); - anonymousPipes = new HashSet(); - outpipes = new HashMap(); + anonymousPipes = new HashSet<>(); + outpipes = new HashMap<>(); + } - // TODO: Uncomment the following line when ROUTER will become true ROUTER - // rather than generic router socket. - // If peer disconnect there's noone to send reply to anyway. We can drop - // all the outstanding requests from that peer. - // options.delayOnDisconnect = false; + @Override + protected void destroy() + { + assert (anonymousPipes.isEmpty()); + assert (outpipes.isEmpty()); - options.recvIdentity = true; + super.destroy(); } @Override - public void xattachPipe(Pipe pipe, boolean icanhasall) + public void xattachPipe(Pipe pipe, boolean subscribe2all) { assert (pipe != null); + if (probeRouter) { + Msg probe = new Msg(); + pipe.write(probe); + // assert (rc) is not applicable here, since it is not a bug. + pipe.flush(); + } boolean identityOk = identifyPeer(pipe); if (identityOk) { fq.attach(pipe); @@ -119,14 +141,31 @@ public void xattachPipe(Pipe pipe, boolean icanhasall) @Override public boolean xsetsockopt(int option, Object optval) { + if (option == ZMQ.ZMQ_CONNECT_RID) { + connectRid = Options.parseString(option, optval); + return true; + } + if (option == ZMQ.ZMQ_ROUTER_RAW) { + rawSocket = Options.parseBoolean(option, optval); + if (rawSocket) { + options.recvIdentity = false; + options.rawSocket = true; + } + return true; + } if (option == ZMQ.ZMQ_ROUTER_MANDATORY) { - mandatory = (Integer) optval == 1; + mandatory = Options.parseBoolean(option, optval); + return true; + } + if (option == ZMQ.ZMQ_PROBE_ROUTER) { + probeRouter = Options.parseBoolean(option, optval); return true; } if (option == ZMQ.ZMQ_ROUTER_HANDOVER) { - handover = (Integer) optval == 1; + handover = Options.parseBoolean(option, optval); return true; } + errno.set(ZError.EINVAL); return false; } @@ -162,14 +201,13 @@ public void xreadActivated(Pipe pipe) @Override public void xwriteActivated(Pipe pipe) { - for (Map.Entry it : outpipes.entrySet()) { - if (it.getValue().pipe == pipe) { - assert (!it.getValue().active); - it.getValue().active = true; - return; + for (Outpipe out : outpipes.values()) { + if (out.pipe == pipe) { + assert (!out.active); + out.active = true; + break; } } - assert (false); } @Override @@ -189,7 +227,7 @@ protected boolean xsend(Msg msg) // Find the pipe associated with the identity stored in the prefix. // If there's no such pipe just silently ignore the message, unless // mandatory is set. - Blob identity = Blob.createBlob(msg.data(), true); + Blob identity = Blob.createBlob(msg); Outpipe op = outpipes.get(identity); if (op != null) { @@ -214,13 +252,28 @@ else if (mandatory) { return true; } + // Ignore the MORE flag for raw-sock or assert? + if (options.rawSocket) { + msg.resetFlags(Msg.MORE); + } + // Check whether this is the last part of the message. moreOut = msg.hasMore(); // Push the message into the pipe. If there's no out pipe, just drop it. if (currentOut != null) { + // Close the remote connection if user has asked to do so + // by sending zero length message. + // Pending messages in the pipe will be dropped (on receiving term- ack) + if (rawSocket && msg.size() == 0) { + currentOut.terminate(false); + currentOut = null; + return true; + } + boolean ok = currentOut.write(msg); if (!ok) { + // Message failed to send - we must close it ourselves. currentOut = null; } else if (!moreOut) { @@ -289,13 +342,14 @@ protected Msg xrecv() } // Rollback any message parts that were sent but not yet flushed. - protected void rollback() + protected boolean rollback() { if (currentOut != null) { currentOut.rollback(); currentOut = null; moreOut = false; } + return true; } @Override @@ -350,45 +404,70 @@ protected boolean xhasOut() return true; } + @Override + protected Blob getCredential() + { + return fq.getCredential(); + } + private boolean identifyPeer(Pipe pipe) { Blob identity; - Msg msg = pipe.read(); - if (msg == null) { - return false; - } - - if (msg.size() == 0) { - // Fall back on the auto-generation - ByteBuffer buf = ByteBuffer.allocate(5); - buf.put((byte) 0); - buf.putInt(nextPeerId++); - identity = Blob.createBlob(buf.array(), false); + if (connectRid != null && !connectRid.isEmpty()) { + identity = Blob.createBlob(connectRid.getBytes(ZMQ.CHARSET)); + connectRid = null; + Outpipe outpipe = outpipes.get(identity); + assert (outpipe == null); // Not allowed to duplicate an existing rid } else { - identity = Blob.createBlob(msg.data(), true); - - if (outpipes.containsKey(identity)) { - if (!handover) { + if (options.rawSocket) { + // Always assign identity for raw-socket + ByteBuffer buffer = ByteBuffer.allocate(5); + buffer.put((byte) 0); + Wire.putUInt32(buffer, nextRid++); + identity = Blob.createBlob(buffer.array()); + } + else { + // Pick up handshake cases and also case where next identity is set + Msg msg = pipe.read(); + if (msg == null) { return false; } - // We will allow the new connection to take over this - // identity. Temporarily assign a new identity to the - // existing pipe so we can terminate it asynchronously. - ByteBuffer buf = ByteBuffer.allocate(5); - buf.put((byte) 0); - buf.putInt(nextPeerId++); - Blob newIdentity = Blob.createBlob(buf.array(), false); - - // Remove the existing identity entry to allow the new - // connection to take the identity. - Outpipe existingOutpipe = outpipes.remove(identity); - existingOutpipe.pipe.setIdentity(newIdentity); - - outpipes.put(newIdentity, existingOutpipe); - - existingOutpipe.pipe.terminate(true); + + if (msg.size() == 0) { + // Fall back on the auto-generation + ByteBuffer buf = ByteBuffer.allocate(5); + buf.put((byte) 0); + Wire.putUInt32(buf, nextRid++); + identity = Blob.createBlob(buf.array()); + } + else { + identity = Blob.createBlob(msg); + + if (outpipes.containsKey(identity)) { + if (!handover) { + // Ignore peers with duplicate ID + return false; + } + // We will allow the new connection to take over this + // identity. Temporarily assign a new identity to the + // existing pipe so we can terminate it asynchronously. + ByteBuffer buf = ByteBuffer.allocate(5); + buf.put((byte) 0); + Wire.putUInt32(buf, nextRid++); + Blob newIdentity = Blob.createBlob(buf.array()); + + // Remove the existing identity entry to allow the new + // connection to take the identity. + Outpipe existingOutpipe = outpipes.remove(identity); + existingOutpipe.pipe.setIdentity(newIdentity); + + outpipes.put(newIdentity, existingOutpipe); + + existingOutpipe.pipe.terminate(true); + } + } } } diff --git a/src/main/java/zmq/Blob.java b/src/main/java/zmq/util/Blob.java similarity index 72% rename from src/main/java/zmq/Blob.java rename to src/main/java/zmq/util/Blob.java index 951c740bc..231e0b07a 100644 --- a/src/main/java/zmq/Blob.java +++ b/src/main/java/zmq/util/Blob.java @@ -1,7 +1,9 @@ -package zmq; +package zmq.util; import java.util.Arrays; +import zmq.Msg; + public class Blob { private final byte[] buf; @@ -11,7 +13,7 @@ private Blob(byte[] data) buf = data; } - public static Blob createBlob(byte[] data, boolean copy) + private static Blob createBlob(byte[] data, boolean copy) { if (copy) { byte[] b = new byte[data.length]; @@ -23,6 +25,16 @@ public static Blob createBlob(byte[] data, boolean copy) } } + public static Blob createBlob(Msg msg) + { + return createBlob(msg.data(), true); + } + + public static Blob createBlob(byte[] data) + { + return createBlob(data, false); + } + public int size() { return buf.length; diff --git a/src/main/java/zmq/Clock.java b/src/main/java/zmq/util/Clock.java similarity index 82% rename from src/main/java/zmq/Clock.java rename to src/main/java/zmq/util/Clock.java index a96cdbf2d..bff06b9b9 100644 --- a/src/main/java/zmq/Clock.java +++ b/src/main/java/zmq/util/Clock.java @@ -1,4 +1,6 @@ -package zmq; +package zmq.util; + +import java.util.concurrent.TimeUnit; public class Clock { @@ -15,7 +17,7 @@ private Clock() // High precision timestamp. public static long nowUS() { - return System.nanoTime() * 1000L; + return TimeUnit.MICROSECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS); } // Low precision timestamp. In tight loops generating it can be diff --git a/src/main/java/zmq/util/Errno.java b/src/main/java/zmq/util/Errno.java new file mode 100644 index 000000000..82f61293c --- /dev/null +++ b/src/main/java/zmq/util/Errno.java @@ -0,0 +1,35 @@ +package zmq.util; + +// Emulates the errno mechanism present in C++, in a per-thread basis. +public final class Errno +{ + private static final ThreadLocal local = new ThreadLocal() + { + @Override + protected Integer initialValue() + { + return 0; // by default + } + }; + + public int get() + { + return local.get(); + } + + public void set(int errno) + { + local.set(errno); + } + + public boolean is(int err) + { + return get() == err; + } + + @Override + public String toString() + { + return "Errno[" + get() + "]"; + } +} diff --git a/src/main/java/zmq/util/MultiMap.java b/src/main/java/zmq/util/MultiMap.java new file mode 100644 index 000000000..e4ddddab6 --- /dev/null +++ b/src/main/java/zmq/util/MultiMap.java @@ -0,0 +1,152 @@ +package zmq.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +// custom implementation of a collection mapping multiple values, tailored for use in the lib +public final class MultiMap, V> +{ + // sorts entries according to the natural order of the keys + private final class EntryComparator implements Comparator> + { + @Override + public int compare(Entry first, Entry second) + { + return first.getValue().compareTo(second.getValue()); + } + } + + private final Comparator> comparator = new EntryComparator(); + + // where all the data will be put + private final Map> data; + // inverse mapping to speed-up the process + private final Map inverse; + + public MultiMap() + { + data = new HashMap<>(); + inverse = new HashMap<>(); + } + + public void clear() + { + data.clear(); + inverse.clear(); + } + + public Collection> entries() + { + ArrayList> list = new ArrayList<>(inverse.entrySet()); + Collections.sort(list, comparator); + return list; + } + + public Collection values() + { + return inverse.keySet(); + } + + public V find(V copy) + { + K key = inverse.get(copy); + if (key != null) { + List list = data.get(key); + return list.get(list.indexOf(copy)); + } + return null; + } + + public boolean hasValues(K key) + { + List list = data.get(key); + if (list == null) { + return false; + } + return !list.isEmpty(); + } + + public boolean isEmpty() + { + return inverse.isEmpty(); + } + + private List getList(K key) + { + List list = data.get(key); + if (list == null) { + list = new ArrayList(); + data.put(key, list); + } + return list; + } + + public void insert(K key, V value) + { + getList(key).add(value); + + K old = inverse.put(value, key); + assert (old == null); + } + + private void putAll(K key, Collection values) + { + getList(key).addAll(values); + + for (V value : values) { + K old = inverse.put(value, key); + assert (old == null); + } + } + + public void putAll(MultiMap src) + { + for (Entry> entry : src.data.entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + } + + public Collection remove(K key) + { + List removed = data.remove(key); + if (removed != null) { + for (V val : removed) { + inverse.remove(val); + } + } + return removed; + } + + public boolean remove(V value) + { + K key = inverse.remove(value); + if (key != null) { + return getList(key).remove(value); + } + return false; + } + + public void remove(Entry entry) + { + K key = entry.getValue(); + V value = entry.getKey(); + + List list = data.get(key); + if (list != null) { + list.remove(value); + } + inverse.remove(value); + } + + @Override + public String toString() + { + return data.toString(); + } +} diff --git a/src/main/java/zmq/util/Utils.java b/src/main/java/zmq/util/Utils.java new file mode 100644 index 000000000..f5b2dd911 --- /dev/null +++ b/src/main/java/zmq/util/Utils.java @@ -0,0 +1,135 @@ +package zmq.util; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Array; +import java.net.ServerSocket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SocketChannel; +import java.security.SecureRandom; + +import zmq.io.net.Address; +import zmq.io.net.tcp.TcpUtils; + +public class Utils +{ + private Utils() + { + } + + private static final SecureRandom random = new SecureRandom(); + + public static int randomInt() + { + return random.nextInt(); + } + + public static byte[] randomBytes(int length) + { + byte[] bytes = new byte[length]; + random.nextBytes(bytes); + return bytes; + } + + public static int findOpenPort() throws IOException + { + ServerSocket tmpSocket = new ServerSocket(0); + int portNumber = tmpSocket.getLocalPort(); + tmpSocket.close(); + return portNumber; + } + + public static void unblockSocket(SelectableChannel... channels) throws IOException + { + TcpUtils.unblockSocket(channels); + } + + @SuppressWarnings("unchecked") + public static T[] realloc(Class klass, T[] src, int size, boolean ended) + { + T[] dest; + + if (size > src.length) { + dest = (T[]) Array.newInstance(klass, size); + if (ended) { + System.arraycopy(src, 0, dest, 0, src.length); + } + else { + System.arraycopy(src, 0, dest, size - src.length, src.length); + } + } + else if (size < src.length) { + dest = (T[]) Array.newInstance(klass, size); + if (ended) { + System.arraycopy(src, src.length - size, dest, 0, size); + } + else { + System.arraycopy(src, 0, dest, 0, size); + } + } + else { + dest = src; + } + return dest; + } + + public static byte[] bytes(ByteBuffer buf) + { + byte[] d = new byte[buf.limit()]; + buf.get(d); + return d; + } + + public static byte[] realloc(byte[] src, int size) + { + byte[] dest = new byte[size]; + if (src != null) { + System.arraycopy(src, 0, dest, 0, src.length); + } + + return dest; + } + + public static boolean delete(File path) + { + if (!path.exists()) { + return false; + } + boolean ret = true; + if (path.isDirectory()) { + File[] files = path.listFiles(); + if (files != null) { + for (File f : files) { + ret = ret && delete(f); + } + } + } + return ret && path.delete(); + } + + public static Address getPeerIpAddress(SocketChannel fd) + { + SocketAddress address = fd.socket().getRemoteSocketAddress(); + + return new Address(address); + } + + public static String dump(ByteBuffer buffer, int pos, int limit) + { + int oldpos = buffer.position(); + int oldlimit = buffer.limit(); + buffer.limit(limit).position(pos); + + StringBuilder builder = new StringBuilder("["); + for (int idx = buffer.position(); idx < buffer.limit(); ++idx) { + builder.append(buffer.get(idx)); + builder.append(','); + } + builder.append(']'); + + buffer.limit(oldlimit).position(oldpos); + return builder.toString(); + } +} diff --git a/src/main/java/zmq/ValueReference.java b/src/main/java/zmq/util/ValueReference.java similarity index 68% rename from src/main/java/zmq/ValueReference.java rename to src/main/java/zmq/util/ValueReference.java index 84119f6b0..7db1a7980 100644 --- a/src/main/java/zmq/ValueReference.java +++ b/src/main/java/zmq/util/ValueReference.java @@ -1,4 +1,6 @@ -package zmq; +package zmq.util; + +import java.util.Objects; public class ValueReference { @@ -22,4 +24,10 @@ public final void set(V value) { this.value = value; } + + @Override + public String toString() + { + return Objects.toString(value); + } } diff --git a/src/main/java/zmq/util/Wire.java b/src/main/java/zmq/util/Wire.java new file mode 100644 index 000000000..d2cc9ca54 --- /dev/null +++ b/src/main/java/zmq/util/Wire.java @@ -0,0 +1,100 @@ +package zmq.util; + +import java.nio.ByteBuffer; + +import zmq.Msg; + +// Helper functions to convert different integer +// types to/from network byte order. +public class Wire +{ + private Wire() + { + } + + public static int getUInt16(byte[] bytes) + { + return (bytes[0] & 0xff) << 8 | bytes[1] & 0xff; + } + + public static byte[] putUInt16(int value) + { + byte[] bytes = new byte[2]; + + bytes[0] = (byte) (value >>> 8); + bytes[1] = (byte) (value & 0xff); + + return bytes; + } + + public static int getUInt32(ByteBuffer buf) + { + return getUInt32(buf, 0); + } + + public static int getUInt32(Msg msg, int offset) + { + return msg.getInt(offset); + } + + public static int getUInt32(ByteBuffer buf, int offset) + { + return (buf.get(offset) & 0xff) << 24 | (buf.get(offset + 1) & 0xff) << 16 | (buf.get(offset + 2) & 0xff) << 8 + | (buf.get(offset + 3) & 0xff); + } + + public static int getUInt32(byte[] bytes, int offset) + { + return (bytes[offset] & 0xff) << 24 | (bytes[offset + 1] & 0xff) << 16 | (bytes[offset + 2] & 0xff) << 8 + | (bytes[offset + 3] & 0xff); + } + + public static byte[] putUInt32(int value) + { + byte[] bytes = new byte[4]; + + bytes[0] = (byte) ((value >>> 24) & 0xff); + bytes[1] = (byte) ((value >>> 16) & 0xff); + bytes[2] = (byte) ((value >>> 8) & 0xff); + bytes[3] = (byte) ((value & 0xff)); + + return bytes; + } + + public static ByteBuffer putUInt32(ByteBuffer buf, int value) + { + buf.put((byte) ((value >>> 24) & 0xff)); + buf.put((byte) ((value >>> 16) & 0xff)); + buf.put((byte) ((value >>> 8) & 0xff)); + buf.put((byte) ((value & 0xff))); + + return buf; + } + + public static ByteBuffer putUInt64(ByteBuffer buf, long value) + { + buf.put((byte) ((value >>> 56) & 0xff)); + buf.put((byte) ((value >>> 48) & 0xff)); + buf.put((byte) ((value >>> 40) & 0xff)); + buf.put((byte) ((value >>> 32) & 0xff)); + buf.put((byte) ((value >>> 24) & 0xff)); + buf.put((byte) ((value >>> 16) & 0xff)); + buf.put((byte) ((value >>> 8) & 0xff)); + buf.put((byte) ((value) & 0xff)); + + return buf; + } + + public static long getUInt64(ByteBuffer buf, int offset) + { + return (long) (buf.get(offset) & 0xff) << 56 | (long) (buf.get(offset + 1) & 0xff) << 48 + | (long) (buf.get(offset + 2) & 0xff) << 40 | (long) (buf.get(offset + 3) & 0xff) << 32 + | (long) (buf.get(offset + 4) & 0xff) << 24 | (long) (buf.get(offset + 5) & 0xff) << 16 + | (long) (buf.get(offset + 6) & 0xff) << 8 | (long) buf.get(offset + 7) & 0xff; + } + + public static long getUInt64(Msg msg, int offset) + { + return msg.getLong(offset); + } +} diff --git a/src/main/java/zmq/util/Z85.java b/src/main/java/zmq/util/Z85.java new file mode 100644 index 000000000..ffcbbfe35 --- /dev/null +++ b/src/main/java/zmq/util/Z85.java @@ -0,0 +1,97 @@ +package zmq.util; + +import java.nio.ByteBuffer; + +// Z85 codec, taken from 0MQ RFC project, implements RFC32 Z85 encoding +public class Z85 +{ + private Z85() + { + } + + // Maps base 256 to base 85 + private static final String encoder = "0123456789" + "abcdefghij" + "klmnopqrst" + "uvwxyzABCD" + "EFGHIJKLMN" + + "OPQRSTUVWX" + "YZ.-:+=^!/" + "*?&<>()[]{" + "}@%$#"; + + // Maps base 85 to base 256 + // We chop off lower 32 and higher 128 ranges + private static final byte[] decoder = { 0x00, 0x44, 0x00, 0x54, 0x53, 0x52, 0x48, 0x00, 0x4B, 0x4C, 0x46, 0x41, + 0x00, 0x3F, 0x3E, 0x45, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x40, 0x00, 0x49, 0x42, + 0x4A, 0x47, 0x51, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x4D, 0x00, 0x4E, 0x43, 0x00, 0x00, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, + 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x4F, 0x00, 0x50, 0x00, 0x00 }; + + // -------------------------------------------------------------------------- + // Encode a binary frame as a string; destination string MUST be at least + // size * 5 / 4 bytes long plus 1 byte for the null terminator. Returns + // dest. Size must be a multiple of 4. + // Returns NULL and sets errno = EINVAL for invalid input. + + public static String encode(byte[] data, int size) + { + if (size % 4 != 0) { + return null; + } + StringBuilder builder = new StringBuilder(); + int byteNbr = 0; + long value = 0; + while (byteNbr < size) { + // Accumulate value in base 256 (binary) + int d = data[byteNbr++] & 0xff; + if (d < 0) { + System.out.print(""); + } + value = value * 256 + d; + if (byteNbr % 4 == 0) { + // Output value in base 85 + int divisor = 85 * 85 * 85 * 85; + while (divisor != 0) { + int index = (int) (value / divisor % 85); + if (index < 0) { + System.out.print(""); + } + builder.append(encoder.charAt(index)); + divisor /= 85; + } + value = 0; + } + } + assert (builder.length() == size * 5 / 4); + return builder.toString(); + } + + // -------------------------------------------------------------------------- + // Decode an encoded string into a binary frame; dest must be at least + // strlen (string) * 4 / 5 bytes long. Returns dest. strlen (string) + // must be a multiple of 5. + // Returns NULL and sets errno = EINVAL for invalid input. + + public static byte[] decode(String string) + { + if (string.length() % 5 != 0) { + return null; + } + ByteBuffer buf = ByteBuffer.allocate(string.length() * 4 / 5); + + int byteNbr = 0; + int charNbr = 0; + int stringLen = string.length(); + long value = 0; + while (charNbr < stringLen) { + // Accumulate value in base 85 + value = value * 85 + (decoder[string.charAt(charNbr++) - 32] & 0xff); + if (charNbr % 5 == 0) { + // Output value in base 256 + int divisor = 256 * 256 * 256; + while (divisor != 0) { + buf.put(byteNbr++, (byte) ((value / divisor) % 256)); + divisor /= 256; + } + value = 0; + } + } + assert (byteNbr == string.length() * 4 / 5); + return buf.array(); + } +} diff --git a/src/test/java/guide/MDP.java b/src/test/java/guide/MDP.java index 574cb1f93..dec78dddb 100644 --- a/src/test/java/guide/MDP.java +++ b/src/test/java/guide/MDP.java @@ -2,13 +2,14 @@ import java.util.Arrays; -import org.zeromq.ZMQ; import org.zeromq.ZFrame; +import org.zeromq.ZMQ; /** * Majordomo Protocol definitions, Java version */ -public enum MDP { +public enum MDP +{ /** * This is the version of MDP/Client we implement @@ -21,27 +22,32 @@ public enum MDP { W_WORKER("MDPW01"), // MDP/Server commands, as byte values - W_READY(1), - W_REQUEST(2), - W_REPLY(3), - W_HEARTBEAT(4), + W_READY(1), + W_REQUEST(2), + W_REPLY(3), + W_HEARTBEAT(4), W_DISCONNECT(5); private final byte[] data; - MDP(String value) { + MDP(String value) + { this.data = value.getBytes(ZMQ.CHARSET); } - MDP(int value) { //watch for ints>255, will be truncated + + MDP(int value) + { //watch for ints>255, will be truncated byte b = (byte) (value & 0xFF); this.data = new byte[] { b }; } - public ZFrame newFrame () { + public ZFrame newFrame() + { return new ZFrame(data); } - public boolean frameEquals (ZFrame frame) { + public boolean frameEquals(ZFrame frame) + { return Arrays.equals(data, frame.getData()); } } diff --git a/src/test/java/guide/ZHelper.java b/src/test/java/guide/ZHelper.java index d4f459e22..6a7d4458d 100644 --- a/src/test/java/guide/ZHelper.java +++ b/src/test/java/guide/ZHelper.java @@ -1,50 +1,51 @@ package guide; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.Context; -import org.zeromq.ZMQ.Socket; - import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Random; +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Context; +import org.zeromq.ZMQ.Socket; + public class ZHelper { - private static Random rand = new Random(System.currentTimeMillis ()); + private static Random rand = new Random(System.currentTimeMillis()); /** * Receives all message parts from socket, prints neatly */ - public static void dump (Socket sock) + public static void dump(Socket sock) { System.out.println("----------------------------------------"); - while(true) { - byte [] msg = sock.recv (0); + while (true) { + byte[] msg = sock.recv(0); boolean isText = true; String data = ""; - for (int i = 0; i< msg.length; i++) { + for (int i = 0; i < msg.length; i++) { if (msg[i] < 32 || msg[i] > 127) isText = false; - data += String.format ("%02X", msg[i]); + data += String.format("%02X", msg[i]); } if (isText) - data = new String (msg, ZMQ.CHARSET); + data = new String(msg, ZMQ.CHARSET); - System.out.println (String.format ("[%03d] %s", msg.length, data)); - if (!sock.hasReceiveMore ()) + System.out.println(String.format("[%03d] %s", msg.length, data)); + if (!sock.hasReceiveMore()) break; } } - public static void setId (Socket sock) + public static void setId(Socket sock) { - String identity = String.format ("%04X-%04X", rand.nextInt (), rand.nextInt ()); + String identity = String.format("%04X-%04X", rand.nextInt(), rand.nextInt()); - sock.setIdentity (identity.getBytes (ZMQ.CHARSET)); + sock.setIdentity(identity.getBytes(ZMQ.CHARSET)); } - public static List buildZPipe(Context ctx) { + public static List buildZPipe(Context ctx) + { Socket socket1 = ctx.socket(ZMQ.PAIR); socket1.setLinger(0); socket1.setHWM(1); diff --git a/src/test/java/guide/asyncsrv.java b/src/test/java/guide/asyncsrv.java index 1c5139cfd..affb76ea6 100644 --- a/src/test/java/guide/asyncsrv.java +++ b/src/test/java/guide/asyncsrv.java @@ -1,13 +1,13 @@ package guide; +import java.util.Random; + import org.zeromq.ZContext; import org.zeromq.ZFrame; import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; import org.zeromq.ZMsg; -import org.zeromq.ZMQ.Poller; - -import java.util.Random; // //Asynchronous client-to-server (DEALER to ROUTER) @@ -16,7 +16,6 @@ //it easier to start and stop the example. Each task has its own //context and conceptually acts as a separate process. - public class asyncsrv { //--------------------------------------------------------------------- @@ -27,9 +26,12 @@ public class asyncsrv private static Random rand = new Random(System.nanoTime()); - private static class client_task implements Runnable { + private static class client_task implements Runnable + { - public void run() { + @Override + public void run() + { ZContext ctx = new ZContext(); Socket client = ctx.createSocket(ZMQ.DEALER); @@ -54,7 +56,7 @@ public void run() { } client.send(String.format("request #%d", ++requestNbr), 0); } - ctx.destroy(); + ctx.close(); } } @@ -64,8 +66,11 @@ public void run() { //one request at a time but one client can talk to multiple workers at //once. - private static class server_task implements Runnable { - public void run() { + private static class server_task implements Runnable + { + @Override + public void run() + { ZContext ctx = new ZContext(); // Frontend socket talks to clients over TCP @@ -90,14 +95,18 @@ public void run() { //Each worker task works on one request at a time and sends a random number //of replies back, with random delays between replies: - private static class server_worker implements Runnable { + private static class server_worker implements Runnable + { private ZContext ctx; - public server_worker(ZContext ctx) { + public server_worker(ZContext ctx) + { this.ctx = ctx; } - public void run() { + @Override + public void run() + { Socket worker = ctx.createSocket(ZMQ.DEALER); worker.connect("inproc://backend"); @@ -115,7 +124,8 @@ public void run() { // Sleep for some fraction of a second try { Thread.sleep(rand.nextInt(1000) + 1); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } address.send(worker, ZFrame.REUSE + ZFrame.MORE); content.send(worker, ZFrame.REUSE); @@ -130,7 +140,8 @@ public void run() { //The main thread simply starts several clients, and a server, and then //waits for the server to finish. - public static void main(String[] args) throws Exception { + public static void main(String[] args) throws Exception + { ZContext ctx = new ZContext(); new Thread(new client_task()).start(); new Thread(new client_task()).start(); @@ -139,6 +150,6 @@ public static void main(String[] args) throws Exception { // Run for 5 seconds then quit Thread.sleep(5 * 1000); - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/bstar.java b/src/test/java/guide/bstar.java index 867427945..04d024d46 100644 --- a/src/test/java/guide/bstar.java +++ b/src/test/java/guide/bstar.java @@ -12,35 +12,37 @@ public class bstar { // States we can be in at any point in time - enum State { - STATE_PRIMARY, // Primary, waiting for peer to connect - STATE_BACKUP, // Backup, waiting for peer to connect - STATE_ACTIVE, // Active - accepting connections - STATE_PASSIVE // Passive - not accepting connections + enum State + { + STATE_PRIMARY, // Primary, waiting for peer to connect + STATE_BACKUP, // Backup, waiting for peer to connect + STATE_ACTIVE, // Active - accepting connections + STATE_PASSIVE // Passive - not accepting connections } // Events, which start with the states our peer can be in - enum Event { - PEER_PRIMARY, // HA peer is pending primary - PEER_BACKUP, // HA peer is pending backup - PEER_ACTIVE, // HA peer is active - PEER_PASSIVE, // HA peer is passive - CLIENT_REQUEST // Client makes request + enum Event + { + PEER_PRIMARY, // HA peer is pending primary + PEER_BACKUP, // HA peer is pending backup + PEER_ACTIVE, // HA peer is active + PEER_PASSIVE, // HA peer is passive + CLIENT_REQUEST // Client makes request } - private ZContext ctx; // Our private context - private ZLoop loop; // Reactor loop - private Socket statepub; // State publisher - private Socket statesub; // State subscriber - private State state; // Current state - private Event event; // Current event - private long peerExpiry; // When peer is considered 'dead' + private ZContext ctx; // Our private context + private ZLoop loop; // Reactor loop + private Socket statepub; // State publisher + private Socket statesub; // State subscriber + private State state; // Current state + private Event event; // Current event + private long peerExpiry; // When peer is considered 'dead' private ZLoop.IZLoopHandler voterFn; // Voting socket handler - private Object voterArg; // Arguments for voting handler + private Object voterArg; // Arguments for voting handler private ZLoop.IZLoopHandler activeFn; // Call when become active - private Object activeArg; // Arguments for handler + private Object activeArg; // Arguments for handler private ZLoop.IZLoopHandler passiveFn; // Call when become passive - private Object passiveArg; // Arguments for handler + private Object passiveArg; // Arguments for handler // The finite-state machine is the same as in the proof-of-concept server. // To understand this reactor in detail, first read the ZLoop class. @@ -48,7 +50,7 @@ enum Event { // We send state information this often // If peer doesn't respond in two heartbeats, it is 'dead' - private final static int BSTAR_HEARTBEAT = 1000; // In msecs + private final static int BSTAR_HEARTBEAT = 1000; // In msecs // Binary Star finite state machine (applies event to state) // Returns false if there was an exception, true if event was valid. @@ -61,47 +63,45 @@ private boolean execute() // Accepts CLIENT_REQUEST events in this state if (state == State.STATE_PRIMARY) { if (event == Event.PEER_BACKUP) { - System.out.printf ("I: connected to backup (passive), ready active\n"); + System.out.printf("I: connected to backup (passive), ready active\n"); state = State.STATE_ACTIVE; if (activeFn != null) activeFn.handle(loop, null, activeArg); } - else - if (event == Event.PEER_ACTIVE) { - System.out.printf ("I: connected to backup (active), ready passive\n"); + else if (event == Event.PEER_ACTIVE) { + System.out.printf("I: connected to backup (active), ready passive\n"); state = State.STATE_PASSIVE; if (passiveFn != null) passiveFn.handle(loop, null, passiveArg); } - else - if (event == Event.CLIENT_REQUEST) { + else if (event == Event.CLIENT_REQUEST) { // Allow client requests to turn us into the active if we've // waited sufficiently long to believe the backup is not // currently acting as active (i.e., after a failover) assert (peerExpiry > 0); if (System.currentTimeMillis() >= peerExpiry) { - System.out.printf ("I: request from client, ready as active\n"); + System.out.printf("I: request from client, ready as active\n"); state = State.STATE_ACTIVE; if (activeFn != null) activeFn.handle(loop, null, activeArg); - } else + } + else // Don't respond to clients yet - it's possible we're // performing a failback and the backup is currently active rc = false; } } - else - if (state == State.STATE_BACKUP) { + else if (state == State.STATE_BACKUP) { if (event == Event.PEER_ACTIVE) { - System.out.printf ("I: connected to primary (active), ready passive\n"); + System.out.printf("I: connected to primary (active), ready passive\n"); state = State.STATE_PASSIVE; if (passiveFn != null) passiveFn.handle(loop, null, passiveArg); } else - // Reject client connections when acting as backup - if (event == Event.CLIENT_REQUEST) - rc = false; + // Reject client connections when acting as backup + if (event == Event.CLIENT_REQUEST) + rc = false; } else // .split active and passive states @@ -109,7 +109,7 @@ private boolean execute() if (state == State.STATE_ACTIVE) { if (event == Event.PEER_ACTIVE) { // Two actives would mean split-brain - System.out.printf ("E: fatal error - dual actives, aborting\n"); + System.out.printf("E: fatal error - dual actives, aborting\n"); rc = false; } } @@ -119,29 +119,26 @@ private boolean execute() if (state == State.STATE_PASSIVE) { if (event == Event.PEER_PRIMARY) { // Peer is restarting - become active, peer will go passive - System.out.printf ("I: primary (passive) is restarting, ready active\n"); + System.out.printf("I: primary (passive) is restarting, ready active\n"); state = State.STATE_ACTIVE; } - else - if (event == Event.PEER_BACKUP) { + else if (event == Event.PEER_BACKUP) { // Peer is restarting - become active, peer will go passive - System.out.printf ("I: backup (passive) is restarting, ready active\n"); + System.out.printf("I: backup (passive) is restarting, ready active\n"); state = State.STATE_ACTIVE; } - else - if (event == Event.PEER_PASSIVE) { + else if (event == Event.PEER_PASSIVE) { // Two passives would mean cluster would be non-responsive - System.out.printf ("E: fatal error - dual passives, aborting\n"); + System.out.printf("E: fatal error - dual passives, aborting\n"); rc = false; } - else - if (event == Event.CLIENT_REQUEST) { + else if (event == Event.CLIENT_REQUEST) { // Peer becomes active if timeout has passed // It's the client request that triggers the failover assert (peerExpiry > 0); - if (System.currentTimeMillis () >= peerExpiry) { + if (System.currentTimeMillis() >= peerExpiry) { // If peer is dead, switch to the active state - System.out.printf ("I: failover successful, ready active\n"); + System.out.printf("I: failover successful, ready active\n"); state = State.STATE_ACTIVE; } else @@ -161,11 +158,11 @@ private void updatePeerExpiry() peerExpiry = System.currentTimeMillis() + 2 * BSTAR_HEARTBEAT; } - // Reactor event handlers... // Publish our state to peer - private static IZLoopHandler SendState = new IZLoopHandler () { + private static IZLoopHandler SendState = new IZLoopHandler() + { @Override public int handle(ZLoop loop, PollItem item, Object arg) @@ -177,7 +174,8 @@ public int handle(ZLoop loop, PollItem item, Object arg) }; // Receive state from peer, execute finite state machine - private static IZLoopHandler RecvState = new IZLoopHandler () { + private static IZLoopHandler RecvState = new IZLoopHandler() + { @Override public int handle(ZLoop loop, PollItem item, Object arg) @@ -193,7 +191,8 @@ public int handle(ZLoop loop, PollItem item, Object arg) }; // Application wants to speak to us, see if it's possible - private static IZLoopHandler VoterReady = new IZLoopHandler () { + private static IZLoopHandler VoterReady = new IZLoopHandler() + { @Override public int handle(ZLoop loop, PollItem item, Object arg) @@ -217,11 +216,12 @@ public int handle(ZLoop loop, PollItem item, Object arg) // This is the constructor for our {{bstar}} class. We have to tell it // whether we're primary or backup server, as well as our local and // remote endpoints to bind and connect to: - public bstar(boolean primary, String local, String remote) { + public bstar(boolean primary, String local, String remote) + { // Initialize the Binary Star ctx = new ZContext(); loop = new ZLoop(ctx); - state = primary? State.STATE_PRIMARY: State.STATE_BACKUP; + state = primary ? State.STATE_PRIMARY : State.STATE_BACKUP; // Create publisher for state going to peer statepub = ctx.createSocket(ZMQ.PUB); diff --git a/src/test/java/guide/bstarcli.java b/src/test/java/guide/bstarcli.java index 5815a0984..efec82199 100644 --- a/src/test/java/guide/bstarcli.java +++ b/src/test/java/guide/bstarcli.java @@ -1,5 +1,7 @@ package guide; +import java.nio.channels.Selector; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; @@ -9,17 +11,17 @@ // real work; it just demonstrates the Binary Star failover model. public class bstarcli { - private static final long REQUEST_TIMEOUT = 1000; // msecs - private static final long SETTLE_DELAY = 2000; // Before failing over + private static final long REQUEST_TIMEOUT = 1000; // msecs + private static final long SETTLE_DELAY = 2000; // Before failing over public static void main(String[] argv) throws Exception { ZContext ctx = new ZContext(); - + Selector selector = ctx.createSelector(); String[] server = { "tcp://localhost:5001", "tcp://localhost:5002" }; int serverNbr = 0; - System.out.printf ("I: connecting to server at %s...\n", server [serverNbr]); + System.out.printf("I: connecting to server at %s...\n", server[serverNbr]); Socket client = ctx.createSocket(ZMQ.REQ); client.connect(server[serverNbr]); @@ -37,7 +39,7 @@ public static void main(String[] argv) throws Exception // Poll socket for a reply, with timeout int rc = poller.poll(REQUEST_TIMEOUT); if (rc == -1) - break; // Interrupted + break; // Interrupted // .split main body of client // We use a Lazy Pirate strategy in the client. If there's no @@ -50,23 +52,21 @@ public static void main(String[] argv) throws Exception // We got a reply from the server, must match getSequence String reply = client.recvStr(); if (Integer.parseInt(reply) == sequence) { - System.out.printf ("I: server replied OK (%s)\n", reply); + System.out.printf("I: server replied OK (%s)\n", reply); expectReply = false; - Thread.sleep(1000); // One request per second + Thread.sleep(1000); // One request per second } - else - System.out.printf ("E: bad reply from server: %s\n", reply); + else System.out.printf("E: bad reply from server: %s\n", reply); } else { - System.out.printf ("W: no response from server, failing over\n"); + System.out.printf("W: no response from server, failing over\n"); // Old socket is confused; close it and open a new one poller.unregister(client); ctx.destroySocket(client); serverNbr = (serverNbr + 1) % 2; Thread.sleep(SETTLE_DELAY); - System.out.printf("I: connecting to server at %s...\n", - server[serverNbr]); + System.out.printf("I: connecting to server at %s...\n", server[serverNbr]); client = ctx.createSocket(ZMQ.REQ); client.connect(server[serverNbr]); poller.register(client, ZMQ.Poller.POLLIN); @@ -76,7 +76,7 @@ public static void main(String[] argv) throws Exception } } } - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/bstarsrv.java b/src/test/java/guide/bstarsrv.java index 6c39e8cf4..56d3f2cd2 100644 --- a/src/test/java/guide/bstarsrv.java +++ b/src/test/java/guide/bstarsrv.java @@ -11,30 +11,32 @@ public class bstarsrv { // States we can be in at any point in time - enum State { - STATE_PRIMARY, // Primary, waiting for peer to connect - STATE_BACKUP, // Backup, waiting for peer to connect - STATE_ACTIVE, // Active - accepting connections - STATE_PASSIVE // Passive - not accepting connections + enum State + { + STATE_PRIMARY, // Primary, waiting for peer to connect + STATE_BACKUP, // Backup, waiting for peer to connect + STATE_ACTIVE, // Active - accepting connections + STATE_PASSIVE // Passive - not accepting connections } // Events, which start with the states our peer can be in - enum Event { - PEER_PRIMARY, // HA peer is pending primary - PEER_BACKUP, // HA peer is pending backup - PEER_ACTIVE, // HA peer is active - PEER_PASSIVE, // HA peer is passive - CLIENT_REQUEST // Client makes request + enum Event + { + PEER_PRIMARY, // HA peer is pending primary + PEER_BACKUP, // HA peer is pending backup + PEER_ACTIVE, // HA peer is active + PEER_PASSIVE, // HA peer is passive + CLIENT_REQUEST // Client makes request } // Our finite state machine - private State state; // Current state - private Event event; // Current event - private long peerExpiry; // When peer is considered 'dead' + private State state; // Current state + private Event event; // Current event + private long peerExpiry; // When peer is considered 'dead' // We send state information this often // If peer doesn't respond in two heartbeats, it is 'dead' - private final static long HEARTBEAT = 1000; // In msecs + private final static long HEARTBEAT = 1000; // In msecs // .split Binary Star state machine // The heart of the Binary Star design is its finite-state machine (FSM). @@ -49,26 +51,24 @@ private boolean stateMachine() // ACTIVE or PASSIVE depending on events we get from our peer: if (state == State.STATE_PRIMARY) { if (event == Event.PEER_BACKUP) { - System.out.printf ("I: connected to backup (passive), ready active\n"); + System.out.printf("I: connected to backup (passive), ready active\n"); state = State.STATE_ACTIVE; } - else - if (event == Event.PEER_ACTIVE) { - System.out.printf ("I: connected to backup (active), ready passive\n"); + else if (event == Event.PEER_ACTIVE) { + System.out.printf("I: connected to backup (active), ready passive\n"); state = State.STATE_PASSIVE; } // Accept client connections } - else - if (state == State.STATE_BACKUP) { + else if (state == State.STATE_BACKUP) { if (event == Event.PEER_ACTIVE) { - System.out.printf ("I: connected to primary (active), ready passive\n"); + System.out.printf("I: connected to primary (active), ready passive\n"); state = State.STATE_PASSIVE; } else - // Reject client connections when acting as backup - if (event == Event.CLIENT_REQUEST) - exception = true; + // Reject client connections when acting as backup + if (event == Event.CLIENT_REQUEST) + exception = true; } else // .split active and passive states @@ -76,7 +76,7 @@ private boolean stateMachine() if (state == State.STATE_ACTIVE) { if (event == Event.PEER_ACTIVE) { // Two actives would mean split-brain - System.out.printf ("E: fatal error - dual actives, aborting\n"); + System.out.printf("E: fatal error - dual actives, aborting\n"); exception = true; } } @@ -86,29 +86,26 @@ private boolean stateMachine() if (state == State.STATE_PASSIVE) { if (event == Event.PEER_PRIMARY) { // Peer is restarting - become active, peer will go passive - System.out.printf ("I: primary (passive) is restarting, ready active\n"); + System.out.printf("I: primary (passive) is restarting, ready active\n"); state = State.STATE_ACTIVE; } - else - if (event == Event.PEER_BACKUP) { + else if (event == Event.PEER_BACKUP) { // Peer is restarting - become active, peer will go passive - System.out.printf ("I: backup (passive) is restarting, ready active\n"); + System.out.printf("I: backup (passive) is restarting, ready active\n"); state = State.STATE_ACTIVE; } - else - if (event == Event.PEER_PASSIVE) { + else if (event == Event.PEER_PASSIVE) { // Two passives would mean cluster would be non-responsive - System.out.printf ("E: fatal error - dual passives, aborting\n"); + System.out.printf("E: fatal error - dual passives, aborting\n"); exception = true; } - else - if (event == Event.CLIENT_REQUEST) { + else if (event == Event.CLIENT_REQUEST) { // Peer becomes active if timeout has passed // It's the client request that triggers the failover assert (peerExpiry > 0); - if (System.currentTimeMillis () >= peerExpiry) { + if (System.currentTimeMillis() >= peerExpiry) { // If peer is dead, switch to the active state - System.out.printf ("I: failover successful, ready active\n"); + System.out.printf("I: failover successful, ready active\n"); state = State.STATE_ACTIVE; } else @@ -125,7 +122,8 @@ private boolean stateMachine() // three sockets; one to publish state, one to subscribe to state, and // one for client requests/replies: - public static void main(String[] argv) { + public static void main(String[] argv) + { // Arguments can be either of: // -p primary server, at tcp://localhost:5001 // -b backup server, at tcp://localhost:5002 @@ -143,8 +141,7 @@ public static void main(String[] argv) { statesub.connect("tcp://localhost:5004"); fsm.state = State.STATE_PRIMARY; } - else - if (argv.length == 1 && argv[0].equals("-b")) { + else if (argv.length == 1 && argv[0].equals("-b")) { System.out.printf("I: Backup passive, waiting for primary (active)\n"); frontend.bind("tcp://*:5002"); statepub.bind("tcp://*:5004"); @@ -172,7 +169,7 @@ public static void main(String[] argv) { timeLeft = 0; int rc = poller.poll(timeLeft); if (rc == -1) - break; // Context has been shut down + break; // Context has been shut down if (poller.pollin(0)) { // Have a client request @@ -181,15 +178,14 @@ public static void main(String[] argv) { if (fsm.stateMachine() == false) // Answer client by echoing request back msg.send(frontend); - else - msg.destroy(); + else msg.destroy(); } if (poller.pollin(1)) { // Have state from our peer, execute as event String message = statesub.recvStr(); fsm.event = Event.values()[Integer.parseInt(message)]; if (fsm.stateMachine()) - break; // Error, so exit + break; // Error, so exit fsm.peerExpiry = System.currentTimeMillis() + 2 * HEARTBEAT; } // If we timed out, send state to peer @@ -199,9 +195,9 @@ public static void main(String[] argv) { } } if (Thread.currentThread().isInterrupted()) - System.out.printf ("W: interrupted\n"); + System.out.printf("W: interrupted\n"); // Shutdown sockets and context - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/bstarsrv2.java b/src/test/java/guide/bstarsrv2.java index be7dcccd6..5f43f850b 100644 --- a/src/test/java/guide/bstarsrv2.java +++ b/src/test/java/guide/bstarsrv2.java @@ -1,11 +1,9 @@ package guide; -import org.zeromq.ZContext; import org.zeromq.ZLoop; import org.zeromq.ZLoop.IZLoopHandler; import org.zeromq.ZMQ; import org.zeromq.ZMQ.PollItem; -import org.zeromq.ZMQ.Socket; import org.zeromq.ZMsg; // Binary Star server, using bstar reactor @@ -22,7 +20,8 @@ public int handle(ZLoop loop, PollItem item, Object arg) } }; - public static void main(String[] argv) { + public static void main(String[] argv) + { // Arguments can be either of: // -p primary server, at tcp://localhost:5001 // -b backup server, at tcp://localhost:5002 @@ -31,13 +30,12 @@ public static void main(String[] argv) { if (argv.length == 1 && argv[0].equals("-p")) { System.out.printf("I: Primary active, waiting for backup (passive)\n"); bs = new bstar(true, "tcp://*:5003", "tcp://localhost:5004"); - bs.voter ("tcp://*:5001", ZMQ.ROUTER, Echo, null); + bs.voter("tcp://*:5001", ZMQ.ROUTER, Echo, null); } - else - if (argv.length == 1 && argv[0].equals("-b")) { + else if (argv.length == 1 && argv[0].equals("-b")) { System.out.printf("I: Backup passive, waiting for primary (active)\n"); bs = new bstar(false, "tcp://*:5004", "tcp://localhost:5003"); - bs.voter ("tcp://*:5002", ZMQ.ROUTER, Echo, null); + bs.voter("tcp://*:5002", ZMQ.ROUTER, Echo, null); } else { System.out.printf("Usage: bstarsrv { -p | -b }\n"); diff --git a/src/test/java/guide/clone.java b/src/test/java/guide/clone.java index fdcb61966..8feca3e5a 100644 --- a/src/test/java/guide/clone.java +++ b/src/test/java/guide/clone.java @@ -1,5 +1,9 @@ package guide; +import java.nio.channels.Selector; +import java.util.HashMap; +import java.util.Map; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; @@ -8,13 +12,10 @@ import org.zeromq.ZThread; import org.zeromq.ZThread.IAttachedRunnable; -import java.util.HashMap; -import java.util.Map; - public class clone { - private ZContext ctx; // Our context wrapper - private Socket pipe; // Pipe through to clone agent + private ZContext ctx; // Our context wrapper + private Socket pipe; // Pipe through to clone agent // .split constructor and destructor // Here are the constructor and destructor for the clone class. Note that @@ -92,13 +93,14 @@ public String get(String key) // .split working with servers // The backend agent manages a set of servers, which we implement using // our simple class model: - private static class Server { - private String address; // Server address - private int port; // Server port - private Socket snapshot; // Snapshot socket - private Socket subscriber; // Incoming updates - private long expiry; // When server expires - private int requests; // How many snapshot requests made? + private static class Server + { + private String address; // Server address + private int port; // Server port + private Socket snapshot; // Snapshot socket + private Socket subscriber; // Incoming updates + private long expiry; // When server expires + private int requests; // How many snapshot requests made? protected Server(ZContext ctx, String address, int port, String subtree) { @@ -122,29 +124,30 @@ protected void destroy() // Here is the implementation of the backend agent itself: // Number of servers to which we will talk to - private final static int SERVER_MAX = 2; + private final static int SERVER_MAX = 2; // Server considered dead if silent for this long - private final static int SERVER_TTL = 5000; // msecs + private final static int SERVER_TTL = 5000; // msecs // States we can be in - private final static int STATE_INITIAL = 0; // Before asking server for state - private final static int STATE_SYNCING = 1; // Getting state from server - private final static int STATE_ACTIVE = 2; // Getting new updates from server - - private static class Agent { - private ZContext ctx; // Context wrapper - private Socket pipe; // Pipe back to application - private Map kvmap; // Actual key/value table - private String subtree; // Subtree specification, if any - private Server[] server; - private int nbrServers; // 0 to SERVER_MAX - private int state; // Current state - private int curServer; // If active, server 0 or 1 - private long sequence; // Last kvmsg processed - private Socket publisher; // Outgoing updates - - protected Agent (ZContext ctx, Socket pipe) + private final static int STATE_INITIAL = 0; // Before asking server for state + private final static int STATE_SYNCING = 1; // Getting state from server + private final static int STATE_ACTIVE = 2; // Getting new updates from server + + private static class Agent + { + private ZContext ctx; // Context wrapper + private Socket pipe; // Pipe back to application + private Map kvmap; // Actual key/value table + private String subtree; // Subtree specification, if any + private Server[] server; + private int nbrServers; // 0 to SERVER_MAX + private int state; // Current state + private int curServer; // If active, server 0 or 1 + private long sequence; // Last kvmsg processed + private Socket publisher; // Outgoing updates + + protected Agent(ZContext ctx, Socket pipe) { this.ctx = ctx; this.pipe = pipe; @@ -170,24 +173,20 @@ private boolean controlMessage() ZMsg msg = ZMsg.recvMsg(pipe); String command = msg.popString(); if (command == null) - return false; // Interrupted + return false; // Interrupted if (command.equals("SUBTREE")) { subtree = msg.popString(); } - else - if (command.equals("CONNECT")) { + else if (command.equals("CONNECT")) { String address = msg.popString(); String service = msg.popString(); if (nbrServers < SERVER_MAX) { - server [nbrServers++] = new Server( - ctx, address, Integer.parseInt(service), subtree); + server[nbrServers++] = new Server(ctx, address, Integer.parseInt(service), subtree); // We broadcast updates to all known servers - publisher.connect(String.format("%s:%d", - address, Integer.parseInt(service) + 2)); + publisher.connect(String.format("%s:%d", address, Integer.parseInt(service) + 2)); } - else - System.out.printf("E: too many servers (max. %d)\n", SERVER_MAX); + else System.out.printf("E: too many servers (max. %d)\n", SERVER_MAX); } else // .split set and get commands @@ -208,14 +207,12 @@ private boolean controlMessage() kvmsg.send(publisher); kvmsg.destroy(); } - else - if (command.equals("GET")) { + else if (command.equals("GET")) { String key = msg.popString(); String value = kvmap.get(key); if (value != null) pipe.send(value); - else - pipe.send(""); + else pipe.send(""); } msg.destroy(); @@ -230,6 +227,7 @@ private static class CloneAgent implements IAttachedRunnable public void run(Object[] args, ZContext ctx, Socket pipe) { Agent self = new Agent(ctx, pipe); + Selector selector = ctx.createSelector(); Poller poller = ctx.createPoller(1); poller.register(pipe, Poller.POLLIN); @@ -243,8 +241,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) // In this state we ask the server for a snapshot, // if we have a server to talk to... if (self.nbrServers > 0) { - System.out.printf("I: waiting for server at %s:%d...\n", - server.address, server.port); + System.out.printf("I: waiting for server at %s:%d...\n", server.address, server.port); if (server.requests < 2) { server.snapshot.sendMore("ICANHAZ?"); server.snapshot.send(self.subtree); @@ -257,8 +254,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) poller.register(pipe, Poller.POLLIN); poller.register(server.snapshot, Poller.POLLIN); } - else - pollSize = 1; + else pollSize = 1; break; case STATE_SYNCING: @@ -288,17 +284,16 @@ public void run(Object[] args, ZContext ctx, Socket pipe) // server is dead: int rc = poller.poll(pollTimer); if (rc == -1) - break; // Context has been shut down + break; // Context has been shut down if (poller.pollin(0)) { if (!self.controlMessage()) - break; // Interrupted + break; // Interrupted } - else - if (pollSize == 2 && poller.pollin(1)) { + else if (pollSize == 2 && poller.pollin(1)) { kvmsg msg = kvmsg.recv(poller.getSocket(1)); if (msg == null) - break; // Interrupted + break; // Interrupted // Anything from server resets its expiry time server.expiry = System.currentTimeMillis() + SERVER_TTL; @@ -308,29 +303,24 @@ public void run(Object[] args, ZContext ctx, Socket pipe) if (msg.getKey().equals("KTHXBAI")) { self.sequence = msg.getSequence(); self.state = STATE_ACTIVE; - System.out.printf("I: received from %s:%d snapshot=%d\n", - server.address, server.port, + System.out.printf("I: received from %s:%d snapshot=%d\n", server.address, server.port, self.sequence); msg.destroy(); } } - else - if (self.state == STATE_ACTIVE) { + else if (self.state == STATE_ACTIVE) { // Discard out-of-sequence updates, incl. hugz if (msg.getSequence() > self.sequence) { self.sequence = msg.getSequence(); - System.out.printf("I: received from %s:%d update=%d\n", - server.address, server.port, + System.out.printf("I: received from %s:%d update=%d\n", server.address, server.port, self.sequence); } - else - msg.destroy(); + else msg.destroy(); } } else { // Server has died, failover to next - System.out.printf("I: server at %s:%d didn't give hugz\n", - server.address, server.port); + System.out.printf("I: server at %s:%d didn't give hugz\n", server.address, server.port); self.curServer = (self.curServer + 1) % self.nbrServers; self.state = STATE_INITIAL; } diff --git a/src/test/java/guide/clonecli1.java b/src/test/java/guide/clonecli1.java index 592441223..4b18eece9 100644 --- a/src/test/java/guide/clonecli1.java +++ b/src/test/java/guide/clonecli1.java @@ -13,29 +13,32 @@ * @author Danish Shrestha * */ -public class clonecli1 { - private static Map kvMap = new HashMap(); - private static AtomicLong sequence = new AtomicLong(); +public class clonecli1 +{ + private static Map kvMap = new HashMap(); + private static AtomicLong sequence = new AtomicLong(); - public void run() { - ZContext ctx = new ZContext(); - Socket subscriber = ctx.createSocket(ZMQ.SUB); - subscriber.connect("tcp://localhost:5556"); - subscriber.subscribe(ZMQ.SUBSCRIPTION_ALL); + public void run() + { + ZContext ctx = new ZContext(); + Socket subscriber = ctx.createSocket(ZMQ.SUB); + subscriber.connect("tcp://localhost:5556"); + subscriber.subscribe(ZMQ.SUBSCRIPTION_ALL); - while (true) { - kvsimple kvMsg = kvsimple.recv(subscriber); + while (true) { + kvsimple kvMsg = kvsimple.recv(subscriber); if (kvMsg == null) break; clonecli1.kvMap.put(kvMsg.getKey(), kvMsg); System.out.println("receiving " + kvMsg); sequence.incrementAndGet(); - } - ctx.destroy(); - } + } + ctx.close(); + } - public static void main(String[] args) { - new clonecli1().run(); - } + public static void main(String[] args) + { + new clonecli1().run(); + } } diff --git a/src/test/java/guide/clonecli2.java b/src/test/java/guide/clonecli2.java index ad1e90699..b8a3617c5 100644 --- a/src/test/java/guide/clonecli2.java +++ b/src/test/java/guide/clonecli2.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Context; @@ -14,37 +13,39 @@ * @author Danish Shrestha * */ -public class clonecli2 { - private static Map kvMap = new HashMap(); +public class clonecli2 +{ + private static Map kvMap = new HashMap(); - public void run() { - Context ctx = ZMQ.context(1); - Socket snapshot = ctx.socket(ZMQ.DEALER); - snapshot.connect("tcp://localhost:5556"); + public void run() + { + Context ctx = ZMQ.context(1); + Socket snapshot = ctx.socket(ZMQ.DEALER); + snapshot.connect("tcp://localhost:5556"); - Socket subscriber = ctx.socket(ZMQ.SUB); - subscriber.connect("tcp://localhost:5557"); - subscriber.subscribe(ZMQ.SUBSCRIPTION_ALL); + Socket subscriber = ctx.socket(ZMQ.SUB); + subscriber.connect("tcp://localhost:5557"); + subscriber.subscribe(ZMQ.SUBSCRIPTION_ALL); - // get state snapshot - snapshot.send("ICANHAZ?".getBytes(ZMQ.CHARSET), 0); + // get state snapshot + snapshot.send("ICANHAZ?".getBytes(ZMQ.CHARSET), 0); long sequence = 0; - while (true) { + while (true) { kvsimple kvMsg = kvsimple.recv(snapshot); if (kvMsg == null) break; - sequence = kvMsg.getSequence(); - if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { - System.out.println("Received snapshot = " + kvMsg.getSequence()); - break; // done - } + sequence = kvMsg.getSequence(); + if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { + System.out.println("Received snapshot = " + kvMsg.getSequence()); + break; // done + } - System.out.println("receiving " + kvMsg.getSequence()); - clonecli2.kvMap.put(kvMsg.getKey(), kvMsg); - } + System.out.println("receiving " + kvMsg.getSequence()); + clonecli2.kvMap.put(kvMsg.getKey(), kvMsg); + } - // now apply pending updates, discard out-of-getSequence messages - while (true) { + // now apply pending updates, discard out-of-getSequence messages + while (true) { kvsimple kvMsg = kvsimple.recv(subscriber); if (kvMsg == null) @@ -55,10 +56,11 @@ public void run() { System.out.println("receiving " + sequence); clonecli2.kvMap.put(kvMsg.getKey(), kvMsg); } - } - } + } + } - public static void main(String[] args) { - new clonecli2().run(); - } + public static void main(String[] args) + { + new clonecli2().run(); + } } diff --git a/src/test/java/guide/clonecli3.java b/src/test/java/guide/clonecli3.java index a39e758cb..30ded96f4 100644 --- a/src/test/java/guide/clonecli3.java +++ b/src/test/java/guide/clonecli3.java @@ -4,89 +4,92 @@ import java.util.HashMap; import java.util.Map; import java.util.Random; -import java.util.concurrent.atomic.AtomicLong; import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; + /** * Clone client Model Three * @author Danish Shrestha * */ -public class clonecli3 { - private static Map kvMap = new HashMap(); +public class clonecli3 +{ + private static Map kvMap = new HashMap(); - public void run() { - ZContext ctx = new ZContext(); - Socket snapshot = ctx.createSocket(ZMQ.DEALER); - snapshot.connect("tcp://localhost:5556"); + public void run() + { + ZContext ctx = new ZContext(); + Socket snapshot = ctx.createSocket(ZMQ.DEALER); + snapshot.connect("tcp://localhost:5556"); - Socket subscriber = ctx.createSocket(ZMQ.SUB); - subscriber.connect("tcp://localhost:5557"); - subscriber.subscribe(ZMQ.SUBSCRIPTION_ALL); + Socket subscriber = ctx.createSocket(ZMQ.SUB); + subscriber.connect("tcp://localhost:5557"); + subscriber.subscribe(ZMQ.SUBSCRIPTION_ALL); - Socket push = ctx.createSocket(ZMQ.PUSH); - push.connect("tcp://localhost:5558"); + Socket push = ctx.createSocket(ZMQ.PUSH); + push.connect("tcp://localhost:5558"); - // get state snapshot + // get state snapshot long sequence = 0; snapshot.send("ICANHAZ?".getBytes(ZMQ.CHARSET), 0); - while (true) { + while (true) { kvsimple kvMsg = kvsimple.recv(snapshot); if (kvMsg == null) - break; // Interrupted + break; // Interrupted - sequence = kvMsg.getSequence(); - if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { - System.out.println("Received snapshot = " + kvMsg.getSequence()); - break; // done - } + sequence = kvMsg.getSequence(); + if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { + System.out.println("Received snapshot = " + kvMsg.getSequence()); + break; // done + } - System.out.println("receiving " + kvMsg.getSequence()); - clonecli3.kvMap.put(kvMsg.getKey(), kvMsg); - } + System.out.println("receiving " + kvMsg.getSequence()); + clonecli3.kvMap.put(kvMsg.getKey(), kvMsg); + } - Poller poller = ctx.createPoller(1); - poller.register(subscriber); + Poller poller = ctx.createPoller(1); + poller.register(subscriber); - Random random = new Random(); + Random random = new Random(); - // now apply pending updates, discard out-of-getSequence messages - long alarm = System.currentTimeMillis() + 5000; - while (true) { - int rc = poller.poll(Math.max(0, alarm - System.currentTimeMillis())); + // now apply pending updates, discard out-of-getSequence messages + long alarm = System.currentTimeMillis() + 5000; + while (true) { + int rc = poller.poll(Math.max(0, alarm - System.currentTimeMillis())); if (rc == -1) - break; // Context has been shut down + break; // Context has been shut down - if (poller.pollin(0)) { + if (poller.pollin(0)) { kvsimple kvMsg = kvsimple.recv(subscriber); if (kvMsg == null) - break; // Interrupted + break; // Interrupted if (kvMsg.getSequence() > sequence) { sequence = kvMsg.getSequence(); System.out.println("receiving " + sequence); clonecli3.kvMap.put(kvMsg.getKey(), kvMsg); } - } - - if (System.currentTimeMillis() >= alarm) { - int key = random.nextInt(10000); - int body = random.nextInt(1000000); - - ByteBuffer b = ByteBuffer.allocate(4); - b.asIntBuffer().put(body); - - kvsimple kvUpdateMsg = new kvsimple(key + "", 0, b.array()); - kvUpdateMsg.send(push); - alarm = System.currentTimeMillis() + 1000; - } - } - ctx.destroy(); - } - - public static void main(String[] args) { - new clonecli3().run(); - } + } + + if (System.currentTimeMillis() >= alarm) { + int key = random.nextInt(10000); + int body = random.nextInt(1000000); + + ByteBuffer b = ByteBuffer.allocate(4); + b.asIntBuffer().put(body); + + kvsimple kvUpdateMsg = new kvsimple(key + "", 0, b.array()); + kvUpdateMsg.send(push); + alarm = System.currentTimeMillis() + 1000; + } + } + ctx.close(); + } + + public static void main(String[] args) + { + new clonecli3().run(); + } } diff --git a/src/test/java/guide/clonecli4.java b/src/test/java/guide/clonecli4.java index 0a305401b..750bd70cd 100644 --- a/src/test/java/guide/clonecli4.java +++ b/src/test/java/guide/clonecli4.java @@ -1,15 +1,14 @@ package guide; -import org.zeromq.ZContext; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.Poller; -import org.zeromq.ZMQ.Socket; - import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.Random; -import java.util.concurrent.atomic.AtomicLong; + +import org.zeromq.ZContext; +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Poller; +import org.zeromq.ZMQ.Socket; /** * Clone client Model Four @@ -19,81 +18,83 @@ public class clonecli4 { // This client is identical to clonecli3 except for where we // handles subtrees. - private final static String SUBTREE = "/client/"; + private final static String SUBTREE = "/client/"; - private static Map kvMap = new HashMap(); + private static Map kvMap = new HashMap(); - public void run() { - ZContext ctx = new ZContext(); - Socket snapshot = ctx.createSocket(ZMQ.DEALER); - snapshot.connect("tcp://localhost:5556"); + public void run() + { + ZContext ctx = new ZContext(); + Socket snapshot = ctx.createSocket(ZMQ.DEALER); + snapshot.connect("tcp://localhost:5556"); - Socket subscriber = ctx.createSocket(ZMQ.SUB); + Socket subscriber = ctx.createSocket(ZMQ.SUB); subscriber.connect("tcp://localhost:5557"); subscriber.subscribe(SUBTREE.getBytes(ZMQ.CHARSET)); - Socket push = ctx.createSocket(ZMQ.PUSH); - push.connect("tcp://localhost:5558"); + Socket push = ctx.createSocket(ZMQ.PUSH); + push.connect("tcp://localhost:5558"); - // get state snapshot - snapshot.sendMore("ICANHAZ?"); + // get state snapshot + snapshot.sendMore("ICANHAZ?"); snapshot.send(SUBTREE); long sequence = 0; while (true) { kvsimple kvMsg = kvsimple.recv(snapshot); if (kvMsg == null) - break; // Interrupted + break; // Interrupted - sequence = kvMsg.getSequence(); - if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { - System.out.println("Received snapshot = " + kvMsg.getSequence()); - break; // done - } + sequence = kvMsg.getSequence(); + if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { + System.out.println("Received snapshot = " + kvMsg.getSequence()); + break; // done + } - System.out.println("receiving " + kvMsg.getSequence()); - clonecli4.kvMap.put(kvMsg.getKey(), kvMsg); - } + System.out.println("receiving " + kvMsg.getSequence()); + clonecli4.kvMap.put(kvMsg.getKey(), kvMsg); + } - Poller poller = ctx.createPoller(1); - poller.register(subscriber); + Poller poller = ctx.createPoller(1); + poller.register(subscriber); - Random random = new Random(); + Random random = new Random(); - // now apply pending updates, discard out-of-getSequence messages - long alarm = System.currentTimeMillis() + 5000; - while (true) { - int rc = poller.poll(Math.max(0, alarm - System.currentTimeMillis())); + // now apply pending updates, discard out-of-getSequence messages + long alarm = System.currentTimeMillis() + 5000; + while (true) { + int rc = poller.poll(Math.max(0, alarm - System.currentTimeMillis())); if (rc == -1) - break; // Context has been shut down + break; // Context has been shut down - if (poller.pollin(0)) { + if (poller.pollin(0)) { kvsimple kvMsg = kvsimple.recv(subscriber); if (kvMsg == null) - break; // Interrupted + break; // Interrupted if (kvMsg.getSequence() > sequence) { sequence = kvMsg.getSequence(); System.out.println("receiving " + sequence); clonecli4.kvMap.put(kvMsg.getKey(), kvMsg); } - } - - if (System.currentTimeMillis() >= alarm) { - String key = String.format("%s%d", SUBTREE, random.nextInt(10000)); - int body = random.nextInt(1000000); - - ByteBuffer b = ByteBuffer.allocate(4); - b.asIntBuffer().put(body); - - kvsimple kvUpdateMsg = new kvsimple(key, 0, b.array()); - kvUpdateMsg.send(push); - alarm = System.currentTimeMillis() + 1000; - } - } - ctx.destroy(); - } - - public static void main(String[] args) { - new clonecli4().run(); - } + } + + if (System.currentTimeMillis() >= alarm) { + String key = String.format("%s%d", SUBTREE, random.nextInt(10000)); + int body = random.nextInt(1000000); + + ByteBuffer b = ByteBuffer.allocate(4); + b.asIntBuffer().put(body); + + kvsimple kvUpdateMsg = new kvsimple(key, 0, b.array()); + kvUpdateMsg.send(push); + alarm = System.currentTimeMillis() + 1000; + } + } + ctx.close(); + } + + public static void main(String[] args) + { + new clonecli4().run(); + } } diff --git a/src/test/java/guide/clonecli5.java b/src/test/java/guide/clonecli5.java index 47604f105..b3dfe0695 100644 --- a/src/test/java/guide/clonecli5.java +++ b/src/test/java/guide/clonecli5.java @@ -1,15 +1,14 @@ package guide; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; - /** * Clone client Model Five * @@ -18,84 +17,85 @@ public class clonecli5 { // This client is identical to clonecli3 except for where we // handles subtrees. - private final static String SUBTREE = "/client/"; + private final static String SUBTREE = "/client/"; + public void run() + { + ZContext ctx = new ZContext(); + Socket snapshot = ctx.createSocket(ZMQ.DEALER); + snapshot.connect("tcp://localhost:5556"); - public void run() { - ZContext ctx = new ZContext(); - Socket snapshot = ctx.createSocket(ZMQ.DEALER); - snapshot.connect("tcp://localhost:5556"); - - Socket subscriber = ctx.createSocket(ZMQ.SUB); + Socket subscriber = ctx.createSocket(ZMQ.SUB); subscriber.connect("tcp://localhost:5557"); subscriber.subscribe(SUBTREE.getBytes(ZMQ.CHARSET)); - Socket publisher = ctx.createSocket(ZMQ.PUSH); - publisher.connect("tcp://localhost:5558"); + Socket publisher = ctx.createSocket(ZMQ.PUSH); + publisher.connect("tcp://localhost:5558"); Map kvMap = new HashMap(); // get state snapshot - snapshot.sendMore("ICANHAZ?"); + snapshot.sendMore("ICANHAZ?"); snapshot.send(SUBTREE); long sequence = 0; while (true) { kvmsg kvMsg = kvmsg.recv(snapshot); if (kvMsg == null) - break; // Interrupted + break; // Interrupted - sequence = kvMsg.getSequence(); - if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { - System.out.println("Received snapshot = " + kvMsg.getSequence()); + sequence = kvMsg.getSequence(); + if ("KTHXBAI".equalsIgnoreCase(kvMsg.getKey())) { + System.out.println("Received snapshot = " + kvMsg.getSequence()); kvMsg.destroy(); - break; // done - } + break; // done + } - System.out.println("receiving " + kvMsg.getSequence()); + System.out.println("receiving " + kvMsg.getSequence()); kvMsg.store(kvMap); - } + } - Poller poller = ctx.createPoller(1); - poller.register(subscriber); + Poller poller = ctx.createPoller(1); + poller.register(subscriber); - Random random = new Random(); + Random random = new Random(); - // now apply pending updates, discard out-of-getSequence messages - long alarm = System.currentTimeMillis() + 5000; - while (true) { - int rc = poller.poll(Math.max(0, alarm - System.currentTimeMillis())); + // now apply pending updates, discard out-of-getSequence messages + long alarm = System.currentTimeMillis() + 5000; + while (true) { + int rc = poller.poll(Math.max(0, alarm - System.currentTimeMillis())); if (rc == -1) - break; // Context has been shut down + break; // Context has been shut down - if (poller.pollin(0)) { + if (poller.pollin(0)) { kvmsg kvMsg = kvmsg.recv(subscriber); if (kvMsg == null) - break; // Interrupted + break; // Interrupted if (kvMsg.getSequence() > sequence) { sequence = kvMsg.getSequence(); System.out.println("receiving " + sequence); kvMsg.store(kvMap); - } else - kvMsg.destroy(); - } + } + else kvMsg.destroy(); + } - if (System.currentTimeMillis() >= alarm) { - kvmsg kvMsg = new kvmsg(0); + if (System.currentTimeMillis() >= alarm) { + kvmsg kvMsg = new kvmsg(0); kvMsg.fmtKey("%s%d", SUBTREE, random.nextInt(10000)); kvMsg.fmtBody("%d", random.nextInt(1000000)); kvMsg.setProp("ttl", "%d", random.nextInt(30)); kvMsg.send(publisher); kvMsg.destroy(); - alarm = System.currentTimeMillis() + 1000; - } - } - ctx.destroy(); - } + alarm = System.currentTimeMillis() + 1000; + } + } + ctx.close(); + } - public static void main(String[] args) { - new clonecli5().run(); - } + public static void main(String[] args) + { + new clonecli5().run(); + } } diff --git a/src/test/java/guide/clonecli6.java b/src/test/java/guide/clonecli6.java index 2e03c57c1..a1dbe64a9 100644 --- a/src/test/java/guide/clonecli6.java +++ b/src/test/java/guide/clonecli6.java @@ -1,13 +1,6 @@ package guide; -import org.zeromq.ZContext; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.Socket; - -import java.util.HashMap; -import java.util.Map; import java.util.Random; -import java.util.concurrent.atomic.AtomicLong; /** * Clone client model 6 @@ -15,7 +8,9 @@ public class clonecli6 { private final static String SUBTREE = "/client/"; - public void run() { + + public void run() + { // Create distributed hash instance clone clone = new clone(); @@ -30,17 +25,19 @@ public void run() { while (!Thread.currentThread().isInterrupted()) { // Set random value, check it was stored String key = String.format("%s%d", SUBTREE, rand.nextInt(10000)); - String value= String.format("%d", rand.nextInt(1000000)); + String value = String.format("%d", rand.nextInt(1000000)); clone.set(key, value, rand.nextInt(30)); try { Thread.sleep(1000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } } clone.destroy(); - } + } - public static void main(String[] args) { - new clonecli6().run(); - } + public static void main(String[] args) + { + new clonecli6().run(); + } } diff --git a/src/test/java/guide/clonesrv1.java b/src/test/java/guide/clonesrv1.java index 50e9fb61b..848c763d8 100644 --- a/src/test/java/guide/clonesrv1.java +++ b/src/test/java/guide/clonesrv1.java @@ -14,39 +14,42 @@ * @author Danish Shrestha * */ -public class clonesrv1 { - private static AtomicLong sequence = new AtomicLong(); - - public void run() { - Context ctx = ZMQ.context(1); - Socket publisher = ctx.socket(ZMQ.PUB); - publisher.bind("tcp://*:5556"); - - try { - Thread.sleep(200); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - Random random = new Random(); - - while (true) { - long currentSequenceNumber = sequence.incrementAndGet(); - int key = random.nextInt(10000); - int body = random.nextInt(1000000); - - ByteBuffer b = ByteBuffer.allocate(4); - b.asIntBuffer().put(body); - - kvsimple kvMsg = new kvsimple(key + "", currentSequenceNumber, - b.array()); - kvMsg.send(publisher); - System.out.println("sending " + kvMsg); - - } - } - - public static void main(String[] args) { - new clonesrv1().run(); - } +public class clonesrv1 +{ + private static AtomicLong sequence = new AtomicLong(); + + public void run() + { + Context ctx = ZMQ.context(1); + Socket publisher = ctx.socket(ZMQ.PUB); + publisher.bind("tcp://*:5556"); + + try { + Thread.sleep(200); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + + Random random = new Random(); + + while (true) { + long currentSequenceNumber = sequence.incrementAndGet(); + int key = random.nextInt(10000); + int body = random.nextInt(1000000); + + ByteBuffer b = ByteBuffer.allocate(4); + b.asIntBuffer().put(body); + + kvsimple kvMsg = new kvsimple(key + "", currentSequenceNumber, b.array()); + kvMsg.send(publisher); + System.out.println("sending " + kvMsg); + + } + } + + public static void main(String[] args) + { + new clonesrv1().run(); + } } diff --git a/src/test/java/guide/clonesrv2.java b/src/test/java/guide/clonesrv2.java index 8bbec5082..4f4e5bfee 100644 --- a/src/test/java/guide/clonesrv2.java +++ b/src/test/java/guide/clonesrv2.java @@ -4,9 +4,8 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Random; import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicLong; +import java.util.Random; import org.zeromq.ZContext; import org.zeromq.ZMQ; @@ -21,74 +20,79 @@ * @author Danish Shrestha * */ -public class clonesrv2 { +public class clonesrv2 +{ - public void run() { - ZContext ctx = new ZContext(); - Socket publisher = ctx.createSocket(ZMQ.PUB); - publisher.bind("tcp://*:5557"); + public void run() + { + ZContext ctx = new ZContext(); + Socket publisher = ctx.createSocket(ZMQ.PUB); + publisher.bind("tcp://*:5557"); - Socket updates = ZThread.fork(ctx, new StateManager()); + Socket updates = ZThread.fork(ctx, new StateManager()); - Random random = new Random(); + Random random = new Random(); long sequence = 0; - while (!Thread.currentThread().isInterrupted()) { - long currentSequenceNumber = ++sequence; - int key = random.nextInt(10000); - int body = random.nextInt(1000000); + while (!Thread.currentThread().isInterrupted()) { + long currentSequenceNumber = ++sequence; + int key = random.nextInt(10000); + int body = random.nextInt(1000000); - ByteBuffer b = ByteBuffer.allocate(4); - b.asIntBuffer().put(body); + ByteBuffer b = ByteBuffer.allocate(4); + b.asIntBuffer().put(body); - kvsimple kvMsg = new kvsimple(key + "", currentSequenceNumber, b.array()); - kvMsg.send(publisher); - kvMsg.send(updates); // send a message to State Manager thead. + kvsimple kvMsg = new kvsimple(key + "", currentSequenceNumber, b.array()); + kvMsg.send(publisher); + kvMsg.send(updates); // send a message to State Manager thead. try { Thread.sleep(1000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } } System.out.printf(" Interrupted\n%d messages out\n", sequence); ctx.destroy(); - } + } - public static class StateManager implements IAttachedRunnable { - private static Map kvMap = new LinkedHashMap(); + public static class StateManager implements IAttachedRunnable + { + private static Map kvMap = new LinkedHashMap(); - @Override - public void run(Object[] args, ZContext ctx, Socket pipe) { - pipe.send("READY"); // optional + @Override + public void run(Object[] args, ZContext ctx, Socket pipe) + { + pipe.send("READY"); // optional - Socket snapshot = ctx.createSocket(ZMQ.ROUTER); - snapshot.bind("tcp://*:5556"); + Socket snapshot = ctx.createSocket(ZMQ.ROUTER); + snapshot.bind("tcp://*:5556"); - Poller poller = ctx.createPoller(2); - poller.register(pipe, ZMQ.Poller.POLLIN); - poller.register(snapshot, ZMQ.Poller.POLLIN); + Poller poller = ctx.createPoller(2); + poller.register(pipe, ZMQ.Poller.POLLIN); + poller.register(snapshot, ZMQ.Poller.POLLIN); - long stateSequence = 0; - while (!Thread.currentThread().isInterrupted()) { - if (poller.poll() < 0) - break; // Context has been shut down + long stateSequence = 0; + while (!Thread.currentThread().isInterrupted()) { + if (poller.poll() < 0) + break; // Context has been shut down - // apply state updates from main thread - if (poller.pollin(0)) { - kvsimple kvMsg = kvsimple.recv(pipe); + // apply state updates from main thread + if (poller.pollin(0)) { + kvsimple kvMsg = kvsimple.recv(pipe); if (kvMsg == null) break; - StateManager.kvMap.put(kvMsg.getKey(), kvMsg); - stateSequence = kvMsg.getSequence(); - } + StateManager.kvMap.put(kvMsg.getKey(), kvMsg); + stateSequence = kvMsg.getSequence(); + } - // execute state snapshot request - if (poller.pollin(1)) { - byte[] identity = snapshot.recv(0); + // execute state snapshot request + if (poller.pollin(1)) { + byte[] identity = snapshot.recv(0); if (identity == null) break; - String request = new String(snapshot.recv(0), ZMQ.CHARSET); + String request = new String(snapshot.recv(0), ZMQ.CHARSET); if (!request.equals("ICANHAZ?")) { System.out.println("E: bad request, aborting"); @@ -108,17 +112,19 @@ public void run(Object[] args, ZContext ctx, Socket pipe) { snapshot.send(identity, ZMQ.SNDMORE); kvsimple message = new kvsimple("KTHXBAI", stateSequence, ZMQ.MESSAGE_SEPARATOR); message.send(snapshot); - } - } - } - - private void sendMessage(kvsimple msg, byte[] identity, Socket snapshot) { - snapshot.send(identity, ZMQ.SNDMORE); - msg.send(snapshot); - } - } - - public static void main(String[] args) { - new clonesrv2().run(); - } + } + } + } + + private void sendMessage(kvsimple msg, byte[] identity, Socket snapshot) + { + snapshot.send(identity, ZMQ.SNDMORE); + msg.send(snapshot); + } + } + + public static void main(String[] args) + { + new clonesrv2().run(); + } } diff --git a/src/test/java/guide/clonesrv3.java b/src/test/java/guide/clonesrv3.java index 441dbddf4..8efcee5df 100644 --- a/src/test/java/guide/clonesrv3.java +++ b/src/test/java/guide/clonesrv3.java @@ -15,10 +15,12 @@ * @author Danish Shrestha * */ -public class clonesrv3 { +public class clonesrv3 +{ private static Map kvMap = new LinkedHashMap(); - public void run() { + public void run() + { ZContext ctx = new ZContext(); @@ -38,12 +40,12 @@ public void run() { long sequence = 0; while (!Thread.currentThread().isInterrupted()) { if (poller.poll(1000) < 0) - break; // Context has been shut down + break; // Context has been shut down // apply state updates from main thread if (poller.pollin(0)) { kvsimple kvMsg = kvsimple.recv(collector); - if (kvMsg == null) // Interrupted + if (kvMsg == null) // Interrupted break; kvMsg.setSequence(++sequence); kvMsg.send(publisher); @@ -55,7 +57,7 @@ public void run() { if (poller.pollin(1)) { byte[] identity = snapshot.recv(0); if (identity == null) - break; // Interrupted + break; // Interrupted String request = snapshot.recvStr(); if (!request.equals("ICANHAZ?")) { @@ -78,16 +80,18 @@ public void run() { message.send(snapshot); } } - System.out.printf (" Interrupted\n%d messages handled\n", sequence); - ctx.destroy(); + System.out.printf(" Interrupted\n%d messages handled\n", sequence); + ctx.close(); } - private void sendMessage(kvsimple msg, byte[] identity, Socket snapshot) { + private void sendMessage(kvsimple msg, byte[] identity, Socket snapshot) + { snapshot.send(identity, ZMQ.SNDMORE); msg.send(snapshot); } - public static void main(String[] args) { - new clonesrv3().run(); - } + public static void main(String[] args) + { + new clonesrv3().run(); + } } diff --git a/src/test/java/guide/clonesrv4.java b/src/test/java/guide/clonesrv4.java index 5d4c284e5..07b71cab4 100644 --- a/src/test/java/guide/clonesrv4.java +++ b/src/test/java/guide/clonesrv4.java @@ -1,15 +1,15 @@ package guide; -import org.zeromq.ZContext; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.Poller; -import org.zeromq.ZMQ.Socket; - import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; +import org.zeromq.ZContext; +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Poller; +import org.zeromq.ZMQ.Socket; + /** * Clone server Model Four */ @@ -17,7 +17,8 @@ public class clonesrv4 { private static Map kvMap = new LinkedHashMap(); - public void run() { + public void run() + { ZContext ctx = new ZContext(); @@ -37,12 +38,12 @@ public void run() { long sequence = 0; while (!Thread.currentThread().isInterrupted()) { if (poller.poll(1000) < 0) - break; // Context has been shut down + break; // Context has been shut down // apply state updates from main thread if (poller.pollin(0)) { kvsimple kvMsg = kvsimple.recv(collector); - if (kvMsg == null) // Interrupted + if (kvMsg == null) // Interrupted break; kvMsg.setSequence(++sequence); kvMsg.send(publisher); @@ -54,7 +55,7 @@ public void run() { if (poller.pollin(1)) { byte[] identity = snapshot.recv(0); if (identity == null) - break; // Interrupted + break; // Interrupted // .until // Request is in second frame of message @@ -67,7 +68,6 @@ public void run() { String subtree = snapshot.recvStr(); - Iterator> iter = kvMap.entrySet().iterator(); while (iter.hasNext()) { Entry entry = iter.next(); @@ -83,17 +83,19 @@ public void run() { message.send(snapshot); } } - System.out.printf (" Interrupted\n%d messages handled\n", sequence); - ctx.destroy(); + System.out.printf(" Interrupted\n%d messages handled\n", sequence); + ctx.close(); } - private void sendMessage(kvsimple msg, byte[] identity, String subtree, Socket snapshot) { + private void sendMessage(kvsimple msg, byte[] identity, String subtree, Socket snapshot) + { snapshot.send(identity, ZMQ.SNDMORE); snapshot.send(subtree, ZMQ.SNDMORE); msg.send(snapshot); } - public static void main(String[] args) { - new clonesrv4().run(); - } + public static void main(String[] args) + { + new clonesrv4().run(); + } } diff --git a/src/test/java/guide/clonesrv5.java b/src/test/java/guide/clonesrv5.java index ab87d4770..4ec159330 100644 --- a/src/test/java/guide/clonesrv5.java +++ b/src/test/java/guide/clonesrv5.java @@ -1,5 +1,10 @@ package guide; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + import org.zeromq.ZContext; import org.zeromq.ZLoop; import org.zeromq.ZLoop.IZLoopHandler; @@ -7,22 +12,17 @@ import org.zeromq.ZMQ.PollItem; import org.zeromq.ZMQ.Socket; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - // Clone server - Model Five public class clonesrv5 { - private ZContext ctx; // Context wrapper - private Map kvmap; // Key-value store - private ZLoop loop; // zloop reactor - private int port; // Main port we're working on - private long sequence; // How many updates we're at - private Socket snapshot; // Handle snapshot requests - private Socket publisher; // Publish updates to clients - private Socket collector; // Collect updates from clients + private ZContext ctx; // Context wrapper + private Map kvmap; // Key-value store + private ZLoop loop; // zloop reactor + private int port; // Main port we're working on + private long sequence; // How many updates we're at + private Socket snapshot; // Handle snapshot requests + private Socket publisher; // Publish updates to clients + private Socket collector; // Collect updates from clients // .split snapshot handler // This is the reactor handler for the snapshot socket; it accepts @@ -44,12 +44,11 @@ public int handle(ZLoop loop, PollItem item, Object arg) if (request.equals("ICANHAZ?")) { subtree = socket.recvStr(); } - else - System.out.printf("E: bad request, aborting\n"); + else System.out.printf("E: bad request, aborting\n"); if (subtree != null) { // Send state socket to client - for (Entry entry: srv.kvmap.entrySet()) { + for (Entry entry : srv.kvmap.entrySet()) { sendSingle(entry.getValue(), identity, subtree, socket); } @@ -84,8 +83,7 @@ public int handle(ZLoop loop, PollItem item, Object arg) msg.send(srv.publisher); int ttl = Integer.parseInt(msg.getProp("ttl")); if (ttl > 0) - msg.setProp("ttl", - "%d", System.currentTimeMillis() + ttl * 1000); + msg.setProp("ttl", "%d", System.currentTimeMillis() + ttl * 1000); msg.store(srv.kvmap); System.out.printf("I: publishing update=%d\n", srv.sequence); } @@ -101,7 +99,7 @@ public int handle(ZLoop loop, PollItem item, Object arg) { clonesrv5 srv = (clonesrv5) arg; if (srv.kvmap != null) { - for (kvmsg msg: new ArrayList(srv.kvmap.values())) { + for (kvmsg msg : new ArrayList(srv.kvmap.values())) { srv.flushSingle(msg); } } @@ -109,7 +107,7 @@ public int handle(ZLoop loop, PollItem item, Object arg) } } - public clonesrv5 () + public clonesrv5() { port = 5556; ctx = new ZContext(); @@ -118,7 +116,7 @@ public clonesrv5 () loop.verbose(false); // Set up our clone server sockets - snapshot = ctx.createSocket(ZMQ.ROUTER); + snapshot = ctx.createSocket(ZMQ.ROUTER); snapshot.bind(String.format("tcp://*:%d", port)); publisher = ctx.createSocket(ZMQ.PUB); publisher.bind(String.format("tcp://*:%d", port + 1)); @@ -144,8 +142,8 @@ public void run() private static void sendSingle(kvmsg msg, byte[] identity, String subtree, Socket socket) { if (msg.getKey().startsWith(subtree)) { - socket.send (identity, // Choose recipient - ZMQ.SNDMORE); + socket.send(identity, // Choose recipient + ZMQ.SNDMORE); msg.send(socket); } } diff --git a/src/test/java/guide/clonesrv6.java b/src/test/java/guide/clonesrv6.java index b994e93f4..e57ee5e78 100644 --- a/src/test/java/guide/clonesrv6.java +++ b/src/test/java/guide/clonesrv6.java @@ -1,12 +1,5 @@ package guide; -import org.zeromq.ZContext; -import org.zeromq.ZLoop; -import org.zeromq.ZLoop.IZLoopHandler; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.PollItem; -import org.zeromq.ZMQ.Socket; - import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -14,22 +7,29 @@ import java.util.Map; import java.util.Map.Entry; +import org.zeromq.ZContext; +import org.zeromq.ZLoop; +import org.zeromq.ZLoop.IZLoopHandler; +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.PollItem; +import org.zeromq.ZMQ.Socket; + // Clone server - Model Six public class clonesrv6 { - private ZContext ctx; // Context wrapper - private Map kvmap; // Key-value store - private bstar bStar; // Bstar reactor core - private long sequence; // How many updates we're at - private int port; // Main port we're working on - private int peer; // Main port of our peer - private Socket publisher; // Publish updates and hugz - private Socket collector; // Collect updates from clients - private Socket subscriber; // Get updates from peer - private List pending; // Pending updates from clients - private boolean primary; // TRUE if we're primary - private boolean active; // TRUE if we're active - private boolean passive; // TRUE if we're passive + private ZContext ctx; // Context wrapper + private Map kvmap; // Key-value store + private bstar bStar; // Bstar reactor core + private long sequence; // How many updates we're at + private int port; // Main port we're working on + private int peer; // Main port of our peer + private Socket publisher; // Publish updates and hugz + private Socket collector; // Collect updates from clients + private Socket subscriber; // Get updates from peer + private List pending; // Pending updates from clients + private boolean primary; // TRUE if we're primary + private boolean active; // TRUE if we're active + private boolean passive; // TRUE if we're passive private static class Snapshots implements IZLoopHandler { @@ -47,12 +47,11 @@ public int handle(ZLoop loop, PollItem item, Object arg) if (request.equals("ICANHAZ?")) { subtree = socket.recvStr(); } - else - System.out.printf("E: bad request, aborting\n"); + else System.out.printf("E: bad request, aborting\n"); if (subtree != null) { // Send state socket to client - for (Entry entry: srv.kvmap.entrySet()) { + for (Entry entry : srv.kvmap.entrySet()) { sendSingle(entry.getValue(), identity, subtree, socket); } @@ -80,26 +79,24 @@ public int handle(ZLoop loop, PollItem item, Object arg) kvmsg msg = kvmsg.recv(socket); if (msg != null) { - if(srv.active){ + if (srv.active) { msg.setSequence(++srv.sequence); msg.send(srv.publisher); int ttl = Integer.parseInt(msg.getProp("ttl")); if (ttl > 0) - msg.setProp("ttl", - "%d", System.currentTimeMillis() + ttl * 1000); + msg.setProp("ttl", "%d", System.currentTimeMillis() + ttl * 1000); msg.store(srv.kvmap); System.out.printf("I: publishing update=%d\n", srv.sequence); - } else { + } + else { // If we already got message from active, drop it, else // hold on pending list if (srv.wasPending(msg)) msg.destroy(); - else - srv.pending.add(msg); + else srv.pending.add(msg); } } - return 0; } } @@ -132,7 +129,7 @@ public int handle(ZLoop loop, PollItem item, Object arg) { clonesrv6 srv = (clonesrv6) arg; if (srv.kvmap != null) { - for (kvmsg msg: new ArrayList(srv.kvmap.values())) { + for (kvmsg msg : new ArrayList(srv.kvmap.values())) { srv.flushSingle(msg); } } @@ -159,7 +156,7 @@ public int handle(ZLoop loop, PollItem item, Object arg) srv.bStar.zloop().removePoller(poller); // Apply pending list to own hash table - for (kvmsg msg: srv.pending) { + for (kvmsg msg : srv.pending) { msg.setSequence(++srv.sequence); msg.send(srv.publisher); msg.store(srv.kvmap); @@ -178,7 +175,7 @@ public int handle(ZLoop loop, PollItem item, Object arg) clonesrv6 srv = (clonesrv6) arg; if (srv.kvmap != null) { - for (kvmsg msg: srv.kvmap.values()) + for (kvmsg msg : srv.kvmap.values()) msg.destroy(); } srv.active = false; @@ -208,23 +205,22 @@ public int handle(ZLoop loop, PollItem item, Object arg) Socket snapshot = srv.ctx.createSocket(ZMQ.DEALER); snapshot.connect(String.format("tcp://localhost:%d", srv.peer)); - System.out.printf("I: asking for snapshot from: tcp://localhost:%d\n", - srv.peer); + System.out.printf("I: asking for snapshot from: tcp://localhost:%d\n", srv.peer); snapshot.sendMore("ICANHAZ?"); snapshot.send(""); // blank subtree to get all while (true) { kvmsg msg = kvmsg.recv(snapshot); if (msg == null) - break; // Interrupted + break; // Interrupted if (msg.getKey().equals("KTHXBAI")) { srv.sequence = msg.getSequence(); msg.destroy(); - break; // Done + break; // Done } msg.store(srv.kvmap); } - System.out.printf("I: received snapshot=%d\n",srv.sequence); + System.out.printf("I: received snapshot=%d\n", srv.sequence); srv.ctx.destroySocket(snapshot); } @@ -245,7 +241,7 @@ public int handle(ZLoop loop, PollItem item, Object arg) if (msg.getSequence() > srv.sequence) { srv.sequence = msg.getSequence(); msg.store(srv.kvmap); - System.out.printf("I: received update=%d\n",srv.sequence); + System.out.printf("I: received update=%d\n", srv.sequence); } } msg.destroy(); @@ -257,19 +253,16 @@ public int handle(ZLoop loop, PollItem item, Object arg) public clonesrv6(boolean primary) { if (primary) { - bStar = new bstar(true, "tcp://*:5003", - "tcp://localhost:5004"); - bStar.voter("tcp://*:5556", - ZMQ.ROUTER, new Snapshots(), this); + bStar = new bstar(true, "tcp://*:5003", "tcp://localhost:5004"); + bStar.voter("tcp://*:5556", ZMQ.ROUTER, new Snapshots(), this); port = 5556; peer = 5566; this.primary = true; - } else { - bStar = new bstar(false, "tcp://*:5004", - "tcp://localhost:5003"); - bStar.voter("tcp://*:5566", - ZMQ.ROUTER, new Snapshots(), this); + } + else { + bStar = new bstar(false, "tcp://*:5004", "tcp://localhost:5003"); + bStar.voter("tcp://*:5566", ZMQ.ROUTER, new Snapshots(), this); port = 5566; peer = 5556; @@ -289,7 +282,7 @@ public clonesrv6(boolean primary) collector = ctx.createSocket(ZMQ.SUB); collector.subscribe(ZMQ.SUBSCRIPTION_ALL); publisher.bind(String.format("tcp://*:%d", port + 1)); - collector.bind(String.format("tcp://*:%d", port+2)); + collector.bind(String.format("tcp://*:%d", port + 2)); // Set up our own clone client interface to peer subscriber = ctx.createSocket(ZMQ.SUB); @@ -319,11 +312,11 @@ public void run() bStar.start(); // Interrupted, so shut down - for (kvmsg value: pending) + for (kvmsg value : pending) value.destroy(); bStar.destroy(); - for (kvmsg value: kvmap.values()) + for (kvmsg value : kvmap.values()) value.destroy(); ctx.destroy(); @@ -334,8 +327,8 @@ public void run() private static void sendSingle(kvmsg msg, byte[] identity, String subtree, Socket socket) { if (msg.getKey().startsWith(subtree)) { - socket.send (identity, // Choose recipient - ZMQ.SNDMORE); + socket.send(identity, // Choose recipient + ZMQ.SNDMORE); msg.send(socket); } } @@ -347,11 +340,11 @@ private static void sendSingle(kvmsg msg, byte[] identity, String subtree, Socke // If message was already on pending list, remove it and return TRUE, // else return FALSE. - boolean wasPending (kvmsg msg) + boolean wasPending(kvmsg msg) { Iterator it = pending.iterator(); while (it.hasNext()) { - if(java.util.Arrays.equals(msg.UUID(), it.next().UUID())){ + if (java.util.Arrays.equals(msg.UUID(), it.next().UUID())) { it.remove(); return true; } @@ -360,7 +353,6 @@ boolean wasPending (kvmsg msg) return false; } - // We purge ephemeral values using exactly the same code as in // the previous clonesrv5 example. // .skip @@ -394,9 +386,11 @@ public static void main(String[] args) if (args.length == 1 && "-p".equals(args[0])) { srv = new clonesrv6(true); - } else if (args.length == 1 && "-b".equals(args[0])) { + } + else if (args.length == 1 && "-b".equals(args[0])) { srv = new clonesrv6(false); - } else { + } + else { System.out.printf("Usage: clonesrv4 { -p | -b }\n"); System.exit(0); } diff --git a/src/test/java/guide/espresso.java b/src/test/java/guide/espresso.java index e3cc4c717..66b3aabaa 100644 --- a/src/test/java/guide/espresso.java +++ b/src/test/java/guide/espresso.java @@ -1,5 +1,7 @@ package guide; +import java.util.Random; + import org.zeromq.ZContext; import org.zeromq.ZFrame; import org.zeromq.ZMQ; @@ -7,8 +9,6 @@ import org.zeromq.ZThread; import org.zeromq.ZThread.IAttachedRunnable; -import java.util.Random; - // Espresso Pattern // This shows how to capture data using a pub-sub proxy public class espresso @@ -31,7 +31,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) while (count < 5) { String string = subscriber.recvStr(); if (string == null) - break; // Interrupted + break; // Interrupted count++; } ctx.destroySocket(subscriber); @@ -52,10 +52,11 @@ public void run(Object[] args, ZContext ctx, Socket pipe) while (!Thread.currentThread().isInterrupted()) { String string = String.format("%c-%05d", 'A' + rand.nextInt(10), rand.nextInt(100000)); if (!publisher.send(string)) - break; // Interrupted + break; // Interrupted try { - Thread.sleep(100); // Wait for 1/10th second - } catch (InterruptedException e) { + Thread.sleep(100); // Wait for 1/10th second + } + catch (InterruptedException e) { } } ctx.destroySocket(publisher); @@ -75,7 +76,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) while (true) { ZFrame frame = ZFrame.recvFrame(pipe); if (frame == null) - break; // Interrupted + break; // Interrupted frame.print(null); frame.destroy(); } @@ -97,7 +98,7 @@ public static void main(String[] argv) Socket publisher = ctx.createSocket(ZMQ.XPUB); publisher.bind("tcp://*:6001"); Socket listener = ZThread.fork(ctx, new Listener()); - ZMQ.proxy (subscriber, publisher, listener); + ZMQ.proxy(subscriber, publisher, listener); System.out.println(" interrupted"); // Tell attached threads to exit diff --git a/src/test/java/guide/flcliapi.java b/src/test/java/guide/flcliapi.java index 18f0b766e..b511d5846 100644 --- a/src/test/java/guide/flcliapi.java +++ b/src/test/java/guide/flcliapi.java @@ -1,5 +1,11 @@ package guide; +import java.nio.channels.Selector; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; @@ -8,11 +14,6 @@ import org.zeromq.ZThread; import org.zeromq.ZThread.IAttachedRunnable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - // flcliapi class - Freelance Pattern agent class // Implements the Freelance Protocol at http://rfc.zeromq.org/spec:10 public class flcliapi @@ -20,9 +21,9 @@ public class flcliapi // If not a single service replies within this time, give up private static final int GLOBAL_TIMEOUT = 2500; // PING interval for servers we think are alive - private static final int PING_INTERVAL = 2000; // msecs + private static final int PING_INTERVAL = 2000; // msecs // Server considered dead if silent for this long - private static final int SERVER_TTL = 6000; // msecs + private static final int SERVER_TTL = 6000; // msecs // .split API structure // This API works in two halves, a common pattern for APIs that need to @@ -32,8 +33,8 @@ public class flcliapi // inproc pipe socket: // Structure of our frontend class - private ZContext ctx; // Our context wrapper - private Socket pipe; // Pipe through to flcliapi agent + private ZContext ctx; // Our context wrapper + private Socket pipe; // Pipe through to flcliapi agent public flcliapi() { @@ -61,8 +62,9 @@ public void connect(String endpoint) msg.send(pipe); try { - Thread.sleep(100); // Allow connection to come up - } catch (InterruptedException e) { + Thread.sleep(100); // Allow connection to come up + } + catch (InterruptedException e) { } } @@ -82,7 +84,6 @@ public ZMsg request(ZMsg request) return reply; } - // .split backend agent // Here we see the backend agent. It runs as an attached thread, talking // to its parent over a pipe socket. It is a fairly complex piece of work @@ -92,10 +93,10 @@ public ZMsg request(ZMsg request) // Simple class for one server we talk to private static class Server { - private String endpoint; // Server identity/endpoint - private boolean alive; // 1 if known to be alive - private long pingAt; // Next ping at this time - private long expires; // Expires at this time + private String endpoint; // Server identity/endpoint + private boolean alive; // 1 if known to be alive + private long pingAt; // Next ping at this time + private long expires; // Expires at this time protected Server(String endpoint) { @@ -104,6 +105,7 @@ protected Server(String endpoint) pingAt = System.currentTimeMillis() + PING_INTERVAL; expires = System.currentTimeMillis() + SERVER_TTL; } + protected void destroy() { } @@ -134,15 +136,15 @@ private long tickless(long tickless) // Simple class for one background agent private static class Agent { - private ZContext ctx; // Own context - private Socket pipe; // Socket to talk back to application - private Socket router; // Socket to talk to servers - private Map servers; // Servers we've connected to - private List actives; // Servers we know are alive - private int sequence; // Number of requests ever sent - private ZMsg request; // Current request if any - private ZMsg reply; // Current reply if any - private long expires; // Timeout for request/reply + private ZContext ctx; // Own context + private Socket pipe; // Socket to talk back to application + private Socket router; // Socket to talk to servers + private Map servers; // Servers we've connected to + private List actives; // Servers we know are alive + private int sequence; // Number of requests ever sent + private ZMsg request; // Current request if any + private ZMsg reply; // Current reply if any + private long expires; // Timeout for request/reply protected Agent(ZContext ctx, Socket pipe) { @@ -155,7 +157,7 @@ protected Agent(ZContext ctx, Socket pipe) protected void destroy() { - for(Server server: servers.values()) + for (Server server : servers.values()) server.destroy(); } @@ -179,9 +181,8 @@ private void controlMessage() server.pingAt = System.currentTimeMillis() + PING_INTERVAL; server.expires = System.currentTimeMillis() + SERVER_TTL; } - else - if (command.equals("REQUEST")) { - assert (request == null); // Strict request-reply cycle + else if (command.equals("REQUEST")) { + assert (request == null); // Strict request-reply cycle // Prefix request with getSequence number and empty envelope String sequenceText = String.format("%d", ++sequence); msg.push(sequenceText); @@ -221,12 +222,12 @@ private void routerMessage() request.destroy(); request = null; } - else - reply.destroy(); + else reply.destroy(); } } + // .split backend agent implementation // Finally, here's the agent task itself, which polls its two sockets // and processes incoming messages: @@ -237,6 +238,7 @@ static private class FreelanceAgent implements IAttachedRunnable public void run(Object[] args, ZContext ctx, Socket pipe) { Agent agent = new Agent(ctx, pipe); + Selector selector = ctx.createSelector(); Poller poller = ctx.createPoller(2); poller.register(agent.pipe, Poller.POLLIN); @@ -245,11 +247,10 @@ public void run(Object[] args, ZContext ctx, Socket pipe) while (!Thread.currentThread().isInterrupted()) { // Calculate tickless timer, up to 1 hour long tickless = System.currentTimeMillis() + 1000 * 3600; - if (agent.request != null - && tickless > agent.expires) + if (agent.request != null && tickless > agent.expires) tickless = agent.expires; - for (Server server: agent.servers.values()) { + for (Server server : agent.servers.values()) { long newTickless = server.tickless(tickless); if (newTickless > 0) tickless = newTickless; @@ -257,7 +258,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) int rc = poller.poll(tickless - System.currentTimeMillis()); if (rc == -1) - break; // Context has been shut down + break; // Context has been shut down if (poller.pollin(0)) agent.controlMessage(); @@ -293,7 +294,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) // Disconnect and delete any expired servers // Send heartbeats to idle servers if needed - for (Server server: agent.servers.values()) + for (Server server : agent.servers.values()) server.ping(agent.router); } agent.destroy(); diff --git a/src/test/java/guide/flclient1.java b/src/test/java/guide/flclient1.java index 64c865d09..d0d401d6b 100644 --- a/src/test/java/guide/flclient1.java +++ b/src/test/java/guide/flclient1.java @@ -1,5 +1,7 @@ package guide; +import java.nio.channels.Selector; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; @@ -11,10 +13,11 @@ public class flclient1 { private static final int REQUEST_TIMEOUT = 1000; - private static final int MAX_RETRIES = 3; // Before we abandon + private static final int MAX_RETRIES = 3; // Before we abandon - private static ZMsg tryRequest (ZContext ctx, String endpoint, ZMsg request) + private static ZMsg tryRequest(ZContext ctx, String endpoint, ZMsg request) { + Selector selector = ctx.createSelector(); System.out.printf("I: trying echo service at %s...\n", endpoint); Socket client = ctx.createSocket(ZMQ.REQ); client.connect(endpoint); @@ -41,7 +44,7 @@ private static ZMsg tryRequest (ZContext ctx, String endpoint, ZMsg request) // to. If it has two or more servers to talk to, it will try each server just // once: - public static void main (String[] argv) + public static void main(String[] argv) { ZContext ctx = new ZContext(); ZMsg request = new ZMsg(); @@ -50,16 +53,15 @@ public static void main (String[] argv) int endpoints = argv.length; if (endpoints == 0) - System.out.printf ("I: syntax: flclient1 ...\n"); - else - if (endpoints == 1) { + System.out.printf("I: syntax: flclient1 ...\n"); + else if (endpoints == 1) { // For one endpoint, we retry N times int retries; for (retries = 0; retries < MAX_RETRIES; retries++) { - String endpoint = argv [0]; + String endpoint = argv[0]; reply = tryRequest(ctx, endpoint, request); if (reply != null) - break; // Successful + break; // Successful System.out.printf("W: no response from %s, retrying...\n", endpoint); } } @@ -68,17 +70,18 @@ public static void main (String[] argv) int endpointNbr; for (endpointNbr = 0; endpointNbr < endpoints; endpointNbr++) { String endpoint = argv[endpointNbr]; - reply = tryRequest (ctx, endpoint, request); + reply = tryRequest(ctx, endpoint, request); if (reply != null) - break; // Successful - System.out.printf ("W: no response from %s\n", endpoint); + break; // Successful + System.out.printf("W: no response from %s\n", endpoint); } } if (reply != null) { - System.out.printf ("Service is running OK\n"); + System.out.printf("Service is running OK\n"); reply.destroy(); } - request.destroy();; + request.destroy(); + ; ctx.destroy(); } diff --git a/src/test/java/guide/flclient2.java b/src/test/java/guide/flclient2.java index db854ad85..8114a965c 100644 --- a/src/test/java/guide/flclient2.java +++ b/src/test/java/guide/flclient2.java @@ -1,5 +1,7 @@ package guide; +import java.nio.channels.Selector; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; @@ -17,10 +19,10 @@ public class flclient2 // Here is the {{flclient}} class implementation. Each instance has a // context, a DEALER socket it uses to talk to the servers, a counter // of how many servers it's connected to, and a request getSequence number: - private ZContext ctx; // Our context wrapper - private Socket socket; // DEALER socket talking to servers - private int servers; // How many servers we have connected to - private int sequence; // Number of requests ever sent + private ZContext ctx; // Our context wrapper + private Socket socket; // DEALER socket talking to servers + private int servers; // How many servers we have connected to + private int sequence; // Number of requests ever sent public flclient2() { @@ -41,6 +43,8 @@ private void connect(String endpoint) private ZMsg request(ZMsg request) { + Selector selector = ctx.createSelector(); + // Prefix request with getSequence number and empty envelope String sequenceText = String.format("%d", ++sequence); request.push(sequenceText); @@ -79,10 +83,10 @@ private ZMsg request(ZMsg request) } - public static void main (String[] argv) + public static void main(String[] argv) { if (argv.length == 0) { - System.out.printf ("I: syntax: flclient2 ...\n"); + System.out.printf("I: syntax: flclient2 ...\n"); System.exit(0); } @@ -107,8 +111,7 @@ public static void main (String[] argv) } reply.destroy(); } - System.out.printf ("Average round trip cost: %d usec\n", - (int) (System.currentTimeMillis() - start) / 10); + System.out.printf("Average round trip cost: %d usec\n", (int) (System.currentTimeMillis() - start) / 10); client.destroy(); } diff --git a/src/test/java/guide/flclient3.java b/src/test/java/guide/flclient3.java index 340bdc9e7..e5ba67a3b 100644 --- a/src/test/java/guide/flclient3.java +++ b/src/test/java/guide/flclient3.java @@ -1,16 +1,12 @@ package guide; -import org.zeromq.ZContext; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.PollItem; -import org.zeromq.ZMQ.Socket; import org.zeromq.ZMsg; // Freelance client - Model 3 // Uses flcliapi class to encapsulate Freelance pattern public class flclient3 { - public static void main (String[] argv) + public static void main(String[] argv) { // Create new freelance client object flcliapi client = new flcliapi(); @@ -33,8 +29,7 @@ public static void main (String[] argv) } reply.destroy(); } - System.out.printf("Average round trip cost: %d usec\n", - (int) (System.currentTimeMillis() - start) / 10); + System.out.printf("Average round trip cost: %d usec\n", (int) (System.currentTimeMillis() - start) / 10); client.destroy(); } diff --git a/src/test/java/guide/flserver1.java b/src/test/java/guide/flserver1.java index 38be9ae94..9db5046d3 100644 --- a/src/test/java/guide/flserver1.java +++ b/src/test/java/guide/flserver1.java @@ -19,16 +19,16 @@ public static void main(String[] args) Socket server = ctx.createSocket(ZMQ.REP); server.bind(args[0]); - System.out.printf ("I: echo service is ready at %s\n", args[0]); + System.out.printf("I: echo service is ready at %s\n", args[0]); while (true) { ZMsg msg = ZMsg.recvMsg(server); if (msg == null) - break; // Interrupted + break; // Interrupted msg.send(server); } if (Thread.currentThread().isInterrupted()) - System.out.printf ("W: interrupted\n"); + System.out.printf("W: interrupted\n"); - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/flserver2.java b/src/test/java/guide/flserver2.java index f35e8401b..4011ee83b 100644 --- a/src/test/java/guide/flserver2.java +++ b/src/test/java/guide/flserver2.java @@ -20,11 +20,11 @@ public static void main(String[] args) Socket server = ctx.createSocket(ZMQ.REP); server.bind(args[0]); - System.out.printf ("I: echo service is ready at %s\n", args[0]); + System.out.printf("I: echo service is ready at %s\n", args[0]); while (true) { ZMsg request = ZMsg.recvMsg(server); if (request == null) - break; // Interrupted + break; // Interrupted // Fail nastily if run against wrong client assert (request.size() == 2); @@ -38,8 +38,8 @@ public static void main(String[] args) reply.send(server); } if (Thread.currentThread().isInterrupted()) - System.out.printf ("W: interrupted\n"); + System.out.printf("W: interrupted\n"); - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/flserver3.java b/src/test/java/guide/flserver3.java index bf54c47ee..2c2389656 100644 --- a/src/test/java/guide/flserver3.java +++ b/src/test/java/guide/flserver3.java @@ -21,7 +21,7 @@ public static void main(String[] args) Socket server = ctx.createSocket(ZMQ.ROUTER); server.setIdentity(connectEndpoint.getBytes(ZMQ.CHARSET)); server.bind(bindEndpoint); - System.out.printf ("I: service is ready at %s\n", bindEndpoint); + System.out.printf("I: service is ready at %s\n", bindEndpoint); while (!Thread.currentThread().isInterrupted()) { ZMsg request = ZMsg.recvMsg(server); @@ -29,7 +29,7 @@ public static void main(String[] args) request.dump(System.out); if (request == null) - break; // Interrupted + break; // Interrupted // Frame 0: identity of client // Frame 1: PING, or client control frame @@ -50,8 +50,8 @@ public static void main(String[] args) reply.send(server); } if (Thread.currentThread().isInterrupted()) - System.out.printf ("W: interrupted\n"); + System.out.printf("W: interrupted\n"); - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/hwclient.java b/src/test/java/guide/hwclient.java index f9affe282..0fd98f43f 100644 --- a/src/test/java/guide/hwclient.java +++ b/src/test/java/guide/hwclient.java @@ -8,26 +8,28 @@ import org.zeromq.ZMQ; -public class hwclient{ +public class hwclient +{ - public static void main (String[] args){ + public static void main(String[] args) + { ZMQ.Context context = ZMQ.context(1); // Socket to talk to server System.out.println("Connecting to hello world server"); ZMQ.Socket socket = context.socket(ZMQ.REQ); - socket.connect ("tcp://localhost:5555"); + socket.connect("tcp://localhost:5555"); - for(int requestNbr = 0; requestNbr != 10; requestNbr++) { - String request = "Hello" ; - System.out.println("Sending Hello " + requestNbr ); - socket.send(request.getBytes (ZMQ.CHARSET), 0); + for (int requestNbr = 0; requestNbr != 10; requestNbr++) { + String request = "Hello"; + System.out.println("Sending Hello " + requestNbr); + socket.send(request.getBytes(ZMQ.CHARSET), 0); byte[] reply = socket.recv(0); - System.out.println("Received " + new String (reply, ZMQ.CHARSET) + " " + requestNbr); + System.out.println("Received " + new String(reply, ZMQ.CHARSET) + " " + requestNbr); } - + socket.close(); context.term(); } diff --git a/src/test/java/guide/hwserver.java b/src/test/java/guide/hwserver.java index 43a98d5f8..83ce0b325 100644 --- a/src/test/java/guide/hwserver.java +++ b/src/test/java/guide/hwserver.java @@ -8,29 +8,31 @@ import org.zeromq.ZMQ; -public class hwserver{ +public class hwserver +{ - public static void main (String[] args) throws Exception{ + public static void main(String[] args) throws Exception + { ZMQ.Context context = ZMQ.context(1); // Socket to talk to clients ZMQ.Socket socket = context.socket(ZMQ.REP); - socket.bind ("tcp://*:5555"); + socket.bind("tcp://*:5555"); - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { byte[] reply = socket.recv(0); System.out.println("Received " + ": [" + new String(reply, ZMQ.CHARSET) + "]"); // Create a "Hello" message. - String request = "world" ; + String request = "world"; // Send the message - socket.send(request.getBytes (ZMQ.CHARSET), 0); + socket.send(request.getBytes(ZMQ.CHARSET), 0); Thread.sleep(1000); // Do some 'work' } - + socket.close(); context.term(); } diff --git a/src/test/java/guide/identity.java b/src/test/java/guide/identity.java index a06aad92f..45cff8d26 100644 --- a/src/test/java/guide/identity.java +++ b/src/test/java/guide/identity.java @@ -7,9 +7,11 @@ /** * Demonstrate identities as used by the request-reply pattern. */ -public class identity { +public class identity +{ - public static void main (String[] args) throws InterruptedException { + public static void main(String[] args) throws InterruptedException + { Context context = ZMQ.context(1); Socket sink = context.socket(ZMQ.ROUTER); @@ -19,18 +21,18 @@ public static void main (String[] args) throws InterruptedException { Socket anonymous = context.socket(ZMQ.REQ); anonymous.connect("inproc://example"); - anonymous.send ("ROUTER uses a generated UUID",0); - ZHelper.dump (sink); + anonymous.send("ROUTER uses a generated UUID", 0); + ZHelper.dump(sink); // Then set the identity ourself Socket identified = context.socket(ZMQ.REQ); - identified.setIdentity("Hello".getBytes (ZMQ.CHARSET)); - identified.connect ("inproc://example"); + identified.setIdentity("Hello".getBytes(ZMQ.CHARSET)); + identified.connect("inproc://example"); identified.send("ROUTER socket uses REQ's socket identity", 0); - ZHelper.dump (sink); + ZHelper.dump(sink); - sink.close (); - anonymous.close (); + sink.close(); + anonymous.close(); identified.close(); context.term(); diff --git a/src/test/java/guide/interrupt.java b/src/test/java/guide/interrupt.java index ccf9127ec..b2058947b 100644 --- a/src/test/java/guide/interrupt.java +++ b/src/test/java/guide/interrupt.java @@ -9,45 +9,53 @@ import org.zeromq.ZMQ; import org.zeromq.ZMQException; -public class interrupt { - public static void main (String[] args) { - // Prepare our context and socket - final ZMQ.Context context = ZMQ.context(1); - - final Thread zmqThread = new Thread() { - @Override - public void run() { - ZMQ.Socket socket = context.socket(ZMQ.REP); - socket.bind("tcp://*:5555"); - - while (!Thread.currentThread().isInterrupted()) { - try { - socket.recv (0); - } catch (ZMQException e) { - if (e.getErrorCode () == ZMQ.Error.ETERM.getCode ()) { - break; +public class interrupt +{ + public static void main(String[] args) + { + // Prepare our context and socket + final ZMQ.Context context = ZMQ.context(1); + + final Thread zmqThread = new Thread() + { + @Override + public void run() + { + ZMQ.Socket socket = context.socket(ZMQ.REP); + socket.bind("tcp://*:5555"); + + while (!Thread.currentThread().isInterrupted()) { + try { + socket.recv(0); + } + catch (ZMQException e) { + if (e.getErrorCode() == ZMQ.Error.ETERM.getCode()) { + break; + } } } - } - socket.setLinger(0); - socket.close(); - } - }; - - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - System.out.println("W: interrupt received, killing server..."); - context.term(); - try { - zmqThread.interrupt(); - zmqThread.join(); - } catch (InterruptedException e) { + socket.setLinger(0); + socket.close(); + } + }; + + Runtime.getRuntime().addShutdownHook(new Thread() + { + @Override + public void run() + { + System.out.println("W: interrupt received, killing server..."); + context.term(); + try { + zmqThread.interrupt(); + zmqThread.join(); + } + catch (InterruptedException e) { + } } - } - }); + }); - zmqThread.start(); - } + zmqThread.start(); + } } diff --git a/src/test/java/guide/kvmsg.java b/src/test/java/guide/kvmsg.java index 013bf4c9f..634f06d59 100644 --- a/src/test/java/guide/kvmsg.java +++ b/src/test/java/guide/kvmsg.java @@ -1,9 +1,5 @@ package guide; -import org.zeromq.ZContext; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.Socket; - import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; @@ -11,10 +7,14 @@ import java.util.Properties; import java.util.UUID; +import org.zeromq.ZContext; +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Socket; + public class kvmsg { // Keys are short strings - private static final int KVMSG_KEY_MAX = 255; + private static final int KVMSG_KEY_MAX = 255; // Message is formatted on wire as 4 frames: // frame 0: getKey (0MQ string) @@ -22,12 +22,12 @@ public class kvmsg // frame 2: uuid (blob, 16 bytes) // frame 3: properties (0MQ string) // frame 4: body (blob) - private static final int FRAME_KEY = 0; - private static final int FRAME_SEQ = 1; - private static final int FRAME_UUID = 2; - private static final int FRAME_PROPS = 3; - private static final int FRAME_BODY = 4; - private static final int KVMSG_FRAMES = 5; + private static final int FRAME_KEY = 0; + private static final int FRAME_SEQ = 1; + private static final int FRAME_UUID = 2; + private static final int FRAME_PROPS = 3; + private static final int FRAME_BODY = 4; + private static final int KVMSG_FRAMES = 5; // Presence indicators for each frame private boolean[] present = new boolean[KVMSG_FRAMES]; @@ -37,7 +37,7 @@ public class kvmsg private String key; // List of properties, as name=value strings private Properties props; - private int props_size; + private int props_size; // .split property encoding // These two helpers serialize a list of properties to and from a @@ -46,7 +46,7 @@ public class kvmsg private void encodeProps() { ByteBuffer msg = ByteBuffer.allocate(props_size); - for (Entry o: props.entrySet()) { + for (Entry o : props.entrySet()) { String prop = o.getKey().toString() + "=" + o.getValue().toString() + "\n"; msg.put(prop.getBytes(ZMQ.CHARSET)); } @@ -64,13 +64,12 @@ private void decodeProps() return; System.out.println("" + msg.length + " :" + new String(msg, ZMQ.CHARSET)); - for (String prop: new String(msg, ZMQ.CHARSET).split("\n")) { + for (String prop : new String(msg, ZMQ.CHARSET).split("\n")) { String[] split = prop.split("="); props.setProperty(split[0], split[1]); } } - // .split constructor and destructor // Here are the constructor and destructor for the class: @@ -105,7 +104,7 @@ public static kvmsg recv(Socket socket) break; } // Verify multipart framing - boolean rcvmore = (frameNbr < KVMSG_FRAMES - 1)? true: false; + boolean rcvmore = (frameNbr < KVMSG_FRAMES - 1) ? true : false; if (socket.hasReceiveMore() != rcvmore) { self.destroy(); break; @@ -115,6 +114,7 @@ public static kvmsg recv(Socket socket) self.decodeProps(); return self; } + // Send getKey-value message to socket; any empty frames are sent as such. public void send(Socket socket) { @@ -128,7 +128,7 @@ public void send(Socket socket) byte[] copy = ZMQ.MESSAGE_SEPARATOR; if (present[frameNbr]) copy = frame[frameNbr]; - socket.send(copy, (frameNbr < KVMSG_FRAMES - 1)? ZMQ.SNDMORE: 0); + socket.send(copy, (frameNbr < KVMSG_FRAMES - 1) ? ZMQ.SNDMORE : 0); } } @@ -143,8 +143,7 @@ public kvmsg dup() for (frameNbr = 0; frameNbr < KVMSG_FRAMES; frameNbr++) { if (present[frameNbr]) { kvmsg.frame[frameNbr] = new byte[frame[frameNbr].length]; - System.arraycopy(frame[frameNbr], 0, - kvmsg.frame[frameNbr], 0, frame[frameNbr].length); + System.arraycopy(frame[frameNbr], 0, kvmsg.frame[frameNbr], 0, frame[frameNbr].length); kvmsg.present[frameNbr] = true; } } @@ -169,8 +168,7 @@ public String getKey() } return key; } - else - return null; + else return null; } // Set message getKey as provided @@ -183,7 +181,7 @@ public void setKey(String key) } // Set message getKey using printf format - public void fmtKey(String fmt, Object ... args) + public void fmtKey(String fmt, Object... args) { setKey(String.format(fmt, args)); } @@ -196,8 +194,7 @@ public long getSequence() ByteBuffer source = ByteBuffer.wrap(frame[FRAME_SEQ]); return source.getLong(); } - else - return 0; + else return 0; } // Set message getSequence number @@ -216,8 +213,7 @@ public byte[] body() { if (present[FRAME_BODY]) return frame[FRAME_BODY]; - else - return null; + else return null; } // Set message body @@ -225,7 +221,7 @@ public void setBody(byte[] body) { byte[] msg = new byte[body.length]; System.arraycopy(body, 0, msg, 0, body.length); - frame [FRAME_BODY] = msg; + frame[FRAME_BODY] = msg; present[FRAME_BODY] = true; } @@ -240,8 +236,7 @@ public int size() { if (present[FRAME_BODY]) return frame[FRAME_BODY].length; - else - return 0; + else return 0; } // .until @@ -251,8 +246,7 @@ public byte[] UUID() { if (present[FRAME_UUID]) return frame[FRAME_UUID]; - else - return null; + else return null; } // Sets the UUID to a randomly generated value @@ -274,14 +268,13 @@ public String getProp(String name) // Set message property. Property name cannot contain '='. Max length of // value is 255 chars. - public void setProp(String name, String fmt, Object ... args) + public void setProp(String name, String fmt, Object... args) { String value = String.format(fmt, args); Object old = props.setProperty(name, value); if (old != null) props_size -= old.toString().length(); - else - props_size += name.length() + 2; + else props_size += name.length() + 2; props_size += value.length(); } @@ -296,8 +289,8 @@ public void store(Map hash) if (present[FRAME_KEY] && present[FRAME_BODY]) { hash.put(getKey(), this); } - } else - hash.remove(getKey()); + } + else hash.remove(getKey()); } // .split dump method @@ -313,7 +306,7 @@ public void dump() // .until System.err.printf("[size:%d] ", size); System.err.printf("["); - for (String key: props.stringPropertyNames()) { + for (String key : props.stringPropertyNames()) { System.err.printf("%s=%s;", key, props.getProperty(key)); } System.err.printf("]"); @@ -340,7 +333,7 @@ public void test(boolean verbose) Socket input = ctx.createSocket(ZMQ.DEALER); input.connect("ipc://kvmsg_selftest.ipc"); - Map kvmap = new HashMap(); + Map kvmap = new HashMap(); // .until // Test send and receive of simple message @@ -353,7 +346,7 @@ public void test(boolean verbose) kvmsg.send(output); kvmsg.store(kvmap); - kvmsg = kvmsg.recv(input); + kvmsg = guide.kvmsg.recv(input); if (verbose) kvmsg.dump(); assert (kvmsg.getKey().equals("getKey")); @@ -373,7 +366,7 @@ public void test(boolean verbose) kvmsg.send(output); kvmsg.destroy(); - kvmsg = kvmsg.recv(input); + kvmsg = guide.kvmsg.recv(input); if (verbose) kvmsg.dump(); assert (kvmsg.key.equals("getKey")); @@ -382,7 +375,7 @@ public void test(boolean verbose) // .skip // Shutdown and destroy all objects - ctx.destroy(); + ctx.close(); System.out.printf("OK\n"); diff --git a/src/test/java/guide/kvsimple.java b/src/test/java/guide/kvsimple.java index c82df8dd5..9fe306f41 100644 --- a/src/test/java/guide/kvsimple.java +++ b/src/test/java/guide/kvsimple.java @@ -5,101 +5,113 @@ import org.zeromq.ZMQ; import org.zeromq.ZMQ.Socket; + /** * * A simple getKey value message class * @author Danish Shrestha * */ -public class kvsimple { - private final String key; - private long sequence; - private final byte[] body; +public class kvsimple +{ + private final String key; + private long sequence; + private final byte[] body; - public kvsimple(String key, long sequence, byte[] body) { - this.key = key; - this.sequence = sequence; - this.body = body; // clone if needed - } + public kvsimple(String key, long sequence, byte[] body) + { + this.key = key; + this.sequence = sequence; + this.body = body; // clone if needed + } - public String getKey() { - return key; - } + public String getKey() + { + return key; + } - public long getSequence() { - return sequence; - } + public long getSequence() + { + return sequence; + } public void setSequence(long sequence) { this.sequence = sequence; } - public byte[] getBody() { - return body; - } + public byte[] getBody() + { + return body; + } - public void send(Socket publisher) { + public void send(Socket publisher) + { - publisher.send(key.getBytes(ZMQ.CHARSET), ZMQ.SNDMORE); + publisher.send(key.getBytes(ZMQ.CHARSET), ZMQ.SNDMORE); - ByteBuffer bb = ByteBuffer.allocate(8); - bb.asLongBuffer().put(sequence); - publisher.send(bb.array(), ZMQ.SNDMORE); + ByteBuffer bb = ByteBuffer.allocate(8); + bb.asLongBuffer().put(sequence); + publisher.send(bb.array(), ZMQ.SNDMORE); - publisher.send(body, 0); - } + publisher.send(body, 0); + } - public static kvsimple recv(Socket updates) { - byte [] data = updates.recv(0); + public static kvsimple recv(Socket updates) + { + byte[] data = updates.recv(0); if (data == null || !updates.hasReceiveMore()) return null; - String key = new String(data, ZMQ.CHARSET); + String key = new String(data, ZMQ.CHARSET); data = updates.recv(0); if (data == null || !updates.hasReceiveMore()) return null; - Long sequence = ByteBuffer.wrap(data).getLong(); - byte[] body = updates.recv(0); + Long sequence = ByteBuffer.wrap(data).getLong(); + byte[] body = updates.recv(0); if (body == null || updates.hasReceiveMore()) return null; - return new kvsimple(key, sequence, body); - } - - @Override - public String toString() { - return "kvsimple [getKey=" + key + ", getSequence=" + sequence + ", body=" + Arrays.toString(body) + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(body); - result = prime * result + ((key == null) ? 0 : key.hashCode()); - result = prime * result + (int) (sequence ^ (sequence >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - kvsimple other = (kvsimple) obj; - if (!Arrays.equals(body, other.body)) - return false; - if (key == null) { - if (other.key != null) - return false; - } else if (!key.equals(other.key)) - return false; - if (sequence != other.sequence) - return false; - return true; - } + return new kvsimple(key, sequence, body); + } + + @Override + public String toString() + { + return "kvsimple [getKey=" + key + ", getSequence=" + sequence + ", body=" + Arrays.toString(body) + "]"; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(body); + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + (int) (sequence ^ (sequence >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + kvsimple other = (kvsimple) obj; + if (!Arrays.equals(body, other.body)) + return false; + if (key == null) { + if (other.key != null) + return false; + } + else if (!key.equals(other.key)) + return false; + if (sequence != other.sequence) + return false; + return true; + } } diff --git a/src/test/java/guide/lbbroker.java b/src/test/java/guide/lbbroker.java index bca8988ea..7ede97ff6 100644 --- a/src/test/java/guide/lbbroker.java +++ b/src/test/java/guide/lbbroker.java @@ -8,7 +8,8 @@ import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; -public class lbbroker { +public class lbbroker +{ private static final int NBR_CLIENTS = 10; private static final int NBR_WORKERS = 3; @@ -23,14 +24,14 @@ public void run() Context context = ZMQ.context(1); // Prepare our context and sockets - Socket client = context.socket(ZMQ.REQ); - ZHelper.setId (client); // Set a printable identity + Socket client = context.socket(ZMQ.REQ); + ZHelper.setId(client); // Set a printable identity client.connect("ipc://frontend.ipc"); // Send request, get reply client.send("HELLO"); - String reply = client.recvStr (); + String reply = client.recvStr(); System.out.println("Client: " + reply); client.close(); @@ -50,30 +51,29 @@ public void run() { Context context = ZMQ.context(1); // Prepare our context and sockets - Socket worker = context.socket(ZMQ.REQ); - ZHelper.setId (worker); // Set a printable identity + Socket worker = context.socket(ZMQ.REQ); + ZHelper.setId(worker); // Set a printable identity worker.connect("ipc://backend.ipc"); // Tell backend we're ready for work worker.send("READY"); - while(!Thread.currentThread ().isInterrupted ()) - { - String address = worker.recvStr (); - String empty = worker.recvStr (); + while (!Thread.currentThread().isInterrupted()) { + String address = worker.recvStr(); + String empty = worker.recvStr(); assert (empty.length() == 0); // Get request, send reply - String request = worker.recvStr (); + String request = worker.recvStr(); System.out.println("Worker: " + request); - worker.sendMore (address); - worker.sendMore (""); + worker.sendMore(address); + worker.sendMore(""); worker.send("OK"); } - worker.close (); - context.term (); + worker.close(); + context.term(); } } @@ -84,11 +84,12 @@ public void run() * a response back to a client. The load-balancing data structure is * just a queue of next available workers. */ - public static void main (String[] args) { + public static void main(String[] args) + { Context context = ZMQ.context(1); // Prepare our context and sockets - Socket frontend = context.socket(ZMQ.ROUTER); - Socket backend = context.socket(ZMQ.ROUTER); + Socket frontend = context.socket(ZMQ.ROUTER); + Socket backend = context.socket(ZMQ.ROUTER); frontend.bind("ipc://frontend.ipc"); backend.bind("ipc://backend.ipc"); @@ -121,32 +122,32 @@ public static void main (String[] args) { items.register(backend, Poller.POLLIN); //  Poll front-end only if we have available workers - if(workerQueue.size() > 0) + if (workerQueue.size() > 0) items.register(frontend, Poller.POLLIN); if (items.poll() < 0) - break; // Interrupted + break; // Interrupted // Handle worker activity on backend if (items.pollin(0)) { // Queue worker address for LRU routing - workerQueue.add (backend.recvStr ()); + workerQueue.add(backend.recvStr()); // Second frame is empty - String empty = backend.recvStr (); + String empty = backend.recvStr(); assert (empty.length() == 0); // Third frame is READY or else a client reply address - String clientAddr = backend.recvStr (); + String clientAddr = backend.recvStr(); // If client reply, send rest back to frontend if (!clientAddr.equals("READY")) { - empty = backend.recvStr (); + empty = backend.recvStr(); assert (empty.length() == 0); - String reply = backend.recvStr (); + String reply = backend.recvStr(); frontend.sendMore(clientAddr); frontend.sendMore(""); frontend.send(reply); @@ -160,20 +161,20 @@ public static void main (String[] args) { if (items.pollin(1)) { // Now get next client request, route to LRU worker // Client request is [address][empty][request] - String clientAddr = frontend.recvStr (); + String clientAddr = frontend.recvStr(); - String empty = frontend.recvStr (); + String empty = frontend.recvStr(); assert (empty.length() == 0); - String request = frontend.recvStr (); + String request = frontend.recvStr(); String workerAddr = workerQueue.poll(); - backend.sendMore (workerAddr); - backend.sendMore (""); - backend.sendMore (clientAddr ); - backend.sendMore (""); - backend.send (request); + backend.sendMore(workerAddr); + backend.sendMore(""); + backend.sendMore(clientAddr); + backend.sendMore(""); + backend.send(request); } } diff --git a/src/test/java/guide/lbbroker2.java b/src/test/java/guide/lbbroker2.java index bd9e8933f..fc05b3622 100644 --- a/src/test/java/guide/lbbroker2.java +++ b/src/test/java/guide/lbbroker2.java @@ -1,5 +1,9 @@ package guide; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; + import org.zeromq.ZContext; import org.zeromq.ZFrame; import org.zeromq.ZMQ; @@ -8,19 +12,15 @@ import org.zeromq.ZMsg; import org.zeromq.ZThread; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.Queue; - /** * Load-balancing broker * Demonstrates use of the high level API */ public class lbbroker2 { - private static final int NBR_CLIENTS = 10; - private static final int NBR_WORKERS = 3; - private static byte[] WORKER_READY = { '\001' }; // Signals worker is ready + private static final int NBR_CLIENTS = 10; + private static final int NBR_WORKERS = 3; + private static byte[] WORKER_READY = { '\001' }; // Signals worker is ready /** * Basic request-reply client using REQ socket @@ -28,22 +28,22 @@ public class lbbroker2 private static class ClientTask implements ZThread.IDetachedRunnable { @Override - public void run (Object ... args) + public void run(Object... args) { ZContext context = new ZContext(); // Prepare our context and sockets - Socket client = context.createSocket (ZMQ.REQ); - ZHelper.setId (client); // Set a printable identity + Socket client = context.createSocket(ZMQ.REQ); + ZHelper.setId(client); // Set a printable identity client.connect("ipc://frontend.ipc"); // Send request, get reply client.send("HELLO"); - String reply = client.recvStr (); + String reply = client.recvStr(); System.out.println("Client: " + reply); - context.destroy (); + context.close(); } } @@ -53,30 +53,29 @@ public void run (Object ... args) private static class WorkerTask implements ZThread.IDetachedRunnable { @Override - public void run (Object ... args) + public void run(Object... args) { ZContext context = new ZContext(); // Prepare our context and sockets - Socket worker = context.createSocket (ZMQ.REQ); - ZHelper.setId (worker); // Set a printable identity + Socket worker = context.createSocket(ZMQ.REQ); + ZHelper.setId(worker); // Set a printable identity worker.connect("ipc://backend.ipc"); // Tell backend we're ready for work - ZFrame frame = new ZFrame (WORKER_READY); - frame.send (worker, 0); + ZFrame frame = new ZFrame(WORKER_READY); + frame.send(worker, 0); - while(true) - { - ZMsg msg = ZMsg.recvMsg (worker); + while (true) { + ZMsg msg = ZMsg.recvMsg(worker); if (msg == null) break; - msg.getLast ().reset ("OK"); - msg.send (worker); + msg.getLast().reset("OK"); + msg.send(worker); } - context.destroy (); + context.close(); } } @@ -85,23 +84,24 @@ public void run (Object ... args) * the previous lbbroker example but uses higher level classes to start child threads * to hold the list of workers, and to read and send messages: */ - public static void main (String[] args) { + public static void main(String[] args) + { ZContext context = new ZContext(); // Prepare our context and sockets - Socket frontend = context.createSocket (ZMQ.ROUTER); - Socket backend = context.createSocket (ZMQ.ROUTER); + Socket frontend = context.createSocket(ZMQ.ROUTER); + Socket backend = context.createSocket(ZMQ.ROUTER); frontend.bind("ipc://frontend.ipc"); backend.bind("ipc://backend.ipc"); int clientNbr; for (clientNbr = 0; clientNbr < NBR_CLIENTS; clientNbr++) - ZThread.start (new ClientTask ()); + ZThread.start(new ClientTask()); for (int workerNbr = 0; workerNbr < NBR_WORKERS; workerNbr++) - ZThread.start (new WorkerTask ()); + ZThread.start(new WorkerTask()); // Queue of available workers - Queue workerQueue = new LinkedList (); + Queue workerQueue = new LinkedList(); // Here is the main loop for the load-balancer. It works the same way // as the previous example, but is a lot shorter because ZMsg class gives @@ -116,42 +116,41 @@ public static void main (String[] args) { items.register(backend, Poller.POLLIN); //  Poll front-end only if we have available workers - if(workerQueue.size() > 0) + if (workerQueue.size() > 0) items.register(frontend, Poller.POLLIN); if (items.poll() < 0) - break; // Interrupted + break; // Interrupted // Handle worker activity on backend if (items.pollin(0)) { - ZMsg msg = ZMsg.recvMsg (backend); + ZMsg msg = ZMsg.recvMsg(backend); if (msg == null) - break; // Interrupted + break; // Interrupted - ZFrame identity = msg.unwrap (); + ZFrame identity = msg.unwrap(); // Queue worker address for LRU routing - workerQueue.add (identity); + workerQueue.add(identity); // Forward message to client if it's not a READY - ZFrame frame = msg.getFirst (); - if (Arrays.equals (frame.getData (), WORKER_READY)) - msg.destroy (); - else - msg.send (frontend); + ZFrame frame = msg.getFirst(); + if (Arrays.equals(frame.getData(), WORKER_READY)) + msg.destroy(); + else msg.send(frontend); } if (items.pollin(1)) { // Get client request, route to first available worker - ZMsg msg = ZMsg.recvMsg (frontend); + ZMsg msg = ZMsg.recvMsg(frontend); if (msg != null) { - msg.wrap (workerQueue.poll ()); - msg.send (backend); + msg.wrap(workerQueue.poll()); + msg.send(backend); } } } - context.destroy (); + context.close(); } } diff --git a/src/test/java/guide/lbbroker3.java b/src/test/java/guide/lbbroker3.java index f0fea7329..6bd6e59f1 100644 --- a/src/test/java/guide/lbbroker3.java +++ b/src/test/java/guide/lbbroker3.java @@ -1,18 +1,18 @@ package guide; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; + import org.zeromq.ZContext; import org.zeromq.ZFrame; -import org.zeromq.ZMsg; import org.zeromq.ZLoop; import org.zeromq.ZMQ; import org.zeromq.ZMQ.PollItem; import org.zeromq.ZMQ.Socket; +import org.zeromq.ZMsg; import org.zeromq.ZThread; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.Queue; - /** * Load-balancing broker * Demonstrates use of the ZLoop API and reactor style @@ -21,9 +21,9 @@ */ public class lbbroker3 { - private static final int NBR_CLIENTS = 10; - private static final int NBR_WORKERS = 3; - private static byte[] WORKER_READY = { '\001' }; + private static final int NBR_CLIENTS = 10; + private static final int NBR_WORKERS = 3; + private static byte[] WORKER_READY = { '\001' }; /** * Basic request-reply client using REQ socket @@ -31,22 +31,22 @@ public class lbbroker3 private static class ClientTask implements ZThread.IDetachedRunnable { @Override - public void run (Object ... args) + public void run(Object... args) { ZContext context = new ZContext(); // Prepare our context and sockets - Socket client = context.createSocket (ZMQ.REQ); - ZHelper.setId (client); // Set a printable identity + Socket client = context.createSocket(ZMQ.REQ); + ZHelper.setId(client); // Set a printable identity client.connect("ipc://frontend.ipc"); // Send request, get reply client.send("HELLO"); - String reply = client.recvStr (); + String reply = client.recvStr(); System.out.println("Client: " + reply); - context.destroy (); + context.close(); } } @@ -56,38 +56,38 @@ public void run (Object ... args) private static class WorkerTask implements ZThread.IDetachedRunnable { @Override - public void run (Object ... args) + public void run(Object... args) { ZContext context = new ZContext(); // Prepare our context and sockets - Socket worker = context.createSocket (ZMQ.REQ); - ZHelper.setId (worker); // Set a printable identity + Socket worker = context.createSocket(ZMQ.REQ); + ZHelper.setId(worker); // Set a printable identity worker.connect("ipc://backend.ipc"); // Tell backend we're ready for work - ZFrame frame = new ZFrame (WORKER_READY); - frame.send (worker, 0); + ZFrame frame = new ZFrame(WORKER_READY); + frame.send(worker, 0); - while(true) - { - ZMsg msg = ZMsg.recvMsg (worker); + while (true) { + ZMsg msg = ZMsg.recvMsg(worker); if (msg == null) break; - msg.getLast ().reset ("OK"); - msg.send (worker); + msg.getLast().reset("OK"); + msg.send(worker); } - context.destroy (); + context.close(); } } //Our load-balancer structure, passed to reactor handlers - private static class LBBroker { - Socket frontend; // Listen to clients - Socket backend; // Listen to workers - Queue workers; // List of ready workers + private static class LBBroker + { + Socket frontend; // Listen to clients + Socket backend; // Listen to workers + Queue workers; // List of ready workers }; /** @@ -95,20 +95,22 @@ private static class LBBroker { * reactor passes it to a handler function. We have two handlers; one * for the frontend, one for the backend: */ - private static class FrontendHandler implements ZLoop.IZLoopHandler { + private static class FrontendHandler implements ZLoop.IZLoopHandler + { @Override - public int handle(ZLoop loop, PollItem item, Object arg_) { + public int handle(ZLoop loop, PollItem item, Object arg_) + { - LBBroker arg = (LBBroker)arg_; - ZMsg msg = ZMsg.recvMsg (arg.frontend); + LBBroker arg = (LBBroker) arg_; + ZMsg msg = ZMsg.recvMsg(arg.frontend); if (msg != null) { msg.wrap(arg.workers.poll()); msg.send(arg.backend); // Cancel reader on frontend if we went from 1 to 0 workers if (arg.workers.size() == 0) { - loop.removePoller (new PollItem (arg.frontend, 0)); + loop.removePoller(new PollItem(arg.frontend, 0)); } } return 0; @@ -116,12 +118,14 @@ public int handle(ZLoop loop, PollItem item, Object arg_) { } - private static class BackendHandler implements ZLoop.IZLoopHandler { + private static class BackendHandler implements ZLoop.IZLoopHandler + { @Override - public int handle(ZLoop loop, PollItem item, Object arg_) { + public int handle(ZLoop loop, PollItem item, Object arg_) + { - LBBroker arg = (LBBroker)arg_; + LBBroker arg = (LBBroker) arg_; ZMsg msg = ZMsg.recvMsg(arg.backend); if (msg != null) { ZFrame address = msg.unwrap(); @@ -130,54 +134,54 @@ public int handle(ZLoop loop, PollItem item, Object arg_) { // Enable reader on frontend if we went from 0 to 1 workers if (arg.workers.size() == 1) { - PollItem newItem = new PollItem (arg.frontend, ZMQ.Poller.POLLIN); - loop.addPoller (newItem, frontendHandler, arg); + PollItem newItem = new PollItem(arg.frontend, ZMQ.Poller.POLLIN); + loop.addPoller(newItem, frontendHandler, arg); } // Forward message to client if it's not a READY ZFrame frame = msg.getFirst(); - if (Arrays.equals (frame.getData(), WORKER_READY)) + if (Arrays.equals(frame.getData(), WORKER_READY)) msg.destroy(); - else - msg.send(arg.frontend); + else msg.send(arg.frontend); } return 0; } } private final static FrontendHandler frontendHandler = new FrontendHandler(); - private final static BackendHandler backendHandler = new BackendHandler(); + private final static BackendHandler backendHandler = new BackendHandler(); /** * And the main task now sets-up child tasks, then starts its reactor. * If you press Ctrl-C, the reactor exits and the main task shuts down. */ - public static void main (String[] args) { + public static void main(String[] args) + { ZContext context = new ZContext(); - LBBroker arg = new LBBroker (); + LBBroker arg = new LBBroker(); // Prepare our context and sockets - arg.frontend = context.createSocket (ZMQ.ROUTER); - arg.backend = context.createSocket (ZMQ.ROUTER); + arg.frontend = context.createSocket(ZMQ.ROUTER); + arg.backend = context.createSocket(ZMQ.ROUTER); arg.frontend.bind("ipc://frontend.ipc"); arg.backend.bind("ipc://backend.ipc"); int clientNbr; for (clientNbr = 0; clientNbr < NBR_CLIENTS; clientNbr++) - ZThread.start (new ClientTask ()); + ZThread.start(new ClientTask()); for (int workerNbr = 0; workerNbr < NBR_WORKERS; workerNbr++) - ZThread.start (new WorkerTask ()); + ZThread.start(new WorkerTask()); // Queue of available workers - arg.workers = new LinkedList (); + arg.workers = new LinkedList(); // Prepare reactor and fire it up ZLoop reactor = new ZLoop(context); - PollItem item = new PollItem (arg.backend, ZMQ.Poller.POLLIN); - reactor.addPoller (item, backendHandler, arg); - reactor.start (); + PollItem item = new PollItem(arg.backend, ZMQ.Poller.POLLIN); + reactor.addPoller(item, backendHandler, arg); + reactor.start(); - context.destroy (); + context.close(); } } diff --git a/src/test/java/guide/lpclient.java b/src/test/java/guide/lpclient.java index 4be6d9003..045191b8a 100644 --- a/src/test/java/guide/lpclient.java +++ b/src/test/java/guide/lpclient.java @@ -1,5 +1,7 @@ package guide; +import java.nio.channels.Selector; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; @@ -14,13 +16,14 @@ public class lpclient { - private final static int REQUEST_TIMEOUT = 2500; // msecs, (> 1000!) - private final static int REQUEST_RETRIES = 3; // Before we abandon + private final static int REQUEST_TIMEOUT = 2500; // msecs, (> 1000!) + private final static int REQUEST_RETRIES = 3; // Before we abandon private final static String SERVER_ENDPOINT = "tcp://localhost:5555"; public static void main(String[] argv) { ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); System.out.println("I: connecting to server"); Socket client = ctx.createSocket(ZMQ.REQ); assert (client != null); @@ -41,7 +44,7 @@ public static void main(String[] argv) // Poll socket for a reply, with timeout int rc = poller.poll(REQUEST_TIMEOUT); if (rc == -1) - break; // Interrupted + break; // Interrupted // Here we process a server reply and exit our loop if the // reply is valid. If we didn't a reply we close the client @@ -52,19 +55,20 @@ public static void main(String[] argv) // We got a reply from the server, must match getSequence String reply = client.recvStr(); if (reply == null) - break; // Interrupted + break; // Interrupted if (Integer.parseInt(reply) == sequence) { System.out.printf("I: server replied OK (%s)\n", reply); retriesLeft = REQUEST_RETRIES; expect_reply = 0; - } else - System.out.printf("E: malformed reply from server: %s\n", - reply); + } + else System.out.printf("E: malformed reply from server: %s\n", reply); - } else if (--retriesLeft == 0) { + } + else if (--retriesLeft == 0) { System.out.println("E: server seems to be offline, abandoning\n"); break; - } else { + } + else { System.out.println("W: no response from server, retrying\n"); // Old socket is confused; close it and open a new one poller.unregister(client); @@ -78,6 +82,6 @@ public static void main(String[] argv) } } } - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/lpserver.java b/src/test/java/guide/lpserver.java index d66a54c07..49e1c285f 100644 --- a/src/test/java/guide/lpserver.java +++ b/src/test/java/guide/lpserver.java @@ -33,12 +33,13 @@ public static void main(String[] argv) throws Exception if (cycles > 3 && rand.nextInt(3) == 0) { System.out.println("I: simulating a crash"); break; - } else if (cycles > 3 && rand.nextInt(3) == 0) { + } + else if (cycles > 3 && rand.nextInt(3) == 0) { System.out.println("I: simulating CPU overload"); Thread.sleep(2000); } System.out.printf("I: normal request (%s)\n", request); - Thread.sleep(1000); // Do some heavy work + Thread.sleep(1000); // Do some heavy work server.send(request); } server.close(); diff --git a/src/test/java/guide/lruqueue3.java b/src/test/java/guide/lruqueue3.java index 3ef131b73..6ecdbbe51 100644 --- a/src/test/java/guide/lruqueue3.java +++ b/src/test/java/guide/lruqueue3.java @@ -3,16 +3,17 @@ import java.util.LinkedList; import java.util.Queue; +import org.zeromq.ZContext; +import org.zeromq.ZFrame; import org.zeromq.ZLoop; import org.zeromq.ZMQ; import org.zeromq.ZMQ.PollItem; import org.zeromq.ZMQ.Socket; -import org.zeromq.ZContext; -import org.zeromq.ZFrame; import org.zeromq.ZMsg; class ClientThread3 extends Thread { + @Override public void run() { ZContext context = new ZContext(); @@ -33,19 +34,21 @@ public void run() String reply = new String(data, ZMQ.CHARSET); try { Thread.sleep(1000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + " Client Sent HELLO"); } - context.destroy(); + context.close(); } } class WorkerThread3 extends Thread { + @Override public void run() { ZContext context = new ZContext(); @@ -69,19 +72,18 @@ public void run() System.out.println(Thread.currentThread().getName() + " Worker Sent OK"); } - context.destroy(); + context.close(); } } //Our LRU queue structure, passed to reactor handlers class LRUQueueArg { - Socket frontend; // Listen to clients - Socket backend; // Listen to workers - Queue workers; // List of ready workers + Socket frontend; // Listen to clients + Socket backend; // Listen to workers + Queue workers; // List of ready workers }; - //In the reactor design, each time a message arrives on a socket, the //reactor passes it to a handler function. We have two handlers; one //for the frontend, one for the backend: @@ -134,8 +136,7 @@ public int handle(ZLoop loop, PollItem item, Object arg_) ZFrame frame = msg.getFirst(); if (new String(frame.getData(), ZMQ.CHARSET).equals(lruqueue3.LRU_READY)) msg.destroy(); - else - msg.send(arg.frontend); + else msg.send(arg.frontend); } return 0; } @@ -145,9 +146,9 @@ public int handle(ZLoop loop, PollItem item, Object arg_) public class lruqueue3 { - public final static String LRU_READY = "\001"; + public final static String LRU_READY = "\001"; protected final static FrontendHandler handle_frontend = new FrontendHandler(); - protected final static BackendHandler handle_backend = new BackendHandler(); + protected final static BackendHandler handle_backend = new BackendHandler(); public static void main(String[] args) { @@ -159,7 +160,6 @@ public static void main(String[] args) arg.frontend = frontend; arg.backend = backend; - frontend.bind("ipc://frontend.ipc"); backend.bind("ipc://backend.ipc"); @@ -182,12 +182,11 @@ public static void main(String[] args) reactor.start(); reactor.destroy(); - for (ZFrame frame : arg.workers) { frame.destroy(); } - context.destroy(); + context.close(); System.exit(0); diff --git a/src/test/java/guide/lvcache.java b/src/test/java/guide/lvcache.java index c4913e65e..bb255e0af 100644 --- a/src/test/java/guide/lvcache.java +++ b/src/test/java/guide/lvcache.java @@ -1,14 +1,15 @@ package guide; +import java.nio.channels.Selector; +import java.util.HashMap; +import java.util.Map; + import org.zeromq.ZContext; import org.zeromq.ZFrame; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; -import java.util.HashMap; -import java.util.Map; - // Last value cache // Uses XPUB subscription messages to re-send data public class lvcache @@ -16,6 +17,7 @@ public class lvcache public static void main(String[] args) { ZContext context = new ZContext(); + Selector selector = context.createSelector(); Socket frontend = context.createSocket(ZMQ.SUB); frontend.bind("tcp://*:5557"); Socket backend = context.createSocket(ZMQ.XPUB); @@ -37,7 +39,7 @@ public static void main(String[] args) // if anything: while (true) { if (poller.poll(1000) == -1) - break; // Interrupted + break; // Interrupted // Any new topic data we cache and then forward if (poller.pollin(0)) { @@ -58,9 +60,9 @@ public static void main(String[] args) break; // Event is one byte 0=unsub or 1=sub, followed by topic byte[] event = frame.getData(); - if (event [0] == 1) { - String topic = new String(event, 1, event.length -1, ZMQ.CHARSET); - System.out.printf ("Sending cached topic %s\n", topic); + if (event[0] == 1) { + String topic = new String(event, 1, event.length - 1, ZMQ.CHARSET); + System.out.printf("Sending cached topic %s\n", topic); String previous = cache.get(topic); if (previous != null) { backend.sendMore(topic); @@ -70,6 +72,6 @@ public static void main(String[] args) frame.destroy(); } } - context.destroy(); + context.close(); } } diff --git a/src/test/java/guide/mdbroker.java b/src/test/java/guide/mdbroker.java index 6ef6e90b6..c8df6b506 100644 --- a/src/test/java/guide/mdbroker.java +++ b/src/test/java/guide/mdbroker.java @@ -15,26 +15,28 @@ * Majordomo Protocol broker * A minimal implementation of http://rfc.zeromq.org/spec:7 and spec:8 */ -public class mdbroker { +public class mdbroker +{ // We'd normally pull these from config data private static final String INTERNAL_SERVICE_PREFIX = "mmi."; - private static final int HEARTBEAT_LIVENESS = 3; // 3-5 is reasonable - private static final int HEARTBEAT_INTERVAL = 2500; // msecs - private static final int HEARTBEAT_EXPIRY = HEARTBEAT_INTERVAL - * HEARTBEAT_LIVENESS; + private static final int HEARTBEAT_LIVENESS = 3; // 3-5 is reasonable + private static final int HEARTBEAT_INTERVAL = 2500; // msecs + private static final int HEARTBEAT_EXPIRY = HEARTBEAT_INTERVAL * HEARTBEAT_LIVENESS; // --------------------------------------------------------------------- /** * This defines a single service. */ - private static class Service { - public final String name; // Service name - Deque requests; // List of client requests - Deque waiting; // List of waiting workers - - public Service(String name) { + private static class Service + { + public final String name; // Service name + Deque requests; // List of client requests + Deque waiting; // List of waiting workers + + public Service(String name) + { this.name = name; this.requests = new ArrayDeque(); this.waiting = new ArrayDeque(); @@ -44,39 +46,41 @@ public Service(String name) { /** * This defines one worker, idle or active. */ - private static class Worker { - String identity;// Identity of worker - ZFrame address;// Address frame to route to + private static class Worker + { + String identity;// Identity of worker + ZFrame address; // Address frame to route to Service service; // Owning service, if known - long expiry;// Expires at unless heartbeat + long expiry; // Expires at unless heartbeat - public Worker(String identity, ZFrame address) { + public Worker(String identity, ZFrame address) + { this.address = address; this.identity = identity; - this.expiry = System.currentTimeMillis() + HEARTBEAT_INTERVAL - * HEARTBEAT_LIVENESS; + this.expiry = System.currentTimeMillis() + HEARTBEAT_INTERVAL * HEARTBEAT_LIVENESS; } } // --------------------------------------------------------------------- - private ZContext ctx;// Our context + private ZContext ctx; // Our context private ZMQ.Socket socket; // Socket for clients & workers - private long heartbeatAt;// When to send HEARTBEAT - private Map services;// known services - private Map workers;// known workers - private Deque waiting;// idle workers + private long heartbeatAt;// When to send HEARTBEAT + private Map services; // known services + private Map workers; // known workers + private Deque waiting; // idle workers - private boolean verbose = false;// Print activity to stdout - private Formatter log = new Formatter(System.out); + private boolean verbose = false; // Print activity to stdout + private Formatter log = new Formatter(System.out); // --------------------------------------------------------------------- /** * Main method - create and start new broker. */ - public static void main(String[] args) { + public static void main(String[] args) + { mdbroker broker = new mdbroker(args.length > 0 && "-v".equals(args[0])); // Can be called multiple times with different endpoints broker.bind("tcp://*:5555"); @@ -86,7 +90,8 @@ public static void main(String[] args) { /** * Initialize broker state. */ - public mdbroker(boolean verbose) { + public mdbroker(boolean verbose) + { this.verbose = verbose; this.services = new HashMap(); this.workers = new HashMap(); @@ -101,7 +106,8 @@ public mdbroker(boolean verbose) { /** * Main broker work happens here */ - public void mediate() { + public void mediate() + { while (!Thread.currentThread().isInterrupted()) { ZMQ.Poller items = ctx.createPoller(1); items.register(socket, ZMQ.Poller.POLLIN); @@ -123,7 +129,8 @@ public void mediate() { if (MDP.C_CLIENT.frameEquals(header)) { processClient(sender, msg); - } else if (MDP.W_WORKER.frameEquals(header)) + } + else if (MDP.W_WORKER.frameEquals(header)) processWorker(sender, msg); else { log.format("E: invalid message:\n"); @@ -145,7 +152,8 @@ public void mediate() { /** * Disconnect all workers, destroy context. */ - private void destroy() { + private void destroy() + { Worker[] deleteList = workers.entrySet().toArray(new Worker[0]); for (Worker worker : deleteList) { deleteWorker(worker, true); @@ -156,22 +164,23 @@ private void destroy() { /** * Process a request coming from a client. */ - private void processClient(ZFrame sender, ZMsg msg) { + private void processClient(ZFrame sender, ZMsg msg) + { assert (msg.size() >= 2); // Service name + body ZFrame serviceFrame = msg.pop(); // Set reply return address to client sender msg.wrap(sender.duplicate()); if (serviceFrame.toString().startsWith(INTERNAL_SERVICE_PREFIX)) serviceInternal(serviceFrame, msg); - else - dispatch(requireService(serviceFrame), msg); + else dispatch(requireService(serviceFrame), msg); serviceFrame.destroy(); } /** * Process message sent to us by a worker. */ - private void processWorker(ZFrame sender, ZMsg msg) { + private void processWorker(ZFrame sender, ZMsg msg) + { assert (msg.size() >= 1); // At least, command ZFrame command = msg.pop(); @@ -182,8 +191,7 @@ private void processWorker(ZFrame sender, ZMsg msg) { if (MDP.W_READY.frameEquals(command)) { // Not first command in session || Reserved service name - if (workerReady - || sender.toString().startsWith(INTERNAL_SERVICE_PREFIX)) + if (workerReady || sender.toString().startsWith(INTERNAL_SERVICE_PREFIX)) deleteWorker(worker, true); else { // Attach worker to service and mark as idle @@ -192,7 +200,8 @@ private void processWorker(ZFrame sender, ZMsg msg) { workerWaiting(worker); serviceFrame.destroy(); } - } else if (MDP.W_REPLY.frameEquals(command)) { + } + else if (MDP.W_REPLY.frameEquals(command)) { if (workerReady) { // Remove & save client return envelope and insert the // protocol header and service name, then rewrap envelope. @@ -202,16 +211,20 @@ private void processWorker(ZFrame sender, ZMsg msg) { msg.wrap(client); msg.send(socket); workerWaiting(worker); - } else { + } + else { deleteWorker(worker, true); } - } else if (MDP.W_HEARTBEAT.frameEquals(command)) { + } + else if (MDP.W_HEARTBEAT.frameEquals(command)) { if (workerReady) { worker.expiry = System.currentTimeMillis() + HEARTBEAT_EXPIRY; - } else { + } + else { deleteWorker(worker, true); } - } else if (MDP.W_DISCONNECT.frameEquals(command)) + } + else if (MDP.W_DISCONNECT.frameEquals(command)) deleteWorker(worker, false); else { log.format("E: invalid message:\n"); @@ -223,7 +236,8 @@ private void processWorker(ZFrame sender, ZMsg msg) { /** * Deletes worker from all data structures, and destroys worker. */ - private void deleteWorker(Worker worker, boolean disconnect) { + private void deleteWorker(Worker worker, boolean disconnect) + { assert (worker != null); if (disconnect) { sendToWorker(worker, MDP.W_DISCONNECT, null, null); @@ -237,7 +251,8 @@ private void deleteWorker(Worker worker, boolean disconnect) { /** * Finds the worker (creates if necessary). */ - private Worker requireWorker(ZFrame address) { + private Worker requireWorker(ZFrame address) + { assert (address != null); String identity = address.strhex(); Worker worker = workers.get(identity); @@ -253,7 +268,8 @@ private Worker requireWorker(ZFrame address) { /** * Locates the service (creates if necessary). */ - private Service requireService(ZFrame serviceFrame) { + private Service requireService(ZFrame serviceFrame) + { assert (serviceFrame != null); String name = serviceFrame.toString(); Service service = services.get(name); @@ -268,7 +284,8 @@ private Service requireService(ZFrame serviceFrame) { * Bind broker to endpoint, can call this multiple times. We use a single * socket for both clients and workers. */ - private void bind(String endpoint) { + private void bind(String endpoint) + { socket.bind(endpoint); log.format("I: MDP broker/0.1.1 is active at %s\n", endpoint); } @@ -276,7 +293,8 @@ private void bind(String endpoint) { /** * Handle internal service according to 8/MMI specification */ - private void serviceInternal(ZFrame serviceFrame, ZMsg msg) { + private void serviceInternal(ZFrame serviceFrame, ZMsg msg) + { String returnCode = "501"; if ("mmi.service".equals(serviceFrame.toString())) { String name = msg.peekLast().toString(); @@ -295,7 +313,8 @@ private void serviceInternal(ZFrame serviceFrame, ZMsg msg) { /** * Send heartbeats to idle workers if it's time */ - public synchronized void sendHeartbeats() { + public synchronized void sendHeartbeats() + { // Send heartbeats to idle workers if it's time if (System.currentTimeMillis() >= heartbeatAt) { for (Worker worker : waiting) { @@ -309,10 +328,10 @@ public synchronized void sendHeartbeats() { * Look for & kill expired workers. Workers are oldest to most recent, so we * stop at the first alive worker. */ - public synchronized void purgeWorkers() { + public synchronized void purgeWorkers() + { for (Worker w = waiting.peekFirst(); w != null - && w.expiry < System.currentTimeMillis(); w = waiting - .peekFirst()) { + && w.expiry < System.currentTimeMillis(); w = waiting.peekFirst()) { log.format("I: deleting expired worker: %s\n", w.identity); deleteWorker(waiting.pollFirst(), false); } @@ -321,7 +340,8 @@ public synchronized void purgeWorkers() { /** * This worker is now waiting for work. */ - public synchronized void workerWaiting(Worker worker) { + public synchronized void workerWaiting(Worker worker) + { // Queue to broker and service waiting lists waiting.addLast(worker); worker.service.waiting.addLast(worker); @@ -332,7 +352,8 @@ public synchronized void workerWaiting(Worker worker) { /** * Dispatch requests to waiting workers as possible */ - private void dispatch(Service service, ZMsg msg) { + private void dispatch(Service service, ZMsg msg) + { assert (service != null); if (msg != null)// Queue message if any service.requests.offerLast(msg); @@ -350,8 +371,8 @@ private void dispatch(Service service, ZMsg msg) { * Send message to worker. If message is provided, sends that message. Does * not destroy the message, this is the caller's job. */ - public void sendToWorker(Worker worker, MDP command, String option, - ZMsg msgp) { + public void sendToWorker(Worker worker, MDP command, String option, ZMsg msgp) + { ZMsg msg = msgp == null ? new ZMsg() : msgp.duplicate(); diff --git a/src/test/java/guide/mdcliapi.java b/src/test/java/guide/mdcliapi.java index 9dad502ad..2e3817b5a 100644 --- a/src/test/java/guide/mdcliapi.java +++ b/src/test/java/guide/mdcliapi.java @@ -12,33 +12,39 @@ * http://rfc.zeromq.org/spec:7. * */ -public class mdcliapi { +public class mdcliapi +{ - private String broker; - private ZContext ctx; + private String broker; + private ZContext ctx; private ZMQ.Socket client; - private long timeout = 2500; - private int retries = 3; - private boolean verbose; - private Formatter log = new Formatter(System.out); + private long timeout = 2500; + private int retries = 3; + private boolean verbose; + private Formatter log = new Formatter(System.out); - public long getTimeout() { + public long getTimeout() + { return timeout; } - public void setTimeout(long timeout) { + public void setTimeout(long timeout) + { this.timeout = timeout; } - public int getRetries() { + public int getRetries() + { return retries; } - public void setRetries(int retries) { + public void setRetries(int retries) + { this.retries = retries; } - public mdcliapi(String broker, boolean verbose) { + public mdcliapi(String broker, boolean verbose) + { this.broker = broker; this.verbose = verbose; ctx = new ZContext(); @@ -48,7 +54,8 @@ public mdcliapi(String broker, boolean verbose) { /** * Connect or reconnect to broker */ - void reconnectToBroker() { + void reconnectToBroker() + { if (client != null) { ctx.destroySocket(client); } @@ -67,7 +74,8 @@ void reconnectToBroker() { * @param request * @return */ - public ZMsg send(String service, ZMsg request) { + public ZMsg send(String service, ZMsg request) + { request.push(new ZFrame(service)); request.push(MDP.C_CLIENT.newFrame()); @@ -90,7 +98,7 @@ public ZMsg send(String service, ZMsg request) { if (items.pollin(0)) { ZMsg msg = ZMsg.recvMsg(client); - if (verbose){ + if (verbose) { log.format("I: received reply: \n"); msg.dump(log.out()); } @@ -107,7 +115,8 @@ public ZMsg send(String service, ZMsg request) { reply = msg; break; - } else { + } + else { items.unregister(client); if (--retriesLeft == 0) { log.format("W: permanent error, abandoning\n"); @@ -121,7 +130,8 @@ public ZMsg send(String service, ZMsg request) { return reply; } - public void destroy() { + public void destroy() + { ctx.destroy(); } } diff --git a/src/test/java/guide/mdcliapi2.java b/src/test/java/guide/mdcliapi2.java index 3fac1e8fa..0fdfb808e 100644 --- a/src/test/java/guide/mdcliapi2.java +++ b/src/test/java/guide/mdcliapi2.java @@ -11,24 +11,28 @@ * Majordomo Protocol Client API, asynchronous Java version. Implements the * MDP/Worker spec at http://rfc.zeromq.org/spec:7. */ -public class mdcliapi2 { +public class mdcliapi2 +{ - private String broker; - private ZContext ctx; + private String broker; + private ZContext ctx; private ZMQ.Socket client; - private long timeout = 2500; - private boolean verbose; - private Formatter log = new Formatter(System.out); + private long timeout = 2500; + private boolean verbose; + private Formatter log = new Formatter(System.out); - public long getTimeout() { + public long getTimeout() + { return timeout; } - public void setTimeout(long timeout) { + public void setTimeout(long timeout) + { this.timeout = timeout; } - public mdcliapi2(String broker, boolean verbose) { + public mdcliapi2(String broker, boolean verbose) + { this.broker = broker; this.verbose = verbose; ctx = new ZContext(); @@ -38,7 +42,8 @@ public mdcliapi2(String broker, boolean verbose) { /** * Connect or reconnect to broker */ - void reconnectToBroker() { + void reconnectToBroker() + { if (client != null) { ctx.destroySocket(client); } @@ -53,7 +58,8 @@ void reconnectToBroker() { * to recover from a broker failure, this is not possible without storing * all unanswered requests and resending them all… */ - public ZMsg recv() { + public ZMsg recv() + { ZMsg reply = null; // Poll socket for a reply, with timeout @@ -91,7 +97,8 @@ public ZMsg recv() { * Send request to broker and get reply by hook or crook Takes ownership of * request message and destroys it when sent. */ - public boolean send(String service, ZMsg request) { + public boolean send(String service, ZMsg request) + { assert (request != null); // Prefix request with protocol frames @@ -108,7 +115,8 @@ public boolean send(String service, ZMsg request) { return request.send(client); } - public void destroy() { + public void destroy() + { ctx.destroy(); } } diff --git a/src/test/java/guide/mdclient.java b/src/test/java/guide/mdclient.java index 8347a4aa1..651005ff2 100644 --- a/src/test/java/guide/mdclient.java +++ b/src/test/java/guide/mdclient.java @@ -5,9 +5,11 @@ /** * Majordomo Protocol client example. Uses the mdcli API to hide all MDP aspects */ -public class mdclient { +public class mdclient +{ - public static void main(String[] args) { + public static void main(String[] args) + { boolean verbose = (args.length > 0 && "-v".equals(args[0])); mdcliapi clientSession = new mdcliapi("tcp://localhost:5555", verbose); @@ -18,8 +20,7 @@ public static void main(String[] args) { ZMsg reply = clientSession.send("echo", request); if (reply != null) reply.destroy(); - else - break; // Interrupt or failure + else break; // Interrupt or failure } System.out.printf("%d requests/replies processed\n", count); diff --git a/src/test/java/guide/mdclient2.java b/src/test/java/guide/mdclient2.java index 86ed71ef9..e94ac1dc7 100644 --- a/src/test/java/guide/mdclient2.java +++ b/src/test/java/guide/mdclient2.java @@ -7,9 +7,11 @@ * all MDP aspects */ -public class mdclient2 { +public class mdclient2 +{ - public static void main(String[] args) { + public static void main(String[] args) + { boolean verbose = (args.length > 0 && "-v".equals(args[0])); mdcliapi2 clientSession = new mdcliapi2("tcp://localhost:5555", verbose); @@ -23,8 +25,7 @@ public static void main(String[] args) { ZMsg reply = clientSession.recv(); if (reply != null) reply.destroy(); - else - break; // Interrupt or failure + else break; // Interrupt or failure } System.out.printf("%d requests/replies processed\n", count); diff --git a/src/test/java/guide/mdworker.java b/src/test/java/guide/mdworker.java index 2f769b744..3e50de89e 100644 --- a/src/test/java/guide/mdworker.java +++ b/src/test/java/guide/mdworker.java @@ -6,12 +6,14 @@ * Majordomo Protocol worker example. Uses the mdwrk API to hide all MDP aspects * */ -public class mdworker { +public class mdworker +{ /** * @param args */ - public static void main(String[] args) { + public static void main(String[] args) + { boolean verbose = (args.length > 0 && "-v".equals(args[0])); mdwrkapi workerSession = new mdwrkapi("tcp://localhost:5555", "echo", verbose); diff --git a/src/test/java/guide/mdwrkapi.java b/src/test/java/guide/mdwrkapi.java index 3978aa09e..e9dc8d3ff 100644 --- a/src/test/java/guide/mdwrkapi.java +++ b/src/test/java/guide/mdwrkapi.java @@ -11,31 +11,33 @@ * Majordomo Protocol Client API, Java version Implements the MDP/Worker spec at * http://rfc.zeromq.org/spec:7. */ -public class mdwrkapi { +public class mdwrkapi +{ private static final int HEARTBEAT_LIVENESS = 3; // 3-5 is reasonable - private String broker; + private String broker; private ZContext ctx; - private String service; + private String service; - private ZMQ.Socket worker; // Socket to broker - private long heartbeatAt;// When to send HEARTBEAT - private int liveness;// How many attempts left - private int heartbeat = 2500;// Heartbeat delay, msecs - private int reconnect = 2500; // Reconnect delay, msecs + private ZMQ.Socket worker; // Socket to broker + private long heartbeatAt; // When to send HEARTBEAT + private int liveness; // How many attempts left + private int heartbeat = 2500; // Heartbeat delay, msecs + private int reconnect = 2500; // Reconnect delay, msecs // Internal state private boolean expectReply = false; // false only at start - private long timeout = 2500; - private boolean verbose;// Print activity to stdout - private Formatter log = new Formatter(System.out); + private long timeout = 2500; + private boolean verbose; // Print activity to stdout + private Formatter log = new Formatter(System.out); // Return address, if any private ZFrame replyTo; - public mdwrkapi(String broker, String service, boolean verbose) { + public mdwrkapi(String broker, String service, boolean verbose) + { assert (broker != null); assert (service != null); this.broker = broker; @@ -52,7 +54,8 @@ public mdwrkapi(String broker, String service, boolean verbose) { * @param option * @param msg */ - void sendToBroker(MDP command, String option, ZMsg msg) { + void sendToBroker(MDP command, String option, ZMsg msg) + { msg = msg != null ? msg.duplicate() : new ZMsg(); // Stack protocol envelope to start of message @@ -73,7 +76,8 @@ void sendToBroker(MDP command, String option, ZMsg msg) { /** * Connect or reconnect to broker */ - void reconnectToBroker() { + void reconnectToBroker() + { if (worker != null) { ctx.destroySocket(worker); } @@ -94,7 +98,8 @@ void reconnectToBroker() { /** * Send reply, if any, to broker and wait for next request. */ - public ZMsg receive(ZMsg reply) { + public ZMsg receive(ZMsg reply) + { // Format and send the reply if we were provided one assert (reply != null || !expectReply); @@ -141,22 +146,27 @@ public ZMsg receive(ZMsg reply) { replyTo = msg.unwrap(); command.destroy(); return msg; // We have a request to process - } else if (MDP.W_HEARTBEAT.frameEquals(command)) { + } + else if (MDP.W_HEARTBEAT.frameEquals(command)) { // Do nothing for heartbeats - } else if (MDP.W_DISCONNECT.frameEquals(command)) { + } + else if (MDP.W_DISCONNECT.frameEquals(command)) { reconnectToBroker(); - } else { + } + else { log.format("E: invalid input message: \n"); msg.dump(log.out()); } command.destroy(); msg.destroy(); - } else if (--liveness == 0) { + } + else if (--liveness == 0) { if (verbose) log.format("W: disconnected from broker - retrying\n"); try { Thread.sleep(reconnect); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { Thread.currentThread().interrupt(); // Restore the // interrupted status break; @@ -176,24 +186,29 @@ public ZMsg receive(ZMsg reply) { return null; } - public void destroy() { + public void destroy() + { ctx.destroy(); } // ============== getters and setters ================= - public int getHeartbeat() { + public int getHeartbeat() + { return heartbeat; } - public void setHeartbeat(int heartbeat) { + public void setHeartbeat(int heartbeat) + { this.heartbeat = heartbeat; } - public int getReconnect() { + public int getReconnect() + { return reconnect; } - public void setReconnect(int reconnect) { + public void setReconnect(int reconnect) + { this.reconnect = reconnect; } diff --git a/src/test/java/guide/mmiecho.java b/src/test/java/guide/mmiecho.java index 0e5455c7d..ed0acae8d 100644 --- a/src/test/java/guide/mmiecho.java +++ b/src/test/java/guide/mmiecho.java @@ -5,9 +5,11 @@ /** * MMI echo query example */ -public class mmiecho { +public class mmiecho +{ - public static void main(String[] args) { + public static void main(String[] args) + { boolean verbose = (args.length > 0 && "-v".equals(args[0])); mdcliapi clientSession = new mdcliapi("tcp://localhost:5555", verbose); @@ -22,9 +24,9 @@ public static void main(String[] args) { if (reply != null) { String replyCode = reply.getFirst().toString(); System.out.printf("Lookup echo service: %s\n", replyCode); - } else { - System.out - .println("E: no response from broker, make sure it's running"); + } + else { + System.out.println("E: no response from broker, make sure it's running"); } clientSession.destroy(); diff --git a/src/test/java/guide/msgqueue.java b/src/test/java/guide/msgqueue.java index 1f8186491..295afe2a5 100644 --- a/src/test/java/guide/msgqueue.java +++ b/src/test/java/guide/msgqueue.java @@ -8,9 +8,11 @@ * Simple message queuing broker * Same as request-reply broker but using QUEUE device. */ -public class msgqueue{ +public class msgqueue +{ - public static void main (String[] args) { + public static void main(String[] args) + { // Prepare our context and sockets Context context = ZMQ.context(1); @@ -23,7 +25,7 @@ public static void main (String[] args) { backend.bind("tcp://*:5560"); // Start the proxy - ZMQ.proxy (frontend, backend, null); + ZMQ.proxy(frontend, backend, null); // We never get here but clean up anyhow frontend.close(); diff --git a/src/test/java/guide/mspoller.java b/src/test/java/guide/mspoller.java index 0e7b066e3..c84063d6a 100644 --- a/src/test/java/guide/mspoller.java +++ b/src/test/java/guide/mspoller.java @@ -6,9 +6,11 @@ // Reading from multiple sockets in Java // This version uses ZMQ.Poller // -public class mspoller { +public class mspoller +{ - public static void main (String[] args) { + public static void main(String[] args) + { ZMQ.Context context = ZMQ.context(1); // Connect to task ventilator @@ -26,7 +28,7 @@ public static void main (String[] args) { items.register(subscriber, ZMQ.Poller.POLLIN); // Process messages from both sockets - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { byte[] message; items.poll(); if (items.pollin(0)) { @@ -38,7 +40,7 @@ public static void main (String[] args) { System.out.println("Process weather update"); } } - receiver.close (); - context.term (); + receiver.close(); + context.term(); } } diff --git a/src/test/java/guide/msreader.java b/src/test/java/guide/msreader.java index a07a9b2d2..cd1a2fff4 100644 --- a/src/test/java/guide/msreader.java +++ b/src/test/java/guide/msreader.java @@ -6,9 +6,11 @@ // Reading from multiple sockets in Java // This version uses a simple recv loop // -public class msreader { +public class msreader +{ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { // Prepare our context and sockets ZMQ.Context context = ZMQ.context(1); @@ -23,10 +25,10 @@ public static void main (String[] args) throws Exception { // Process messages from both sockets // We prioritize traffic from the task ventilator - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { // Process any waiting tasks byte[] task; - while((task = receiver.recv(ZMQ.DONTWAIT)) != null) { + while ((task = receiver.recv(ZMQ.DONTWAIT)) != null) { System.out.println("process task"); } // Process any waiting weather updates @@ -37,8 +39,8 @@ public static void main (String[] args) throws Exception { // No activity, so sleep for 1 msec Thread.sleep(1000); } - receiver.close (); - subscriber.close (); - context.term (); + receiver.close(); + subscriber.close(); + context.term(); } } diff --git a/src/test/java/guide/mtrelay.java b/src/test/java/guide/mtrelay.java index 732d99133..2b9494a76 100644 --- a/src/test/java/guide/mtrelay.java +++ b/src/test/java/guide/mtrelay.java @@ -7,75 +7,81 @@ /** * Multithreaded relay */ -public class mtrelay{ +public class mtrelay +{ private static class Step1 extends Thread { private Context context; - private Step1 (Context context) + private Step1(Context context) { this.context = context; } @Override - public void run(){ + public void run() + { // Signal downstream to step 2 Socket xmitter = context.socket(ZMQ.PAIR); xmitter.connect("inproc://step2"); - System.out.println ("Step 1 ready, signaling step 2"); + System.out.println("Step 1 ready, signaling step 2"); xmitter.send("READY", 0); - xmitter.close (); + xmitter.close(); } } + private static class Step2 extends Thread { private Context context; - private Step2 (Context context) + private Step2(Context context) { this.context = context; } @Override - public void run(){ + public void run() + { // Bind to inproc: endpoint, then start upstream thread Socket receiver = context.socket(ZMQ.PAIR); receiver.bind("inproc://step2"); - Thread step1 = new Step1 (context); + Thread step1 = new Step1(context); step1.start(); // Wait for signal receiver.recv(0); - receiver.close (); + receiver.close(); // Connect to step3 and tell it we're ready Socket xmitter = context.socket(ZMQ.PAIR); xmitter.connect("inproc://step3"); xmitter.send("READY", 0); - xmitter.close (); + xmitter.close(); } } - public static void main (String[] args) { + + public static void main(String[] args) + { Context context = ZMQ.context(1); - + // Bind to inproc: endpoint, then start upstream thread Socket receiver = context.socket(ZMQ.PAIR); receiver.bind("inproc://step3"); - + // Step 2 relays the signal to step 3 - Thread step2 = new Step2 (context); + Thread step2 = new Step2(context); step2.start(); - + // Wait for signal receiver.recv(0); - receiver.close (); - - System.out.println ("Test successful!"); - context.term (); + receiver.close(); + + System.out.println("Test successful!"); + context.term(); } } diff --git a/src/test/java/guide/mtserver.java b/src/test/java/guide/mtserver.java index e7164a045..896d91e28 100644 --- a/src/test/java/guide/mtserver.java +++ b/src/test/java/guide/mtserver.java @@ -7,31 +7,35 @@ /** * Multi threaded Hello World server */ -public class mtserver { +public class mtserver +{ private static class Worker extends Thread { private Context context; - private Worker (Context context) + private Worker(Context context) { this.context = context; } + @Override - public void run() { + public void run() + { ZMQ.Socket socket = context.socket(ZMQ.REP); - socket.connect ("inproc://workers"); + socket.connect("inproc://workers"); while (true) { // Wait for next request from client (C string) - String request = socket.recvStr (0); - System.out.println ( Thread.currentThread().getName() + " Received request: [" + request + "]"); + String request = socket.recvStr(0); + System.out.println(Thread.currentThread().getName() + " Received request: [" + request + "]"); // Do some 'work' try { - Thread.sleep (1000); - } catch (InterruptedException e) { + Thread.sleep(1000); + } + catch (InterruptedException e) { } // Send reply back to client (C string) @@ -40,22 +44,23 @@ public void run() { } } - public static void main (String[] args) { + public static void main(String[] args) + { Context context = ZMQ.context(1); Socket clients = context.socket(ZMQ.ROUTER); - clients.bind ("tcp://*:5555"); + clients.bind("tcp://*:5555"); Socket workers = context.socket(ZMQ.DEALER); - workers.bind ("inproc://workers"); + workers.bind("inproc://workers"); - for(int thread_nbr = 0; thread_nbr < 5; thread_nbr++) { - Thread worker = new Worker (context); + for (int thread_nbr = 0; thread_nbr < 5; thread_nbr++) { + Thread worker = new Worker(context); worker.start(); } // Connect work threads to client threads via a queue - ZMQ.proxy (clients, workers, null); + ZMQ.proxy(clients, workers, null); // We never get here but clean up anyhow clients.close(); diff --git a/src/test/java/guide/pathopub.java b/src/test/java/guide/pathopub.java index 235c34bfd..0a617dfcd 100644 --- a/src/test/java/guide/pathopub.java +++ b/src/test/java/guide/pathopub.java @@ -1,11 +1,11 @@ package guide; +import java.util.Random; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Socket; -import java.util.Random; - // Pathological publisher // Sends out 1,000 topics and then one random update per second public class pathopub @@ -16,8 +16,7 @@ public static void main(String[] args) throws Exception Socket publisher = context.createSocket(ZMQ.PUB); if (args.length == 1) publisher.connect(args[0]); - else - publisher.bind("tcp://*:5556"); + else publisher.bind("tcp://*:5556"); // Ensure subscriber connection has time to complete Thread.sleep(1000); @@ -35,6 +34,6 @@ public static void main(String[] args) throws Exception publisher.send(String.format("%03d", rand.nextInt(1000)), ZMQ.SNDMORE); publisher.send("Off with his head!"); } - context.destroy(); + context.close(); } } diff --git a/src/test/java/guide/pathosub.java b/src/test/java/guide/pathosub.java index 91b33ae32..12fec4eab 100644 --- a/src/test/java/guide/pathosub.java +++ b/src/test/java/guide/pathosub.java @@ -1,11 +1,11 @@ package guide; +import java.util.Random; + import org.zeromq.ZContext; import org.zeromq.ZMQ; import org.zeromq.ZMQ.Socket; -import java.util.Random; - // Pathological subscriber // Subscribes to one random topic and prints received messages public class pathosub @@ -16,8 +16,7 @@ public static void main(String[] args) Socket subscriber = context.createSocket(ZMQ.SUB); if (args.length == 1) subscriber.connect(args[0]); - else - subscriber.connect("tcp://localhost:5556"); + else subscriber.connect("tcp://localhost:5556"); Random rand = new Random(System.currentTimeMillis()); String subscription = String.format("%03d", rand.nextInt(1000)); @@ -28,9 +27,9 @@ public static void main(String[] args) if (topic == null) break; String data = subscriber.recvStr(); - assert(topic.equals(subscription)); + assert (topic.equals(subscription)); System.out.println(data); } - context.destroy(); + context.close(); } } diff --git a/src/test/java/guide/peering1.java b/src/test/java/guide/peering1.java index ded8be87b..d22f2f2bd 100644 --- a/src/test/java/guide/peering1.java +++ b/src/test/java/guide/peering1.java @@ -1,10 +1,11 @@ package guide; +import java.nio.channels.Selector; import java.util.Random; -import org.zeromq.ZMQ.Poller; import org.zeromq.ZContext; import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; // Broker peering simulation (part 1) @@ -27,6 +28,7 @@ public static void main(String[] argv) Random rand = new Random(System.nanoTime()); ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); // Bind state backend to endpoint Socket statebe = ctx.createSocket(ZMQ.PUB); @@ -51,19 +53,20 @@ public static void main(String[] argv) // Poll for activity, or 1 second timeout int rc = poller.poll(1000); if (rc == -1) - break; // Interrupted + break; // Interrupted // Handle incoming status messages if (poller.pollin(0)) { String peer_name = new String(statefe.recv(0), ZMQ.CHARSET); String available = new String(statefe.recv(0), ZMQ.CHARSET); System.out.printf("%s - %s workers free\n", peer_name, available); - } else { + } + else { // Send random values for worker availability statebe.send(self, ZMQ.SNDMORE); statebe.send(String.format("%d", rand.nextInt(10)), 0); } } - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/peering2.java b/src/test/java/guide/peering2.java index 3373ae9d1..ffb275a2c 100644 --- a/src/test/java/guide/peering2.java +++ b/src/test/java/guide/peering2.java @@ -1,13 +1,13 @@ package guide; import java.io.IOException; +import java.nio.channels.Selector; import java.util.ArrayList; import java.util.Random; import org.zeromq.ZContext; import org.zeromq.ZFrame; import org.zeromq.ZMQ; -import org.zeromq.ZMQ.PollItem; import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; import org.zeromq.ZMsg; @@ -18,9 +18,9 @@ public class peering2 { - private static final int NBR_CLIENTS = 10; - private static final int NBR_WORKERS = 3; - private static final String WORKER_READY = "\001"; // Signals worker is ready + private static final int NBR_CLIENTS = 10; + private static final int NBR_WORKERS = 3; + private static final String WORKER_READY = "\001"; // Signals worker is ready // Our own name; in practice this would be configured per node private static String self; @@ -41,14 +41,15 @@ public void run() client.send("HELLO", 0); String reply = client.recvStr(0); if (reply == null) - break; // Interrupted + break; // Interrupted System.out.printf("Client: %s\n", reply); try { Thread.sleep(1000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } } - ctx.destroy(); + ctx.close(); } } @@ -72,13 +73,13 @@ public void run() // Send request, get reply ZMsg msg = ZMsg.recvMsg(worker, 0); if (msg == null) - break; // Interrupted + break; // Interrupted msg.getLast().print("Worker: "); msg.getLast().reset("OK"); msg.send(worker); } - ctx.destroy(); + ctx.close(); } } @@ -98,6 +99,7 @@ public static void main(String[] argv) Random rand = new Random(System.nanoTime()); ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); // Bind cloud frontend to endpoint Socket cloudfe = ctx.createSocket(ZMQ.ROUTER); @@ -124,7 +126,8 @@ public static void main(String[] argv) System.out.println("Press Enter when all brokers are started: "); try { System.in.read(); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); } @@ -160,13 +163,13 @@ public static void main(String[] argv) // If we have no workers anyhow, wait indefinitely int rc = backends.poll(capacity > 0 ? 1000 : -1); if (rc == -1) - break; // Interrupted + break; // Interrupted // Handle reply from local worker ZMsg msg = null; if (backends.pollin(0)) { msg = ZMsg.recvMsg(localbe); if (msg == null) - break; // Interrupted + break; // Interrupted ZFrame address = msg.unwrap(); workers.add(address); capacity++; @@ -182,7 +185,7 @@ public static void main(String[] argv) else if (backends.pollin(1)) { msg = ZMsg.recvMsg(cloudbe); if (msg == null) - break; // Interrupted + break; // Interrupted // We don't use peer broker address for anything ZFrame address = msg.unwrap(); address.destroy(); @@ -213,11 +216,12 @@ else if (backends.pollin(1)) { if (frontends.pollin(1)) { msg = ZMsg.recvMsg(cloudfe); reroutable = 0; - } else if (frontends.pollin(0)) { + } + else if (frontends.pollin(0)) { msg = ZMsg.recvMsg(localfe); reroutable = 1; - } else - break; // No work, go back to backends + } + else break; // No work, go back to backends // If reroutable, send to cloud 20% of the time // Here we'd normally use cloud status information @@ -227,7 +231,8 @@ else if (backends.pollin(1)) { int random_peer = rand.nextInt(argv.length - 1) + 1; msg.push(argv[random_peer]); msg.send(cloudbe); - } else { + } + else { ZFrame frame = workers.remove(0); msg.wrap(frame); msg.send(localbe); @@ -241,6 +246,6 @@ else if (backends.pollin(1)) { frame.destroy(); } - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/peering3.java b/src/test/java/guide/peering3.java index c899d6d4e..5bdb2b51b 100644 --- a/src/test/java/guide/peering3.java +++ b/src/test/java/guide/peering3.java @@ -1,5 +1,6 @@ package guide; +import java.nio.channels.Selector; import java.util.ArrayList; import java.util.Random; @@ -16,9 +17,9 @@ public class peering3 { - private static final int NBR_CLIENTS = 10; - private static final int NBR_WORKERS = 5; - private static final String WORKER_READY = "\001"; // Signals worker is ready + private static final int NBR_CLIENTS = 10; + private static final int NBR_WORKERS = 5; + private static final String WORKER_READY = "\001"; // Signals worker is ready // Our own name; in practice this would be configured per node private static String self; @@ -34,6 +35,7 @@ private static class client_task extends Thread public void run() { ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); Socket client = ctx.createSocket(ZMQ.REQ); client.connect(String.format("ipc://%s-localfe.ipc", self)); Socket monitor = ctx.createSocket(ZMQ.PUSH); @@ -47,7 +49,8 @@ public void run() try { Thread.sleep(rand.nextInt(5) * 1000); - } catch (InterruptedException e1) { + } + catch (InterruptedException e1) { } int burst = rand.nextInt(15); @@ -59,19 +62,19 @@ public void run() // Wait max ten seconds for a reply, then complain int rc = poller.poll(10 * 1000); if (rc == -1) - break; // Interrupted + break; // Interrupted if (poller.pollin(0)) { String reply = client.recvStr(0); if (reply == null) - break; // Interrupted + break; // Interrupted // Worker is supposed to answer us with our task id assert (reply.equals(taskId)); monitor.send(String.format("%s", reply), 0); - } else { - monitor.send( - String.format("E: CLIENT EXIT - lost task %s", taskId), 0); - ctx.destroy(); + } + else { + monitor.send(String.format("E: CLIENT EXIT - lost task %s", taskId), 0); + ctx.close(); return; } burst--; @@ -101,18 +104,19 @@ public void run() // Send request, get reply ZMsg msg = ZMsg.recvMsg(worker, 0); if (msg == null) - break; // Interrupted + break; // Interrupted // Workers are busy for 0/1 seconds try { Thread.sleep(rand.nextInt(2) * 1000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } msg.send(worker); } - ctx.destroy(); + ctx.close(); } } @@ -137,6 +141,7 @@ public static void main(String[] argv) Random rand = new Random(System.nanoTime()); ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); // Prepare local frontend and backend Socket localfe = ctx.createSocket(ZMQ.ROUTER); @@ -144,7 +149,6 @@ public static void main(String[] argv) Socket localbe = ctx.createSocket(ZMQ.ROUTER); localbe.bind(String.format("ipc://%s-localbe.ipc", self)); - // Bind cloud frontend to endpoint Socket cloudfe = ctx.createSocket(ZMQ.ROUTER); cloudfe.setIdentity(self.getBytes(ZMQ.CHARSET)); @@ -212,18 +216,17 @@ public static void main(String[] argv) // If we have no workers anyhow, wait indefinitely int rc = primary.poll(localCapacity > 0 ? 1000 : -1); if (rc == -1) - break; // Interrupted + break; // Interrupted // Track if capacity changes during this iteration int previous = localCapacity; - // Handle reply from local worker ZMsg msg = null; if (primary.pollin(0)) { msg = ZMsg.recvMsg(localbe); if (msg == null) - break; // Interrupted + break; // Interrupted ZFrame address = msg.unwrap(); workers.add(address); localCapacity++; @@ -239,7 +242,7 @@ public static void main(String[] argv) else if (primary.pollin(1)) { msg = ZMsg.recvMsg(cloudbe); if (msg == null) - break; // Interrupted + break; // Interrupted // We don't use peer broker address for anything ZFrame address = msg.unwrap(); address.destroy(); @@ -282,10 +285,11 @@ else if (primary.pollin(1)) { if (secondary.pollin(0)) { msg = ZMsg.recvMsg(localfe); - } else if (localCapacity > 0 && secondary.pollin(1)) { + } + else if (localCapacity > 0 && secondary.pollin(1)) { msg = ZMsg.recvMsg(cloudfe); - } else - break; // No work, go back to backends + } + else break; // No work, go back to backends if (localCapacity > 0) { ZFrame frame = workers.remove(0); @@ -293,7 +297,8 @@ else if (primary.pollin(1)) { msg.send(localbe); localCapacity--; - } else { + } + else { // Route to random broker peer int random_peer = rand.nextInt(argv.length - 1) + 1; msg.push(argv[random_peer]); @@ -317,6 +322,6 @@ else if (primary.pollin(1)) { frame.destroy(); } - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/ppqueue.java b/src/test/java/guide/ppqueue.java index 0e1aa5a57..e478034aa 100644 --- a/src/test/java/guide/ppqueue.java +++ b/src/test/java/guide/ppqueue.java @@ -1,5 +1,6 @@ package guide; +import java.nio.channels.Selector; import java.util.ArrayList; import java.util.Iterator; @@ -14,31 +15,35 @@ // Paranoid Pirate queue // -public class ppqueue { +public class ppqueue +{ - private final static int HEARTBEAT_LIVENESS = 3; // 3-5 is reasonable - private final static int HEARTBEAT_INTERVAL = 1000; // msecs + private final static int HEARTBEAT_LIVENESS = 3; // 3-5 is reasonable + private final static int HEARTBEAT_INTERVAL = 1000; // msecs // Paranoid Pirate Protocol constants - private final static String PPP_READY = "\001"; // Signals worker is ready - private final static String PPP_HEARTBEAT = "\002"; // Signals worker heartbeat + private final static String PPP_READY = "\001"; // Signals worker is ready + private final static String PPP_HEARTBEAT = "\002"; // Signals worker heartbeat // Here we define the worker class; a structure and a set of functions that // as constructor, destructor, and methods on worker objects: - private static class Worker { - ZFrame address; // Address of worker - String identity; // Printable identity - long expiry; // Expires at this time + private static class Worker + { + ZFrame address; // Address of worker + String identity; // Printable identity + long expiry; // Expires at this time - protected Worker(ZFrame address) { + protected Worker(ZFrame address) + { this.address = address; identity = new String(address.getData(), ZMQ.CHARSET); expiry = System.currentTimeMillis() + HEARTBEAT_INTERVAL * HEARTBEAT_LIVENESS; } // The ready method puts a worker to the end of the ready list: - protected void ready(ArrayList workers) { + protected void ready(ArrayList workers) + { Iterator it = workers.iterator(); while (it.hasNext()) { Worker worker = it.next(); @@ -51,7 +56,8 @@ protected void ready(ArrayList workers) { } // The next method returns the next available worker address: - protected static ZFrame next(ArrayList workers) { + protected static ZFrame next(ArrayList workers) + { Worker worker = workers.remove(0); assert (worker != null); ZFrame frame = worker.address; @@ -60,7 +66,8 @@ protected static ZFrame next(ArrayList workers) { // The purge method looks for and kills expired workers. We hold workers // from oldest to most recent, so we stop at the first alive worker: - protected static void purge(ArrayList workers) { + protected static void purge(ArrayList workers) + { Iterator it = workers.iterator(); while (it.hasNext()) { Worker worker = it.next(); @@ -74,15 +81,17 @@ protected static void purge(ArrayList workers) { // The main task is an LRU queue with heartbeating on workers so we can // detect crashed or blocked worker tasks: - public static void main(String[] args) { - ZContext ctx = new ZContext (); + public static void main(String[] args) + { + ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); Socket frontend = ctx.createSocket(ZMQ.ROUTER); Socket backend = ctx.createSocket(ZMQ.ROUTER); - frontend.bind( "tcp://*:5555"); // For clients - backend.bind( "tcp://*:5556"); // For workers + frontend.bind("tcp://*:5555"); // For clients + backend.bind("tcp://*:5556"); // For workers // List of available workers - ArrayList workers = new ArrayList (); + ArrayList workers = new ArrayList(); // Send out heartbeats at regular intervals long heartbeat_at = System.currentTimeMillis() + HEARTBEAT_INTERVAL; @@ -95,14 +104,14 @@ public static void main(String[] args) { boolean workersAvailable = workers.size() > 0; int rc = poller.poll(HEARTBEAT_INTERVAL); if (rc == -1) - break; // Interrupted + break; // Interrupted // Handle worker activity on backend if (poller.pollin(0)) { // Use worker address for LRU routing - ZMsg msg = ZMsg.recvMsg (backend); + ZMsg msg = ZMsg.recvMsg(backend); if (msg == null) - break; // Interrupted + break; // Interrupted // Any sign of life from worker means it's ready ZFrame address = msg.unwrap(); @@ -113,23 +122,21 @@ public static void main(String[] args) { if (msg.size() == 1) { ZFrame frame = msg.getFirst(); String data = new String(frame.getData(), ZMQ.CHARSET); - if (!data.equals(PPP_READY) - && !data.equals( PPP_HEARTBEAT)) { - System.out.println ("E: invalid message from worker"); + if (!data.equals(PPP_READY) && !data.equals(PPP_HEARTBEAT)) { + System.out.println("E: invalid message from worker"); msg.dump(System.out); } msg.destroy(); } - else - msg.send(frontend); + else msg.send(frontend); } if (workersAvailable && poller.pollin(1)) { // Now get next client request, route to next worker - ZMsg msg = ZMsg.recvMsg (frontend); + ZMsg msg = ZMsg.recvMsg(frontend); if (msg == null) - break; // Interrupted + break; // Interrupted msg.push(Worker.next(workers)); - msg.send( backend); + msg.send(backend); } // We handle heartbeating after any socket activity. First we send @@ -137,24 +144,23 @@ public static void main(String[] args) { // dead workers: if (System.currentTimeMillis() >= heartbeat_at) { - for (Worker worker: workers) { + for (Worker worker : workers) { - worker.address.send(backend, - ZFrame.REUSE + ZFrame.MORE); - ZFrame frame = new ZFrame (PPP_HEARTBEAT); + worker.address.send(backend, ZFrame.REUSE + ZFrame.MORE); + ZFrame frame = new ZFrame(PPP_HEARTBEAT); frame.send(backend, 0); } heartbeat_at = System.currentTimeMillis() + HEARTBEAT_INTERVAL; } - Worker.purge (workers); + Worker.purge(workers); } // When we're done, clean up properly - while ( workers.size() > 0) { + while (workers.size() > 0) { Worker worker = workers.remove(0); } workers.clear(); - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/ppworker.java b/src/test/java/guide/ppworker.java index e22ee9bf0..245864232 100644 --- a/src/test/java/guide/ppworker.java +++ b/src/test/java/guide/ppworker.java @@ -1,5 +1,6 @@ package guide; +import java.nio.channels.Selector; import java.util.Random; import org.zeromq.ZContext; @@ -15,14 +16,14 @@ public class ppworker { - private final static int HEARTBEAT_LIVENESS = 3; // 3-5 is reasonable - private final static int HEARTBEAT_INTERVAL = 1000; // msecs - private final static int INTERVAL_INIT = 1000; // Initial reconnect - private final static int INTERVAL_MAX = 32000; // After exponential backoff + private final static int HEARTBEAT_LIVENESS = 3; // 3-5 is reasonable + private final static int HEARTBEAT_INTERVAL = 1000; // msecs + private final static int INTERVAL_INIT = 1000; // Initial reconnect + private final static int INTERVAL_MAX = 32000; // After exponential backoff // Paranoid Pirate Protocol constants - private final static String PPP_READY = "\001"; // Signals worker is ready - private final static String PPP_HEARTBEAT = "\002"; // Signals worker heartbeat + private final static String PPP_READY = "\001"; // Signals worker is ready + private final static String PPP_HEARTBEAT = "\002"; // Signals worker heartbeat // Helper function that returns a new configured socket // connected to the Paranoid Pirate queue @@ -48,6 +49,7 @@ private static Socket worker_socket(ZContext ctx) public static void main(String[] args) { ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); Socket worker = worker_socket(ctx); Poller poller = ctx.createPoller(1); @@ -65,7 +67,7 @@ public static void main(String[] args) while (true) { int rc = poller.poll(HEARTBEAT_INTERVAL); if (rc == -1) - break; // Interrupted + break; // Interrupted if (poller.pollin(0)) { // Get message @@ -73,7 +75,7 @@ public static void main(String[] args) // - 1-part HEARTBEAT -> heartbeat ZMsg msg = ZMsg.recvMsg(worker); if (msg == null) - break; // Interrupted + break; // Interrupted // To test the robustness of the queue implementation we // // simulate various typical problems, such as the worker @@ -87,11 +89,13 @@ public static void main(String[] args) msg.destroy(); msg = null; break; - } else if (cycles > 3 && rand.nextInt(5) == 0) { + } + else if (cycles > 3 && rand.nextInt(5) == 0) { System.out.println("I: simulating CPU overload\n"); try { Thread.sleep(3000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { break; } } @@ -100,46 +104,51 @@ public static void main(String[] args) liveness = HEARTBEAT_LIVENESS; try { Thread.sleep(1000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { break; - } // Do some heavy work - } else - // When we get a heartbeat message from the queue, it means the - // queue was (recently) alive, so reset our liveness indicator: - if (msg.size() == 1) { - ZFrame frame = msg.getFirst(); - if (PPP_HEARTBEAT.equals(new String(frame.getData(), ZMQ.CHARSET))) - liveness = HEARTBEAT_LIVENESS; - else { - System.out.println("E: invalid message\n"); - msg.dump(System.out); - } - msg.destroy(); - } else { + } // Do some heavy work + } + else + // When we get a heartbeat message from the queue, it means the + // queue was (recently) alive, so reset our liveness indicator: + if (msg.size() == 1) { + ZFrame frame = msg.getFirst(); + if (PPP_HEARTBEAT.equals(new String(frame.getData(), ZMQ.CHARSET))) + liveness = HEARTBEAT_LIVENESS; + else { System.out.println("E: invalid message\n"); msg.dump(System.out); } + msg.destroy(); + } + else { + System.out.println("E: invalid message\n"); + msg.dump(System.out); + } interval = INTERVAL_INIT; - } else - // If the queue hasn't sent us heartbeats in a while, destroy the - // socket and reconnect. This is the simplest most brutal way of - // discarding any messages we might have sent in the meantime:// - if (--liveness == 0) { - System.out.println("W: heartbeat failure, can't reach queue\n"); - System.out.printf("W: reconnecting in %sd msec\n", interval); - try { - Thread.sleep(interval); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (interval < INTERVAL_MAX) - interval *= 2; - ctx.destroySocket(worker); - worker = worker_socket(ctx); - liveness = HEARTBEAT_LIVENESS; + } + else + // If the queue hasn't sent us heartbeats in a while, destroy the + // socket and reconnect. This is the simplest most brutal way of + // discarding any messages we might have sent in the meantime:// + if (--liveness == 0) { + System.out.println("W: heartbeat failure, can't reach queue\n"); + System.out.printf("W: reconnecting in %sd msec\n", interval); + try { + Thread.sleep(interval); + } + catch (InterruptedException e) { + e.printStackTrace(); } + if (interval < INTERVAL_MAX) + interval *= 2; + ctx.destroySocket(worker); + worker = worker_socket(ctx); + liveness = HEARTBEAT_LIVENESS; + } + // Send heartbeat to queue if it's time if (System.currentTimeMillis() > heartbeat_at) { heartbeat_at = System.currentTimeMillis() + HEARTBEAT_INTERVAL; @@ -148,7 +157,7 @@ public static void main(String[] args) frame.send(worker, 0); } } - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/psenvpub.java b/src/test/java/guide/psenvpub.java index eb706c17e..c1589da33 100644 --- a/src/test/java/guide/psenvpub.java +++ b/src/test/java/guide/psenvpub.java @@ -8,22 +8,24 @@ * Pubsub envelope publisher */ -public class psenvpub { +public class psenvpub +{ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { // Prepare our context and publisher Context context = ZMQ.context(1); Socket publisher = context.socket(ZMQ.PUB); publisher.bind("tcp://*:5563"); - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { // Write two messages, each with an envelope and content - publisher.sendMore ("A"); - publisher.send ("We don't want to see this"); - publisher.sendMore ("B"); + publisher.sendMore("A"); + publisher.send("We don't want to see this"); + publisher.sendMore("B"); publisher.send("We would like to see this"); } - publisher.close (); - context.term (); + publisher.close(); + context.term(); } } diff --git a/src/test/java/guide/psenvsub.java b/src/test/java/guide/psenvsub.java index c30641d6f..750269106 100644 --- a/src/test/java/guide/psenvsub.java +++ b/src/test/java/guide/psenvsub.java @@ -8,9 +8,11 @@ * Pubsub envelope subscriber */ -public class psenvsub { +public class psenvsub +{ - public static void main (String[] args) { + public static void main(String[] args) + { // Prepare our context and subscriber Context context = ZMQ.context(1); @@ -18,14 +20,14 @@ public static void main (String[] args) { subscriber.connect("tcp://localhost:5563"); subscriber.subscribe("B".getBytes(ZMQ.CHARSET)); - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { // Read envelope with address - String address = subscriber.recvStr (); + String address = subscriber.recvStr(); // Read message contents - String contents = subscriber.recvStr (); + String contents = subscriber.recvStr(); System.out.println(address + " : " + contents); } - subscriber.close (); - context.term (); + subscriber.close(); + context.term(); } } diff --git a/src/test/java/guide/rrbroker.java b/src/test/java/guide/rrbroker.java index b68a08f4e..4c3b0721e 100644 --- a/src/test/java/guide/rrbroker.java +++ b/src/test/java/guide/rrbroker.java @@ -9,14 +9,16 @@ * Simple request-reply broker * */ -public class rrbroker{ +public class rrbroker +{ - public static void main (String[] args) { + public static void main(String[] args) + { // Prepare our context and sockets Context context = ZMQ.context(1); Socket frontend = context.socket(ZMQ.ROUTER); - Socket backend = context.socket(ZMQ.DEALER); + Socket backend = context.socket(ZMQ.DEALER); frontend.bind("tcp://*:5559"); backend.bind("tcp://*:5560"); @@ -43,7 +45,7 @@ public static void main (String[] args) { // Broker it backend.send(message, more ? ZMQ.SNDMORE : 0); - if(!more){ + if (!more) { break; } } @@ -54,8 +56,8 @@ public static void main (String[] args) { message = backend.recv(0); more = backend.hasReceiveMore(); // Broker it - frontend.send(message, more ? ZMQ.SNDMORE : 0); - if(!more){ + frontend.send(message, more ? ZMQ.SNDMORE : 0); + if (!more) { break; } } diff --git a/src/test/java/guide/rrclient.java b/src/test/java/guide/rrclient.java index 61ec91a55..be2eed7aa 100644 --- a/src/test/java/guide/rrclient.java +++ b/src/test/java/guide/rrclient.java @@ -9,23 +9,25 @@ * Connects REQ socket to tcp://localhost:5559 * Sends "Hello" to server, expects "World" back */ -public class rrclient{ +public class rrclient +{ - public static void main (String[] args) { + public static void main(String[] args) + { Context context = ZMQ.context(1); // Socket to talk to server Socket requester = context.socket(ZMQ.REQ); requester.connect("tcp://localhost:5559"); - + System.out.println("launch and connect client."); for (int request_nbr = 0; request_nbr < 10; request_nbr++) { requester.send("Hello", 0); - String reply = requester.recvStr (0); + String reply = requester.recvStr(0); System.out.println("Received reply " + request_nbr + " [" + reply + "]"); } - + requester.close(); context.term(); } diff --git a/src/test/java/guide/rrworker.java b/src/test/java/guide/rrworker.java index 3c9df7765..7df85533b 100644 --- a/src/test/java/guide/rrworker.java +++ b/src/test/java/guide/rrworker.java @@ -9,23 +9,24 @@ // Expects "Hello" from client, replies with "World" public class rrworker { - public static void main (String[] args) throws Exception { - Context context = ZMQ.context (1); + public static void main(String[] args) throws Exception + { + Context context = ZMQ.context(1); // Socket to talk to server - Socket responder = context.socket (ZMQ.REP); - responder.connect ("tcp://localhost:5560"); + Socket responder = context.socket(ZMQ.REP); + responder.connect("tcp://localhost:5560"); - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { // Wait for next request from client - String string = responder.recvStr (0); - System.out.printf ("Received request: [%s]\n", string); + String string = responder.recvStr(0); + System.out.printf("Received request: [%s]\n", string); // Do some 'work' - Thread.sleep (1000); + Thread.sleep(1000); // Send reply back to client - responder.send ("World"); + responder.send("World"); } // We never get here but clean up anyhow diff --git a/src/test/java/guide/rtdealer.java b/src/test/java/guide/rtdealer.java index 940f34ffe..c33289387 100644 --- a/src/test/java/guide/rtdealer.java +++ b/src/test/java/guide/rtdealer.java @@ -1,50 +1,53 @@ package guide; +import java.util.Random; + import org.zeromq.ZMQ; import org.zeromq.ZMQ.Context; import org.zeromq.ZMQ.Socket; -import java.util.Random; - /** * ROUTER-TO-REQ example */ public class rtdealer { - private static Random rand = new Random(); + private static Random rand = new Random(); private static final int NBR_WORKERS = 10; - private static class Worker extends Thread { + private static class Worker extends Thread + { @Override - public void run() { + public void run() + { Context context = ZMQ.context(1); Socket worker = context.socket(ZMQ.DEALER); - ZHelper.setId (worker); // Set a printable identity + ZHelper.setId(worker); // Set a printable identity worker.connect("tcp://localhost:5671"); int total = 0; while (true) { // Tell the broker we're ready for work - worker.sendMore (""); - worker.send ("Hi Boss"); + worker.sendMore(""); + worker.send("Hi Boss"); // Get workload from broker, until finished - worker.recvStr (); // Envelope delimiter - String workload = worker.recvStr (); - boolean finished = workload.equals ("Fired!"); + worker.recvStr(); // Envelope delimiter + String workload = worker.recvStr(); + boolean finished = workload.equals("Fired!"); if (finished) { - System.out.printf ("Completed: %d tasks\n", total); + System.out.printf("Completed: %d tasks\n", total); break; } total++; // Do some random work try { - Thread.sleep (rand.nextInt (500) + 1); - } catch (InterruptedException e) { + Thread.sleep(rand.nextInt(500) + 1); + } + catch (InterruptedException e) { } } worker.close(); @@ -52,39 +55,38 @@ public void run() { } } - /** * While this example runs in a single process, that is just to make * it easier to start and stop the example. Each thread has its own * context and conceptually acts as a separate process. */ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { Context context = ZMQ.context(1); Socket broker = context.socket(ZMQ.ROUTER); broker.bind("tcp://*:5671"); - for (int workerNbr = 0; workerNbr < NBR_WORKERS; workerNbr++) - { - Thread worker = new Worker (); - worker.start (); + for (int workerNbr = 0; workerNbr < NBR_WORKERS; workerNbr++) { + Thread worker = new Worker(); + worker.start(); } // Run for five seconds and then tell workers to end - long endTime = System.currentTimeMillis () + 5000; + long endTime = System.currentTimeMillis() + 5000; int workersFired = 0; while (true) { // Next message gives us least recently used worker - String identity = broker.recvStr (); - broker.sendMore (identity); - broker.recv (0); // Envelope delimiter - broker.recv (0); // Response from worker - broker.sendMore (""); + String identity = broker.recvStr(); + broker.sendMore(identity); + broker.recv(0); // Envelope delimiter + broker.recv(0); // Response from worker + broker.sendMore(""); // Encourage workers until it's time to fire them - if (System.currentTimeMillis () < endTime) - broker.send ("Work harder"); + if (System.currentTimeMillis() < endTime) + broker.send("Work harder"); else { - broker.send ("Fired!"); + broker.send("Fired!"); if (++workersFired == NBR_WORKERS) break; } diff --git a/src/test/java/guide/rtmama.java b/src/test/java/guide/rtmama.java index bf134b5c8..be9ff3ec3 100644 --- a/src/test/java/guide/rtmama.java +++ b/src/test/java/guide/rtmama.java @@ -5,14 +5,17 @@ // //Custom routing Router to Mama (ROUTER to REQ) // -public class rtmama { +public class rtmama +{ - private static final int NBR_WORKERS =10; - - public static class Worker implements Runnable { + private static final int NBR_WORKERS = 10; + + public static class Worker implements Runnable + { private final byte[] END = "END".getBytes(ZMQ.CHARSET); - public void run() { + public void run() + { ZMQ.Context context = ZMQ.context(1); ZMQ.Socket worker = context.socket(ZMQ.REQ); // worker.setIdentity(); will set a random id automatically @@ -23,11 +26,7 @@ public void run() { worker.send("ready", 0); byte[] workerload = worker.recv(0); if (new String(workerload, ZMQ.CHARSET).equals("END")) { - System.out.println( - String.format( - "Processs %d tasks.", total - ) - ); + System.out.println(String.format("Processs %d tasks.", total)); break; } total += 1; @@ -36,38 +35,39 @@ public void run() { context.term(); } } - public static void main(String[] args) { + + public static void main(String[] args) + { ZMQ.Context context = ZMQ.context(1); ZMQ.Socket client = context.socket(ZMQ.ROUTER); client.bind("ipc://routing.ipc"); - for (int i = 0 ; i != NBR_WORKERS; i++) { + for (int i = 0; i != NBR_WORKERS; i++) { new Thread(new Worker()).start(); } - - for (int i = 0 ; i != NBR_WORKERS; i++) { + for (int i = 0; i != NBR_WORKERS; i++) { // LRU worker is next waiting in queue byte[] address = client.recv(0); byte[] empty = client.recv(0); byte[] ready = client.recv(0); - + client.send(address, ZMQ.SNDMORE); client.send("", ZMQ.SNDMORE); client.send("This is the workload", 0); } - - for (int i = 0 ; i != NBR_WORKERS; i++) { + + for (int i = 0; i != NBR_WORKERS; i++) { // LRU worker is next waiting in queue byte[] address = client.recv(0); byte[] empty = client.recv(0); byte[] ready = client.recv(0); - + client.send(address, ZMQ.SNDMORE); client.send("", ZMQ.SNDMORE); client.send("END", 0); } - + // Now ask mamas to shut down and report their results client.close(); context.term(); diff --git a/src/test/java/guide/rtpapa.java b/src/test/java/guide/rtpapa.java index e8df21f0d..996f5a3f5 100644 --- a/src/test/java/guide/rtpapa.java +++ b/src/test/java/guide/rtpapa.java @@ -28,7 +28,8 @@ public static void main(String[] args) // with routing envelope, it will actually match the worker try { Thread.sleep(1000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { e.printStackTrace(); } diff --git a/src/test/java/guide/rtreq.java b/src/test/java/guide/rtreq.java index 7059afedb..c4bfe0f59 100644 --- a/src/test/java/guide/rtreq.java +++ b/src/test/java/guide/rtreq.java @@ -1,48 +1,51 @@ package guide; +import java.util.Random; + import org.zeromq.ZMQ; import org.zeromq.ZMQ.Context; import org.zeromq.ZMQ.Socket; -import java.util.Random; - /** * ROUTER-TO-REQ example */ public class rtreq { - private static Random rand = new Random(); + private static Random rand = new Random(); private static final int NBR_WORKERS = 10; - private static class Worker extends Thread { + private static class Worker extends Thread + { @Override - public void run() { + public void run() + { Context context = ZMQ.context(1); Socket worker = context.socket(ZMQ.REQ); - ZHelper.setId (worker); // Set a printable identity + ZHelper.setId(worker); // Set a printable identity worker.connect("tcp://localhost:5671"); int total = 0; while (true) { // Tell the broker we're ready for work - worker.send ("Hi Boss"); + worker.send("Hi Boss"); // Get workload from broker, until finished - String workload = worker.recvStr (); - boolean finished = workload.equals ("Fired!"); + String workload = worker.recvStr(); + boolean finished = workload.equals("Fired!"); if (finished) { - System.out.printf ("Completed: %d tasks\n", total); + System.out.printf("Completed: %d tasks\n", total); break; } total++; // Do some random work try { - Thread.sleep (rand.nextInt (500) + 1); - } catch (InterruptedException e) { + Thread.sleep(rand.nextInt(500) + 1); + } + catch (InterruptedException e) { } } worker.close(); @@ -50,39 +53,38 @@ public void run() { } } - /** * While this example runs in a single process, that is just to make * it easier to start and stop the example. Each thread has its own * context and conceptually acts as a separate process. */ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { Context context = ZMQ.context(1); Socket broker = context.socket(ZMQ.ROUTER); broker.bind("tcp://*:5671"); - for (int workerNbr = 0; workerNbr < NBR_WORKERS; workerNbr++) - { - Thread worker = new Worker (); - worker.start (); + for (int workerNbr = 0; workerNbr < NBR_WORKERS; workerNbr++) { + Thread worker = new Worker(); + worker.start(); } // Run for five seconds and then tell workers to end - long endTime = System.currentTimeMillis () + 5000; + long endTime = System.currentTimeMillis() + 5000; int workersFired = 0; while (true) { // Next message gives us least recently used worker - String identity = broker.recvStr (); - broker.sendMore (identity); - broker.recvStr (); // Envelope delimiter - broker.recvStr (); // Response from worker - broker.sendMore (""); + String identity = broker.recvStr(); + broker.sendMore(identity); + broker.recvStr(); // Envelope delimiter + broker.recvStr(); // Response from worker + broker.sendMore(""); // Encourage workers until it's time to fire them - if (System.currentTimeMillis () < endTime) - broker.send ("Work harder"); + if (System.currentTimeMillis() < endTime) + broker.send("Work harder"); else { - broker.send ("Fired!"); + broker.send("Fired!"); if (++workersFired == NBR_WORKERS) break; } diff --git a/src/test/java/guide/spqueue.java b/src/test/java/guide/spqueue.java index 6ead4ec64..929333707 100644 --- a/src/test/java/guide/spqueue.java +++ b/src/test/java/guide/spqueue.java @@ -1,11 +1,11 @@ package guide; +import java.nio.channels.Selector; import java.util.ArrayList; import org.zeromq.ZContext; import org.zeromq.ZFrame; import org.zeromq.ZMQ; -import org.zeromq.ZMQ.PollItem; import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; import org.zeromq.ZMsg; @@ -18,15 +18,16 @@ public class spqueue { - private final static String WORKER_READY = "\001"; // Signals worker is ready + private final static String WORKER_READY = "\001"; // Signals worker is ready public static void main(String[] args) { ZContext ctx = new ZContext(); + Selector selector = ctx.createSelector(); Socket frontend = ctx.createSocket(ZMQ.ROUTER); Socket backend = ctx.createSocket(ZMQ.ROUTER); - frontend.bind("tcp://*:5555"); // For clients - backend.bind("tcp://*:5556"); // For workers + frontend.bind("tcp://*:5555"); // For clients + backend.bind("tcp://*:5556"); // For workers // Queue of available workers ArrayList workers = new ArrayList(); @@ -42,14 +43,14 @@ public static void main(String[] args) // Poll frontend only if we have available workers if (rc == -1) - break; // Interrupted + break; // Interrupted // Handle worker activity on backend if (poller.pollin(0)) { // Use worker address for LRU routing ZMsg msg = ZMsg.recvMsg(backend); if (msg == null) - break; // Interrupted + break; // Interrupted ZFrame address = msg.unwrap(); workers.add(address); @@ -57,8 +58,7 @@ public static void main(String[] args) ZFrame frame = msg.getFirst(); if (new String(frame.getData(), ZMQ.CHARSET).equals(WORKER_READY)) msg.destroy(); - else - msg.send(frontend); + else msg.send(frontend); } if (workersAvailable && poller.pollin(1)) { // Get client request, route to first available worker @@ -75,7 +75,7 @@ public static void main(String[] args) frame.destroy(); } workers.clear(); - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/spworker.java b/src/test/java/guide/spworker.java index b1ad93d5b..2f9a84418 100644 --- a/src/test/java/guide/spworker.java +++ b/src/test/java/guide/spworker.java @@ -16,7 +16,7 @@ public class spworker { - private final static String WORKER_READY = "\001"; // Signals worker is ready + private final static String WORKER_READY = "\001"; // Signals worker is ready public static void main(String[] args) throws Exception { @@ -38,7 +38,7 @@ public static void main(String[] args) throws Exception while (true) { ZMsg msg = ZMsg.recvMsg(worker); if (msg == null) - break; // Interrupted + break; // Interrupted // Simulate various problems, after a few cycles cycles++; @@ -46,7 +46,8 @@ public static void main(String[] args) throws Exception System.out.printf("I: (%s) simulating a crash\n", identity); msg.destroy(); break; - } else if (cycles > 3 && rand.nextInt(5) == 0) { + } + else if (cycles > 3 && rand.nextInt(5) == 0) { System.out.printf("I: (%s) simulating CPU overload\n", identity); Thread.sleep(3000); } @@ -54,7 +55,7 @@ public static void main(String[] args) throws Exception Thread.sleep(1000); // Do some heavy work msg.send(worker); } - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/guide/suisnail.java b/src/test/java/guide/suisnail.java index f152697b2..a9d0905b8 100644 --- a/src/test/java/guide/suisnail.java +++ b/src/test/java/guide/suisnail.java @@ -1,5 +1,7 @@ package guide; +import java.util.Random; + // Suicidal Snail import org.zeromq.ZContext; @@ -8,12 +10,10 @@ import org.zeromq.ZThread; import org.zeromq.ZThread.IAttachedRunnable; -import java.util.Random; - public class suisnail { - private static final long MAX_ALLOWED_DELAY = 1000; // msecs - private static Random rand = new Random(System.currentTimeMillis()); + private static final long MAX_ALLOWED_DELAY = 1000; // msecs + private static Random rand = new Random(System.currentTimeMillis()); // This is our subscriber. It connects to the publisher and subscribes // to everything. It sleeps for a short time between messages to @@ -43,7 +43,8 @@ public void run(Object[] args, ZContext ctx, Socket pipe) // Work for 1 msec plus some random additional time try { Thread.sleep(1000 + rand.nextInt(2000)); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { break; } } @@ -73,7 +74,8 @@ public void run(Object[] args, ZContext ctx, Socket pipe) } try { Thread.sleep(1); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } } @@ -83,7 +85,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) // .split main task // The main task simply starts a client and a server, and then // waits for the client to signal that it has died: - public static void main (String[] args) throws Exception + public static void main(String[] args) throws Exception { ZContext ctx = new ZContext(); Socket pubpipe = ZThread.fork(ctx, new Publisher()); diff --git a/src/test/java/guide/syncpub.java b/src/test/java/guide/syncpub.java index a8932f30a..221d113f2 100644 --- a/src/test/java/guide/syncpub.java +++ b/src/test/java/guide/syncpub.java @@ -7,13 +7,15 @@ /** * Synchronized publisher. */ -public class syncpub{ +public class syncpub +{ /** * We wait for 10 subscribers */ protected static int SUBSCRIBERS_EXPECTED = 10; - public static void main (String[] args) { + public static void main(String[] args) + { Context context = ZMQ.context(1); // Socket to talk to clients @@ -39,10 +41,10 @@ public static void main (String[] args) { subscribers++; } // Now broadcast exactly 1M updates followed by END - System.out.println ("Broadcasting messages"); + System.out.println("Broadcasting messages"); int update_nbr; - for (update_nbr = 0; update_nbr < 1000000; update_nbr++){ + for (update_nbr = 0; update_nbr < 1000000; update_nbr++) { publisher.send("Rhubarb", 0); } diff --git a/src/test/java/guide/syncsub.java b/src/test/java/guide/syncsub.java index d6b079c47..6ce341b38 100644 --- a/src/test/java/guide/syncsub.java +++ b/src/test/java/guide/syncsub.java @@ -7,9 +7,11 @@ /** * Synchronized subscriber. */ -public class syncsub{ +public class syncsub +{ - public static void main (String[] args) { + public static void main(String[] args) + { Context context = ZMQ.context(1); // First, connect our subscriber socket diff --git a/src/test/java/guide/tasksink.java b/src/test/java/guide/tasksink.java index 49ab5c50b..3542997c7 100644 --- a/src/test/java/guide/tasksink.java +++ b/src/test/java/guide/tasksink.java @@ -7,9 +7,11 @@ // Binds PULL socket to tcp://localhost:5558 // Collects results from workers via that socket // -public class tasksink { +public class tasksink +{ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { // Prepare our context and socket ZMQ.Context context = ZMQ.context(1); @@ -24,12 +26,13 @@ public static void main (String[] args) throws Exception { // Process 100 confirmations int task_nbr; - int total_msec = 0; // Total calculated cost in msecs + int total_msec = 0; // Total calculated cost in msecs for (task_nbr = 0; task_nbr < 100; task_nbr++) { string = new String(receiver.recv(0), ZMQ.CHARSET).trim(); if ((task_nbr / 10) * 10 == task_nbr) { System.out.print(":"); - } else { + } + else { System.out.print("."); } } diff --git a/src/test/java/guide/tasksink2.java b/src/test/java/guide/tasksink2.java index 750427aed..6cae7ed90 100644 --- a/src/test/java/guide/tasksink2.java +++ b/src/test/java/guide/tasksink2.java @@ -6,22 +6,24 @@ * Task sink - design 2 * Adds pub-sub flow to send kill signal to workers */ -public class tasksink2 { +public class tasksink2 +{ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { // Prepare our context and socket ZMQ.Context context = ZMQ.context(1); ZMQ.Socket receiver = context.socket(ZMQ.PULL); receiver.bind("tcp://*:5558"); - + // Socket for worker control ZMQ.Socket controller = context.socket(ZMQ.PUB); controller.bind("tcp://*:5559"); // Wait for start of batch receiver.recv(0); - + // Start our clock now long tstart = System.currentTimeMillis(); @@ -31,7 +33,8 @@ public static void main (String[] args) throws Exception { receiver.recv(0); if ((task_nbr / 10) * 10 == task_nbr) { System.out.print(":"); - } else { + } + else { System.out.print("."); } System.out.flush(); @@ -40,13 +43,13 @@ public static void main (String[] args) throws Exception { long tend = System.currentTimeMillis(); System.out.println("Total elapsed time: " + (tend - tstart) + " msec"); - + // Send the kill signal to the workers controller.send("KILL", 0); - + // Give it some time to deliver Thread.sleep(1); - + receiver.close(); controller.close(); context.term(); diff --git a/src/test/java/guide/taskvent.java b/src/test/java/guide/taskvent.java index 70ba377fc..1b58c641f 100644 --- a/src/test/java/guide/taskvent.java +++ b/src/test/java/guide/taskvent.java @@ -1,6 +1,7 @@ package guide; import java.util.Random; + import org.zeromq.ZMQ; // @@ -8,9 +9,11 @@ // Binds PUSH socket to tcp://localhost:5557 // Sends batch of tasks to workers via that socket // -public class taskvent { +public class taskvent +{ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { ZMQ.Context context = ZMQ.context(1); // Socket to send messages on @@ -33,7 +36,7 @@ public static void main (String[] args) throws Exception { // Send 100 tasks int task_nbr; - int total_msec = 0; // Total expected cost in msecs + int total_msec = 0; // Total expected cost in msecs for (task_nbr = 0; task_nbr < 100; task_nbr++) { int workload; // Random workload from 1 to 100msecs @@ -44,7 +47,7 @@ public static void main (String[] args) throws Exception { sender.send(string, 0); } System.out.println("Total expected cost: " + total_msec + " msec"); - Thread.sleep(1000); // Give 0MQ time to deliver + Thread.sleep(1000); // Give 0MQ time to deliver sink.close(); sender.close(); diff --git a/src/test/java/guide/taskwork.java b/src/test/java/guide/taskwork.java index da9b0264e..6722ed746 100644 --- a/src/test/java/guide/taskwork.java +++ b/src/test/java/guide/taskwork.java @@ -9,9 +9,11 @@ // Connects PUSH socket to tcp://localhost:5558 // Sends results to sink via that socket // -public class taskwork { +public class taskwork +{ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { ZMQ.Context context = ZMQ.context(1); // Socket to receive messages on @@ -23,7 +25,7 @@ public static void main (String[] args) throws Exception { sender.connect("tcp://localhost:5558"); // Process tasks forever - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { String string = new String(receiver.recv(0), ZMQ.CHARSET).trim(); long msec = Long.parseLong(string); // Simple progress indicator for the viewer diff --git a/src/test/java/guide/taskwork2.java b/src/test/java/guide/taskwork2.java index 2661f732e..e6a78e04e 100644 --- a/src/test/java/guide/taskwork2.java +++ b/src/test/java/guide/taskwork2.java @@ -6,9 +6,11 @@ * Task worker - design 2 * Adds pub-sub flow to receive and respond to kill signal */ -public class taskwork2 { +public class taskwork2 +{ - public static void main (String[] args) throws InterruptedException { + public static void main(String[] args) throws InterruptedException + { ZMQ.Context context = ZMQ.context(1); ZMQ.Socket receiver = context.socket(ZMQ.PULL); @@ -31,7 +33,7 @@ public static void main (String[] args) throws InterruptedException { if (items.pollin(0)) { - String message = receiver.recvStr (0); + String message = receiver.recvStr(0); long nsec = Long.parseLong(message); // Simple progress indicator for the viewer diff --git a/src/test/java/guide/ticlient.java b/src/test/java/guide/ticlient.java index 8e2a6706d..b2ea0d318 100644 --- a/src/test/java/guide/ticlient.java +++ b/src/test/java/guide/ticlient.java @@ -12,8 +12,7 @@ public class ticlient { - static ZMsg - serviceCall (mdcliapi session, String service, ZMsg request) + static ZMsg serviceCall(mdcliapi session, String service, ZMsg request) { ZMsg reply = session.send(service, request); if (reply != null) { @@ -25,15 +24,15 @@ public class ticlient else if (status.streq("400")) { System.out.println("E: client fatal error, aborting"); } - else - if (status.streq("500")) { + else if (status.streq("500")) { System.out.println("E: server fatal error, aborting"); } reply.destroy(); } - return null; // Didn't succeed; don't care why not + return null; // Didn't succeed; don't care why not } - public static void main (String[] args) throws Exception + + public static void main(String[] args) throws Exception { boolean verbose = (args.length > 0 && args[0].equals("-v")); mdcliapi session = new mdcliapi("tcp://localhost:5555", verbose); @@ -42,8 +41,7 @@ public static void main (String[] args) throws Exception ZMsg request = new ZMsg(); request.add("echo"); request.add("Hello world"); - ZMsg reply = serviceCall( - session, "titanic.request", request); + ZMsg reply = serviceCall(session, "titanic.request", request); ZFrame uuid = null; if (reply != null) { @@ -56,12 +54,11 @@ public static void main (String[] args) throws Exception Thread.sleep(100); request = new ZMsg(); request.add(uuid.duplicate()); - reply = serviceCall( - session, "titanic.reply", request); + reply = serviceCall(session, "titanic.reply", request); if (reply != null) { String replyString = reply.getLast().toString(); - System.out.printf ("Reply: %s\n", replyString); + System.out.printf("Reply: %s\n", replyString); reply.destroy(); // 3. Close request @@ -73,12 +70,10 @@ public static void main (String[] args) throws Exception } else { System.out.println("I: no reply yet, trying again..."); - Thread.sleep (5000); // Try again in 5 seconds + Thread.sleep(5000); // Try again in 5 seconds } } uuid.destroy(); session.destroy(); } } - - diff --git a/src/test/java/guide/titanic.java b/src/test/java/guide/titanic.java index 445998c6e..ddb3805f2 100644 --- a/src/test/java/guide/titanic.java +++ b/src/test/java/guide/titanic.java @@ -1,15 +1,5 @@ package guide; -import org.zeromq.ZContext; -import org.zeromq.ZFrame; -import org.zeromq.ZMQ; -import org.zeromq.ZMQ.Poller; -import org.zeromq.ZMQ.Socket; -import org.zeromq.ZMsg; -import org.zeromq.ZThread; -import org.zeromq.ZThread.IAttachedRunnable; -import org.zeromq.ZThread.IDetachedRunnable; - import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -22,6 +12,16 @@ import java.io.RandomAccessFile; import java.util.UUID; +import org.zeromq.ZContext; +import org.zeromq.ZFrame; +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Poller; +import org.zeromq.ZMQ.Socket; +import org.zeromq.ZMsg; +import org.zeromq.ZThread; +import org.zeromq.ZThread.IAttachedRunnable; +import org.zeromq.ZThread.IDetachedRunnable; + public class titanic { // Return a new UUID as a printable character string @@ -32,16 +32,18 @@ static String generateUUID() return UUID.randomUUID().toString(); } - private static final String TITANIC_DIR = ".titanic"; + private static final String TITANIC_DIR = ".titanic"; // Returns freshly allocated request filename for given UUID - private static String requestFilename(String uuid) { + private static String requestFilename(String uuid) + { String filename = String.format("%s/%s.req", TITANIC_DIR, uuid); return filename; } // Returns freshly allocated reply filename for given UUID - private static String replyFilename(String uuid) { + private static String replyFilename(String uuid) + { String filename = String.format("%s/%s.rep", TITANIC_DIR, uuid); return filename; } @@ -57,8 +59,7 @@ static class TitanicRequest implements IAttachedRunnable @Override public void run(Object[] args, ZContext ctx, Socket pipe) { - mdwrkapi worker = new mdwrkapi( - "tcp://localhost:5555", "titanic.request", false); + mdwrkapi worker = new mdwrkapi("tcp://localhost:5555", "titanic.request", false); ZMsg reply = null; while (true) { @@ -66,7 +67,7 @@ public void run(Object[] args, ZContext ctx, Socket pipe) // And then get next request from broker ZMsg request = worker.receive(reply); if (request == null) - break; // Interrupted, exit + break; // Interrupted, exit // Ensure message directory exists new File(TITANIC_DIR).mkdirs(); @@ -78,13 +79,16 @@ public void run(Object[] args, ZContext ctx, Socket pipe) try { file = new DataOutputStream(new FileOutputStream(filename)); ZMsg.save(request, file); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); - } finally { + } + finally { try { if (file != null) file.close(); - } catch (IOException e) { + } + catch (IOException e) { } } request.destroy(); @@ -114,14 +118,13 @@ static class TitanicReply implements IDetachedRunnable @Override public void run(Object[] args) { - mdwrkapi worker = new mdwrkapi( - "tcp://localhost:5555", "titanic.reply", false); + mdwrkapi worker = new mdwrkapi("tcp://localhost:5555", "titanic.reply", false); ZMsg reply = null; while (true) { ZMsg request = worker.receive(reply); if (request == null) - break; // Interrupted, exit + break; // Interrupted, exit String uuid = request.popString(); String reqFilename = requestFilename(uuid); @@ -133,13 +136,16 @@ public void run(Object[] args) file = new DataInputStream(new FileInputStream(repFilename)); reply = ZMsg.load(file); reply.push("200"); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); - } finally { + } + finally { try { if (file != null) file.close(); - } catch (IOException e) { + } + catch (IOException e) { } } } @@ -147,8 +153,7 @@ public void run(Object[] args) reply = new ZMsg(); if (new File(reqFilename).exists()) reply.push("300"); //Pending - else - reply.push("400"); //Unknown + else reply.push("400"); //Unknown } request.destroy(); } @@ -165,14 +170,13 @@ static class TitanicClose implements IDetachedRunnable @Override public void run(Object[] args) { - mdwrkapi worker = new mdwrkapi( - "tcp://localhost:5555", "titanic.close", false); + mdwrkapi worker = new mdwrkapi("tcp://localhost:5555", "titanic.close", false); ZMsg reply = null; while (true) { ZMsg request = worker.receive(reply); if (request == null) - break; // Interrupted, exit + break; // Interrupted, exit String uuid = request.popString(); String req_filename = requestFilename(uuid); @@ -213,7 +217,7 @@ public static void main(String[] args) // We'll dispatch once per second, if there's no activity int rc = poller.poll(1000); if (rc == -1) - break; // Interrupted + break; // Interrupted if (poller.pollin(0)) { // Ensure message directory exists new File(TITANIC_DIR).mkdirs(); @@ -221,20 +225,23 @@ public static void main(String[] args) // Append UUID to queue, prefixed with '-' for pending ZMsg msg = ZMsg.recvMsg(requestPipe); if (msg == null) - break; // Interrupted + break; // Interrupted String uuid = msg.popString(); BufferedWriter wfile = null; try { wfile = new BufferedWriter(new FileWriter(TITANIC_DIR + "/queue", true)); wfile.write("-" + uuid + "\n"); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); break; - } finally { + } + finally { try { if (wfile != null) - wfile.close(); - } catch (IOException e) { + wfile.close(); + } + catch (IOException e) { } } msg.destroy(); @@ -249,8 +256,9 @@ public static void main(String[] args) // UUID is prefixed with '-' if still waiting if (entry[0] == '-') { if (verbose) - System.out.printf("I: processing request %s\n", new String(entry, 1, entry.length -1, ZMQ.CHARSET)); - if (serviceSuccess(new String(entry, 1, entry.length -1, ZMQ.CHARSET))) { + System.out.printf("I: processing request %s\n", + new String(entry, 1, entry.length - 1, ZMQ.CHARSET)); + if (serviceSuccess(new String(entry, 1, entry.length - 1, ZMQ.CHARSET))) { // Mark queue entry as processed file.seek(file.getFilePointer() - 37); file.writeBytes("+"); @@ -263,14 +271,18 @@ public static void main(String[] args) if (Thread.currentThread().isInterrupted()) break; } - } catch (FileNotFoundException e) { - } catch (IOException e) { + } + catch (FileNotFoundException e) { + } + catch (IOException e) { e.printStackTrace(); - } finally { + } + finally { if (file != null) { try { file.close(); - } catch (IOException e) { + } + catch (IOException e) { } } } @@ -296,14 +308,17 @@ static boolean serviceSuccess(String uuid) try { file = new DataInputStream(new FileInputStream(filename)); request = ZMsg.load(file); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); return true; - } finally { + } + finally { try { if (file != null) file.close(); - } catch (IOException e) { + } + catch (IOException e) { } } ZFrame service = request.pop(); @@ -311,15 +326,14 @@ static boolean serviceSuccess(String uuid) // Create MDP client session with short timeout mdcliapi client = new mdcliapi("tcp://localhost:5555", false); - client.setTimeout(1000); // 1 sec - client.setRetries(1); // only 1 retry + client.setTimeout(1000); // 1 sec + client.setRetries(1); // only 1 retry // Use MMI protocol to check if service is available ZMsg mmiRequest = new ZMsg(); mmiRequest.add(service); ZMsg mmiReply = client.send("mmi.service", mmiRequest); - boolean serviceOK = (mmiReply != null - && mmiReply.getFirst().toString().equals("200")); + boolean serviceOK = (mmiReply != null && mmiReply.getFirst().toString().equals("200")); mmiReply.destroy(); boolean result = false; @@ -331,22 +345,25 @@ static boolean serviceSuccess(String uuid) try { ofile = new DataOutputStream(new FileOutputStream(filename)); ZMsg.save(reply, ofile); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); return true; - } finally { + } + finally { try { if (file != null) file.close(); - } catch (IOException e) { + } + catch (IOException e) { } } result = true; } - reply.destroy();; + reply.destroy(); + ; } - else - request.destroy(); + else request.destroy(); client.destroy(); return result; diff --git a/src/test/java/guide/tripping.java b/src/test/java/guide/tripping.java index cef35b117..e03163528 100644 --- a/src/test/java/guide/tripping.java +++ b/src/test/java/guide/tripping.java @@ -10,16 +10,19 @@ * Round-trip demonstrator. Broker, Worker and Client are mocked as separate * threads. */ -public class tripping { +public class tripping +{ - static class Broker implements Runnable { + static class Broker implements Runnable + { @Override - public void run() { + public void run() + { ZContext ctx = new ZContext(); Socket frontend = ctx.createSocket(ZMQ.ROUTER); Socket backend = ctx.createSocket(ZMQ.ROUTER); - frontend.setHWM (0); - backend.setHWM (0); + frontend.setHWM(0); + backend.setHWM(0); frontend.bind("tcp://*:5555"); backend.bind("tcp://*:5556"); @@ -50,18 +53,20 @@ public void run() { msg.send(frontend); } } - ctx.destroy(); + ctx.close(); } } - static class Worker implements Runnable { + static class Worker implements Runnable + { @Override - public void run() { + public void run() + { ZContext ctx = new ZContext(); Socket worker = ctx.createSocket(ZMQ.DEALER); - worker.setHWM (0); + worker.setHWM(0); worker.setIdentity("W".getBytes(ZMQ.CHARSET)); worker.connect("tcp://localhost:5556"); while (!Thread.currentThread().isInterrupted()) { @@ -75,20 +80,23 @@ public void run() { } - static class Client implements Runnable { + static class Client implements Runnable + { private static int SAMPLE_SIZE = 10000; @Override - public void run() { + public void run() + { ZContext ctx = new ZContext(); Socket client = ctx.createSocket(ZMQ.DEALER); - client.setHWM (0); + client.setHWM(0); client.setIdentity("C".getBytes(ZMQ.CHARSET)); client.connect("tcp://localhost:5555"); System.out.println("Setting up test"); try { Thread.sleep(100); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { Thread.currentThread().interrupt(); } @@ -105,8 +113,7 @@ public void run() { ZMsg.recvMsg(client).destroy(); } - System.out.printf(" %d calls/second\n", - (1000 * SAMPLE_SIZE) / (System.currentTimeMillis() - start)); + System.out.printf(" %d calls/second\n", (1000 * SAMPLE_SIZE) / (System.currentTimeMillis() - start)); System.out.println("Asynchronous round-trip test"); start = System.currentTimeMillis(); @@ -116,22 +123,21 @@ public void run() { req.addString("hello"); req.send(client); } - for (requests = 0; requests < SAMPLE_SIZE - && !Thread.currentThread().isInterrupted(); requests++) { + for (requests = 0; requests < SAMPLE_SIZE && !Thread.currentThread().isInterrupted(); requests++) { ZMsg.recvMsg(client).destroy(); } - System.out.printf(" %d calls/second\n", - (1000 * SAMPLE_SIZE) / (System.currentTimeMillis() - start)); + System.out.printf(" %d calls/second\n", (1000 * SAMPLE_SIZE) / (System.currentTimeMillis() - start)); ctx.destroy(); } } - public static void main(String[] args) { + public static void main(String[] args) + { - if (args.length==1) + if (args.length == 1) Client.SAMPLE_SIZE = Integer.parseInt(args[0]); Thread brokerThread = new Thread(new Broker()); @@ -150,7 +156,8 @@ public static void main(String[] args) { workerThread.interrupt(); brokerThread.interrupt(); Thread.sleep(200);// give them some time - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } } diff --git a/src/test/java/guide/version.java b/src/test/java/guide/version.java index 3dd465b0d..a3def2a46 100644 --- a/src/test/java/guide/version.java +++ b/src/test/java/guide/version.java @@ -3,12 +3,13 @@ import org.zeromq.ZMQ; // Report 0MQ version -public class version { +public class version +{ - public static void main (String[] args) { - System.out.println(String.format("Version string: %s, Version int: %d", - ZMQ.getVersionString(), - ZMQ.getFullVersion())); + public static void main(String[] args) + { + System.out.println( + String.format("Version string: %s, Version int: %d", ZMQ.getVersionString(), ZMQ.getFullVersion())); } } diff --git a/src/test/java/guide/wuclient.java b/src/test/java/guide/wuclient.java index a562ce8c0..5b81a9cf7 100644 --- a/src/test/java/guide/wuclient.java +++ b/src/test/java/guide/wuclient.java @@ -1,6 +1,7 @@ package guide; import java.util.StringTokenizer; + import org.zeromq.ZMQ; // @@ -8,9 +9,11 @@ // Connects SUB socket to tcp://localhost:5556 // Collects weather updates and finds avg temp in zipcode // -public class wuclient { +public class wuclient +{ - public static void main (String[] args) { + public static void main(String[] args) + { ZMQ.Context context = ZMQ.context(1); // Socket to talk to server @@ -37,9 +40,8 @@ public static void main (String[] args) { total_temp += temperature; } - System.out.println("Average temperature for zipcode '" - + filter + "' was " + (int) (total_temp / update_nbr)); - + System.out.println("Average temperature for zipcode '" + filter + "' was " + (int) (total_temp / update_nbr)); + subscriber.close(); context.term(); } diff --git a/src/test/java/guide/wuproxy.java b/src/test/java/guide/wuproxy.java index e184a730d..44bba66a5 100644 --- a/src/test/java/guide/wuproxy.java +++ b/src/test/java/guide/wuproxy.java @@ -7,25 +7,27 @@ /** * Weather proxy device. */ -public class wuproxy{ +public class wuproxy +{ - public static void main (String[] args) { + public static void main(String[] args) + { // Prepare our context and sockets Context context = ZMQ.context(1); // This is where the weather server sits - Socket frontend = context.socket(ZMQ.SUB); + Socket frontend = context.socket(ZMQ.SUB); frontend.connect("tcp://192.168.55.210:5556"); // This is our public endpoint for subscribers - Socket backend = context.socket(ZMQ.PUB); + Socket backend = context.socket(ZMQ.PUB); backend.bind("tcp://10.1.1.0:8100"); // Subscribe on everything frontend.subscribe(ZMQ.SUBSCRIPTION_ALL); // Run the proxy until the user interrupts us - ZMQ.proxy (frontend, backend, null); + ZMQ.proxy(frontend, backend, null); frontend.close(); backend.close(); diff --git a/src/test/java/guide/wuserver.java b/src/test/java/guide/wuserver.java index 7dea5a29c..c9ebec402 100644 --- a/src/test/java/guide/wuserver.java +++ b/src/test/java/guide/wuserver.java @@ -1,6 +1,7 @@ package guide; import java.util.Random; + import org.zeromq.ZMQ; // @@ -8,9 +9,11 @@ // Binds PUB socket to tcp://*:5556 // Publishes random weather updates // -public class wuserver { +public class wuserver +{ - public static void main (String[] args) throws Exception { + public static void main(String[] args) throws Exception + { // Prepare our context and publisher ZMQ.Context context = ZMQ.context(1); @@ -20,10 +23,10 @@ public static void main (String[] args) throws Exception { // Initialize random number generator Random srandom = new Random(System.currentTimeMillis()); - while (!Thread.currentThread ().isInterrupted ()) { + while (!Thread.currentThread().isInterrupted()) { // Get values that will fool the boss int zipcode, temperature, relhumidity; - zipcode = 10000 + srandom.nextInt(10000) ; + zipcode = 10000 + srandom.nextInt(10000); temperature = srandom.nextInt(215) - 80 + 1; relhumidity = srandom.nextInt(50) + 10 + 1; @@ -32,7 +35,7 @@ public static void main (String[] args) throws Exception { publisher.send(update, 0); } - publisher.close (); - context.term (); + publisher.close(); + context.term(); } } diff --git a/src/test/java/org/zeromq/ByteBuffersTest.java b/src/test/java/org/zeromq/ByteBuffersTest.java new file mode 100644 index 000000000..650010dac --- /dev/null +++ b/src/test/java/org/zeromq/ByteBuffersTest.java @@ -0,0 +1,253 @@ +package org.zeromq; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.CharacterCodingException; + +import org.junit.Assert; +import org.junit.Test; +import org.zeromq.ZMQ.Socket; + +public class ByteBuffersTest +{ + static class Client extends Thread + { + private int port = -1; + + public Client(int port) + { + this.port = port; + } + + @Override + public void run() + { + System.out.println("Start client thread "); + ZMQ.Context context = ZMQ.context(1); + Socket pullConnect = context.socket(ZMQ.PULL); + + pullConnect.connect("tcp://127.0.0.1:" + port); + pullConnect.recv(0); + + pullConnect.close(); + context.close(); + System.out.println("Stop client thread "); + } + } + + @Test + public void testByteBufferSend() throws InterruptedException, IOException + { + int port = Utils.findOpenPort(); + ZMQ.Context context = ZMQ.context(1); + ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.nativeOrder()); + ZMQ.Socket push = null; + ZMQ.Socket pull = null; + try { + push = context.socket(ZMQ.PUSH); + pull = context.socket(ZMQ.PULL); + pull.bind("tcp://*:" + port); + push.connect("tcp://localhost:" + port); + + bb.put("PING".getBytes(ZMQ.CHARSET)); + bb.flip(); + + Thread.sleep(1000); + + push.sendByteBuffer(bb, 0); + String actual = new String(pull.recv(), ZMQ.CHARSET); + assertEquals("PING", actual); + } + finally { + try { + push.close(); + } + catch (Exception ignore) { + } + try { + pull.close(); + } + catch (Exception ignore) { + } + try { + context.term(); + } + catch (Exception ignore) { + } + } + } + + @Test + public void testByteBufferRecv() throws InterruptedException, IOException, CharacterCodingException + { + int port = Utils.findOpenPort(); + ZMQ.Context context = ZMQ.context(1); + ByteBuffer bb = ByteBuffer.allocate(6).order(ByteOrder.nativeOrder()); + ZMQ.Socket push = null; + ZMQ.Socket pull = null; + try { + push = context.socket(ZMQ.PUSH); + pull = context.socket(ZMQ.PULL); + pull.bind("tcp://*:" + port); + push.connect("tcp://localhost:" + port); + + Thread.sleep(1000); + + push.send("PING".getBytes(ZMQ.CHARSET), 0); + pull.recvByteBuffer(bb, 0); + + bb.flip(); + byte[] b = new byte[bb.remaining()]; + bb.duplicate().get(b); + assertEquals("PING", new String(b, ZMQ.CHARSET)); + } + finally { + try { + push.close(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + try { + pull.close(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + try { + context.term(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + } + + } + + @Test + public void testByteBufferLarge() throws InterruptedException, IOException, CharacterCodingException + { + int port = Utils.findOpenPort(); + ZMQ.Context context = ZMQ.context(1); + int[] array = new int[2048 * 2000]; + for (int i = 0; i < array.length; ++i) { + array[i] = i; + } + ByteBuffer bSend = ByteBuffer.allocate(Integer.SIZE / 8 * array.length).order(ByteOrder.nativeOrder()); + bSend.asIntBuffer().put(array); + ByteBuffer bRec = ByteBuffer.allocate(bSend.capacity()).order(ByteOrder.nativeOrder()); + + int size = bSend.capacity() / (1024 * 1024); + System.out.println("Test sending large message (~" + size + "Mb)"); + + ZMQ.Socket push = null; + ZMQ.Socket pull = null; + try { + push = context.socket(ZMQ.PUSH); + pull = context.socket(ZMQ.PULL); + pull.bind("tcp://*:" + port); + push.connect("tcp://localhost:" + port); + + Thread.sleep(1000); + + long start = System.currentTimeMillis(); + push.sendByteBuffer(bSend, 0); + pull.recvByteBuffer(bRec, 0); + long end = System.currentTimeMillis(); + System.out.println("Received ~" + size + "Mb msg in " + (end - start) + " millisec."); + bRec.flip(); + assertEquals(bSend, bRec); + } + catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + finally { + try { + push.close(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + try { + pull.close(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + try { + context.term(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + } + } + + @Test + public void testByteBufferLargeDirect() throws InterruptedException, IOException, CharacterCodingException + { + int port = Utils.findOpenPort(); + ZMQ.Context context = ZMQ.context(1); + int[] array = new int[2048 * 2000]; + for (int i = 0; i < array.length; ++i) { + array[i] = i; + } + ByteBuffer bSend = ByteBuffer.allocateDirect(Integer.SIZE / 8 * array.length).order(ByteOrder.nativeOrder()); + bSend.asIntBuffer().put(array); + ByteBuffer bRec = ByteBuffer.allocateDirect(bSend.capacity()).order(ByteOrder.nativeOrder()); + int[] recArray = new int[array.length]; + + int size = bSend.capacity() / (1024 * 1024); + System.out.println("Test sending direct large message (~" + size + "Mb)"); + + ZMQ.Socket push = null; + ZMQ.Socket pull = null; + try { + push = context.socket(ZMQ.PUSH); + pull = context.socket(ZMQ.PULL); + pull.bind("tcp://*:" + port); + push.connect("tcp://localhost:" + port); + + Thread.sleep(1000); + + long start = System.currentTimeMillis(); + push.sendByteBuffer(bSend, 0); + pull.recvByteBuffer(bRec, 0); + long end = System.currentTimeMillis(); + System.out.println("Received ~" + size + "Mb msg in " + (end - start) + " millisec."); + + bRec.flip(); + bRec.asIntBuffer().get(recArray); + assertArrayEquals(array, recArray); + } + catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + finally { + try { + push.close(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + try { + pull.close(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + try { + context.term(); + } + catch (Exception ignore) { + ignore.printStackTrace(); + } + } + } +} diff --git a/src/test/java/org/zeromq/HighWatermarkTest.java b/src/test/java/org/zeromq/HighWatermarkTest.java new file mode 100644 index 000000000..1f13ea937 --- /dev/null +++ b/src/test/java/org/zeromq/HighWatermarkTest.java @@ -0,0 +1,314 @@ +package org.zeromq; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; + +import org.junit.Test; + +public class HighWatermarkTest +{ + public static final int N_MESSAGES = 30000; + public static final int MESSAGE_SIZE = 50; + + public static final int FILL_WATERMARK = 3000; + public static final int TRACE = 7000; + + public static class Dispatcher implements Runnable + { + private final String control; + + private final String dispatch; + private final boolean trace; + private final String msg; + + public Dispatcher(String msg, String dispatch, String control, boolean trace) + { + this.msg = msg; + this.dispatch = dispatch; + this.control = control; + this.trace = trace; + } + + @Override + public void run() + { + Thread.currentThread().setName("Dispatcher"); + + ZContext context = new ZContext(1); + + // Socket to send messages on + ZMQ.Socket sender = context.createSocket(ZMQ.PUSH); + sender.setImmediate(false); + sender.bind(dispatch); + + ZMQ.Socket controller = context.createSocket(ZMQ.SUB); + controller.subscribe(ZMQ.SUBSCRIPTION_ALL); + controller.connect(control); + + try { + System.out.println("Sending " + N_MESSAGES + " tasks (" + MESSAGE_SIZE + "b) to workers\n"); + + // The first message is "0" and signals start of batch + sender.send("0", 0); + + System.out.println("Started dispatcher on " + dispatch); + + // Send N_MESSAGES tasks + for (int taskNbr = 0; taskNbr < N_MESSAGES; taskNbr++) { + sender.send(taskNbr + " - " + msg, 0); + if (trace) { + System.out.println(taskNbr + " - Dispatcher sent msg"); + } + } + + System.out.println("Dispatcher finished, awaiting for collector finish"); + controller.recvStr(); + // We can finish NOW! + } + finally { + if (trace) { + System.out.println("Dispatcher closing."); + } + context.close(); + System.out.println("Dispatcher done."); + } + } + } + + public static class Worker implements Runnable + { + private final String control; + + private final String dispatch; + private final String collect; + private final boolean trace; + private final int index; + + public Worker(String dispatch, String collect, String control, int index, boolean trace) + { + this.dispatch = dispatch; + this.collect = collect; + this.control = control; + this.index = index; + this.trace = trace; + } + + @Override + public void run() + { + Thread.currentThread().setName("Worker #" + index); + + ZContext context = new ZContext(1); + + // Socket to receive messages on + ZMQ.Socket receiver = context.createSocket(ZMQ.PULL); + receiver.setImmediate(false); + receiver.connect(dispatch); + + // Socket to send messages to + ZMQ.Socket sender = context.createSocket(ZMQ.PUSH); + sender.setImmediate(false); + sender.connect(collect); + + ZMQ.Socket controller = context.createSocket(ZMQ.SUB); + controller.subscribe("FINISH"); + controller.connect(control); + + ZMQ.Poller poller = context.createPoller(3); + poller.register(receiver, ZMQ.Poller.POLLIN); + poller.register(sender, ZMQ.Poller.POLLOUT); + poller.register(controller, ZMQ.Poller.POLLIN); + + int idx = 0; + + try { + System.out.println("Started worker process #" + index); + + // Process tasks forever + while (!Thread.currentThread().isInterrupted()) { + poller.poll(1000); + boolean in = poller.pollin(0); + boolean out = poller.pollout(1); + boolean ctrl = poller.pollin(2); + if (in && out) { + String msg = new String(receiver.recv(0), ZMQ.CHARSET).trim(); + // Simple progress indicator for the viewer + if (trace) { + System.out.println("Worker #" + index + " recv " + msg); + } + else { + if (idx % TRACE == 0) { + System.out.println("Worker #" + index + " recv " + idx + " messages"); + } + } + ++idx; + // the pipes reach the watermark once in a while + if (idx % FILL_WATERMARK == 10) { + LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS)); + } + + // Send results to sink + sender.send("#" + index + " - " + msg, 0); + } + if (ctrl) { + break; + } + } + } + finally { + if (trace) { + System.out.println("Worker #" + index + " closing."); + } + context.close(); + if (trace) { + System.out.println("Worker #" + index + " done."); + } + } + } + } + + public static class Collector implements Runnable + { + private final String control; + + private final boolean trace; + private final String collect; + private final String msg; + + private final int workers; + + private final AtomicBoolean success = new AtomicBoolean(); + + public Collector(String msg, String collect, String control, int workers, boolean trace) + { + this.msg = msg; + this.collect = collect; + this.control = control; + this.workers = workers; + this.trace = trace; + } + + @Override + public void run() + { + Thread.currentThread().setName("Collector"); + if (trace) { + System.out.println("Started collector on " + collect); + } + + // Prepare our context and socket + ZContext context = new ZContext(1); + + ZMQ.Socket receiver = context.createSocket(ZMQ.PULL); + receiver.setImmediate(false); + receiver.bind(collect); + + ZMQ.Socket controller = context.createSocket(ZMQ.PUB); + controller.bind(control); + + try { + // Wait for start of batch + String msg = new String(receiver.recv(0), ZMQ.CHARSET); + + if (trace) { + System.out.println("Collector started"); + } + + for (int taskNbr = 0; taskNbr < N_MESSAGES; taskNbr++) { + if (taskNbr % FILL_WATERMARK == 10) { + LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS)); + } + msg = new String(receiver.recv(0), ZMQ.CHARSET).trim(); + if (trace) { + System.out.println("Collector recv : " + taskNbr + " -> " + msg); + } + else if (taskNbr % TRACE == 0 || taskNbr == 100) { + System.out.println("Collector recv : " + taskNbr + " messages "); + } + + // Test received messages + if (workers == 1) { + if (msg.indexOf(" - " + taskNbr + " - ") != 2) { + System.out.println(taskNbr + " - Message was not correct ! " + msg); + break; + } + } + if (!msg.endsWith(this.msg) && !msg.endsWith(" - 0")) { + System.out.println(taskNbr + " - Message was not correct ! " + msg); + break; + } + } + + controller.send("FINISH"); // Signal dispatcher to finish + } + finally { + context.close(); + System.out.println("Collector done."); + } + + success.set(true); + } + } + + @Test + public void testReliabilityOnWatermark() throws IOException, InterruptedException + { + testWatermark(1); + } + + @Test + public void testReliabilityOnWatermark2() throws IOException, InterruptedException + { + testWatermark(2); + } + + private void testWatermark(int workers) throws IOException, InterruptedException + { + long start = System.currentTimeMillis(); + + ExecutorService threadPool = Executors.newFixedThreadPool(workers + 2); + + String control = "tcp://localhost:" + Utils.findOpenPort(); + String collect = "tcp://localhost:" + Utils.findOpenPort(); + String dispatch = "tcp://localhost:" + Utils.findOpenPort(); + + String msg = randomString(MESSAGE_SIZE); + + Dispatcher dispatcher = new Dispatcher(msg, dispatch, control, false); + Collector collector = new Collector(msg, collect, control, workers, false); + threadPool.submit(dispatcher); + threadPool.submit(collector); + for (int idx = 0; idx < workers; ++idx) { + threadPool.submit(new Worker(dispatch, collect, control, idx + 1, false)); + } + + threadPool.shutdown(); + threadPool.awaitTermination(30, TimeUnit.SECONDS); + long end = System.currentTimeMillis(); + assertThat(collector.success.get(), is(true)); + System.out.println("Test done in " + (end - start) + " millis."); + } + + /*--------------------------------------------------------------*/ + + private static final String ABC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final SecureRandom rnd = new SecureRandom(); + + // http://stackoverflow.com/a/157202 + private static String randomString(int len) + { + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + sb.append(ABC.charAt(rnd.nextInt(ABC.length()))); + } + return sb.toString(); + } +} diff --git a/src/test/java/org/zeromq/TestDisconnectInproc.java b/src/test/java/org/zeromq/TestDisconnectInprocZeromq.java similarity index 93% rename from src/test/java/org/zeromq/TestDisconnectInproc.java rename to src/test/java/org/zeromq/TestDisconnectInprocZeromq.java index 399155bc1..b5ba428b4 100644 --- a/src/test/java/org/zeromq/TestDisconnectInproc.java +++ b/src/test/java/org/zeromq/TestDisconnectInprocZeromq.java @@ -1,11 +1,11 @@ package org.zeromq; -import org.junit.Test; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class TestDisconnectInproc +import org.junit.Test; + +public class TestDisconnectInprocZeromq { @Test public void testDisconnectInproc() throws Exception @@ -46,7 +46,7 @@ public void testDisconnectInproc() throws Exception } if (!pubSocket.hasReceiveMore()) { - break; // Last message part + break; // Last message part } } } @@ -60,7 +60,7 @@ public void testDisconnectInproc() throws Exception if (!subSocket.hasReceiveMore()) { publicationsReceived++; - break; // Last message part + break; // Last message part } } } @@ -85,6 +85,6 @@ public void testDisconnectInproc() throws Exception assertEquals(3, publicationsReceived); assertTrue(!isSubscribed); - ctx.destroy(); + ctx.close(); } } diff --git a/src/test/java/org/zeromq/TestEvents.java b/src/test/java/org/zeromq/TestEvents.java new file mode 100644 index 000000000..5c0bf4f98 --- /dev/null +++ b/src/test/java/org/zeromq/TestEvents.java @@ -0,0 +1,253 @@ +package org.zeromq; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.junit.Test; +import org.zeromq.ZMQ.Context; +import org.zeromq.ZMQ.Socket; + +public class TestEvents +{ + @Test + public void testEventConnected() + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket helper = context.socket(ZMQ.REQ); + int port = helper.bindToRandomPort("tcp://127.0.0.1"); + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + monitor.setReceiveTimeOut(100); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CONNECTED)); + monitor.connect("inproc://monitor.socket"); + + socket.connect("tcp://127.0.0.1:" + port); + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_CONNECTED, event.getEvent()); + + helper.close(); + socket.close(); + monitor.close(); + context.term(); + } + + @Test + public void testEventConnectDelayed() throws IOException + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + monitor.setReceiveTimeOut(100); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CONNECT_DELAYED)); + monitor.connect("inproc://monitor.socket"); + + int randomPort = Utils.findOpenPort(); + + socket.connect("tcp://127.0.0.1:" + randomPort); + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_CONNECT_DELAYED, event.getEvent()); + + socket.close(); + monitor.close(); + context.term(); + } + + @Test + public void testEventConnectRetried() throws InterruptedException, IOException + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + monitor.setReceiveTimeOut(100); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CONNECT_RETRIED)); + monitor.connect("inproc://monitor.socket"); + + int randomPort = Utils.findOpenPort(); + + socket.connect("tcp://127.0.0.1:" + randomPort); + Thread.sleep(1000L); // on windows, this is required, otherwise test fails + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_CONNECT_RETRIED, event.getEvent()); + + socket.close(); + monitor.close(); + context.term(); + } + + @Test + public void testEventListening() + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + monitor.setReceiveTimeOut(100); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_LISTENING)); + monitor.connect("inproc://monitor.socket"); + + socket.bindToRandomPort("tcp://127.0.0.1"); + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_LISTENING, event.getEvent()); + + socket.close(); + monitor.close(); + context.term(); + } + + @Test + public void testEventBindFailed() + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket helper = context.socket(ZMQ.REP); + int port = helper.bindToRandomPort("tcp://127.0.0.1"); + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + monitor.setReceiveTimeOut(100); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_BIND_FAILED)); + monitor.connect("inproc://monitor.socket"); + + try { + socket.bind("tcp://127.0.0.1:" + port); + } + catch (ZMQException ex) { + } + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_BIND_FAILED, event.getEvent()); + + helper.close(); + socket.close(); + monitor.close(); + context.term(); + } + + @Test + public void testEventAccepted() + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + Socket helper = context.socket(ZMQ.REQ); + monitor.setReceiveTimeOut(100); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_ACCEPTED)); + monitor.connect("inproc://monitor.socket"); + + int port = socket.bindToRandomPort("tcp://127.0.0.1"); + + helper.connect("tcp://127.0.0.1:" + port); + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_ACCEPTED, event.getEvent()); + + helper.close(); + socket.close(); + monitor.close(); + context.term(); + } + + @Test + public void testEventClosed() + { + Context context = ZMQ.context(1); + Socket monitor = context.socket(ZMQ.PAIR); + try { + ZMQ.Event event; + + Socket socket = context.socket(ZMQ.REP); + monitor.setReceiveTimeOut(100); + + socket.bindToRandomPort("tcp://127.0.0.1"); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CLOSED)); + monitor.connect("inproc://monitor.socket"); + + socket.close(); + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_CLOSED, event.getEvent()); + + } + finally { + monitor.close(); + context.term(); + } + } + + @Test + public void testEventDisconnected() + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + Socket helper = context.socket(ZMQ.REQ); + monitor.setReceiveTimeOut(100); + + int port = socket.bindToRandomPort("tcp://127.0.0.1"); + helper.connect("tcp://127.0.0.1:" + port); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_DISCONNECTED)); + monitor.connect("inproc://monitor.socket"); + + zmq.ZMQ.sleep(1); + + helper.close(); + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_DISCONNECTED, event.getEvent()); + + socket.close(); + monitor.close(); + context.term(); + } + + @Test + public void testEventMonitorStopped() + { + Context context = ZMQ.context(1); + ZMQ.Event event; + + Socket socket = context.socket(ZMQ.REP); + Socket monitor = context.socket(ZMQ.PAIR); + monitor.setReceiveTimeOut(100); + + assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_MONITOR_STOPPED)); + monitor.connect("inproc://monitor.socket"); + + socket.monitor(null, 0); + event = ZMQ.Event.recv(monitor); + assertNotNull("No event was received", event); + assertEquals(ZMQ.EVENT_MONITOR_STOPPED, event.getEvent()); + + socket.close(); + monitor.close(); + context.term(); + } +} diff --git a/src/test/java/org/zeromq/TestPoller.java b/src/test/java/org/zeromq/TestPoller.java new file mode 100644 index 000000000..f40fc95fe --- /dev/null +++ b/src/test/java/org/zeromq/TestPoller.java @@ -0,0 +1,85 @@ +package org.zeromq; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; +import org.zeromq.ZMQ.Socket; + +public class TestPoller +{ + static class Client implements Runnable + { + private final AtomicBoolean received = new AtomicBoolean(); + private final String address; + + public Client(String addr) + { + this.address = addr; + } + + @Override + public void run() + { + ZMQ.Context context = ZMQ.context(1); + Socket pullConnect = context.socket(ZMQ.PULL); + + pullConnect.connect(address); + + System.out.println("Receiver Started"); + pullConnect.recv(0); + received.set(true); + + pullConnect.close(); + context.close(); + System.out.println("Receiver Stopped"); + } + } + + @Test + public void testPollerPollout() throws Exception + { + int port = Utils.findOpenPort(); + String addr = "tcp://127.0.0.1:" + port; + + Client client = new Client(addr); + + ZMQ.Context context = ZMQ.context(1); + + // Socket to send messages to + ZMQ.Socket sender = context.socket(ZMQ.PUSH); + sender.setImmediate(false); + sender.bind(addr); + + ZMQ.Poller outItems; + outItems = context.poller(); + outItems.register(sender, ZMQ.Poller.POLLOUT); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + while (!Thread.currentThread().isInterrupted()) { + outItems.poll(1000); + if (outItems.pollout(0)) { + boolean rc = sender.send("OK", 0); + assertThat(rc, is(true)); + System.out.println("Sender: wrote message"); + break; + } + else { + System.out.println("Sender: not writable"); + executor.submit(client); + } + } + + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + sender.close(); + System.out.println("Poller test done"); + assertThat(client.received.get(), is(true)); + context.term(); + } +} diff --git a/src/test/java/org/zeromq/TestProxy.java b/src/test/java/org/zeromq/TestProxy.java index b8aa98d9a..22d0b8e83 100644 --- a/src/test/java/org/zeromq/TestProxy.java +++ b/src/test/java/org/zeromq/TestProxy.java @@ -1,155 +1,254 @@ package org.zeromq; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; import org.zeromq.ZMQ.Context; import org.zeromq.ZMQ.Socket; -import org.junit.Test; - -import static org.junit.Assert.assertNotNull; public class TestProxy { static class Client extends Thread { - private int port = -1; - private Socket s = null; - private String name = null; + private final int port; + private final String name; + private final AtomicBoolean result = new AtomicBoolean(); + private final CountDownLatch latch; - public Client(Context ctx, String name, int port) + public Client(String name, int port, CountDownLatch latch) { - s = ctx.socket(ZMQ.REQ); + this.latch = latch; + this.name = name; this.port = port; - - s.setIdentity(name.getBytes(ZMQ.CHARSET)); } @Override public void run() { - s.connect("tcp://127.0.0.1:" + port); - s.send("hello", 0); - String msg = s.recvStr(0); - s.send("world", 0); - msg = s.recvStr(0); + Context ctx = ZMQ.context(1); + assertThat(ctx, notNullValue()); + + Socket socket = ctx.socket(ZMQ.REQ); + boolean rc; +// rc = socket.setImmediate(false); +// assertThat(rc, is(true)); + rc = socket.setIdentity(name.getBytes(ZMQ.CHARSET)); + assertThat(rc, is(true)); + + System.out.println("Start " + name); + setName(name); + + rc = socket.connect("tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + result.set(process(socket)); + latch.countDown(); + socket.close(); + ctx.close(); + System.out.println("Stop " + name); + } - s.close(); + private boolean process(Socket socket) + { + boolean rc = socket.send("hello"); + if (!rc) { + System.out.println(name + " unable to send first message"); + return false; + } + System.out.println(name + " sent 1st message"); + String msg = socket.recvStr(0); + System.out.println(name + " received " + msg); + if (msg == null || !msg.startsWith("OK hello")) { + return false; + } + rc = socket.send("world"); + if (!rc) { + System.out.println(name + " unable to send second message"); + return false; + } + msg = socket.recvStr(0); + System.out.println(name + " received " + msg); + if (msg == null || !msg.startsWith("OK world")) { + return false; + } + + return true; } } static class Dealer extends Thread { - private int port = -1; - private Socket s = null; - private String name = null; + private final int port; + private final String name; + private final AtomicBoolean result = new AtomicBoolean(); + private final CountDownLatch latch; - public Dealer(Context ctx, String name, int port) + public Dealer(String name, int port, CountDownLatch latch) { - s = ctx.socket(ZMQ.DEALER); + this.latch = latch; + this.name = name; this.port = port; - - s.setIdentity(name.getBytes(ZMQ.CHARSET)); } @Override public void run() { - System.out.println("Start dealer " + name); + Context ctx = ZMQ.context(1); + assertThat(ctx, notNullValue()); + + setName(name); + System.out.println("Start " + name); + + Socket socket = ctx.socket(ZMQ.DEALER); + boolean rc; +// rc = socket.setImmediate(false); +// assertThat(rc, is(true)); + rc = socket.setIdentity(name.getBytes(ZMQ.CHARSET)); + assertThat(rc, is(true)); + rc = socket.connect("tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + result.set(process(socket)); + latch.countDown(); + socket.close(); + ctx.close(); + System.out.println("Stop " + name); + } - s.connect("tcp://127.0.0.1:" + port); + private boolean process(Socket socket) + { int count = 0; while (count < 2) { - String msg = s.recvStr(0); - if (msg == null) { - throw new RuntimeException(); + String msg = socket.recvStr(0); + if (msg == null || !msg.startsWith("Client-")) { + System.out.println(name + " Wrong identity " + msg); + return false; } - String identity = msg; + final String identity = msg; System.out.println(name + " received client identity " + identity); - msg = s.recvStr(0); - if (msg == null) { - throw new RuntimeException(); + + msg = socket.recvStr(0); + if (msg == null || !msg.isEmpty()) { + System.out.println("Not bottom " + msg); + return false; } System.out.println(name + " received bottom " + msg); - msg = s.recvStr(0); + msg = socket.recvStr(0); if (msg == null) { - throw new RuntimeException(); + System.out.println(name + " Not data " + msg); + return false; } - String data = msg; + System.out.println(name + " received data " + msg); - System.out.println(name + " received data " + msg + " " + data); - s.send(identity, ZMQ.SNDMORE); - s.send((byte[]) null, ZMQ.SNDMORE); + socket.send(identity, ZMQ.SNDMORE); + socket.send((byte[]) null, ZMQ.SNDMORE); - String response = "OK " + data; + String response = "OK " + msg + " " + name; - s.send(response, 0); + socket.send(response, 0); count++; } - s.close(); - System.out.println("Stop dealer " + name); + return true; } } - static class Main extends Thread + static class Proxy extends Thread { - int frontendPort; - int backendPort; - Context ctx; + private final int frontendPort; + private final int backendPort; + private final int controlPort; + private final AtomicBoolean result = new AtomicBoolean(); - Main(Context ctx, int frontendPort, int backendPort) + Proxy(int frontendPort, int backendPort, int controlPort) { - this.ctx = ctx; this.frontendPort = frontendPort; this.backendPort = backendPort; + this.controlPort = controlPort; } @Override public void run() { + Context ctx = ZMQ.context(1); + assert (ctx != null); + + setName("Proxy"); Socket frontend = ctx.socket(ZMQ.ROUTER); - assertNotNull(frontend); + assertThat(frontend, notNullValue()); frontend.bind("tcp://127.0.0.1:" + frontendPort); +// frontend.setImmediate(false); Socket backend = ctx.socket(ZMQ.DEALER); - assertNotNull(backend); + assertThat(backend, notNullValue()); backend.bind("tcp://127.0.0.1:" + backendPort); +// backend.setImmediate(false); + + Socket control = ctx.socket(ZMQ.PAIR); + assertThat(control, notNullValue()); + control.bind("tcp://127.0.0.1:" + controlPort); - ZMQ.proxy(frontend, backend, null); + ZMQ.proxy(frontend, backend, null, control); frontend.close(); backend.close(); - - assert true; + control.close(); + ctx.close(); + result.set(true); } } @Test - public void testProxy() throws Exception + public void testProxy() throws Exception { int frontendPort = Utils.findOpenPort(); int backendPort = Utils.findOpenPort(); + int controlPort = Utils.findOpenPort(); - Context ctx = ZMQ.context(1); - assert (ctx != null); + Proxy proxy = new Proxy(frontendPort, backendPort, controlPort); + proxy.start(); + Thread.sleep(1000); - Main mt = new Main(ctx, frontendPort, backendPort); - mt.start(); - new Dealer(ctx, "AA", backendPort).start(); - new Dealer(ctx, "BB", backendPort).start(); + CountDownLatch latch = new CountDownLatch(4); + Dealer d1 = new Dealer("Dealer-A", backendPort, latch); + Dealer d2 = new Dealer("Dealer-B", backendPort, latch); + d1.start(); + d2.start(); - Thread.sleep(1000); - Thread c1 = new Client(ctx, "X", frontendPort); + Client c1 = new Client("Client-X", frontendPort, latch); c1.start(); - Thread c2 = new Client(ctx, "Y", frontendPort); + Client c2 = new Client("Client-Y", frontendPort, latch); c2.start(); - c1.join(); - c2.join(); + try { + latch.await(40, TimeUnit.SECONDS); + } + catch (Exception e) { + } - ctx.term(); + Context ctx = ZMQ.context(1); + Socket control = ctx.socket(ZMQ.PAIR); + control.connect("tcp://127.0.0.1:" + controlPort); + control.send("TERMINATE"); + proxy.join(); + control.close(); + ctx.close(); + + assertThat(c1.result.get(), is(true)); + assertThat(c2.result.get(), is(true)); + assertThat(d1.result.get(), is(true)); + assertThat(d2.result.get(), is(true)); + assertThat(proxy.result.get(), is(true)); } } diff --git a/src/test/java/org/zeromq/TestPushPullThreadedTcp.java b/src/test/java/org/zeromq/TestPushPullThreadedTcp.java new file mode 100644 index 000000000..33583f9f3 --- /dev/null +++ b/src/test/java/org/zeromq/TestPushPullThreadedTcp.java @@ -0,0 +1,143 @@ +package org.zeromq; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Ignore; +import org.junit.Test; + +/** + * Tests a PUSH-PULL dialog with several methods, each component being on a + * separate thread. + */ +@Ignore +public class TestPushPullThreadedTcp +{ + private class Worker implements Runnable + { + private final String host; + private final int count; + final AtomicBoolean finished = new AtomicBoolean(); + private int idx; + + public Worker(String host, int count) + { + this.host = host; + this.count = count; + } + + @Override + public void run() + { + ZContext ctx = new ZContext(); + + ZMQ.Socket receiver = ctx.createSocket(ZMQ.PULL); + receiver.setImmediate(false); + receiver.bind(host); + + idx = 0; + while (idx < count) { + if (idx % 5000 == 10) { + zmq.ZMQ.sleep(1); + } + ZMsg msg = ZMsg.recvMsg(receiver); + msg.destroy(); + idx++; + } + + // Clean up. + ctx.destroySocket(receiver); + ctx.close(); + + finished.set(true); + } + } + + private class Client implements Runnable + { + private final String host; + + final AtomicBoolean finished = new AtomicBoolean(); + + private final int count; + + public Client(String host, int count) + { + this.host = host; + this.count = count; + } + + @Override + public void run() + { + ZContext ctx = new ZContext(); + + ZMQ.Socket sender = ctx.createSocket(ZMQ.PUSH); + sender.setImmediate(false); + sender.connect(host); + + int idx = 0; + while (idx++ < count) { + ZMsg msg = new ZMsg(); + msg.add("DATA"); + boolean sent = msg.send(sender); + assertThat(sent, is(true)); + } + zmq.ZMQ.sleep(2); + // Clean up. + ctx.destroySocket(sender); + ctx.close(); + + finished.set(true); + } + } + + @Test + public void testPushPull1() throws Exception + { + test(1); + } + + @Test + public void testPushPull500() throws Exception + { + System.out.println("Sending 500 messages"); + test(500); + } + + @Test + public void testPushPullWithWatermark() throws Exception + { + System.out.println("Sending 20000 messages to trigger watermark limit"); + test(20000); + } + + private void test(int count) throws IOException, InterruptedException + { + long start = System.currentTimeMillis(); + int port = Utils.findOpenPort(); + String host = "tcp://localhost:" + port; + + ExecutorService threadPool = Executors.newFixedThreadPool(2); + + Worker worker = new Worker(host, count); + Client client = new Client(host, count); + + threadPool.submit(worker); + threadPool.submit(client); + + threadPool.shutdown(); + threadPool.awaitTermination(20, TimeUnit.SECONDS); + long end = System.currentTimeMillis(); + System.out.println("Worker received " + worker.idx + " messages"); + assertThat(worker.finished.get(), is(true)); + assertThat(client.finished.get(), is(true)); + System.out.println("Test done in " + (end - start) + " millis."); + } +} diff --git a/src/test/java/org/zeromq/TestReqRouterThreadedTcp.java b/src/test/java/org/zeromq/TestReqRouterThreadedTcp.java index 8a555f0b9..0195144d1 100644 --- a/src/test/java/org/zeromq/TestReqRouterThreadedTcp.java +++ b/src/test/java/org/zeromq/TestReqRouterThreadedTcp.java @@ -4,6 +4,9 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; @@ -12,7 +15,6 @@ /** * Tests a REQ-ROUTER dialog with several methods, * each component being on a separate thread. - * @author fred * */ public class TestReqRouterThreadedTcp @@ -21,10 +23,9 @@ public class TestReqRouterThreadedTcp /** * A very simple server for one reply only. - * @author fred * */ - private class Server extends Thread + private class Server implements Runnable { private final int port; @@ -45,9 +46,13 @@ public void run() ZMQ.Socket server = ctx.createSocket(ZMQ.ROUTER); server.bind("tcp://localhost:" + port); + System.out.println("Server started"); ZMsg msg = ZMsg.recvMsg(server); // only one echo message for this server msg.send(server); + System.out.println("Server sent reply"); + + zmq.ZMQ.sleep(1); msg.destroy(); @@ -57,7 +62,7 @@ public void run() } } - private class Client extends Thread + private class Client implements Runnable { private final int port; @@ -81,19 +86,22 @@ public void run() client.connect("tcp://localhost:" + port); + System.out.println("Client started"); client.send("DATA"); + System.out.println("Client sent message"); inBetween(ctx, client); String reply = client.recvStr(); + System.out.println("Client received message"); assertThat(reply, notNullValue()); assertThat(reply, is("DATA")); + finished.set(true); + // Clean up. ctx.destroySocket(client); ctx.close(); - - finished.set(true); } /** @@ -114,16 +122,17 @@ public ClientPoll(int port) } // same results -// @Override -// protected void inBetween(ZContext ctx, Socket client) { -// // Poll socket for a reply, with timeout -// PollItem items[] = { new PollItem(client, ZMQ.Poller.POLLIN) }; -// int rc = ZMQ.poll(items, 1, REQUEST_TIMEOUT); -// assertThat(rc, is(1)); -// boolean readable = items[0].isReadable(); -// assertThat(readable, is(true)); -// } - + // @Override + // protected void inBetween(ZContext ctx, Socket client) + // { + // // Poll socket for a reply, with timeout + // PollItem items[] = { new PollItem(client, ZMQ.Poller.POLLIN) }; + // int rc = ZMQ.poll(items, 1, REQUEST_TIMEOUT); + // assertThat(rc, is(1)); + // boolean readable = items[0].isReadable(); + // assertThat(readable, is(true)); + // } + // /** * Here we use a poller to check for readability of the message. * This should activate the prefetching mechanism. @@ -151,16 +160,18 @@ protected void inBetween(ZContext ctx, Socket client) @Test public void testReqRouterTcp() throws Exception { + System.out.println("test Req + Router"); int port = Utils.findOpenPort(); - Server server = new Server(port); - - server.start(); + ExecutorService executor = Executors.newFixedThreadPool(2); + Server server = new Server(port); Client client = new Client(port); - client.start(); - server.join(); - client.join(); + executor.submit(server); + executor.submit(client); + + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); boolean finished = client.finished.get(); assertThat(finished, is(true)); @@ -174,16 +185,18 @@ public void testReqRouterTcp() throws Exception @Test public void testReqRouterTcpPoll() throws Exception { + System.out.println("test Req + Router with polling"); int port = Utils.findOpenPort(); - Server server = new Server(port); - - server.start(); + ExecutorService executor = Executors.newFixedThreadPool(2); + Server server = new Server(port); ClientPoll client = new ClientPoll(port); - client.start(); - server.join(); - client.join(); + executor.submit(server); + executor.submit(client); + + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); boolean finished = client.finished.get(); assertThat(finished, is(true)); diff --git a/src/test/java/org/zeromq/TestZActor.java b/src/test/java/org/zeromq/TestZActor.java index dc5472205..a97651828 100644 --- a/src/test/java/org/zeromq/TestZActor.java +++ b/src/test/java/org/zeromq/TestZActor.java @@ -5,8 +5,8 @@ import java.util.UUID; import org.junit.Assert; -import org.junit.Test; import org.junit.Ignore; +import org.junit.Test; import org.zeromq.ZActor.Actor; import org.zeromq.ZMQ.Socket; @@ -42,7 +42,8 @@ public boolean backstage(Socket pipe, ZPoller poller, int events) } }; ZContext context = new ZContext(); - ZActor actor = new ZActor(context, new ZAgent.VerySimpleSelectorCreator(), acting, "LOCK", Arrays.asList("TEST").toArray()); + ZActor actor = new ZActor(context, new ZAgent.VerySimpleSelectorCreator(), acting, "LOCK", + Arrays.asList("TEST").toArray()); Socket pipe = actor.pipe(); boolean rc = pipe.send("HELLO"); Assert.assertTrue("Unable to send a message through pipe", rc); @@ -69,7 +70,7 @@ public boolean backstage(Socket pipe, ZPoller poller, int events) } catch (ZMQException e) { int errno = e.getErrorCode(); - Assert.assertEquals("Expected exception has the wrong code", ZError.ETERM, errno); + Assert.assertEquals("Expected exception has the wrong code", ZError.ETERM, errno); } context.close(); @@ -118,7 +119,8 @@ public boolean destroyed(ZContext ctx, Socket pipe, ZPoller poller) } }; ZContext context = new ZContext(); - ZActor actor = new ZActor(context, new ZAgent.VerySimpleSelectorCreator(), acting, UUID.randomUUID().toString(), Arrays.asList("TEST").toArray()); + ZActor actor = new ZActor(context, new ZAgent.VerySimpleSelectorCreator(), acting, UUID.randomUUID().toString(), + Arrays.asList("TEST").toArray()); ZAgent agent = actor.agent(); agent = actor; @@ -158,7 +160,7 @@ public boolean destroyed(ZContext ctx, Socket pipe, ZPoller poller) } catch (ZMQException e) { int errno = e.getErrorCode(); - Assert.assertEquals("Expected exception has the wrong code", ZError.ETERM, errno); + Assert.assertEquals("Expected exception has the wrong code", ZError.ETERM, errno); } context.close(); diff --git a/src/test/java/org/zeromq/TestZLoop.java b/src/test/java/org/zeromq/TestZLoop.java index a25152ce2..6bc82aee4 100644 --- a/src/test/java/org/zeromq/TestZLoop.java +++ b/src/test/java/org/zeromq/TestZLoop.java @@ -2,18 +2,18 @@ import org.junit.After; import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.zeromq.ZMQ.PollItem; import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; -import org.zeromq.ZMQ.PollItem; -import org.junit.Test; -import org.junit.Before; public class TestZLoop { - private String received; + private String received; private ZContext ctx; - private Socket input; - private Socket output; + private Socket input; + private Socket output; @Before public void setUp() diff --git a/src/test/java/org/zeromq/TestZMQ.java b/src/test/java/org/zeromq/TestZMQ.java index 8b2028c03..1c9758827 100644 --- a/src/test/java/org/zeromq/TestZMQ.java +++ b/src/test/java/org/zeromq/TestZMQ.java @@ -1,262 +1,22 @@ package org.zeromq; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.CharacterCodingException; - import org.junit.Test; import org.zeromq.ZMQ.Context; import org.zeromq.ZMQ.Socket; +import zmq.ZError; + public class TestZMQ { - static class Client extends Thread - { - private int port = -1; - private Socket s = null; - - public Client(Context ctx, int port) - { - this.port = port; - s = ctx.socket(ZMQ.PULL); - } - - @Override - public void run() - { - System.out.println("Start client thread "); - s.connect("tcp://127.0.0.1:" + port); - s.recv(0); - - s.close(); - System.out.println("Stop client thread "); - } - } - - @Test - public void testPollerPollout() throws Exception - { - int port = Utils.findOpenPort(); - - ZMQ.Context context = ZMQ.context(1); - Client client = new Client(context, port); - - // Socket to send messages to - ZMQ.Socket sender = context.socket(ZMQ.PUSH); - sender.bind("tcp://127.0.0.1:" + port); - - ZMQ.Poller outItems; - outItems = context.poller(); - outItems.register(sender, ZMQ.Poller.POLLOUT); - - while (!Thread.currentThread().isInterrupted()) { - outItems.poll(1000); - if (outItems.pollout(0)) { - sender.send("OK", 0); - System.out.println("ok"); - break; - } - else { - System.out.println("not writable"); - client.start(); - } - } - client.join(); - sender.close(); - context.term(); - } - - @Test - public void testByteBufferSend() throws InterruptedException, IOException - { - int port = Utils.findOpenPort(); - ZMQ.Context context = ZMQ.context(1); - ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.nativeOrder()); - ZMQ.Socket push = null; - ZMQ.Socket pull = null; - try { - push = context.socket(ZMQ.PUSH); - pull = context.socket(ZMQ.PULL); - pull.bind("tcp://*:" + port); - push.connect("tcp://localhost:" + port); - bb.put("PING".getBytes(ZMQ.CHARSET)); - bb.flip(); - push.sendByteBuffer(bb, 0); - String actual = new String(pull.recv(), ZMQ.CHARSET); - assertEquals("PING", actual); - } - finally { - try { - push.close(); - } - catch (Exception ignore) { - } - try { - pull.close(); - } - catch (Exception ignore) { - } - try { - context.term(); - } - catch (Exception ignore) { - } - } - } - - @Test - public void testByteBufferRecv() - throws InterruptedException, IOException, CharacterCodingException - { - int port = Utils.findOpenPort(); - ZMQ.Context context = ZMQ.context(1); - ByteBuffer bb = ByteBuffer.allocate(6).order(ByteOrder.nativeOrder()); - ZMQ.Socket push = null; - ZMQ.Socket pull = null; - try { - push = context.socket(ZMQ.PUSH); - pull = context.socket(ZMQ.PULL); - pull.bind("tcp://*:" + port); - push.connect("tcp://localhost:" + port); - push.send("PING".getBytes(ZMQ.CHARSET), 0); - pull.recvByteBuffer(bb, 0); - bb.flip(); - byte[] b = new byte[bb.remaining()]; - bb.duplicate().get(b); - assertEquals("PING", new String(b, ZMQ.CHARSET)); - } - finally { - try { - push.close(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - try { - pull.close(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - try { - context.term(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - } - - } - - @Test - public void testByteBufferLarge() - throws InterruptedException, IOException, CharacterCodingException - { - int port = Utils.findOpenPort(); - ZMQ.Context context = ZMQ.context(1); - int[] array = new int[2048 * 2000]; - for (int i = 0; i < array.length; ++i) { - array[i] = i; - } - ByteBuffer bSend = ByteBuffer.allocate(Integer.SIZE / 8 * array.length).order(ByteOrder.nativeOrder()); - bSend.asIntBuffer().put(array); - ByteBuffer bRec = ByteBuffer.allocate(bSend.capacity()).order(ByteOrder.nativeOrder()); - int[] recArray = new int[array.length]; - - ZMQ.Socket push = null; - ZMQ.Socket pull = null; - try { - push = context.socket(ZMQ.PUSH); - pull = context.socket(ZMQ.PULL); - pull.bind("tcp://*:" + port); - push.connect("tcp://localhost:" + port); - push.sendByteBuffer(bSend, 0); - pull.recvByteBuffer(bRec, 0); - bRec.flip(); - bRec.asIntBuffer().get(recArray); - assertArrayEquals(array, recArray); - } - finally { - try { - push.close(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - try { - pull.close(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - try { - context.term(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - } - } - - @Test - public void testByteBufferLargeDirect() - throws InterruptedException, IOException, CharacterCodingException - { - int port = Utils.findOpenPort(); - ZMQ.Context context = ZMQ.context(1); - int[] array = new int[2048 * 2000]; - for (int i = 0; i < array.length; ++i) { - array[i] = i; - } - ByteBuffer bSend = ByteBuffer.allocateDirect(Integer.SIZE / 8 * array.length).order(ByteOrder.nativeOrder()); - bSend.asIntBuffer().put(array); - ByteBuffer bRec = ByteBuffer.allocateDirect(bSend.capacity()).order(ByteOrder.nativeOrder()); - int[] recArray = new int[array.length]; - - ZMQ.Socket push = null; - ZMQ.Socket pull = null; - try { - push = context.socket(ZMQ.PUSH); - pull = context.socket(ZMQ.PULL); - pull.bind("tcp://*:" + port); - push.connect("tcp://localhost:" + port); - push.sendByteBuffer(bSend, 0); - pull.recvByteBuffer(bRec, 0); - bRec.flip(); - bRec.asIntBuffer().get(recArray); - assertArrayEquals(array, recArray); - } - finally { - try { - push.close(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - try { - pull.close(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - try { - context.term(); - } - catch (Exception ignore) { - ignore.printStackTrace(); - } - } - } - @Test(expected = ZMQException.class) public void testBindSameAddress() throws IOException { @@ -290,257 +50,13 @@ public void testBindInprocSameAddress() ZMQ.Socket socket1 = context.socket(ZMQ.REQ); ZMQ.Socket socket2 = context.socket(ZMQ.REQ); socket1.bind("inproc://address.already.in.use"); - try { - socket2.bind("inproc://address.already.in.use"); - fail("Exception not thrown"); - } - catch (ZMQException e) { - assertEquals(e.getErrorCode(), ZMQ.Error.EADDRINUSE.getCode()); - throw e; - } - finally { - socket1.close(); - socket2.close(); - - context.term(); - } - } - - @Test - public void testEventConnected() - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket helper = context.socket(ZMQ.REQ); - int port = helper.bindToRandomPort("tcp://127.0.0.1"); - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - monitor.setReceiveTimeOut(100); - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CONNECTED)); - monitor.connect("inproc://monitor.socket"); + socket2.bind("inproc://address.already.in.use"); + assertThat(socket2.errno(), is(ZError.EADDRINUSE)); - socket.connect("tcp://127.0.0.1:" + port); - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_CONNECTED, event.getEvent()); + socket1.close(); + socket2.close(); - helper.close(); - socket.close(); - monitor.close(); - context.term(); - } - - @Test - public void testEventConnectDelayed() throws IOException - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - monitor.setReceiveTimeOut(100); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CONNECT_DELAYED)); - monitor.connect("inproc://monitor.socket"); - - int randomPort = Utils.findOpenPort(); - - socket.connect("tcp://127.0.0.1:" + randomPort); - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_CONNECT_DELAYED, event.getEvent()); - - socket.close(); - monitor.close(); - context.term(); - } - - @Test - public void testEventConnectRetried() - throws InterruptedException, IOException - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - monitor.setReceiveTimeOut(100); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CONNECT_RETRIED)); - monitor.connect("inproc://monitor.socket"); - - int randomPort = Utils.findOpenPort(); - - socket.connect("tcp://127.0.0.1:" + randomPort); - Thread.sleep(1000L); // on windows, this is required, otherwise test fails - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_CONNECT_RETRIED, event.getEvent()); - - socket.close(); - monitor.close(); - context.term(); - } - - @Test - public void testEventListening() - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - monitor.setReceiveTimeOut(100); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_LISTENING)); - monitor.connect("inproc://monitor.socket"); - - socket.bindToRandomPort("tcp://127.0.0.1"); - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_LISTENING, event.getEvent()); - - socket.close(); - monitor.close(); - context.term(); - } - - @Test - public void testEventBindFailed() - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket helper = context.socket(ZMQ.REP); - int port = helper.bindToRandomPort("tcp://127.0.0.1"); - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - monitor.setReceiveTimeOut(100); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_BIND_FAILED)); - monitor.connect("inproc://monitor.socket"); - - try { - socket.bind("tcp://127.0.0.1:" + port); - } - catch (ZMQException ex) { - } - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_BIND_FAILED, event.getEvent()); - - helper.close(); - socket.close(); - monitor.close(); - context.term(); - } - - @Test - public void testEventAccepted() - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - Socket helper = context.socket(ZMQ.REQ); - monitor.setReceiveTimeOut(100); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_ACCEPTED)); - monitor.connect("inproc://monitor.socket"); - - int port = socket.bindToRandomPort("tcp://127.0.0.1"); - - helper.connect("tcp://127.0.0.1:" + port); - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_ACCEPTED, event.getEvent()); - - helper.close(); - socket.close(); - monitor.close(); - context.term(); - } - - @Test - public void testEventClosed() - { - Context context = ZMQ.context(1); - Socket monitor = context.socket(ZMQ.PAIR); - try { - ZMQ.Event event; - - Socket socket = context.socket(ZMQ.REP); - monitor.setReceiveTimeOut(100); - - socket.bindToRandomPort("tcp://127.0.0.1"); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_CLOSED)); - monitor.connect("inproc://monitor.socket"); - - socket.close(); - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_CLOSED, event.getEvent()); - - } - finally { - monitor.close(); - context.term(); - } - } - - @Test - public void testEventDisconnected() - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - Socket helper = context.socket(ZMQ.REQ); - monitor.setReceiveTimeOut(100); - - int port = socket.bindToRandomPort("tcp://127.0.0.1"); - helper.connect("tcp://127.0.0.1:" + port); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_DISCONNECTED)); - monitor.connect("inproc://monitor.socket"); - - helper.close(); - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_DISCONNECTED, event.getEvent()); - - socket.close(); - monitor.close(); - context.term(); - } - - @Test - public void testEventMonitorStopped() - { - Context context = ZMQ.context(1); - ZMQ.Event event; - - Socket socket = context.socket(ZMQ.REP); - Socket monitor = context.socket(ZMQ.PAIR); - monitor.setReceiveTimeOut(100); - - assertTrue(socket.monitor("inproc://monitor.socket", ZMQ.EVENT_MONITOR_STOPPED)); - monitor.connect("inproc://monitor.socket"); - - socket.monitor(null, 0); - event = ZMQ.Event.recv(monitor); - assertNotNull("No event was received", event); - assertEquals(ZMQ.EVENT_MONITOR_STOPPED, event.getEvent()); - - socket.close(); - monitor.close(); context.term(); } @@ -551,19 +67,29 @@ public void testSocketUnbind() Socket push = context.socket(ZMQ.PUSH); Socket pull = context.socket(ZMQ.PULL); - pull.setReceiveTimeOut(50); - - int port = pull.bindToRandomPort("tcp://127.0.0.1"); - push.connect("tcp://127.0.0.1:" + port); + boolean rc = pull.setReceiveTimeOut(50); + assertThat(rc, is(true)); +// rc = push.setImmediate(false); +// assertThat(rc, is(true)); +// rc = pull.setImmediate(false); +// assertThat(rc, is(true)); + int port = push.bindToRandomPort("tcp://127.0.0.1"); + rc = pull.connect("tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + System.out.println("Connecting socket to unbind on port " + port); byte[] data = "ABC".getBytes(); - push.send(data); + rc = push.send(data); + assertThat(rc, is(true)); assertArrayEquals(data, pull.recv()); - pull.unbind("tcp://127.0.0.1:" + port); + rc = pull.unbind("tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); - push.send(data); + rc = push.send(data); + assertThat(rc, is(true)); assertNull(pull.recv()); push.close(); @@ -574,17 +100,17 @@ public void testSocketUnbind() @Test public void testContextBlocky() { - Context ctx = ZMQ.context(1); - Socket router = ctx.socket(ZMQ.ROUTER); - long rc = router.getLinger(); - assertEquals(-1, rc); - router.close(); - ctx.setBlocky(false); - router = ctx.socket(ZMQ.ROUTER); - rc = router.getLinger(); - assertEquals(0, rc); - router.close(); - ctx.term(); + Context ctx = ZMQ.context(1); + Socket router = ctx.socket(ZMQ.ROUTER); + long rc = router.getLinger(); + assertEquals(-1, rc); + router.close(); + ctx.setBlocky(false); + router = ctx.socket(ZMQ.ROUTER); + rc = router.getLinger(); + assertEquals(0, rc); + router.close(); + ctx.term(); } @Test(timeout = 1000) diff --git a/src/test/java/org/zeromq/TestZPoller.java b/src/test/java/org/zeromq/TestZPoller.java index 1eec28137..ccc29294d 100644 --- a/src/test/java/org/zeromq/TestZPoller.java +++ b/src/test/java/org/zeromq/TestZPoller.java @@ -28,8 +28,8 @@ public boolean events(Socket socket, int events) static class Server extends Thread { - private final int port; - private final Socket socket; + private final int port; + private final Socket socket; private final ZPoller poller; public Server(ZContext context, int port) diff --git a/src/test/java/org/zeromq/TestZProxy.java b/src/test/java/org/zeromq/TestZProxy.java index 45b0d9740..ce6ffb68b 100644 --- a/src/test/java/org/zeromq/TestZProxy.java +++ b/src/test/java/org/zeromq/TestZProxy.java @@ -1,35 +1,102 @@ package org.zeromq; import java.io.IOException; - import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import org.junit.After; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; import org.zeromq.ZMQ.Socket; public class TestZProxy { - private int frontPort; - private int backPort; - private int capturePort1; - private int capturePort2; - - @Before - public void setUp() throws IOException + private final class ProxyProvider extends ZProxy.Proxy.SimpleProxy { - frontPort = Utils.findOpenPort(); - backPort = Utils.findOpenPort(); - capturePort1 = Utils.findOpenPort(); - capturePort2 = Utils.findOpenPort(); + private final int frontPort; + private final int backPort; + private final int capturePort1; + private final int capturePort2; + + public ProxyProvider() throws IOException + { + frontPort = Utils.findOpenPort(); + backPort = Utils.findOpenPort(); + capturePort1 = Utils.findOpenPort(); + capturePort2 = Utils.findOpenPort(); + } + + @Override + public Socket create(ZContext ctx, ZProxy.Plug place, Object[] extraArgs) + { + Socket socket = null; + if (place == ZProxy.Plug.FRONT) { + socket = ctx.createSocket(ZMQ.ROUTER); + } + if (place == ZProxy.Plug.BACK) { + socket = ctx.createSocket(ZMQ.DEALER); + } + return socket; + } + + @Override + public void configure(Socket socket, ZProxy.Plug place, Object[] extrArgs) + { + if (place == ZProxy.Plug.FRONT) { + socket.bind("tcp://127.0.0.1:" + frontPort); + } + if (place == ZProxy.Plug.BACK) { + socket.bind("tcp://127.0.0.1:" + backPort); + } + if (place == ZProxy.Plug.CAPTURE && socket != null) { + socket.bind("tcp://127.0.0.1:" + capturePort1); + } + } + + @Override + public boolean restart(ZMsg cfg, Socket socket, ZProxy.Plug place, Object[] extraArgs) + { + // System.out.println("HOT restart msg : " + cfg); + if (place == ZProxy.Plug.FRONT) { + socket.unbind("tcp://127.0.0.1:" + frontPort); + waitSomeTime(); + socket.bind("tcp://127.0.0.1:" + frontPort); + } + if (place == ZProxy.Plug.BACK) { + socket.unbind("tcp://127.0.0.1:" + backPort); + waitSomeTime(); + socket.bind("tcp://127.0.0.1:" + backPort); + } + if (place == ZProxy.Plug.CAPTURE && socket != null) { + socket.unbind("tcp://127.0.0.1:" + capturePort1); + waitSomeTime(); + socket.bind("tcp://127.0.0.1:" + capturePort2); + } + String msg = cfg.popString(); + return "COLD".equals(msg); + } + + @Override + public boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture, Object[] args) + { + assert (cfg.popString().equals("TEST-CONFIG")); + ZMsg msg = new ZMsg(); + msg.add("TODO"); + msg.send(pipe); + return true; + } + + @Override + public boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, Socket capture, Object[] args) + { + // TODO test custom commands + return super.custom(pipe, cmd, frontend, backend, capture, args); + } } @Test - public void testAllOptionsAsync() + public void testAllOptionsAsync() throws IOException { System.out.println("All options async"); ZContext ctx = nullContext(); @@ -38,7 +105,7 @@ public void testAllOptionsAsync() } @Test - public void testAllOptionsAsyncNew() + public void testAllOptionsAsyncNew() throws IOException { System.out.println("All options async new"); ZContext ctx = newContext(); @@ -47,7 +114,7 @@ public void testAllOptionsAsyncNew() } @Test - public void testAllOptionsSync() + public void testAllOptionsSync() throws IOException { System.out.println("All options sync"); ZContext ctx = nullContext(); @@ -56,7 +123,7 @@ public void testAllOptionsSync() } @Test - public void testAllOptionsSyncNew() + public void testAllOptionsSyncNew() throws IOException { System.out.println("All options sync new"); ZContext ctx = newContext(); @@ -65,7 +132,7 @@ public void testAllOptionsSyncNew() } @Test - public void testAllOptionsSyncNewHot() + public void testAllOptionsSyncNewHot() throws IOException { System.out.println("All options sync new hot"); ZContext ctx = newContext(); @@ -76,7 +143,7 @@ public void testAllOptionsSyncNewHot() } @Test - public void testAllOptionsSyncNewCold() + public void testAllOptionsSyncNewCold() throws IOException { System.out.println("All options sync new hot with restart"); ZContext ctx = newContext(); @@ -87,7 +154,7 @@ public void testAllOptionsSyncNewCold() } @Test - public void testStateSync() + public void testStateSync() throws IOException { System.out.println("State sync"); ZContext ctx = newContext(); @@ -96,7 +163,7 @@ public void testStateSync() } @Test - public void testStateSyncPause() + public void testStateSyncPause() throws IOException { System.out.println("State sync pause"); @@ -106,7 +173,7 @@ public void testStateSyncPause() } @Test - public void testStateASync() + public void testStateASync() throws IOException { System.out.println("State async"); @@ -115,77 +182,6 @@ public void testStateASync() wait4NewContext(ctx); } - final ZProxy.Proxy provider = new ZProxy.Proxy.SimpleProxy() - { - @Override - public Socket create(ZContext ctx, ZProxy.Plug place, Object[] extraArgs) - { - Socket socket = null; - if (place == ZProxy.Plug.FRONT) { - socket = ctx.createSocket(ZMQ.ROUTER); - } - if (place == ZProxy.Plug.BACK) { - socket = ctx.createSocket(ZMQ.DEALER); - } - return socket; - } - - @Override - public void configure(Socket socket, ZProxy.Plug place, Object[] extrArgs) - { - if (place == ZProxy.Plug.FRONT) { - socket.bind("tcp://127.0.0.1:" + frontPort); - } - if (place == ZProxy.Plug.BACK) { - socket.bind("tcp://127.0.0.1:" + backPort); - } - if (place == ZProxy.Plug.CAPTURE && socket != null) { - socket.bind("tcp://127.0.0.1:" + capturePort1); - } - } - - @Override - public boolean restart(ZMsg cfg, Socket socket, ZProxy.Plug place, Object[] extraArgs) - { -// System.out.println("HOT restart msg : " + cfg); - if (place == ZProxy.Plug.FRONT) { - socket.unbind("tcp://127.0.0.1:" + frontPort); - waitSomeTime(); - socket.bind("tcp://127.0.0.1:" + frontPort); - } - if (place == ZProxy.Plug.BACK) { - socket.unbind("tcp://127.0.0.1:" + backPort); - waitSomeTime(); - socket.bind("tcp://127.0.0.1:" + backPort); - } - if (place == ZProxy.Plug.CAPTURE && socket != null) { - socket.unbind("tcp://127.0.0.1:" + capturePort1); - waitSomeTime(); - socket.bind("tcp://127.0.0.1:" + capturePort2); - } - String msg = cfg.popString(); - return "COLD".equals(msg); - } - - @Override - public boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture, Object[] args) - { - assert (cfg.popString().equals("TEST-CONFIG")); - ZMsg msg = new ZMsg(); - msg.add("TODO"); - msg.send(pipe); - return true; - } - - @Override - public boolean custom(Socket pipe, String cmd, Socket frontend, - Socket backend, Socket capture, Object[] args) - { - // TODO test custom commands - return super.custom(pipe, cmd, frontend, backend, capture, args); - } - }; - @After public void testSignalsSelectors() throws Exception { @@ -200,7 +196,7 @@ private ZContext nullContext() private void wait4NullContext(ZContext ctx) { // it's necessary to wait a bit for unregistering selectors and signalers - LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(100, TimeUnit.MILLISECONDS)); + LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(300, TimeUnit.MILLISECONDS)); } private void waitSomeTime() @@ -219,9 +215,14 @@ private void wait4NewContext(ZContext ctx) ctx.close(); } - private void testAllOptionsAsync(ZContext ctx, ZMsg hot) + private void testAllOptionsAsync(ZContext ctx, ZMsg hot) throws IOException { - ZProxy proxy = ZProxy.newProxy(ctx, "ProxyAsync" + (ctx == null ? "Null" : ""), provider, "ABRACADABRA", Arrays.asList("TEST")); + ZProxy proxy = ZProxy.newProxy( + ctx, + "ProxyAsync" + (ctx == null ? "Null" : ""), + new ProxyProvider(), + "ABRACADABRA", + Arrays.asList("TEST")); final boolean async = false; String status = null; @@ -261,9 +262,14 @@ private void testAllOptionsAsync(ZContext ctx, ZMsg hot) Assert.assertTrue("exit status is not good!", ZProxy.EXITED.equals(status)); } - private void testAllOptionsSync(ZContext ctx, ZMsg hot) + private void testAllOptionsSync(ZContext ctx, ZMsg hot) throws IOException { - ZProxy proxy = ZProxy.newProxy(ctx, "ProxySync" + (ctx == null ? "Null" : ""), provider, "ABRACADABRA", Arrays.asList("TEST")); + ZProxy proxy = ZProxy.newProxy( + ctx, + "ProxySync" + (ctx == null ? "Null" : ""), + new ProxyProvider(), + "ABRACADABRA", + Arrays.asList("TEST")); final boolean sync = true; String status = null; @@ -307,116 +313,131 @@ private void testAllOptionsSync(ZContext ctx, ZMsg hot) Assert.assertEquals("exit status is not good!", ZProxy.EXITED, status); } - private void testStateSync(ZContext ctx) - { - final boolean sync = true; - String status = null; - ZProxy proxy = ZProxy.newProxy(ctx, "ProxyStateSync" + (ctx == null ? "Null" : ""), provider, "ABRACADABRA", Arrays.asList("TEST")); + private void testStateSync(ZContext ctx) throws IOException + { + final boolean sync = true; + String status = null; + ZProxy proxy = ZProxy.newProxy( + ctx, + "ProxyStateSync" + (ctx == null ? "Null" : ""), + new ProxyProvider(), + "ABRACADABRA", + Arrays.asList("TEST")); - status = proxy.start(sync); - Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); + status = proxy.start(sync); + Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); - status = proxy.pause(sync); - Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); + status = proxy.pause(sync); + Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); - status = proxy.stop(sync); - Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); + status = proxy.stop(sync); + Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); - waitSomeTime(); + waitSomeTime(); - // start the proxy after stopping it - status = proxy.start(sync); - Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); + // start the proxy after stopping it + status = proxy.start(sync); + Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); - waitSomeTime(); + waitSomeTime(); - status = proxy.stop(sync); - Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); + status = proxy.stop(sync); + Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); - waitSomeTime(); + waitSomeTime(); - // pause the proxy after stopping it - status = proxy.pause(sync); - Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); + // pause the proxy after stopping it + status = proxy.pause(sync); + Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); - status = proxy.exit(); + status = proxy.exit(); - Assert.assertEquals("exit status is not good!", ZProxy.EXITED, status); - } + Assert.assertEquals("exit status is not good!", ZProxy.EXITED, status); + } - private void testStateSyncPause(ZContext ctx) - { - final boolean sync = true; - String status = null; - ZProxy proxy = ZProxy.newProxy(ctx, "ProxyStatePauseSync" + (ctx == null ? "Null" : ""), provider, "ABRACADABRA", Arrays.asList("TEST")); + private void testStateSyncPause(ZContext ctx) throws IOException + { + final boolean sync = true; + String status = null; + ZProxy proxy = ZProxy.newProxy( + ctx, + "ProxyStatePauseSync" + (ctx == null ? "Null" : ""), + new ProxyProvider(), + "ABRACADABRA", + Arrays.asList("TEST")); - status = proxy.pause(sync); - Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); + status = proxy.pause(sync); + Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); - status = proxy.start(sync); - Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); + status = proxy.start(sync); + Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); - waitSomeTime(); + waitSomeTime(); - status = proxy.stop(sync); - Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); + status = proxy.stop(sync); + Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); - waitSomeTime(); + waitSomeTime(); - // start the proxy after stopping it - status = proxy.start(sync); - Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); + // start the proxy after stopping it + status = proxy.start(sync); + Assert.assertEquals("Start sync status is not good!", ZProxy.STARTED, status); - waitSomeTime(); + waitSomeTime(); - status = proxy.stop(sync); - Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); + status = proxy.stop(sync); + Assert.assertEquals("Stop sync status is not good!", ZProxy.STOPPED, status); - waitSomeTime(); + waitSomeTime(); - // pause the proxy after stopping it - status = proxy.pause(sync); - Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); + // pause the proxy after stopping it + status = proxy.pause(sync); + Assert.assertEquals("Pause sync status is not good!", ZProxy.PAUSED, status); - status = proxy.exit(); + status = proxy.exit(); - Assert.assertEquals("exit status is not good!", ZProxy.EXITED, status); - } + Assert.assertEquals("exit status is not good!", ZProxy.EXITED, status); + } - private void testStateASync(ZContext ctx) - { - final boolean sync = false; - String status = null; - ZProxy proxy = ZProxy.newProxy(ctx, "ProxyStateASync" + (ctx == null ? "Null" : ""), provider, "ABRACADABRA", Arrays.asList("TEST")); + private void testStateASync(ZContext ctx) throws IOException + { + final boolean sync = false; + String status = null; + ZProxy proxy = ZProxy.newProxy( + ctx, + "ProxyStateASync" + (ctx == null ? "Null" : ""), + new ProxyProvider(), + "ABRACADABRA", + Arrays.asList("TEST")); - status = proxy.start(sync); - Assert.assertEquals("Start sync status is not good!", ZProxy.ALIVE, status); + status = proxy.start(sync); + Assert.assertEquals("Start sync status is not good!", ZProxy.ALIVE, status); - status = proxy.pause(sync); - Assert.assertEquals("Pause sync status is not good!", ZProxy.STARTED, status); + status = proxy.pause(sync); + Assert.assertEquals("Pause sync status is not good!", ZProxy.STARTED, status); - status = proxy.stop(sync); - Assert.assertEquals("Stop sync status is not good!", ZProxy.PAUSED, status); + status = proxy.stop(sync); + Assert.assertEquals("Stop sync status is not good!", ZProxy.PAUSED, status); - waitSomeTime(); + waitSomeTime(); - // start the proxy after stopping it - status = proxy.start(sync); - Assert.assertEquals("Start sync status is not good!", ZProxy.STOPPED, status); + // start the proxy after stopping it + status = proxy.start(sync); + Assert.assertEquals("Start sync status is not good!", ZProxy.STOPPED, status); - waitSomeTime(); + waitSomeTime(); - status = proxy.stop(sync); - Assert.assertEquals("Stop sync status is not good!", ZProxy.STARTED, status); + status = proxy.stop(sync); + Assert.assertEquals("Stop sync status is not good!", ZProxy.STARTED, status); - waitSomeTime(); + waitSomeTime(); - // pause the proxy after stopping it - status = proxy.pause(sync); - Assert.assertEquals("Pause sync status is not good!", ZProxy.STOPPED, status); + // pause the proxy after stopping it + status = proxy.pause(sync); + Assert.assertEquals("Pause sync status is not good!", ZProxy.STOPPED, status); - status = proxy.exit(); + status = proxy.exit(); - Assert.assertEquals("exit status is not good!", ZProxy.EXITED, status); - } + Assert.assertEquals("exit status is not good!", ZProxy.EXITED, status); + } } diff --git a/src/test/java/org/zeromq/TestZStar.java b/src/test/java/org/zeromq/TestZStar.java index 5763f1fcd..4abc5e742 100644 --- a/src/test/java/org/zeromq/TestZStar.java +++ b/src/test/java/org/zeromq/TestZStar.java @@ -20,8 +20,7 @@ public String premiere(Socket mic, Object[] args) } @Override - public ZStar.Star create(ZContext ctx, Socket pipe, Selector sel, int count, - ZStar.Star previous, Object[] args) + public ZStar.Star create(ZContext ctx, Socket pipe, Selector sel, int count, ZStar.Star previous, Object[] args) { return new NoNo(); } @@ -83,8 +82,7 @@ public void testNoStar() ZStar.Entourage entourage = new ZStar.Entourage() { @Override - public void breakaleg(ZContext ctx, Fortune fortune, Socket phone, - Object[] bags) + public void breakaleg(ZContext ctx, Fortune fortune, Socket phone, Object[] bags) { // Crepi il lupo! } @@ -101,15 +99,15 @@ public void party(ZContext ctx) }; ZStar star = new ZStar(fortune, "motdelafin", Arrays.asList("TEST", entourage).toArray()); ZMsg msg = star.recv(); - Assert.assertNull("Able to receive a message from a black hole", msg); + Assert.assertNull("Able to receive a message from a black hole", msg); boolean rc = star.sign(); - Assert.assertFalse("Able to detect the presence of a black hole", rc); + Assert.assertFalse("Able to detect the presence of a black hole", rc); rc = star.send("whatever"); - Assert.assertFalse("Able to send a command to a black hole", rc); + Assert.assertFalse("Able to send a command to a black hole", rc); // don't try it // rc = star.pipe().send("boom ?!"); -// star.retire(); + // star.retire(); System.out.println("."); } } diff --git a/src/test/java/org/zeromq/TooManyOpenFilesTester.java b/src/test/java/org/zeromq/TooManyOpenFilesTester.java index e010e6531..8056d9005 100644 --- a/src/test/java/org/zeromq/TooManyOpenFilesTester.java +++ b/src/test/java/org/zeromq/TooManyOpenFilesTester.java @@ -4,41 +4,45 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Ignore; import org.junit.Test; - import org.zeromq.ZMQ.Poller; import org.zeromq.ZMQ.Socket; /** * Tests exhaustion of java file pipes, * each component being on a separate thread. - * @author fred * */ +@Ignore public class TooManyOpenFilesTester { - private static final long REQUEST_TIMEOUT = 1000; // msecs + private static final long REQUEST_TIMEOUT = 2000; // msecs /** * A simple server for one reply only. - * @author fred * */ private class Server extends Thread { private final int port; + private final int idx; /** * Creates a new server. * @param port the port to which to connect. + * @param idx the index of the server */ - public Server(int port) + public Server(int port, int idx) { this.port = port; + this.idx = idx; + setName("Server-" + idx); } @Override @@ -76,7 +80,6 @@ public void run() /** * Simple client. - * @author fred * */ private class Client extends Thread @@ -85,13 +88,17 @@ private class Client extends Thread final AtomicBoolean finished = new AtomicBoolean(); + private final int idx; + /** * Creates a new client. * @param port the port to which to connect. */ - public Client(int port) + public Client(int port, int idx) { this.port = port; + this.idx = idx; + setName("Client-" + idx); } @Override @@ -121,6 +128,7 @@ public void run() /** * Called between the request-reply cycle. * @param client the socket participating to the cycle of request-reply + * @param selector the selector used for polling */ protected void inBetween(ZContext ctx, Socket client) { @@ -131,6 +139,7 @@ protected void inBetween(ZContext ctx, Socket client) /** * Polls while keeping the selector opened. * @param socket the socket to poll + * @param selector the selector used for polling */ private void poll(ZContext ctx, Socket socket) { @@ -147,7 +156,7 @@ private void poll(ZContext ctx, Socket socket) /** * Test exhaustion of java pipes. * Exhaustion can currently come from {@link zmq.Signaler} that are not closed - * or from {@link Selector} that are not closed. + * or from {@link java.nio.Selector} that are not closed. * @throws Exception if something bad occurs. */ @Test @@ -157,11 +166,10 @@ public void testReqRouterTcpPoll() throws Exception // crashed on iteration 3000-ish in my machine for poll selectors; on iteration 16-ish for sockets for (int index = 0; index < 10000; ++index) { long start = System.currentTimeMillis(); - List pairs = new ArrayList(); - int port = Utils.findOpenPort(); + List pairs = new ArrayList<>(); for (int idx = 0; idx < 20; ++idx) { - Pair pair = testWithPoll(port + idx); + Pair pair = testWithPoll(idx); pairs.add(pair); } @@ -177,16 +185,12 @@ public void testReqRouterTcpPoll() throws Exception long end = System.currentTimeMillis(); assertThat(finished, is(true)); - System.out.printf( - "Test %s finished in %s millis.\n", - index, (end - start)); + System.out.printf("Test %s finished in %s millis.\n", index, (end - start)); } } /** * Dummy class to help keep relation between client and server. - * @author fred - * */ private class Pair { @@ -194,13 +198,15 @@ private class Pair private Server server; } - private Pair testWithPoll(int port) + private Pair testWithPoll(int idx) throws IOException { - Server server = new Server(port); + int port = Utils.findOpenPort(); + + Server server = new Server(port, idx); server.start(); - Client client = new Client(port); + Client client = new Client(port, idx); client.start(); Pair pair = new Pair(); diff --git a/src/test/java/org/zeromq/ZBeaconTest.java b/src/test/java/org/zeromq/ZBeaconTest.java index 142cc9619..cfbe2c2d6 100644 --- a/src/test/java/org/zeromq/ZBeaconTest.java +++ b/src/test/java/org/zeromq/ZBeaconTest.java @@ -1,14 +1,14 @@ package org.zeromq; +import static org.junit.Assert.assertEquals; + import java.io.IOException; import java.net.InetAddress; - -import org.junit.Test; -import org.zeromq.ZBeacon.Listener; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; +import org.junit.Test; +import org.zeromq.ZBeacon.Listener; public class ZBeaconTest { diff --git a/src/test/java/org/zeromq/ZMsgTest.java b/src/test/java/org/zeromq/ZMsgTest.java index 237cb5f40..6eb994972 100644 --- a/src/test/java/org/zeromq/ZMsgTest.java +++ b/src/test/java/org/zeromq/ZMsgTest.java @@ -12,7 +12,7 @@ public class ZMsgTest public void testRecvFrame() throws Exception { ZMQ.Context ctx = ZMQ.context(0); - ZMQ.Socket socket = ctx.socket(ZMQ.PULL); + ZMQ.Socket socket = ctx.socket(ZMQ.PULL); ZFrame f = ZFrame.recvFrame(socket, ZMQ.NOBLOCK); Assert.assertNull(f); @@ -25,7 +25,7 @@ public void testRecvFrame() throws Exception public void testRecvMsg() throws Exception { ZMQ.Context ctx = ZMQ.context(0); - ZMQ.Socket socket = ctx.socket(ZMQ.PULL); + ZMQ.Socket socket = ctx.socket(ZMQ.PULL); ZMsg msg = ZMsg.recvMsg(socket, ZMQ.NOBLOCK); Assert.assertNull(msg); diff --git a/src/test/java/org/zeromq/ZSocketTest.java b/src/test/java/org/zeromq/ZSocketTest.java index 1b9202d37..d741f82cc 100644 --- a/src/test/java/org/zeromq/ZSocketTest.java +++ b/src/test/java/org/zeromq/ZSocketTest.java @@ -1,11 +1,11 @@ package org.zeromq; +import static org.junit.Assert.assertEquals; + import java.io.IOException; import org.junit.Test; -import static org.junit.Assert.assertEquals; - public class ZSocketTest { @Test @@ -13,7 +13,8 @@ public void pushPullTest() throws IOException { int port = Utils.findOpenPort(); - try (final ZSocket pull = new ZSocket(ZMQ.PULL); + try ( + final ZSocket pull = new ZSocket(ZMQ.PULL); final ZSocket push = new ZSocket(ZMQ.PUSH)) { pull.bind("tcp://*:" + port); push.connect("tcp://127.0.0.1:" + port); diff --git a/src/test/java/perf/InprocLat.java b/src/test/java/perf/InprocLat.java index eb69ffc36..d6470ea6d 100644 --- a/src/test/java/perf/InprocLat.java +++ b/src/test/java/perf/InprocLat.java @@ -15,6 +15,7 @@ static class Worker implements Runnable { private Ctx ctx; private int roundtripCount; + Worker(Ctx ctx, int roundtripCount) { this.ctx = ctx; @@ -67,8 +68,8 @@ public static void main(String[] argv) throws Exception return; } - int messageSize = atoi(argv [0]); - int roundtripCount = atoi(argv [1]); + int messageSize = atoi(argv[0]); + int roundtripCount = atoi(argv[1]); Ctx ctx = ZMQ.init(1); if (ctx == null) { @@ -138,7 +139,7 @@ private static void printf(String string) System.out.println(string); } - private static void printf(String string, Object ... args) + private static void printf(String string, Object... args) { System.out.println(String.format(string, args)); } diff --git a/src/test/java/perf/LocalLat.java b/src/test/java/perf/LocalLat.java index 79ca85414..f2584d226 100644 --- a/src/test/java/perf/LocalLat.java +++ b/src/test/java/perf/LocalLat.java @@ -24,13 +24,12 @@ public static void main(String[] args) Msg msg; if (args.length != 3) { - printf("usage: local_lat " - + "\n"); + printf("usage: local_lat " + "\n"); return; } - bindTo = args [0]; - messageSize = atoi(args [1]); - roundtripCount = atoi(args [2]); + bindTo = args[0]; + messageSize = atoi(args[1]); + roundtripCount = atoi(args[2]); ctx = ZMQ.init(1); if (ctx == null) { @@ -84,7 +83,7 @@ private static void printf(String string) System.out.println(string); } - private static void printf(String string, Object ... args) + private static void printf(String string, Object... args) { System.out.println(String.format(string, args)); } diff --git a/src/test/java/perf/LocalThr.java b/src/test/java/perf/LocalThr.java index d41368cfa..3823b0229 100644 --- a/src/test/java/perf/LocalThr.java +++ b/src/test/java/perf/LocalThr.java @@ -15,7 +15,7 @@ public static void main(String[] argv) { String bindTo; long messageCount; - int messageSize; + int messageSize; Ctx ctx; SocketBase s; boolean rc; @@ -30,9 +30,9 @@ public static void main(String[] argv) printf("usage: local_thr \n"); return; } - bindTo = argv [0]; - messageSize = atoi(argv [1]); - messageCount = atol(argv [2]); + bindTo = argv[0]; + messageSize = atoi(argv[1]); + messageCount = atol(argv[2]); ctx = ZMQ.init(1); if (ctx == null) { @@ -79,8 +79,7 @@ public static void main(String[] argv) elapsed = 1; } - throughput = (long) - ((double) messageCount / (double) elapsed * 1000000L); + throughput = (long) ((double) messageCount / (double) elapsed * 1000000L); megabits = (double) (throughput * messageSize * 8) / 1000000; printf("message elapsed: %.3f \n", (double) elapsed / 1000000L); @@ -94,7 +93,7 @@ public static void main(String[] argv) ZMQ.term(ctx); } - private static void printf(String str, Object ... args) + private static void printf(String str, Object... args) { // TODO Auto-generated method stub System.out.println(String.format(str, args)); diff --git a/src/test/java/perf/RemoteThr.java b/src/test/java/perf/RemoteThr.java index 934516f56..9bfdd1d02 100644 --- a/src/test/java/perf/RemoteThr.java +++ b/src/test/java/perf/RemoteThr.java @@ -15,7 +15,7 @@ public static void main(String[] argv) { String connectTo; long messageCount; - int messageSize; + int messageSize; Ctx ctx; SocketBase s; boolean rc; diff --git a/src/test/java/zmq/ConnectRidTest.java b/src/test/java/zmq/ConnectRidTest.java new file mode 100644 index 000000000..9c298a1fa --- /dev/null +++ b/src/test/java/zmq/ConnectRidTest.java @@ -0,0 +1,184 @@ +package zmq; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.util.Utils; + +public class ConnectRidTest +{ + @Test + public void testStream2stream() throws IOException, InterruptedException + { + System.out.println("Test Stream 2 stream"); + int port = Utils.findOpenPort(); + String host = "tcp://localhost:" + port; + + Msg msg = new Msg("hi 1".getBytes(ZMQ.CHARSET)); + + Ctx ctx = ZMQ.init(1); + assert (ctx != null); + + // Set up listener STREAM. + SocketBase bind = ZMQ.socket(ctx, ZMQ.ZMQ_STREAM); + assert (bind != null); + + ZMQ.setSocketOption(bind, ZMQ.ZMQ_CONNECT_RID, "connectRid"); + ZMQ.setSocketOption(bind, ZMQ.ZMQ_LINGER, 0); + + boolean rc = ZMQ.bind(bind, host); + assert (rc); + + // Set up connection stream. + SocketBase connect = ZMQ.socket(ctx, ZMQ.ZMQ_STREAM); + assert (connect != null); + + ZMQ.setSocketOption(connect, ZMQ.ZMQ_LINGER, 0); + + // Do the connection. + ZMQ.setSocketOption(connect, ZMQ.ZMQ_CONNECT_RID, "connectRid"); + rc = ZMQ.connect(connect, host); + assert (rc); + + ZMQ.sleep(1); + + /* Uncomment to test assert on duplicate rid. + // Test duplicate connect attempt. + ZMQ.setSocketOption(connect, ZMQ_CONNECT_RID, "conn1", 6); + rc = ZMQ.connect(connect, host); + assert (rc); + */ + + // Send data to the bound stream. + int ret = ZMQ.send(connect, "connectRid", ZMQ.ZMQ_SNDMORE); + assert (10 == ret); + ret = ZMQ.send(connect, msg, 0); + assert (4 == ret); + + // Accept data on the bound stream. + Msg recv = ZMQ.recv(bind, 0); + assert (recv != null); + + recv = ZMQ.recv(bind, 0); + assert (recv != null); + + ZMQ.close(bind); + ZMQ.close(connect); + + ZMQ.term(ctx); + } + + @Test + public void testRouter2routerNamed() throws IOException, InterruptedException + { + System.out.println("Test Router 2 Router named"); + testRouter2router(true); + } + + @Test + public void testRouter2routerUnnamed() throws IOException, InterruptedException + { + System.out.println("Test Router 2 Router unnamed"); + testRouter2router(false); + } + + private void testRouter2router(boolean named) throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://localhost:" + port; + + Msg msg = new Msg("hi 1".getBytes(ZMQ.CHARSET)); + + Ctx ctx = ZMQ.init(1); + assert (ctx != null); + + // Set up listener STREAM. + SocketBase bind = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); + assert (bind != null); + + ZMQ.setSocketOption(bind, ZMQ.ZMQ_LINGER, 0); + + boolean rc = ZMQ.bind(bind, host); + assert (rc); + + // Set up connection stream. + SocketBase connect = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); + assert (connect != null); + + ZMQ.setSocketOption(connect, ZMQ.ZMQ_LINGER, 0); + + // Do the connection. + ZMQ.setSocketOption(connect, ZMQ.ZMQ_CONNECT_RID, "connectRid"); + rc = ZMQ.connect(connect, host); + assert (rc); + + if (named) { + ZMQ.setSocketOption(bind, ZMQ.ZMQ_IDENTITY, "X"); + ZMQ.setSocketOption(connect, ZMQ.ZMQ_IDENTITY, "Y"); + } + + ZMQ.sleep(1); + + /* Uncomment to test assert on duplicate rid. + // Test duplicate connect attempt. + ZMQ.setSocketOption(connect, ZMQ_CONNECT_RID, "conn1", 6); + rc = ZMQ.connect(connect, host); + assert (rc); + */ + + // Send data to the bound stream. + int ret = ZMQ.send(connect, "connectRid", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(10)); + ret = ZMQ.send(connect, msg, 0); + assertThat(ret, is(4)); + + Msg recv = null; + // Receive the name. + Msg name = ZMQ.recv(bind, 0); + assertThat(name, notNullValue()); + assert (name != null); + if (named) { + assertThat(name.data()[0], is((byte) 'Y')); + } + else { + assertThat(name.data()[0], is((byte) 0)); + } + + // Receive the data. + recv = ZMQ.recv(bind, 0); + assertThat(recv, notNullValue()); + + // Send some data back. + if (named) { + ret = ZMQ.send(bind, name, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(1)); + } + else { + ret = ZMQ.send(bind, name, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(5)); + } + + ret = ZMQ.send(bind, "ok", 0); + assertThat(ret, is(2)); + + // If bound socket identity naming a problem, we'll likely see something funky here. + recv = ZMQ.recv(connect, 0); + assertThat(recv, notNullValue()); + assertThat(recv.data()[0], is((byte) 'c')); + + recv = ZMQ.recv(connect, 0); + assertThat(recv, notNullValue()); + assertThat(recv.data()[0], is((byte) 'o')); + assertThat(recv.data()[1], is((byte) 'k')); + + ZMQ.close(bind); + ZMQ.close(connect); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/DiffServerTest.java b/src/test/java/zmq/DiffServerTest.java new file mode 100644 index 000000000..9e8f7ff96 --- /dev/null +++ b/src/test/java/zmq/DiffServerTest.java @@ -0,0 +1,55 @@ +package zmq; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; + +import zmq.util.Utils; + +public class DiffServerTest +{ + @Test + public void test() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://localhost:" + port; + int tos = 0x28; + + Ctx ctx = ZMQ.init(1); + assert (ctx != null); + + SocketBase bind = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); + assert (bind != null); + + ZMQ.setSocketOption(bind, ZMQ.ZMQ_TOS, tos); + + boolean rc = ZMQ.bind(bind, host); + assert (rc); + + int option = ZMQ.getSocketOption(bind, ZMQ.ZMQ_TOS); + Assert.assertEquals(tos, option); + + SocketBase connect = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); + assert (connect != null); + + tos = 0x58; + ZMQ.setSocketOption(connect, ZMQ.ZMQ_TOS, tos); + + rc = ZMQ.connect(connect, host); + assert (rc); + + option = ZMQ.getSocketOption(connect, ZMQ.ZMQ_TOS); + Assert.assertEquals(tos, option); + + // Wireshark can be used to verify that the server socket is + // using DSCP 0x28 in packets to the client while the client + // is using 0x58 in packets to the server. + Helper.bounce(bind, connect); + + ZMQ.close(bind); + ZMQ.close(connect); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/Helper.java b/src/test/java/zmq/Helper.java index c0ac9c1d7..c44e93ee9 100644 --- a/src/test/java/zmq/Helper.java +++ b/src/test/java/zmq/Helper.java @@ -1,5 +1,8 @@ package zmq; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -9,8 +12,12 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; + +import zmq.io.IOThread; +import zmq.io.SessionBase; +import zmq.io.net.Address; +import zmq.pipe.Pipe; +import zmq.util.Errno; public class Helper { @@ -26,7 +33,7 @@ public static class DummyCtx extends Ctx public static class DummySocketChannel implements WritableByteChannel { - private int bufsize; + private int bufsize; private byte[] buf; public DummySocketChannel() @@ -106,21 +113,20 @@ public static class DummySession extends SessionBase public DummySession() { - this(new DummyIOThread(), false, new DummySocket(), new Options(), new Address("tcp", "localhost:9090", false)); + this(new DummyIOThread(), false, new DummySocket(), new Options(), new Address("tcp", "localhost:9090")); } - public DummySession(IOThread ioThread, boolean connect, - SocketBase socket, Options options, Address addr) + public DummySession(IOThread ioThread, boolean connect, SocketBase socket, Options options, Address addr) { super(ioThread, connect, socket, options, addr); } @Override - public int pushMsg(Msg msg) + public boolean pushMsg(Msg msg) { System.out.println("session.write " + msg); out.add(msg); - return 0; + return true; } @Override @@ -166,15 +172,98 @@ public static void bounce(SocketBase sb, SocketBase sc) msg = ZMQ.recv(sc, 0); assert (rc == 32); rcvmore = ZMQ.getSocketOption(sc, ZMQ.ZMQ_RCVMORE); - assertThat(rcvmore , is(1L)); - msg = ZMQ.recv(sc, 0); + assertThat(rcvmore, is(1L)); + msg = ZMQ.recv(sc, 0); assert (rc == 32); rcvmore = ZMQ.getSocketOption(sc, ZMQ.ZMQ_RCVMORE); - assertThat(rcvmore , is(0L)); + assertThat(rcvmore, is(0L)); // Check whether the message is still the same. //assert (memcmp (buf2, content, 32) == 0); } + public static void expectBounceFail(SocketBase server, SocketBase client) + { + final byte[] content = "12345678ABCDEFGH12345678abcdefgh".getBytes(ZMQ.CHARSET); + final int timeout = 250; + final Errno errno = new Errno(); + + // Send message from client to server + ZMQ.setSocketOption(client, ZMQ.ZMQ_SNDTIMEO, timeout); + + int rc = ZMQ.send(client, content, 32, ZMQ.ZMQ_SNDMORE); + assert ((rc == 32) || ((rc == -1) && errno.is(ZError.EAGAIN))); + rc = ZMQ.send(client, content, 32, 0); + assert ((rc == 32) || ((rc == -1) && errno.is(ZError.EAGAIN))); + + // Receive message at server side (should not succeed) + ZMQ.setSocketOption(server, ZMQ.ZMQ_RCVTIMEO, timeout); + + Msg msg = ZMQ.recv(server, 0); + assert (msg == null); + assert errno.is(ZError.EAGAIN); + + // Send message from server to client to test other direction + // If connection failed, send may block, without a timeout + ZMQ.setSocketOption(server, ZMQ.ZMQ_SNDTIMEO, timeout); + + rc = ZMQ.send(server, content, 32, ZMQ.ZMQ_SNDMORE); + assert (rc == 32 || ((rc == -1) && errno.is(ZError.EAGAIN))); + rc = ZMQ.send(server, content, 32, 0); + assert (rc == 32 || ((rc == -1) && errno.is(ZError.EAGAIN))); + + // Receive message at client side (should not succeed) + ZMQ.setSocketOption(client, ZMQ.ZMQ_RCVTIMEO, timeout); + msg = ZMQ.recv(client, 0); + assert (msg == null); + assert errno.is(ZError.EAGAIN); + } + + public static int send(SocketBase socket, String data) + { + return ZMQ.send(socket, data, 0); + } + + public static int sendMore(SocketBase socket, String data) + { + return ZMQ.send(socket, data, ZMQ.ZMQ_MORE); + } + + public static String recv(SocketBase socket) + { + Msg msg = ZMQ.recv(socket, 0); + assert (msg != null); + return new String(msg.data(), ZMQ.CHARSET); + } + + // Sends a message composed of frames that are C strings or null frames. + // The list must be terminated by SEQ_END. + // Example: s_send_seq (req, "ABC", 0, "DEF", SEQ_END); + public static void sendSeq(SocketBase socket, String... data) + { + int rc = 0; + for (int idx = 0; idx < data.length - 1; ++idx) { + rc = sendMore(socket, data[idx]); + assert (rc == data[idx].length()); + } + rc = send(socket, data[data.length - 1]); + assert (rc == data[data.length - 1].length()); + } + + // Receives message a number of frames long and checks that the frames have + // the given data which can be either C strings or 0 for a null frame. + // The list must be terminated by SEQ_END. + // Example: s_recv_seq (rep, "ABC", 0, "DEF", SEQ_END); + public static void recvSeq(SocketBase socket, String... data) + { + String rc; + for (int idx = 0; idx < data.length - 1; ++idx) { + rc = recv(socket); + assert (data[idx].equals(rc)); + } + rc = recv(socket); + assert (data[data.length - 1].equals(rc)); + } + public static void send(Socket sa, String data) throws IOException { byte[] content = data.getBytes(ZMQ.CHARSET); diff --git a/src/test/java/zmq/ImmediateTest.java b/src/test/java/zmq/ImmediateTest.java new file mode 100644 index 000000000..c148002b1 --- /dev/null +++ b/src/test/java/zmq/ImmediateTest.java @@ -0,0 +1,234 @@ +package zmq; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.util.Utils; + +public class ImmediateTest +{ + @Test + public void testImmediate1() throws Exception + { + System.out.println("Scenario 1"); + // TEST 1. + // First we're going to attempt to send messages to two + // pipes, one connected, the other not. We should see + // the PUSH load balancing to both pipes, and hence half + // of the messages getting queued, as connect() creates a + // pipe immediately. + + int pushPort1 = Utils.findOpenPort(); + int pushPort2 = Utils.findOpenPort(); + + Ctx context = ZMQ.createContext(); + assertThat(context, notNullValue()); + + SocketBase to = ZMQ.socket(context, ZMQ.ZMQ_PULL); + assertThat(to, notNullValue()); + + int val = 0; + boolean rc = ZMQ.setSocketOption(to, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); + rc = ZMQ.bind(to, "tcp://*:" + pushPort1); + assertThat(rc, is(true)); + + // Create a socket pushing to two endpoints - only 1 message should arrive. + SocketBase from = ZMQ.socket(context, ZMQ.ZMQ_PUSH); + assertThat(from, notNullValue()); + + val = 0; + rc = ZMQ.setSocketOption(from, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); + // This pipe will not connect + rc = ZMQ.connect(from, "tcp://localhost:" + pushPort2); + assertThat(rc, is(true)); + // This pipe will + rc = ZMQ.connect(from, "tcp://localhost:" + pushPort1); + assertThat(rc, is(true)); + + // We send 10 messages, 5 should just get stuck in the queue + // for the not-yet-connected pipe + for (int i = 0; i < 10; ++i) { + String message = "message "; + message += ('0' + i); + int sent = ZMQ.send(from, message, 0); + assertThat(sent >= 0, is(true)); + } + + ZMQ.sleep(1); + // We now consume from the connected pipe + // - we should see just 5 + int timeout = 250; + ZMQ.setSocketOption(to, ZMQ.ZMQ_RCVTIMEO, timeout); + + int seen = 0; + for (int i = 0; i < 10; ++i) { + Msg msg = ZMQ.recv(to, 0); + if (msg == null) { + break; // Break when we didn't get a message + } + seen++; + } + assertThat(seen, is(5)); + + ZMQ.close(from); + ZMQ.close(to); + ZMQ.term(context); + } + + @Test + public void testConnectDelay2() throws Exception + { + System.out.println("Scenario 2"); + // TEST 2 + // This time we will do the same thing, connect two pipes, + // one of which will succeed in connecting to a bound + // receiver, the other of which will fail. However, we will + // also set the delay attach on connect flag, which should + // cause the pipe attachment to be delayed until the connection + // succeeds. + int validPort = Utils.findOpenPort(); + int invalidPort = Utils.findOpenPort(); + Ctx context = ZMQ.createContext(); + + SocketBase to = ZMQ.socket(context, ZMQ.ZMQ_PULL); + assertThat(to, notNullValue()); + boolean rc = ZMQ.bind(to, "tcp://*:" + validPort); + assertThat(rc, is(true)); + + int val = 0; + rc = ZMQ.setSocketOption(to, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); + + // Create a socket pushing to two endpoints - all messages should arrive. + SocketBase from = ZMQ.socket(context, ZMQ.ZMQ_PUSH); + assertThat(from, notNullValue()); + + val = 0; + rc = ZMQ.setSocketOption(from, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); + + // Set the key flag + rc = ZMQ.setSocketOption(from, ZMQ.ZMQ_IMMEDIATE, false); + assertThat(rc, is(true)); + + // Connect to the invalid socket + rc = ZMQ.connect(from, "tcp://localhost:" + invalidPort); + assertThat(rc, is(true)); + // Connect to the valid socket + rc = ZMQ.connect(from, "tcp://localhost:" + validPort); + assertThat(rc, is(true)); + + for (int i = 0; i < 10; ++i) { + String message = "message "; + message += ('0' + i); + int sent = ZMQ.send(from, message, 0); + assertThat(sent, is(message.length())); + } + + int timeout = 250; + ZMQ.setSocketOption(to, ZMQ.ZMQ_RCVTIMEO, timeout); + + int seen = 0; + for (int i = 0; i < 10; ++i) { + Msg msg = ZMQ.recv(to, 0); + if (msg == null) { + break; + } + seen++; + } + assertThat(seen, is(10)); + + ZMQ.close(from); + ZMQ.close(to); + ZMQ.term(context); + } + + @Test + public void testConnectDelay3() throws Exception + { + System.out.print("Scenario 3"); + // TEST 3 + // This time we want to validate that the same blocking behaviour + // occurs with an existing connection that is broken. We will send + // messages to a connected pipe, disconnect and verify the messages + // block. Then we reconnect and verify messages flow again. + int port = Utils.findOpenPort(); + Ctx context = ZMQ.createContext(); + + SocketBase backend = ZMQ.socket(context, ZMQ.ZMQ_DEALER); + assertThat(backend, notNullValue()); + + SocketBase frontend = ZMQ.socket(context, ZMQ.ZMQ_DEALER); + assertThat(frontend, notNullValue()); + + final int linger = 0; + ZMQ.setSocketOption(backend, ZMQ.ZMQ_LINGER, linger); + ZMQ.setSocketOption(frontend, ZMQ.ZMQ_LINGER, linger); + + // Frontend connects to backend using IMMEDIATE + ZMQ.setSocketOption(frontend, ZMQ.ZMQ_IMMEDIATE, false); + + boolean rc = ZMQ.bind(backend, "tcp://*:" + port); + assertThat(rc, is(true)); + + rc = ZMQ.connect(frontend, "tcp://localhost:" + port); + assertThat(rc, is(true)); + + System.out.print("."); + // Ping backend to frontend so we know when the connection is up + int sent = ZMQ.send(backend, "Hello", 0); + assertThat(sent, is(5)); + System.out.print("Ping"); + Msg msg = ZMQ.recv(frontend, 0); + System.out.print("."); + assertThat(msg.size(), is(5)); + + // Send message from frontend to backend + sent = ZMQ.send(frontend, "Hello", ZMQ.ZMQ_DONTWAIT); + assertThat(sent, is(5)); + + System.out.print("Message sent"); + ZMQ.close(backend); + System.out.print("."); + + // Give time to process disconnect + // There's no way to do this except with a sleep + ZMQ.sleep(2); + + System.out.print("Message send fail"); + // Send a message, should fail + sent = ZMQ.send(frontend, "Hello", ZMQ.ZMQ_DONTWAIT); + assertThat(sent, is(-1)); + + // Recreate backend socket + backend = ZMQ.socket(context, ZMQ.ZMQ_DEALER); + ZMQ.setSocketOption(backend, ZMQ.ZMQ_LINGER, linger); + rc = ZMQ.bind(backend, "tcp://*:" + port); + assertThat(rc, is(true)); + + System.out.print("."); + // Ping backend to frontend so we know when the connection is up + sent = ZMQ.send(backend, "Hello", 0); + assertThat(sent, is(5)); + System.out.print("Ping"); + msg = ZMQ.recv(frontend, 0); + System.out.print("."); + assertThat(msg.size(), is(5)); + + System.out.print("Message sent"); + // After the reconnect, should succeed + sent = ZMQ.send(frontend, "Hello", ZMQ.ZMQ_DONTWAIT); + assertThat(sent, is(5)); + + System.out.print("."); + ZMQ.close(backend); + ZMQ.close(frontend); + ZMQ.term(context); + System.out.println("Done"); + } +} diff --git a/src/test/java/zmq/InprocDisconnectTest.java b/src/test/java/zmq/InprocDisconnectTest.java new file mode 100644 index 000000000..c33515b3c --- /dev/null +++ b/src/test/java/zmq/InprocDisconnectTest.java @@ -0,0 +1,117 @@ +package zmq; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.channels.Selector; + +import org.junit.Test; + +import zmq.poll.PollItem; + +public class InprocDisconnectTest +{ + @Test + public void testDisconnectInproc() throws Exception + { + Ctx context = ZMQ.createContext(); + Selector selector = context.createSelector(); + try { + testDisconnectInproc(context, selector); + } + finally { + context.closeSelector(selector); + } + ZMQ.term(context); + } + + private void testDisconnectInproc(Ctx context, Selector selector) throws Exception + { + int publicationsReceived = 0; + boolean isSubscribed = false; + + SocketBase pubSocket = ZMQ.socket(context, ZMQ.ZMQ_XPUB); + SocketBase subSocket = ZMQ.socket(context, ZMQ.ZMQ_SUB); + + ZMQ.setSocketOption(subSocket, ZMQ.ZMQ_SUBSCRIBE, "foo".getBytes()); + ZMQ.bind(pubSocket, "inproc://someInProcDescriptor"); + + int more; + int iteration = 0; + + while (true) { + PollItem[] items = { new PollItem(subSocket, ZMQ.ZMQ_POLLIN), // read publications + new PollItem(pubSocket, ZMQ.ZMQ_POLLIN) // read subscriptions + }; + int rc = ZMQ.poll(selector, items, 2, 100L); + + if (items[1].isReadable()) { + while (true) { + Msg msg = ZMQ.recv(pubSocket, 0); + int msgSize = msg.size(); + byte[] buffer = msg.data(); + + if (buffer[0] == 0) { + assertTrue(isSubscribed); + System.out.printf("unsubscribing from '%s'\n", new String(buffer, 1, msgSize - 1)); + isSubscribed = false; + } + else { + assert (!isSubscribed); + System.out.printf("subscribing on '%s'\n", new String(buffer, 1, msgSize - 1)); + isSubscribed = true; + } + + more = ZMQ.getSocketOption(pubSocket, ZMQ.ZMQ_RCVMORE); + + if (more == 0) { + break; // Last message part + } + } + } + + if (items[0].isReadable()) { + while (true) { + Msg msg = ZMQ.recv(subSocket, 0); + int msgSize = msg.size(); + byte[] buffer = msg.data(); + + System.out.printf("received on subscriber '%s'\n", new String(buffer, 0, msgSize)); + + more = ZMQ.getSocketOption(subSocket, ZMQ.ZMQ_RCVMORE); + + if (more == 0) { + publicationsReceived++; + break; // Last message part + } + } + } + + if (iteration == 1) { + ZMQ.connect(subSocket, "inproc://someInProcDescriptor"); + } + + if (iteration == 4) { + ZMQ.disconnect(subSocket, "inproc://someInProcDescriptor"); + } + + if (iteration > 4 && rc == 0) { + break; + } + + Msg channelEnvlp = new Msg("foo".getBytes(ZMQ.CHARSET)); + ZMQ.sendMsg(pubSocket, channelEnvlp, ZMQ.ZMQ_SNDMORE); + + Msg message = new Msg("this is foo!".getBytes(ZMQ.CHARSET)); + ZMQ.sendMsg(pubSocket, message, 0); + iteration++; + } + + assertEquals(3, publicationsReceived); + assertFalse(isSubscribed); + + ZMQ.close(pubSocket); + ZMQ.close(subSocket); + } +} diff --git a/src/test/java/zmq/InprocUnbindTest.java b/src/test/java/zmq/InprocUnbindTest.java new file mode 100644 index 000000000..d89240796 --- /dev/null +++ b/src/test/java/zmq/InprocUnbindTest.java @@ -0,0 +1,32 @@ +package zmq; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +public class InprocUnbindTest +{ + @Test + public void testUnbindInproc() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.init(1); + assert (ctx != null); + + SocketBase bind = ZMQ.socket(ctx, ZMQ.ZMQ_REP); + assertThat(bind, notNullValue()); + + boolean rc = ZMQ.bind(bind, "inproc://a"); + assertThat(rc, is(true)); + + rc = ZMQ.unbind(bind, "inproc://a"); + assertThat(rc, is(true)); + + ZMQ.close(bind); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/TestConnectDelay.java b/src/test/java/zmq/TestConnectDelay.java index db021c020..e545ebdd8 100644 --- a/src/test/java/zmq/TestConnectDelay.java +++ b/src/test/java/zmq/TestConnectDelay.java @@ -1,14 +1,20 @@ package zmq; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + import org.junit.Test; -import static org.junit.Assert.assertEquals; +import zmq.util.Utils; +@SuppressWarnings("deprecation") public class TestConnectDelay { @Test public void testConnectDelay1() throws Exception { + System.out.println("Scenario 1"); // TEST 1. // First we're going to attempt to send messages to two // pipes, one connected, the other not. We should see @@ -20,59 +26,65 @@ public void testConnectDelay1() throws Exception int pushPort2 = Utils.findOpenPort(); Ctx context = ZMQ.createContext(); - assert (context != null); + assertThat(context, notNullValue()); SocketBase to = ZMQ.socket(context, ZMQ.ZMQ_PULL); - assert (to != null); + assertThat(to, notNullValue()); int val = 0; - ZMQ.setSocketOption(to, ZMQ.ZMQ_LINGER, val); - boolean rc = ZMQ.bind(to, "tcp://*:" + pushPort1); - assert (rc); + boolean rc = ZMQ.setSocketOption(to, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); + rc = ZMQ.bind(to, "tcp://*:" + pushPort1); + assertThat(rc, is(true)); // Create a socket pushing to two endpoints - only 1 message should arrive. SocketBase from = ZMQ.socket(context, ZMQ.ZMQ_PUSH); - assert (from != null); + assertThat(from, notNullValue()); val = 0; - ZMQ.setSocketOption(from, ZMQ.ZMQ_LINGER, val); + rc = ZMQ.setSocketOption(from, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); + // This pipe will not connect rc = ZMQ.connect(from, "tcp://localhost:" + pushPort2); - assert (rc); + assertThat(rc, is(true)); + // This pipe will rc = ZMQ.connect(from, "tcp://localhost:" + pushPort1); - assert (rc); + assertThat(rc, is(true)); + // We send 10 messages, 5 should just get stuck in the queue + // for the not-yet-connected pipe for (int i = 0; i < 10; ++i) { String message = "message "; message += ('0' + i); int sent = ZMQ.send(from, message, 0); - assert (sent >= 0); + assertThat(sent >= 0, is(true)); } + ZMQ.sleep(1); // We now consume from the connected pipe // - we should see just 5 - int timeout = 1000; + int timeout = 250; ZMQ.setSocketOption(to, ZMQ.ZMQ_RCVTIMEO, timeout); int seen = 0; for (int i = 0; i < 10; ++i) { Msg msg = ZMQ.recv(to, 0); if (msg == null) { - break; + break; // Break when we didn't get a message } seen++; } - assertEquals(seen, 5); + assertThat(seen, is(5)); ZMQ.close(from); - ZMQ.close(to); - ZMQ.term(context); } @Test public void testConnectDelay2() throws Exception { + System.out.println("Scenario 2"); // TEST 2 // This time we will do the same thing, connect two pipes, // one of which will succeed in connecting to a bound @@ -80,45 +92,46 @@ public void testConnectDelay2() throws Exception // also set the delay attach on connect flag, which should // cause the pipe attachment to be delayed until the connection // succeeds. - int validPort = Utils.findOpenPort(); + int validPort = Utils.findOpenPort(); int invalidPort = Utils.findOpenPort(); Ctx context = ZMQ.createContext(); SocketBase to = ZMQ.socket(context, ZMQ.ZMQ_PULL); - assert (to != null); + assertThat(to, notNullValue()); boolean rc = ZMQ.bind(to, "tcp://*:" + validPort); - assert (rc); + assertThat(rc, is(true)); int val = 0; - ZMQ.setSocketOption(to, ZMQ.ZMQ_LINGER, val); - assert (rc); + rc = ZMQ.setSocketOption(to, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); // Create a socket pushing to two endpoints - all messages should arrive. SocketBase from = ZMQ.socket(context, ZMQ.ZMQ_PUSH); - assert (from != null); + assertThat(from, notNullValue()); val = 0; - ZMQ.setSocketOption(from, ZMQ.ZMQ_LINGER, val); + rc = ZMQ.setSocketOption(from, ZMQ.ZMQ_LINGER, val); + assertThat(rc, is(true)); // Set the key flag - val = 1; - ZMQ.setSocketOption(from, ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT, val); + rc = ZMQ.setSocketOption(from, ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT, true); + assertThat(rc, is(true)); // Connect to the invalid socket rc = ZMQ.connect(from, "tcp://localhost:" + invalidPort); - assert (rc); + assertThat(rc, is(true)); // Connect to the valid socket rc = ZMQ.connect(from, "tcp://localhost:" + validPort); - assert (rc); + assertThat(rc, is(true)); for (int i = 0; i < 10; ++i) { String message = "message "; message += ('0' + i); int sent = ZMQ.send(from, message, 0); - assert (sent >= 0); + assertThat(sent, is(message.length())); } - int timeout = 1000; + int timeout = 250; ZMQ.setSocketOption(to, ZMQ.ZMQ_RCVTIMEO, timeout); int seen = 0; @@ -129,18 +142,17 @@ public void testConnectDelay2() throws Exception } seen++; } - assertEquals(seen, 10); + assertThat(seen, is(10)); ZMQ.close(from); - ZMQ.close(to); - ZMQ.term(context); } @Test public void testConnectDelay3() throws Exception { + System.out.print("Scenario 3"); // TEST 3 // This time we want to validate that the same blocking behaviour // occurs with an existing connection that is broken. We will send @@ -150,70 +162,74 @@ public void testConnectDelay3() throws Exception Ctx context = ZMQ.createContext(); SocketBase backend = ZMQ.socket(context, ZMQ.ZMQ_DEALER); - assert (backend != null); + assertThat(backend, notNullValue()); SocketBase frontend = ZMQ.socket(context, ZMQ.ZMQ_DEALER); - assert (frontend != null); - - int val = 0; - ZMQ.setSocketOption(backend, ZMQ.ZMQ_LINGER, val); + assertThat(frontend, notNullValue()); - val = 0; - ZMQ.setSocketOption(frontend, ZMQ.ZMQ_LINGER, val); + final int linger = 0; + ZMQ.setSocketOption(backend, ZMQ.ZMQ_LINGER, linger); + ZMQ.setSocketOption(frontend, ZMQ.ZMQ_LINGER, linger); // Frontend connects to backend using DELAY_ATTACH_ON_CONNECT - val = 1; - ZMQ.setSocketOption(frontend, ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT, val); + ZMQ.setSocketOption(frontend, ZMQ.ZMQ_DELAY_ATTACH_ON_CONNECT, true); boolean rc = ZMQ.bind(backend, "tcp://*:" + port); - assert (rc); + assertThat(rc, is(true)); rc = ZMQ.connect(frontend, "tcp://localhost:" + port); - assert (rc); + assertThat(rc, is(true)); + System.out.print("."); // Ping backend to frontend so we know when the connection is up int sent = ZMQ.send(backend, "Hello", 0); - assertEquals(5, sent); - + assertThat(sent, is(5)); + System.out.print("Ping"); Msg msg = ZMQ.recv(frontend, 0); - assertEquals(5, msg.size()); + System.out.print("."); + assertThat(msg.size(), is(5)); // Send message from frontend to backend sent = ZMQ.send(frontend, "Hello", ZMQ.ZMQ_DONTWAIT); - assertEquals(5, sent); + assertThat(sent, is(5)); + System.out.print("Message sent"); ZMQ.close(backend); + System.out.print("."); // Give time to process disconnect // There's no way to do this except with a sleep - Thread.sleep(1000); + ZMQ.sleep(2); + System.out.print("Message send fail"); // Send a message, should fail sent = ZMQ.send(frontend, "Hello", ZMQ.ZMQ_DONTWAIT); - assertEquals(-1, sent); + assertThat(sent, is(-1)); // Recreate backend socket backend = ZMQ.socket(context, ZMQ.ZMQ_DEALER); - val = 0; - ZMQ.setSocketOption(backend, ZMQ.ZMQ_LINGER, val); + ZMQ.setSocketOption(backend, ZMQ.ZMQ_LINGER, linger); rc = ZMQ.bind(backend, "tcp://*:" + port); - assert (rc); + assertThat(rc, is(true)); + System.out.print("."); // Ping backend to frontend so we know when the connection is up sent = ZMQ.send(backend, "Hello", 0); - assertEquals(5, sent); - + assertThat(sent, is(5)); + System.out.print("Ping"); msg = ZMQ.recv(frontend, 0); - assertEquals(5, msg.size()); + System.out.print("."); + assertThat(msg.size(), is(5)); + System.out.print("Message sent"); // After the reconnect, should succeed sent = ZMQ.send(frontend, "Hello", ZMQ.ZMQ_DONTWAIT); - assertEquals(5, sent); + assertThat(sent, is(5)); + System.out.print("."); ZMQ.close(backend); - ZMQ.close(frontend); - ZMQ.term(context); + System.out.println("Done"); } } diff --git a/src/test/java/zmq/TestConnectResolve.java b/src/test/java/zmq/TestConnectResolve.java index 642bdd0b8..0193688c7 100644 --- a/src/test/java/zmq/TestConnectResolve.java +++ b/src/test/java/zmq/TestConnectResolve.java @@ -1,11 +1,14 @@ package zmq; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + import java.io.IOException; import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; + +import zmq.util.Utils; public class TestConnectResolve { diff --git a/src/test/java/zmq/TestDecoder.java b/src/test/java/zmq/TestDecoder.java deleted file mode 100644 index 0e524d39b..000000000 --- a/src/test/java/zmq/TestDecoder.java +++ /dev/null @@ -1,197 +0,0 @@ -package zmq; - -import java.nio.ByteBuffer; - -import org.junit.Before; -import org.junit.Test; - -import zmq.Helper.DummySession; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; - -public class TestDecoder -{ - DecoderBase decoder; - Helper.DummySession session; - - @Before - public void setUp() - { - session = new DummySession(); - decoder = new Decoder(64, 256); - decoder.setMsgSink(session); - } - - // as if it read data from socket - private int readShortMessage(ByteBuffer buf) - { - buf.put((byte) 6); - buf.put((byte) 0); // flag - buf.put("hello".getBytes(ZMQ.CHARSET)); - - return buf.position(); - } - - // as if it read data from socket - private int readLongMessage1(ByteBuffer buf) - { - buf.put((byte) 201); - buf.put((byte) 0); // flag - for (int i = 0; i < 6; i++) { - buf.put("0123456789".getBytes(ZMQ.CHARSET)); - } - buf.put("01".getBytes(ZMQ.CHARSET)); - return buf.position(); - } - - private int readLongMessage2(ByteBuffer buf) - { - buf.put("23456789".getBytes(ZMQ.CHARSET)); - for (int i = 0; i < 13; i++) { - buf.put("0123456789".getBytes(ZMQ.CHARSET)); - } - return buf.position(); - } - - @Test - public void testReader() - { - ByteBuffer in = decoder.getBuffer(); - int insize = readShortMessage(in); - - assertThat(insize, is(7)); - in.flip(); - int process = decoder.processBuffer(in, insize); - assertThat(process, is(7)); - } - - @Test - public void testReaderLong() - { - ByteBuffer in = decoder.getBuffer(); - int insize = readLongMessage1(in); - - assertThat(insize, is(64)); - in.flip(); - int process = decoder.processBuffer(in, insize); - assertThat(process, is(64)); - - in = decoder.getBuffer(); - assertThat(in.capacity(), is(200)); - assertThat(in.position(), is(62)); - - insize = readLongMessage2(in); - - assertThat(insize, is(200)); - process = decoder.processBuffer(in, 138); - assertThat(process, is(138)); - assertThat(in.array()[199], is((byte) '9')); - } - - @Test - public void testReaderMultipleMsg() - { - ByteBuffer in = decoder.getBuffer(); - int insize = readShortMessage(in); - assertThat(insize, is(7)); - readShortMessage(in); - - in.flip(); - int processed = decoder.processBuffer(in, 14); - assertThat(processed, is(14)); - assertThat(in.position(), is(14)); - - assertThat(session.out.size(), is(2)); - } - - static class CustomDecoder extends DecoderBase - { - private static final int READ_HEADER = 0; - private static final int READ_BODY = 1; - - byte[] header = new byte[10]; - Msg msg; - int size = -1; - IMsgSink sink; - - public CustomDecoder(int bufsize, long maxmsgsize) - { - super(bufsize); - nextStep(header, 10, READ_HEADER); - } - - @Override - protected boolean next() - { - switch (state()) { - case READ_HEADER: - return readHeader(); - case READ_BODY: - return readBody(); - } - return false; - } - - private boolean readHeader() - { - assertThat(new String(header, 0, 6, ZMQ.CHARSET), is("HEADER")); - ByteBuffer b = ByteBuffer.wrap(header, 6, 4); - size = b.getInt(); - - msg = new Msg(size); - nextStep(msg, READ_BODY); - - return true; - } - - private boolean readBody() - { - sink.pushMsg(msg); - nextStep(header, 10, READ_HEADER); - return true; - } - - @Override - public boolean stalled() - { - return state() == READ_BODY; - } - - @Override - public void setMsgSink(IMsgSink msgSink) - { - sink = msgSink; - } - } - - @Test - public void testCustomDecoder() - { - CustomDecoder cdecoder = new CustomDecoder(32, 64); - cdecoder.setMsgSink(session); - - ByteBuffer in = cdecoder.getBuffer(); - int insize = readHeader(in); - assertThat(insize, is(10)); - readBody(in); - - in.flip(); - int processed = cdecoder.processBuffer(in, 30); - assertThat(processed, is(30)); - assertThat(cdecoder.size, is(20)); - assertThat(session.out.size(), is(1)); - } - - private void readBody(ByteBuffer in) - { - in.put("1234567890".getBytes(ZMQ.CHARSET)); - in.put("1234567890".getBytes(ZMQ.CHARSET)); - } - - private int readHeader(ByteBuffer in) - { - in.put("HEADER".getBytes(ZMQ.CHARSET)); - in.putInt(20); - return in.position(); - } -} diff --git a/src/test/java/zmq/TestEncoder.java b/src/test/java/zmq/TestEncoder.java deleted file mode 100644 index 91a73ed53..000000000 --- a/src/test/java/zmq/TestEncoder.java +++ /dev/null @@ -1,181 +0,0 @@ -package zmq; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.junit.Before; -import org.junit.Test; - -import zmq.Helper.DummySession; -import zmq.Helper.DummySocketChannel; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; - -public class TestEncoder -{ - EncoderBase encoder; - Helper.DummySession session; - DummySocketChannel sock; - @Before - public void setUp() - { - session = new DummySession(); - encoder = new Encoder(64); - encoder.setMsgSource(session); - sock = new DummySocketChannel(); - } - // as if it read data from socket - private Msg readShortMessage() - { - Msg msg = new Msg("hello".getBytes(ZMQ.CHARSET)); - return msg; - } - - // as if it read data from socket - private Msg readLongMessage1() - { - Msg msg = new Msg(200); - for (int i = 0; i < 20; i++) { - msg.put("0123456789".getBytes(ZMQ.CHARSET)); - } - return msg; - } - - @Test - public void testReader() - { - Msg msg = readShortMessage(); - session.pushMsg(msg); - Transfer out = encoder.getData(null); - int outsize = out.remaining(); - - assertThat(outsize, is(7)); - int written = write(out); - assertThat(written, is(7)); - int remaning = out.remaining(); - assertThat(remaning, is(0)); - } - - private int write(Transfer out) - { - try { - return out.transferTo(sock); - } - catch (IOException e) { - e.printStackTrace(); - return -1; - } - } - - @Test - public void testReaderLong() - { - Msg msg = readLongMessage1(); - session.pushMsg(msg); - Transfer out = encoder.getData(null); - - int insize = out.remaining(); - - assertThat(insize, is(64)); - int written = write(out); - assertThat(written, is(64)); - - out = encoder.getData(null); - int remaning = out.remaining(); - assertThat(remaning, is(138)); - - written = write(out); - assertThat(written, is(64)); - - remaning = out.remaining(); - assertThat(remaning, is(74)); - - written = write(out); - assertThat(written, is(64)); - - remaning = out.remaining(); - assertThat(remaning, is(10)); - - written = write(out); - assertThat(written, is(10)); - - remaning = out.remaining(); - assertThat(remaning, is(0)); - - } - - static class CustomEncoder extends EncoderBase - { - public static final boolean RAW_ENCODER = true; - private static final int read_header = 0; - private static final int read_body = 1; - - ByteBuffer header = ByteBuffer.allocate(10); - Msg msg; - int size = -1; - IMsgSource source; - - public CustomEncoder(int bufsize) - { - super(bufsize); - nextStep((Msg) null, read_body, true); - } - - @Override - protected boolean next() - { - switch (state()) { - case read_header: - return readHeader(); - case read_body: - return readBody(); - } - return false; - } - - private boolean readHeader() - { - nextStep(msg.data(), msg.size(), - read_body, !msg.hasMore()); - return true; - } - - private boolean readBody() - { - msg = source.pullMsg(); - - if (msg == null) { - return false; - } - header.clear(); - header.put("HEADER".getBytes(ZMQ.CHARSET)); - header.putInt(msg.size()); - header.flip(); - nextStep(header.array(), 10, read_header, !msg.hasMore()); - return true; - } - - @Override - public void setMsgSource(IMsgSource msgSource) - { - source = msgSource; - } - } - - @Test - public void testCustomDecoder() - { - CustomEncoder cencoder = new CustomEncoder(32); - cencoder.setMsgSource(session); - Msg msg = new Msg("12345678901234567890".getBytes(ZMQ.CHARSET)); - session.pushMsg(msg); - - Transfer out = cencoder.getData(null); - write(out); - byte[] data = sock.data(); - - assertThat(new String(data, 0, 6, ZMQ.CHARSET), is("HEADER")); - assertThat((int) data[9], is(20)); - assertThat(new String(data, 10, 20, ZMQ.CHARSET), is("12345678901234567890")); - } -} diff --git a/src/test/java/zmq/TestHwm.java b/src/test/java/zmq/TestHwm.java index 8596cef2d..9fad3c17a 100644 --- a/src/test/java/zmq/TestHwm.java +++ b/src/test/java/zmq/TestHwm.java @@ -1,9 +1,10 @@ package zmq; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; public class TestHwm { diff --git a/src/test/java/zmq/TestLastEndpoint.java b/src/test/java/zmq/TestLastEndpoint.java index df1b6ebfd..328a45055 100644 --- a/src/test/java/zmq/TestLastEndpoint.java +++ b/src/test/java/zmq/TestLastEndpoint.java @@ -1,12 +1,15 @@ package zmq; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + import java.io.IOException; import java.util.UUID; import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; + +import zmq.util.Utils; public class TestLastEndpoint { diff --git a/src/test/java/zmq/TestMonitor.java b/src/test/java/zmq/TestMonitor.java index dd13b0fd2..03f34683b 100644 --- a/src/test/java/zmq/TestMonitor.java +++ b/src/test/java/zmq/TestMonitor.java @@ -1,17 +1,20 @@ package zmq; -import org.junit.Test; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import zmq.util.Utils; public class TestMonitor { static class SocketMonitor extends Thread { - private Ctx ctx; - private int events; + private Ctx ctx; + private int events; private String monitorAddr; public SocketMonitor(Ctx ctx, String monitorAddr) @@ -38,27 +41,16 @@ public void run() switch (event.event) { // listener specific case ZMQ.ZMQ_EVENT_LISTENING: - events |= ZMQ.ZMQ_EVENT_LISTENING; - break; case ZMQ.ZMQ_EVENT_ACCEPTED: - events |= ZMQ.ZMQ_EVENT_ACCEPTED; - break; - // connecter specific + // connecter specific case ZMQ.ZMQ_EVENT_CONNECTED: - events |= ZMQ.ZMQ_EVENT_CONNECTED; - break; case ZMQ.ZMQ_EVENT_CONNECT_DELAYED: - events |= ZMQ.ZMQ_EVENT_CONNECT_DELAYED; - break; - // generic - either end of the socket + // generic - either end of the socket case ZMQ.ZMQ_EVENT_CLOSE_FAILED: - events |= ZMQ.ZMQ_EVENT_CLOSE_FAILED; - break; case ZMQ.ZMQ_EVENT_CLOSED: - events |= ZMQ.ZMQ_EVENT_CLOSED; - break; case ZMQ.ZMQ_EVENT_DISCONNECTED: - events |= ZMQ.ZMQ_EVENT_DISCONNECTED; + case ZMQ.ZMQ_EVENT_MONITOR_STOPPED: + events |= event.event; break; default: // out of band / unexpected event @@ -74,27 +66,29 @@ public void testMonitor() throws Exception { int port = Utils.findOpenPort(); String addr = "tcp://127.0.0.1:" + port; - SocketMonitor [] threads = new SocketMonitor [3]; + SocketMonitor[] threads = new SocketMonitor[3]; // Create the infrastructure Ctx ctx = ZMQ.init(1); assertThat(ctx, notNullValue()); // set socket monitor SocketBase rep = ZMQ.socket(ctx, ZMQ.ZMQ_REP); assertThat(rep, notNullValue()); - try { - ZMQ.monitorSocket(rep, addr, 0); - assertTrue(false); - } - catch (IllegalArgumentException e) { - } + boolean rc = ZMQ.monitorSocket(rep, addr, 0); + assertThat(rc, is(false)); + assertThat(rep.errno.get(), is(ZError.EPROTONOSUPPORT)); // REP socket monitor, all events - boolean rc = ZMQ.monitorSocket(rep, "inproc://monitor.rep", ZMQ.ZMQ_EVENT_ALL); + rc = ZMQ.monitorSocket(rep, "inproc://monitor.rep", ZMQ.ZMQ_EVENT_ALL); assertThat(rc, is(true)); - threads [0] = new SocketMonitor(ctx, "inproc://monitor.rep"); - threads [0].start(); + threads[0] = new SocketMonitor(ctx, "inproc://monitor.rep"); + threads[0].start(); + threads[1] = new SocketMonitor(ctx, "inproc://monitor.req"); + threads[1].start(); + threads[2] = new SocketMonitor(ctx, "inproc://monitor.req2"); + threads[2].start(); + ZMQ.sleep(1); rc = ZMQ.bind(rep, addr); assertThat(rc, is(true)); @@ -105,9 +99,6 @@ public void testMonitor() throws Exception rc = ZMQ.monitorSocket(req, "inproc://monitor.req", ZMQ.ZMQ_EVENT_ALL); assertThat(rc, is(true)); - threads [1] = new SocketMonitor(ctx, "inproc://monitor.req"); - threads [1].start(); - rc = ZMQ.connect(req, addr); assertThat(rc, is(true)); @@ -119,9 +110,6 @@ public void testMonitor() throws Exception rc = ZMQ.monitorSocket(req2, "inproc://monitor.req2", ZMQ.ZMQ_EVENT_CONNECTED); assertThat(rc, is(true)); - threads [2] = new SocketMonitor(ctx, "inproc://monitor.req2"); - threads [2].start(); - rc = ZMQ.connect(req2, addr); assertThat(rc, is(true)); @@ -130,13 +118,17 @@ public void testMonitor() throws Exception // Allow a window for socket events as connect can be async ZMQ.sleep(1); + ZMQ.setSocketOption(rep, ZMQ.ZMQ_LINGER, 0); // Close the REP socket ZMQ.close(rep); // Allow some time for detecting error states ZMQ.sleep(1); + + ZMQ.setSocketOption(req, ZMQ.ZMQ_LINGER, 0); // Close the REQ socket ZMQ.close(req); + ZMQ.setSocketOption(req2, ZMQ.ZMQ_LINGER, 0); // Close the 2nd REQ socket ZMQ.close(req2); @@ -147,18 +139,18 @@ public void testMonitor() throws Exception // Expected REP socket events // We expect to at least observe these events - assertTrue((threads[0].events & ZMQ.ZMQ_EVENT_LISTENING) > 0); + assertTrue((threads[0].events & ZMQ.ZMQ_EVENT_LISTENING) > 0); assertTrue((threads[0].events & ZMQ.ZMQ_EVENT_ACCEPTED) > 0); assertTrue((threads[0].events & ZMQ.ZMQ_EVENT_CLOSED) > 0); + assertTrue((threads[0].events & ZMQ.ZMQ_EVENT_MONITOR_STOPPED) > 0); // Expected REQ socket events - assertTrue((threads[1].events & ZMQ.ZMQ_EVENT_CONNECTED) > 0); - assertTrue((threads[1].events & ZMQ.ZMQ_EVENT_DISCONNECTED) > 0); - assertTrue((threads[1].events & ZMQ.ZMQ_EVENT_CLOSED) > 0); + assertTrue((threads[1].events & ZMQ.ZMQ_EVENT_CONNECT_DELAYED) > 0); + assertTrue((threads[1].events & ZMQ.ZMQ_EVENT_CONNECTED) > 0); + assertTrue((threads[1].events & ZMQ.ZMQ_EVENT_MONITOR_STOPPED) > 0); // Expected 2nd REQ socket events - assertTrue((threads[2].events & ZMQ.ZMQ_EVENT_CONNECTED) > 0); - assertTrue((threads[2].events & ZMQ.ZMQ_EVENT_CLOSED) == 0); + assertTrue(threads[2].events == ZMQ.ZMQ_EVENT_CONNECTED); threads[0].join(); threads[1].join(); diff --git a/src/test/java/zmq/TestMsg.java b/src/test/java/zmq/TestMsg.java index 15d128af7..083efb579 100644 --- a/src/test/java/zmq/TestMsg.java +++ b/src/test/java/zmq/TestMsg.java @@ -1,8 +1,9 @@ package zmq; -import org.junit.Test; import java.nio.ByteBuffer; +import org.junit.Test; + public class TestMsg { @Test(expected = IllegalArgumentException.class) diff --git a/src/test/java/zmq/TestMsgFlags.java b/src/test/java/zmq/TestMsgFlags.java index 33f32fcf0..2d6e55ac4 100644 --- a/src/test/java/zmq/TestMsgFlags.java +++ b/src/test/java/zmq/TestMsgFlags.java @@ -1,9 +1,10 @@ package zmq; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; public class TestMsgFlags { diff --git a/src/test/java/zmq/TestPairIpc.java b/src/test/java/zmq/TestPairIpc.java deleted file mode 100644 index 8863a53b1..000000000 --- a/src/test/java/zmq/TestPairIpc.java +++ /dev/null @@ -1,40 +0,0 @@ -package zmq; - -import java.util.UUID; - -import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; - -public class TestPairIpc -{ - // Create REQ/ROUTER wiring. - - @Test - public void testPairIpc() - { - Ctx ctx = ZMQ.init(1); - assertThat(ctx, notNullValue()); - SocketBase sb = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); - assertThat(sb, notNullValue()); - - UUID random = UUID.randomUUID(); - - boolean brc = ZMQ.bind(sb, "ipc:///tmp/tester" + random.toString()); - assertThat(brc, is(true)); - - SocketBase sc = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); - assertThat(sc, notNullValue()); - - brc = ZMQ.connect(sc, "ipc:///tmp/tester" + random.toString()); - assertThat(brc, is(true)); - - Helper.bounce(sb, sc); - - // Tear down the wiring. - ZMQ.close(sb); - ZMQ.close(sc); - ZMQ.term(ctx); - } -} diff --git a/src/test/java/zmq/TestProxyTcp.java b/src/test/java/zmq/TestProxyTcp.java deleted file mode 100644 index 362157eb4..000000000 --- a/src/test/java/zmq/TestProxyTcp.java +++ /dev/null @@ -1,312 +0,0 @@ -package zmq; - -import java.io.IOException; -import java.net.Socket; -import java.nio.ByteBuffer; - -import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; - -public class TestProxyTcp -{ - static class Client extends Thread - { - private int port; - - public Client(int port) - { - this.port = port; - } - - @Override - public void run() - { - System.out.println("Start client thread"); - try { - Socket s = new Socket("127.0.0.1", port); - Helper.send(s, "hellow"); - Helper.send(s, "1234567890abcdefghizklmnopqrstuvwxyz"); - Helper.send(s, "end"); - Helper.send(s, "end"); - s.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - System.out.println("Stop client thread"); - } - } - - static class Dealer extends Thread - { - private final SocketBase s; - private final String name; - private final int port; - - public Dealer(Ctx ctx, String name, int port) - { - this.s = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); - this.name = name; - this.port = port; - } - - @Override - public void run() - { - System.out.println("Start dealer " + name); - - ZMQ.connect(s, "tcp://127.0.0.1:" + port); - int i = 0; - while (true) { - Msg msg = s.recv(0); - if (msg == null) { - throw new RuntimeException("hello"); - } - System.out.println("REP recieved " + msg); - String data = new String(msg.data(), 0, msg.size(), ZMQ.CHARSET); - - Msg response = null; - if ((i % 3) == 2) { - response = new Msg(msg.size() + 3); - response.put("OK ".getBytes(ZMQ.CHARSET)) - .put(msg.data()); - } - else { - response = new Msg(msg.data()); - } - - s.send(response, (i % 3) == 2 ? 0 : ZMQ.ZMQ_SNDMORE); - i++; - if (data.equals("end")) { - break; - } - } - s.close(); - System.out.println("Stop dealer " + name); - } - } - - static class ProxyDecoder extends DecoderBase - { - private static final int READ_HEADER = 0; - private static final int READ_BODY = 1; - - byte [] header = new byte [4]; - Msg msg; - int size = -1; - boolean identitySent = false; - Msg bottom; - IMsgSink msgSink; - - public ProxyDecoder(int bufsize, long maxmsgsize) - { - super(bufsize); - nextStep(header, 4, READ_HEADER); - - bottom = new Msg(); - bottom.setFlags(Msg.MORE); - } - - @Override - protected boolean next() - { - switch (state()) { - case READ_HEADER: - return readHeader(); - case READ_BODY: - return readBody(); - } - return false; - } - - private boolean readHeader() - { - size = Integer.parseInt(new String(header, ZMQ.CHARSET)); - System.out.println("Received " + size); - msg = new Msg(size); - nextStep(msg, READ_BODY); - - return true; - } - - private boolean readBody() - { - if (msgSink == null) { - return false; - } - - System.out.println("Received body " + new String(msg.data(), ZMQ.CHARSET)); - - if (!identitySent) { - Msg identity = new Msg(); - msgSink.pushMsg(identity); - identitySent = true; - } - - msgSink.pushMsg(bottom); - msgSink.pushMsg(msg); - - nextStep(header, 4, READ_HEADER); - return true; - } - - @Override - public boolean stalled() - { - return state() == READ_BODY; - } - - @Override - public void setMsgSink(IMsgSink msgSink) - { - this.msgSink = msgSink; - } - } - - static class ProxyEncoder extends EncoderBase - { - public static final boolean RAW_ENCODER = true; - private static final int WRITE_HEADER = 0; - private static final int WRITE_BODY = 1; - - ByteBuffer header = ByteBuffer.allocate(4); - Msg msg; - int size = -1; - boolean messageReady; - boolean identityRecieved; - IMsgSource msgSource; - - public ProxyEncoder(int bufsize) - { - super(bufsize); - nextStep((Msg) null, WRITE_HEADER, true); - messageReady = false; - identityRecieved = false; - } - - @Override - protected boolean next() - { - switch (state()) { - case WRITE_HEADER: - return writeHeader(); - case WRITE_BODY: - return writeBody(); - } - return false; - } - - private boolean writeBody() - { - System.out.println("writer body "); - nextStep(msg, WRITE_HEADER, !msg.hasMore()); - - return true; - } - - private boolean writeHeader() - { - if (msgSource == null) { - return false; - } - - msg = msgSource.pullMsg(); - - if (msg == null) { - return false; - } - if (!identityRecieved) { - identityRecieved = true; - nextStep(header.array(), msg.size() < 255 ? 2 : 10, WRITE_BODY, false); - return true; - } - else - if (!messageReady) { - messageReady = true; - msg = msgSource.pullMsg(); - - if (msg == null) { - return false; - } - } - messageReady = false; - System.out.println("write header " + msg.size()); - - header.clear(); - header.put(String.format("%04d", msg.size()).getBytes(ZMQ.CHARSET)); - header.flip(); - nextStep(header.array(), 4, WRITE_BODY, false); - return true; - } - - @Override - public void setMsgSource(IMsgSource msgSource) - { - this.msgSource = msgSource; - } - } - - static class Main extends Thread - { - private Ctx ctx; - private int routerPort; - private int dealerPort; - - Main(Ctx ctx, int routerPort, int dealerPort) - { - this.ctx = ctx; - this.routerPort = routerPort; - this.dealerPort = dealerPort; - } - - @Override - public void run() - { - boolean rc; - SocketBase sa = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); - assertThat(sa, notNullValue()); - - sa.setSocketOpt(ZMQ.ZMQ_DECODER, ProxyDecoder.class); - sa.setSocketOpt(ZMQ.ZMQ_ENCODER, ProxyEncoder.class); - - rc = ZMQ.bind(sa, "tcp://127.0.0.1:" + routerPort); - assertThat(rc, is(true)); - - SocketBase sb = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); - assertThat(sb, notNullValue()); - rc = ZMQ.bind(sb, "tcp://127.0.0.1:" + dealerPort); - assertThat(rc, is(true)); - - ZMQ.proxy(sa, sb, null); - - ZMQ.close(sa); - ZMQ.close(sb); - } - } - - @Test - public void testProxyTcp() throws Exception - { - int routerPort = Utils.findOpenPort(); - int dealerPort = Utils.findOpenPort(); - - Ctx ctx = ZMQ.init(1); - assertThat(ctx, notNullValue()); - - Main mt = new Main(ctx, routerPort, dealerPort); - mt.start(); - - new Dealer(ctx, "A", dealerPort).start(); - new Dealer(ctx, "B", dealerPort).start(); - - Thread.sleep(1000); - Thread client = new Client(routerPort); - client.start(); - - client.join(); - - ZMQ.term(ctx); - } -} diff --git a/src/test/java/zmq/TestPubsubTcp.java b/src/test/java/zmq/TestPubsubTcp.java deleted file mode 100644 index 780a6c33b..000000000 --- a/src/test/java/zmq/TestPubsubTcp.java +++ /dev/null @@ -1,46 +0,0 @@ -package zmq; - -import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; - -public class TestPubsubTcp -{ - @Test - public void testPubsubTcp() throws Exception - { - int port = Utils.findOpenPort(); - Ctx ctx = ZMQ.init(1); - assertThat(ctx, notNullValue()); - - SocketBase sb = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); - assertThat(sb, notNullValue()); - boolean rc = ZMQ.bind(sb, "tcp://127.0.0.1:" + port); - assertThat(rc, is(true)); - - SocketBase sc = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); - assertThat(sc, notNullValue()); - - sc.setSocketOpt(ZMQ.ZMQ_SUBSCRIBE, "topic"); - - rc = ZMQ.connect(sc, "tcp://127.0.0.1:" + port); - assertThat(rc, is(true)); - - ZMQ.sleep(2); - - sb.send(new Msg("topic abc".getBytes(ZMQ.CHARSET)), 0); - sb.send(new Msg("topix defg".getBytes(ZMQ.CHARSET)), 0); - sb.send(new Msg("topic defgh".getBytes(ZMQ.CHARSET)), 0); - - Msg msg = sc.recv(0); - assertThat(msg.size(), is(9)); - - msg = sc.recv(0); - assertThat(msg.size(), is(11)); - - ZMQ.close(sc); - ZMQ.close(sb); - ZMQ.term(ctx); - } -} diff --git a/src/test/java/zmq/TestReqrepTcp.java b/src/test/java/zmq/TestReqrepTcp.java deleted file mode 100644 index 127886e9c..000000000 --- a/src/test/java/zmq/TestReqrepTcp.java +++ /dev/null @@ -1,33 +0,0 @@ -package zmq; - -import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; - -public class TestReqrepTcp -{ - @Test - public void testReqrepTcp() throws Exception - { - int port = Utils.findOpenPort(); - Ctx ctx = ZMQ.init(1); - assertThat(ctx, notNullValue()); - - SocketBase sb = ZMQ.socket(ctx, ZMQ.ZMQ_REP); - assertThat(sb, notNullValue()); - boolean rc = ZMQ.bind(sb, "tcp://127.0.0.1:" + port); - assertThat(rc, is(true)); - - SocketBase sc = ZMQ.socket(ctx, ZMQ.ZMQ_REQ); - assertThat(sc, notNullValue()); - rc = ZMQ.connect(sc, "tcp://127.0.0.1:" + port); - assertThat(rc, is(true)); - - Helper.bounce(sb, sc); - - ZMQ.close(sc); - ZMQ.close(sb); - ZMQ.term(ctx); - } -} diff --git a/src/test/java/zmq/TestShutdownStress.java b/src/test/java/zmq/TestShutdownStress.java index 30942d1df..29ede9bfb 100644 --- a/src/test/java/zmq/TestShutdownStress.java +++ b/src/test/java/zmq/TestShutdownStress.java @@ -1,11 +1,14 @@ package zmq; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + import java.io.IOException; import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; + +import zmq.util.Utils; public class TestShutdownStress { @@ -13,7 +16,7 @@ public class TestShutdownStress class Worker implements Runnable { - int port; + int port; SocketBase s; Worker(SocketBase s) throws IOException diff --git a/src/test/java/zmq/TestSubForward.java b/src/test/java/zmq/TestSubForward.java deleted file mode 100644 index 7d4e179b6..000000000 --- a/src/test/java/zmq/TestSubForward.java +++ /dev/null @@ -1,70 +0,0 @@ -package zmq; - -import java.io.IOException; - -import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; - -public class TestSubForward -{ - // Create REQ/ROUTER wiring. - - @Test - public void testSubForward() throws IOException - { - int port1 = Utils.findOpenPort(); - int port2 = Utils.findOpenPort(); - - Ctx ctx = ZMQ.init(1); - assertThat(ctx, notNullValue()); - - SocketBase xpub = ZMQ.socket(ctx, ZMQ.ZMQ_XPUB); - assertThat(xpub, notNullValue()); - boolean rc = ZMQ.bind(xpub, "tcp://127.0.0.1:" + port1); - - SocketBase xsub = ZMQ.socket(ctx, ZMQ.ZMQ_XSUB); - assertThat(xsub, notNullValue()); - rc = ZMQ.bind(xsub, "tcp://127.0.0.1:" + port2); - assertThat(rc, is(true)); - - SocketBase pub = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); - assertThat(pub, notNullValue()); - rc = ZMQ.connect(pub, "tcp://127.0.0.1:" + port2); - assertThat(rc, is(true)); - - SocketBase sub = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); - assertThat(sub, notNullValue()); - rc = ZMQ.connect(sub, "tcp://127.0.0.1:" + port1); - assertThat(rc, is(true)); - - ZMQ.setSocketOption(sub, ZMQ.ZMQ_SUBSCRIBE, ""); - Msg msg = ZMQ.recv(xpub, 0); - assertThat(msg, notNullValue()); - int n = ZMQ.send(xsub, msg, 0); - assertThat(n, not(0)); - - ZMQ.sleep(1); - - n = ZMQ.send(pub, null, 0, 0); - assertThat(n, is(0)); - - msg = ZMQ.recv(xsub, 0); - assertThat(msg, notNullValue()); - - n = ZMQ.send(xpub, msg, 0); - assertThat(n, is(0)); - - msg = ZMQ.recv(sub, 0); - assertThat(msg, notNullValue()); - - // Tear down the wiring. - ZMQ.close(xpub); - ZMQ.close(xsub); - ZMQ.close(pub); - ZMQ.close(sub); - ZMQ.term(ctx); - } -} diff --git a/src/test/java/zmq/TestTermEndpoint.java b/src/test/java/zmq/TestTermEndpoint.java index ce59c21a1..68dd154a7 100644 --- a/src/test/java/zmq/TestTermEndpoint.java +++ b/src/test/java/zmq/TestTermEndpoint.java @@ -1,9 +1,12 @@ package zmq; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.util.Utils; public class TestTermEndpoint { @@ -26,7 +29,7 @@ public void testTermEndpoint() throws Exception assertThat(rc, is(true)); // Pass one message through to ensure the connection is established. - int r = ZMQ.send(push, "ABC", 0); + int r = ZMQ.send(push, "ABC", 0); assertThat(r, is(3)); Msg msg = ZMQ.recv(pull, 0); assertThat(msg.size(), is(3)); @@ -39,7 +42,7 @@ public void testTermEndpoint() throws Exception ZMQ.sleep(1); // Check that sending would block (there's no outbound connection). - r = ZMQ.send(push, "ABC", ZMQ.ZMQ_DONTWAIT); + r = ZMQ.send(push, "ABC", ZMQ.ZMQ_DONTWAIT); assertThat(r, is(-1)); // Clean up. @@ -64,7 +67,7 @@ public void testTermEndpoint() throws Exception assertThat(rc, is(true)); // Pass one message through to ensure the connection is established. - r = ZMQ.send(push, "ABC", 0); + r = ZMQ.send(push, "ABC", 0); assertThat(r, is(3)); msg = ZMQ.recv(pull, 0); assertThat(msg.size(), is(3)); @@ -77,7 +80,7 @@ public void testTermEndpoint() throws Exception ZMQ.sleep(1); // Check that sending would block (there's no outbound connection). - r = ZMQ.send(push, "ABC", ZMQ.ZMQ_DONTWAIT); + r = ZMQ.send(push, "ABC", ZMQ.ZMQ_DONTWAIT); assertThat(r, is(-1)); // Clean up. diff --git a/src/test/java/zmq/TestTimeo.java b/src/test/java/zmq/TestTimeo.java index 08e81c977..940bb928b 100644 --- a/src/test/java/zmq/TestTimeo.java +++ b/src/test/java/zmq/TestTimeo.java @@ -1,17 +1,18 @@ package zmq; -import org.junit.Test; - -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; public class TestTimeo { class Worker implements Runnable { Ctx ctx; + Worker(Ctx ctx) { this.ctx = ctx; @@ -50,7 +51,7 @@ public void testTimeo() throws Exception ZMQ.setSocketOption(sb, ZMQ.ZMQ_RCVTIMEO, timeout); long watch = ZMQ.startStopwatch(); msg = ZMQ.recv(sb, 0); - assertThat(msg , nullValue()); + assertThat(msg, nullValue()); long elapsed = ZMQ.stopStopwatch(watch); assertThat(elapsed > 440000 && elapsed < 550000, is(true)); @@ -62,7 +63,7 @@ public void testTimeo() throws Exception watch = ZMQ.startStopwatch(); msg = ZMQ.recv(sb, 0); - assertThat(msg , nullValue()); + assertThat(msg, nullValue()); elapsed = ZMQ.stopStopwatch(watch); assertThat(elapsed > 1900000 && elapsed < 2100000, is(true)); thread.join(); diff --git a/src/test/java/zmq/io/MetadataTest.java b/src/test/java/zmq/io/MetadataTest.java new file mode 100644 index 000000000..2089dacff --- /dev/null +++ b/src/test/java/zmq/io/MetadataTest.java @@ -0,0 +1,143 @@ +package zmq.io; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class MetadataTest +{ + private static class ZapHandler implements Runnable + { + private final SocketBase handler; + + public ZapHandler(SocketBase handler) + { + this.handler = handler; + } + + @SuppressWarnings("unused") + @Override + public void run() + { + byte[] metadata = { 5, 'H', 'e', 'l', 'l', 'o', 0, 0, 0, 5, 'W', 'o', 'r', 'l', 'd' }; + + // Process ZAP requests forever + while (true) { + Msg version = ZMQ.recv(handler, 0); + if (version == null) { + break; // Terminating + } + Msg sequence = ZMQ.recv(handler, 0); + Msg domain = ZMQ.recv(handler, 0); + Msg address = ZMQ.recv(handler, 0); + Msg identity = ZMQ.recv(handler, 0); + Msg mechanism = ZMQ.recv(handler, 0); + + assertThat(new String(version.data(), ZMQ.CHARSET), is("1.0")); + assertThat(new String(mechanism.data(), ZMQ.CHARSET), is("NULL")); + + int ret = ZMQ.send(handler, version, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, sequence, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(1)); + + System.out.println("Sending ZAP reply"); + if ("DOMAIN".equals(new String(domain.data(), ZMQ.CHARSET))) { + ret = ZMQ.send(handler, "200", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "OK", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(2)); + ret = ZMQ.send(handler, "anonymous", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(9)); + ret = ZMQ.send(handler, metadata, metadata.length, 0); + assertThat(ret, is(metadata.length)); + } + else { + ret = ZMQ.send(handler, "400", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "BAD DOMAIN", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(10)); + ret = ZMQ.send(handler, "", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(0)); + ret = ZMQ.send(handler, "", 0); + assertThat(ret, is(0)); + } + } + ZMQ.closeZeroLinger(handler); + } + } + + @Test + public void testMetadata() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://127.0.0.1:" + port; + + Ctx ctx = ZMQ.createContext(); + + // Spawn ZAP handler + // We create and bind ZAP socket in main thread to avoid case + // where child thread does not start up fast enough. + SocketBase handler = ZMQ.socket(ctx, ZMQ.ZMQ_REP); + assertThat(handler, notNullValue()); + boolean rc = ZMQ.bind(handler, "inproc://zeromq.zap.01"); + assertThat(rc, is(true)); + + Thread thread = new Thread(new ZapHandler(handler)); + thread.start(); + + // Server socket will accept connections + SocketBase server = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(server, notNullValue()); + SocketBase client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(server, ZMQ.ZMQ_ZAP_DOMAIN, "DOMAIN"); + + rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + int ret = ZMQ.send(client, "This is a message", 0); + assertThat(ret, is(17)); + + Msg msg = ZMQ.recv(server, 0); + assertThat(msg, notNullValue()); + + String prop = ZMQ.getMessageMetadata(msg, "Socket-Type"); + assertThat(prop, is("DEALER")); + + prop = ZMQ.getMessageMetadata(msg, "User-Id"); + assertThat(prop, is("anonymous")); + + prop = ZMQ.getMessageMetadata(msg, "Peer-Address"); + assertThat(prop.startsWith("127.0.0.1:"), is(true)); + + prop = ZMQ.getMessageMetadata(msg, "no such"); + assertThat(prop, nullValue()); + + prop = ZMQ.getMessageMetadata(msg, "Hello"); + assertThat(prop, is("World")); + + ZMQ.closeZeroLinger(server); + ZMQ.closeZeroLinger(client); + + // Shutdown + ZMQ.term(ctx); + // Wait until ZAP handler terminates + thread.join(); + } +} diff --git a/src/test/java/zmq/io/coder/AbstractDecoderTest.java b/src/test/java/zmq/io/coder/AbstractDecoderTest.java new file mode 100644 index 000000000..6227604ed --- /dev/null +++ b/src/test/java/zmq/io/coder/AbstractDecoderTest.java @@ -0,0 +1,142 @@ +package zmq.io.coder; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.nio.ByteBuffer; + +import org.junit.Test; + +import zmq.Msg; +import zmq.ZMQ; +import zmq.io.coder.IDecoder.Step; +import zmq.util.ValueReference; + +public abstract class AbstractDecoderTest +{ + DecoderBase decoder; + + // as if it read data from socket + abstract int readShortMessage(ByteBuffer buf); + + // as if it read data from socket + abstract int readLongMessage1(ByteBuffer buf); + + abstract int readLongMessage2(ByteBuffer buf); + + abstract int readExtraLongMessage(ByteBuffer buf); + + @Test + public void testReader() + { + ByteBuffer in = decoder.getBuffer(); + int insize = readShortMessage(in); + + assertThat(insize, is(7)); + in.flip(); + ValueReference process = new ValueReference<>(0); + decoder.decode(in, insize, process); + assertThat(process.get(), is(7)); + Msg msg = decoder.msg(); + assertThat(msg, notNullValue()); + assertThat(msg.flags(), is(1)); + + } + + @Test + public void testReaderLong() + { + ByteBuffer in = decoder.getBuffer(); + int insize = readLongMessage1(in); + + assertThat(insize, is(64)); + in.flip(); + ValueReference process = new ValueReference<>(0); + decoder.decode(in, insize, process); + assertThat(process.get(), is(64)); + + in = decoder.getBuffer(); + assertThat(in.capacity(), is(200)); + assertThat(in.position(), is(62)); + + in.put("23456789".getBytes(ZMQ.CHARSET)); + insize = readLongMessage2(in); + + assertThat(insize, is(200)); + decoder.decode(in, 138, process); + assertThat(process.get(), is(138)); + assertThat(in.array()[199], is((byte) 'x')); + + Msg msg = decoder.msg(); + assertThat(msg, notNullValue()); + byte last = msg.data()[199]; + assertThat(last, is((byte) 'x')); + assertThat(msg.size(), is(200)); + assertThat(msg.flags(), is(1)); + } + + @Test + public void testReaderExtraLong() + { + ByteBuffer in = decoder.getBuffer(); + int insize = readExtraLongMessage(in); + + // assertThat(insize, is(62)); + in.flip(); + ValueReference process = new ValueReference<>(0); + decoder.decode(in, insize, process); + assertThat(process.get(), is(insize)); + + in = decoder.getBuffer(); + assertThat(in.capacity(), is(330)); + assertThat(in.position(), is(52)); + + in.put("23456789".getBytes(ZMQ.CHARSET)); + in.put("0123456789".getBytes(ZMQ.CHARSET)); + insize = readLongMessage2(in); + + assertThat(insize, is(200)); + insize = readLongMessage2(in); + + assertThat(insize, is(330)); + + decoder.decode(in, 278, process); + assertThat(process.get(), is(278)); + assertThat(in.array()[329], is((byte) 'x')); + + Msg msg = decoder.msg(); + assertThat(msg, notNullValue()); + byte last = msg.data()[329]; + assertThat(last, is((byte) 'x')); + assertThat(msg.size(), is(330)); + } + + @Test + public void testReaderMultipleMsg() + { + ByteBuffer in = decoder.getBuffer(); + int insize = readShortMessage(in); + assertThat(insize, is(7)); + readShortMessage(in); + + in.flip(); + ValueReference processed = new ValueReference<>(0); + decoder.decode(in, 14, processed); + assertThat(processed.get(), is(7)); + assertThat(in.position(), is(7)); + + assertThat(decoder.msg(), notNullValue()); + + Step.Result result = decoder.decode(in, 6, processed); + assertThat(processed.get(), is(6)); + assertThat(in.position(), is(13)); + assertThat(result, is(Step.Result.MORE_DATA)); + + result = decoder.decode(in, 10, processed); + assertThat(processed.get(), is(1)); + assertThat(in.position(), is(14)); + assertThat(result, is(Step.Result.DECODED)); + assertThat(decoder.msg(), notNullValue()); + } +} diff --git a/src/test/java/zmq/io/coder/CustomDecoderTest.java b/src/test/java/zmq/io/coder/CustomDecoderTest.java new file mode 100644 index 000000000..9ba71ce49 --- /dev/null +++ b/src/test/java/zmq/io/coder/CustomDecoderTest.java @@ -0,0 +1,143 @@ +package zmq.io.coder; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.nio.ByteBuffer; + +import org.junit.Test; + +import zmq.Config; +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.io.coder.IDecoder.Step; +import zmq.util.Errno; +import zmq.util.ValueReference; + +public class CustomDecoderTest +{ + static class CustomDecoder extends Decoder + { + private final Step readHeader = new Step() + { + @Override + public Step.Result apply() + { + return readHeader(); + } + }; + private final Step readBody = new Step() + { + @Override + public Step.Result apply() + { + return readBody(); + } + }; + + byte[] header = new byte[10]; + Msg msg; + int size = -1; + + public CustomDecoder(int bufsize, long maxmsgsize) + { + super(new Errno(), bufsize, maxmsgsize, Config.MSG_ALLOCATION_HEAP_THRESHOLD.getValue()); + nextStep(header, 10, readHeader); + } + + private Step.Result readHeader() + { + assertThat(new String(header, 0, 6, ZMQ.CHARSET), is("HEADER")); + ByteBuffer b = ByteBuffer.wrap(header, 6, 4); + size = b.getInt(); + + msg = allocate(size); + nextStep(msg, readBody); + + return Step.Result.MORE_DATA; + } + + private Step.Result readBody() + { + nextStep(header, 10, readHeader); + return Step.Result.DECODED; + } + + @Override + protected long binarySize(Msg msg) + { + return msg.size(); + } + } + + @Test + public void testCustomDecoder() + { + CustomDecoder cdecoder = new CustomDecoder(32, 64); + + ByteBuffer in = cdecoder.getBuffer(); + int insize = readHeader(in); + assertThat(insize, is(10)); + readBody(in); + + in.flip(); + ValueReference processed = new ValueReference<>(0); + Step.Result result = cdecoder.decode(in, 30, processed); + assertThat(processed.get(), is(30)); + assertThat(cdecoder.size, is(20)); + assertThat(result, is(Step.Result.DECODED)); + } + + private void readBody(ByteBuffer in) + { + in.put("1234567890".getBytes(ZMQ.CHARSET)); + in.put("1234567890".getBytes(ZMQ.CHARSET)); + } + + private int readHeader(ByteBuffer in) + { + in.put("HEADER".getBytes(ZMQ.CHARSET)); + in.putInt(20); + return in.position(); + } + + @SuppressWarnings("deprecation") + @Test + public void testAssignCustomDecoder() + { + Ctx ctx = ZMQ.createContext(); + + SocketBase socket = ctx.createSocket(ZMQ.ZMQ_PAIR); + + boolean rc = socket.setSocketOpt(ZMQ.ZMQ_DECODER, CustomDecoder.class); + assertThat(rc, is(true)); + + ZMQ.close(socket); + ZMQ.term(ctx); + } + + private static class WrongDecoder extends CustomDecoder + { + public WrongDecoder(int bufsize) + { + super(bufsize, 0); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testAssignWrongCustomDecoder() + { + Ctx ctx = ZMQ.createContext(); + + SocketBase socket = ctx.createSocket(ZMQ.ZMQ_PAIR); + + boolean rc = socket.setSocketOpt(ZMQ.ZMQ_DECODER, WrongDecoder.class); + assertThat(rc, is(false)); + + ZMQ.close(socket); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/io/coder/CustomEncoderTest.java b/src/test/java/zmq/io/coder/CustomEncoderTest.java new file mode 100644 index 000000000..7adf0a437 --- /dev/null +++ b/src/test/java/zmq/io/coder/CustomEncoderTest.java @@ -0,0 +1,138 @@ +package zmq.io.coder; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Errno; +import zmq.util.ValueReference; + +public class CustomEncoderTest +{ + private Helper.DummySocketChannel sock = new Helper.DummySocketChannel(); + + private int write(ByteBuffer out) + { + try { + return sock.write(out); + } + catch (IOException e) { + e.printStackTrace(); + return -1; + } + } + + static class CustomEncoder extends EncoderBase + { + public static final boolean RAW_ENCODER = true; + private final Runnable readHeader = new Runnable() + { + @Override + public void run() + { + readHeader(); + } + }; + private final Runnable readBody = new Runnable() + { + @Override + public void run() + { + readBody(); + } + }; + + ByteBuffer header = ByteBuffer.allocate(10); + + public CustomEncoder(int bufsize, long maxmsgsize) + { + super(new Errno(), bufsize); + initStep(readBody, true); + } + + private void readHeader() + { + nextStep(inProgress, readBody, !inProgress.hasMore()); + } + + private void readBody() + { + if (inProgress == null) { + return; + } + header.clear(); + header.put("HEADER".getBytes(ZMQ.CHARSET)); + header.putInt(inProgress.size()); + header.flip(); + nextStep(header, header.limit(), readHeader, false); + } + + } + + @Test + public void testCustomEncoder() + { + CustomEncoder cencoder = new CustomEncoder(32, Integer.MAX_VALUE / 2); + + Msg msg = new Msg("12345678901234567890".getBytes(ZMQ.CHARSET)); + cencoder.loadMsg(msg); + ValueReference ref = new ValueReference<>(); + int outsize = cencoder.encode(ref, 0); + assertThat(outsize, is(30)); + ByteBuffer out = ref.get(); + out.flip(); + write(out); + byte[] data = sock.data(); + + assertThat(new String(data, 0, 6, ZMQ.CHARSET), is("HEADER")); + assertThat((int) data[9], is(20)); + assertThat(new String(data, 10, 20, ZMQ.CHARSET), is("12345678901234567890")); + } + + @SuppressWarnings("deprecation") + @Test + public void testAssignCustomEncoder() + { + Ctx ctx = ZMQ.createContext(); + + SocketBase socket = ctx.createSocket(ZMQ.ZMQ_PAIR); + + boolean rc = socket.setSocketOpt(ZMQ.ZMQ_ENCODER, CustomEncoder.class); + assertThat(rc, is(true)); + + ZMQ.close(socket); + ZMQ.term(ctx); + } + + private static class WrongEncoder extends CustomEncoder + { + public WrongEncoder(int bufsize) + { + super(bufsize, 0); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testAssignWrongCustomEncoder() + { + Ctx ctx = ZMQ.createContext(); + + SocketBase socket = ctx.createSocket(ZMQ.ZMQ_PAIR); + + boolean rc = socket.setSocketOpt(ZMQ.ZMQ_ENCODER, WrongEncoder.class); + assertThat(rc, is(false)); + + ZMQ.close(socket); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/io/coder/V1DecoderTest.java b/src/test/java/zmq/io/coder/V1DecoderTest.java new file mode 100644 index 000000000..7da14abc9 --- /dev/null +++ b/src/test/java/zmq/io/coder/V1DecoderTest.java @@ -0,0 +1,67 @@ +package zmq.io.coder; + +import java.nio.ByteBuffer; + +import org.junit.Before; + +import zmq.Config; +import zmq.ZMQ; +import zmq.io.coder.v1.V1Decoder; +import zmq.util.Errno; +import zmq.util.Wire; + +public class V1DecoderTest extends AbstractDecoderTest +{ + @Before + public void setUp() + { + decoder = new V1Decoder(new Errno(), 64, 512, Config.MSG_ALLOCATION_HEAP_THRESHOLD.getValue()); + } + + // as if it read data from socket + @Override + int readShortMessage(ByteBuffer buf) + { + buf.put((byte) 6); + buf.put((byte) 1); // flag + buf.put("hello".getBytes(ZMQ.CHARSET)); + + return buf.position(); + } + + // as if it read data from socket + @Override + int readLongMessage1(ByteBuffer buf) + { + buf.put((byte) 201); + buf.put((byte) 3); // flag + for (int i = 0; i < 6; i++) { + buf.put("0123456789".getBytes(ZMQ.CHARSET)); + } + buf.put("01".getBytes(ZMQ.CHARSET)); + return buf.position(); + } + + @Override + int readLongMessage2(ByteBuffer buf) + { + for (int i = 0; i < 13; i++) { + buf.put("0123456789".getBytes(ZMQ.CHARSET)); + } + buf.put(buf.position() - 1, (byte) 'x'); + return buf.position(); + } + + @Override + int readExtraLongMessage(ByteBuffer buf) + { + buf.put((byte) 0xff); + Wire.putUInt64(buf, 331); + buf.put((byte) 0); // flag + for (int i = 0; i < 5; i++) { + buf.put("0123456789".getBytes(ZMQ.CHARSET)); + } + buf.put("01".getBytes(ZMQ.CHARSET)); + return buf.position(); + } +} diff --git a/src/test/java/zmq/io/coder/V1EncoderTest.java b/src/test/java/zmq/io/coder/V1EncoderTest.java new file mode 100644 index 000000000..82751bc23 --- /dev/null +++ b/src/test/java/zmq/io/coder/V1EncoderTest.java @@ -0,0 +1,81 @@ +package zmq.io.coder; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.nio.ByteBuffer; + +import org.junit.Test; + +import zmq.Msg; +import zmq.ZMQ; +import zmq.io.coder.v1.V1Encoder; +import zmq.util.Errno; +import zmq.util.ValueReference; + +public class V1EncoderTest +{ + private EncoderBase encoder = new V1Encoder(new Errno(), 64); + + // as if it read data from socket + private Msg readShortMessage() + { + Msg msg = new Msg("hello".getBytes(ZMQ.CHARSET)); + return msg; + } + + // as if it read data from socket + private Msg readLongMessage1() + { + Msg msg = new Msg(200); + for (int i = 0; i < 20; i++) { + msg.put("0123456789".getBytes(ZMQ.CHARSET)); + } + return msg; + } + + @Test + public void testReader() + { + Msg msg = readShortMessage(); + encoder.loadMsg(msg); + ValueReference ref = new ValueReference<>(); + int outsize = encoder.encode(ref, 0); + ByteBuffer out = ref.get(); + + assertThat(out, notNullValue()); + assertThat(outsize, is(7)); + assertThat(out.position(), is(7)); + } + + @Test + public void testReaderLong() + { + Msg msg = readLongMessage1(); + ValueReference ref = new ValueReference<>(); + int outsize = encoder.encode(ref, 0); + assertThat(outsize, is(0)); + ByteBuffer out = ref.get(); + assertThat(out, nullValue()); + + encoder.loadMsg(msg); + outsize = encoder.encode(ref, 64); + assertThat(outsize, is(64)); + out = ref.get(); + int position = out.position(); + int limit = out.limit(); + assertThat(limit, is(64)); + assertThat(position, is(64)); + + ref.set(null); + outsize = encoder.encode(ref, 64); + assertThat(outsize, is(138)); + out = ref.get(); + position = out.position(); + limit = out.limit(); + assertThat(position, is(62)); + assertThat(limit, is(200)); + } +} diff --git a/src/test/java/zmq/io/coder/V2DecoderTest.java b/src/test/java/zmq/io/coder/V2DecoderTest.java new file mode 100644 index 000000000..0fe296b7f --- /dev/null +++ b/src/test/java/zmq/io/coder/V2DecoderTest.java @@ -0,0 +1,67 @@ +package zmq.io.coder; + +import java.nio.ByteBuffer; + +import org.junit.Before; + +import zmq.Config; +import zmq.ZMQ; +import zmq.io.coder.v2.V2Decoder; +import zmq.io.coder.v2.V2Protocol; +import zmq.util.Errno; +import zmq.util.Wire; + +public class V2DecoderTest extends AbstractDecoderTest +{ + @Before + public void setUp() + { + decoder = new V2Decoder(new Errno(), 64, 512, Config.MSG_ALLOCATION_HEAP_THRESHOLD.getValue()); + } + + // as if it read data from socket + @Override + int readShortMessage(ByteBuffer buf) + { + buf.put((byte) V2Protocol.MORE_FLAG); // flag + buf.put((byte) 5); + buf.put("hello".getBytes(ZMQ.CHARSET)); + + return buf.position(); + } + + // as if it read data from socket + @Override + int readLongMessage1(ByteBuffer buf) + { + buf.put((byte) 1); // flag + buf.put((byte) 200); + for (int i = 0; i < 6; i++) { + buf.put("0123456789".getBytes(ZMQ.CHARSET)); + } + buf.put("01".getBytes(ZMQ.CHARSET)); + return buf.position(); + } + + @Override + int readLongMessage2(ByteBuffer buf) + { + for (int i = 0; i < 13; i++) { + buf.put("0123456789".getBytes(ZMQ.CHARSET)); + } + buf.put(buf.position() - 1, (byte) 'x'); + return buf.position(); + } + + @Override + int readExtraLongMessage(ByteBuffer buf) + { + buf.put((byte) V2Protocol.LARGE_FLAG); // flag + Wire.putUInt64(buf, 330); + for (int i = 0; i < 5; i++) { + buf.put("0123456789".getBytes(ZMQ.CHARSET)); + } + buf.put("01".getBytes(ZMQ.CHARSET)); + return buf.position(); + } +} diff --git a/src/test/java/zmq/io/coder/V2EncoderTest.java b/src/test/java/zmq/io/coder/V2EncoderTest.java new file mode 100644 index 000000000..e0c3a48cb --- /dev/null +++ b/src/test/java/zmq/io/coder/V2EncoderTest.java @@ -0,0 +1,82 @@ +package zmq.io.coder; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.nio.ByteBuffer; + +import org.junit.Test; + +import zmq.Msg; +import zmq.ZMQ; +import zmq.io.coder.v2.V2Encoder; +import zmq.util.Errno; +import zmq.util.ValueReference; + +public class V2EncoderTest +{ + private EncoderBase encoder = new V2Encoder(new Errno(), 64); + + // as if it read data from socket + private Msg readShortMessage() + { + Msg msg = new Msg("hello".getBytes(ZMQ.CHARSET)); + return msg; + } + + // as if it read data from socket + private Msg readLongMessage1() + { + Msg msg = new Msg(200); + for (int i = 0; i < 20; i++) { + msg.put("0123456789".getBytes(ZMQ.CHARSET)); + } + return msg; + } + + @Test + public void testReader() + { + Msg msg = readShortMessage(); + encoder.loadMsg(msg); + + ValueReference ref = new ValueReference<>(); + int outsize = encoder.encode(ref, 0); + ByteBuffer out = ref.get(); + + assertThat(out, notNullValue()); + assertThat(outsize, is(7)); + assertThat(out.position(), is(7)); + } + + @Test + public void testReaderLong() + { + Msg msg = readLongMessage1(); + ValueReference ref = new ValueReference<>(); + int outsize = encoder.encode(ref, 0); + assertThat(outsize, is(0)); + ByteBuffer out = ref.get(); + assertThat(out, nullValue()); + + encoder.loadMsg(msg); + outsize = encoder.encode(ref, 64); + assertThat(outsize, is(64)); + out = ref.get(); + int position = out.position(); + int limit = out.limit(); + assertThat(limit, is(64)); + assertThat(position, is(64)); + + ref.set(null); + outsize = encoder.encode(ref, 64); + assertThat(outsize, is(138)); + out = ref.get(); + position = out.position(); + limit = out.limit(); + assertThat(position, is(62)); + assertThat(limit, is(200)); + } +} diff --git a/src/test/java/zmq/io/mechanism/SecurityCurveTest.java b/src/test/java/zmq/io/mechanism/SecurityCurveTest.java new file mode 100644 index 000000000..cb770ae88 --- /dev/null +++ b/src/test/java/zmq/io/mechanism/SecurityCurveTest.java @@ -0,0 +1,293 @@ +package zmq.io.mechanism; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.io.mechanism.curve.Curve; +import zmq.util.Utils; +import zmq.util.Z85; + +public class SecurityCurveTest +{ + private static class ZapHandler implements Runnable + { + private final SocketBase handler; + private final String clientPublic; + + public ZapHandler(SocketBase handler, String clientPublic) + { + this.handler = handler; + this.clientPublic = clientPublic; + } + + @SuppressWarnings("unused") + @Override + public void run() + { + // Process ZAP requests forever + while (true) { + Msg version = ZMQ.recv(handler, 0); + if (version == null) { + break; // Terminating + } + Msg sequence = ZMQ.recv(handler, 0); + Msg domain = ZMQ.recv(handler, 0); + Msg address = ZMQ.recv(handler, 0); + Msg identity = ZMQ.recv(handler, 0); + Msg mechanism = ZMQ.recv(handler, 0); + Msg clientKey = ZMQ.recv(handler, 0); + + final String clientKeyText = Z85.encode(clientKey.data(), clientKey.size()); + + assertThat(new String(version.data(), ZMQ.CHARSET), is("1.0")); + assertThat(new String(mechanism.data(), ZMQ.CHARSET), is("CURVE")); + assertThat(new String(identity.data(), ZMQ.CHARSET), is("IDENT")); + + int ret = ZMQ.send(handler, version, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, sequence, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(1)); + + System.out.println("Sending ZAP CURVE reply"); + if (clientKeyText.equals(clientPublic)) { + ret = ZMQ.send(handler, "200", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "OK", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(2)); + ret = ZMQ.send(handler, "anonymous", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(9)); + ret = ZMQ.send(handler, "", 0); + assertThat(ret, is(0)); + } + else { + ret = ZMQ.send(handler, "400", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "Invalid client public key", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(25)); + ret = ZMQ.send(handler, "", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(0)); + ret = ZMQ.send(handler, "", 0); + assertThat(ret, is(0)); + } + } + ZMQ.closeZeroLinger(handler); + } + } + + @Test + public void testCurveMechanismSecurity() throws IOException, InterruptedException + { + boolean installed = Curve.installed(); + if (!installed) { + System.out.println("CURVE encryption not installed, skipping test"); + return; + } + + Curve cryptoBox = new Curve(); + // Generate new keypairs for this test + // We'll generate random test keys at startup + String[] clientKeys = cryptoBox.keypairZ85(); + String clientPublic = clientKeys[0]; + String clientSecret = clientKeys[1]; + + String[] serverKeys = cryptoBox.keypairZ85(); + String serverPublic = serverKeys[0]; + String serverSecret = serverKeys[1]; + + int port = Utils.findOpenPort(); + String host = "tcp://127.0.0.1:" + port; + + Ctx ctx = ZMQ.createContext(); + + // Spawn ZAP handler + // We create and bind ZAP socket in main thread to avoid case + // where child thread does not start up fast enough. + SocketBase handler = ZMQ.socket(ctx, ZMQ.ZMQ_REP); + assertThat(handler, notNullValue()); + boolean rc = ZMQ.bind(handler, "inproc://zeromq.zap.01"); + assertThat(rc, is(true)); + + Thread thread = new Thread(new ZapHandler(handler, clientPublic)); + thread.start(); + + // Server socket will accept connections + SocketBase server = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(server, notNullValue()); + + ZMQ.setSocketOption(server, ZMQ.ZMQ_CURVE_SERVER, true); + ZMQ.setSocketOption(server, ZMQ.ZMQ_CURVE_SECRETKEY, serverSecret); + ZMQ.setSocketOption(server, ZMQ.ZMQ_IDENTITY, "IDENT"); + rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + + // Check CURVE security with valid credentials + System.out.println("Test Correct CURVE security"); + SocketBase client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SERVERKEY, serverPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_PUBLICKEY, clientPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SECRETKEY, clientSecret); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.bounce(server, client); + ZMQ.close(client); + + // Check CURVE security with a garbage server key + // This will be caught by the curve_server class, not passed to ZAP + System.out.println("Test bad server key CURVE security"); + String garbageKey = "0000000000000000000000000000000000000000"; + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SERVERKEY, garbageKey); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_PUBLICKEY, clientPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SECRETKEY, clientSecret); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Check CURVE security with a garbage client public key + // This will be caught by the curve_server class, not passed to ZAP + System.out.println("Test bad client public key CURVE security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SERVERKEY, serverPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_PUBLICKEY, garbageKey); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SECRETKEY, clientSecret); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Check CURVE security with a garbage client secret key + // This will be caught by the curve_server class, not passed to ZAP + System.out.println("Test bad client public key CURVE security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SERVERKEY, serverPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_PUBLICKEY, clientPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SECRETKEY, garbageKey); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Check CURVE security with bogus client credentials + // This must be caught by the ZAP handler + String[] bogus = cryptoBox.keypairZ85(); + String bogusPublic = bogus[0]; + String bogusSecret = bogus[1]; + + System.out.println("Test bad client credentials CURVE security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SERVERKEY, serverPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_PUBLICKEY, bogusPublic); + ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SECRETKEY, bogusSecret); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Check CURVE security with NULL client credentials + // This must be caught by the curve_server class, not passed to ZAP + System.out.println("Test NULL client with CURVE security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Check CURVE security with PLAIN client credentials + // This must be caught by the curve_server class, not passed to ZAP + System.out.println("Test PLAIN client with CURVE security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_PLAIN_USERNAME, "user"); + ZMQ.setSocketOption(client, ZMQ.ZMQ_PLAIN_PASSWORD, "pass"); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Unauthenticated messages from a vanilla socket shouldn't be received + System.out.println("Test unauthenticated from vanilla socket CURVE security"); + + Socket sock = new Socket("127.0.0.1", port); + // send anonymous ZMTP/1.0 greeting + OutputStream out = sock.getOutputStream(); + out.write(new StringBuilder().append(0x01).append(0x00).toString().getBytes(ZMQ.CHARSET)); + // send sneaky message that shouldn't be received + out.write( + new StringBuilder().append(0x08).append(0x00).append("sneaky").append(0x00).toString() + .getBytes(ZMQ.CHARSET)); + int timeout = 250; + ZMQ.setSocketOption(server, ZMQ.ZMQ_RCVTIMEO, timeout); + + Msg msg = ZMQ.recv(server, 0); + assertThat(msg, nullValue()); + + sock.close(); + + // Check return codes for invalid buffer sizes + // TODO + System.out.println("Test return codes for invalid buffer sizes with CURVE security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + rc = ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SERVERKEY, new byte[123]); + assertThat(rc, is(false)); + assertThat(client.errno.get(), is(ZError.EINVAL)); + + rc = ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_PUBLICKEY, new byte[123]); + assertThat(rc, is(false)); + assertThat(client.errno.get(), is(ZError.EINVAL)); + + rc = ZMQ.setSocketOption(client, ZMQ.ZMQ_CURVE_SECRETKEY, new byte[123]); + assertThat(rc, is(false)); + assertThat(client.errno.get(), is(ZError.EINVAL)); + + ZMQ.closeZeroLinger(client); + ZMQ.closeZeroLinger(server); + + // Shutdown + ZMQ.term(ctx); + // Wait until ZAP handler terminates + thread.join(); + } +} diff --git a/src/test/java/zmq/io/mechanism/SecurityNullTest.java b/src/test/java/zmq/io/mechanism/SecurityNullTest.java new file mode 100644 index 000000000..bfa304d85 --- /dev/null +++ b/src/test/java/zmq/io/mechanism/SecurityNullTest.java @@ -0,0 +1,193 @@ +package zmq.io.mechanism; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class SecurityNullTest +{ + private static class ZapHandler implements Runnable + { + private final SocketBase handler; + + public ZapHandler(SocketBase handler) + { + this.handler = handler; + } + + @Override + @SuppressWarnings("unused") + public void run() + { + // Process ZAP requests forever + while (true) { + Msg version = ZMQ.recv(handler, 0); + if (version == null) { + break; // Terminating + } + Msg sequence = ZMQ.recv(handler, 0); + Msg domain = ZMQ.recv(handler, 0); + Msg address = ZMQ.recv(handler, 0); + Msg identity = ZMQ.recv(handler, 0); + Msg mechanism = ZMQ.recv(handler, 0); + + assertThat(new String(version.data(), ZMQ.CHARSET), is("1.0")); + assertThat(new String(mechanism.data(), ZMQ.CHARSET), is("NULL")); + + int ret = ZMQ.send(handler, version, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, sequence, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(1)); + + System.out.println("Sending ZAP NULL reply"); + if ("TEST".equals(new String(domain.data(), ZMQ.CHARSET))) { + ret = ZMQ.send(handler, "200", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "OK", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(2)); + ret = ZMQ.send(handler, "anonymous", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(9)); + ret = ZMQ.send(handler, "", 0); + assertThat(ret, is(0)); + } + else { + ret = ZMQ.send(handler, "400", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "BAD DOMAIN", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(10)); + ret = ZMQ.send(handler, "", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(0)); + ret = ZMQ.send(handler, "", 0); + assertThat(ret, is(0)); + } + } + + ZMQ.closeZeroLinger(handler); + } + } + + @Test + public void testNullMechanismSecurity() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://127.0.0.1:" + port; + + Ctx ctx = ZMQ.createContext(); + + // Spawn ZAP handler + // We create and bind ZAP socket in main thread to avoid case + // where child thread does not start up fast enough. + SocketBase handler = ZMQ.socket(ctx, ZMQ.ZMQ_REP); + assertThat(handler, notNullValue()); + boolean rc = ZMQ.bind(handler, "inproc://zeromq.zap.01"); + assertThat(rc, is(true)); + + Thread thread = new Thread(new ZapHandler(handler)); + thread.start(); + + // We bounce between a binding server and a connecting client + + // We first test client/server with no ZAP domain + // Libzmq does not call our ZAP handler, the connect must succeed + System.out.println("Test NO ZAP domain"); + SocketBase server = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(server, notNullValue()); + SocketBase client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.bounce(server, client); + ZMQ.closeZeroLinger(server); + ZMQ.closeZeroLinger(client); + + // Now define a ZAP domain for the server; this enables + // authentication. We're using the wrong domain so this test + // must fail. + System.out.println("Test WRONG ZAP domain"); + server = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(server, notNullValue()); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(server, ZMQ.ZMQ_ZAP_DOMAIN, "WRONG"); + port = Utils.findOpenPort(); + host = "tcp://127.0.0.1:" + port; + rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(server); + ZMQ.closeZeroLinger(client); + + // Now use the right domain, the test must pass + System.out.println("Test RIGHT ZAP domain"); + server = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(server, notNullValue()); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(server, ZMQ.ZMQ_ZAP_DOMAIN, "TEST"); + port = Utils.findOpenPort(); + host = "tcp://127.0.0.1:" + port; + rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.bounce(server, client); + ZMQ.closeZeroLinger(server); + ZMQ.closeZeroLinger(client); + + // Unauthenticated messages from a vanilla socket shouldn't be received + server = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(server, notNullValue()); + ZMQ.setSocketOption(server, ZMQ.ZMQ_ZAP_DOMAIN, "WRONG"); + port = Utils.findOpenPort(); + host = "tcp://127.0.0.1:" + port; + rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + + Socket sock = new Socket("127.0.0.1", port); + // send anonymous ZMTP/1.0 greeting + OutputStream out = sock.getOutputStream(); + out.write(new StringBuilder().append(0x01).append(0x00).toString().getBytes(ZMQ.CHARSET)); + // send sneaky message that shouldn't be received + out.write( + new StringBuilder().append(0x08).append(0x00).append("sneaky").append(0x00).toString() + .getBytes(ZMQ.CHARSET)); + int timeout = 250; + ZMQ.setSocketOption(server, ZMQ.ZMQ_RCVTIMEO, timeout); + + Msg msg = ZMQ.recv(server, 0); + assertThat(msg, nullValue()); + + sock.close(); + ZMQ.closeZeroLinger(server); + + // Shutdown + ZMQ.term(ctx); + + // Wait until ZAP handler terminates + thread.join(); + } +} diff --git a/src/test/java/zmq/io/mechanism/SecurityPlainTest.java b/src/test/java/zmq/io/mechanism/SecurityPlainTest.java new file mode 100644 index 000000000..3bc7d6c28 --- /dev/null +++ b/src/test/java/zmq/io/mechanism/SecurityPlainTest.java @@ -0,0 +1,183 @@ +package zmq.io.mechanism; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class SecurityPlainTest +{ + private static class ZapHandler implements Runnable + { + private final SocketBase handler; + + public ZapHandler(SocketBase handler) + { + this.handler = handler; + } + + @SuppressWarnings("unused") + @Override + public void run() + { + // Process ZAP requests forever + while (true) { + Msg version = ZMQ.recv(handler, 0); + if (version == null) { + break; // Terminating + } + Msg sequence = ZMQ.recv(handler, 0); + Msg domain = ZMQ.recv(handler, 0); + Msg address = ZMQ.recv(handler, 0); + Msg identity = ZMQ.recv(handler, 0); + Msg mechanism = ZMQ.recv(handler, 0); + Msg username = ZMQ.recv(handler, 0); + Msg password = ZMQ.recv(handler, 0); + + assertThat(new String(version.data(), ZMQ.CHARSET), is("1.0")); + assertThat(new String(mechanism.data(), ZMQ.CHARSET), is("PLAIN")); + assertThat(new String(identity.data(), ZMQ.CHARSET), is("IDENT")); + + int ret = ZMQ.send(handler, version, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, sequence, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(1)); + + System.out.println("Sending ZAP PLAIN reply"); + if ("admin".equals(new String(username.data(), ZMQ.CHARSET)) + && "password".equals(new String(password.data(), ZMQ.CHARSET))) { + ret = ZMQ.send(handler, "200", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "OK", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(2)); + ret = ZMQ.send(handler, "anonymous", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(9)); + ret = ZMQ.send(handler, "", 0); + assertThat(ret, is(0)); + } + else { + ret = ZMQ.send(handler, "400", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(3)); + ret = ZMQ.send(handler, "Invalid username or password", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(28)); + ret = ZMQ.send(handler, "", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(0)); + ret = ZMQ.send(handler, "", 0); + assertThat(ret, is(0)); + } + } + ZMQ.closeZeroLinger(handler); + } + } + + @Test + public void testPlainMechanismSecurity() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://127.0.0.1:" + port; + + Ctx ctx = ZMQ.createContext(); + + // Spawn ZAP handler + // We create and bind ZAP socket in main thread to avoid case + // where child thread does not start up fast enough. + SocketBase handler = ZMQ.socket(ctx, ZMQ.ZMQ_REP); + assertThat(handler, notNullValue()); + boolean rc = ZMQ.bind(handler, "inproc://zeromq.zap.01"); + assertThat(rc, is(true)); + + Thread thread = new Thread(new ZapHandler(handler)); + thread.start(); + + // Server socket will accept connections + SocketBase server = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(server, notNullValue()); + + ZMQ.setSocketOption(server, ZMQ.ZMQ_IDENTITY, "IDENT"); + ZMQ.setSocketOption(server, ZMQ.ZMQ_PLAIN_SERVER, true); + rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + + String username = "admin"; + String password = "password"; + // Check PLAIN security with correct username/password + System.out.println("Test Correct PLAIN security"); + SocketBase client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_PLAIN_USERNAME, username); + ZMQ.setSocketOption(client, ZMQ.ZMQ_PLAIN_PASSWORD, password); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.bounce(server, client); + ZMQ.close(client); + + // Check PLAIN security with badly configured client (as_server) + // This will be caught by the plain_server class, not passed to ZAP + System.out.println("Test badly configured PLAIN security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_PLAIN_SERVER, true); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Check PLAIN security -- failed authentication + System.out.println("Test wrong authentication PLAIN security"); + client = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(client, notNullValue()); + + ZMQ.setSocketOption(client, ZMQ.ZMQ_PLAIN_USERNAME, "wronguser"); + ZMQ.setSocketOption(client, ZMQ.ZMQ_PLAIN_PASSWORD, "wrongpass"); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + Helper.expectBounceFail(server, client); + ZMQ.closeZeroLinger(client); + + // Unauthenticated messages from a vanilla socket shouldn't be received + System.out.println("Test unauthenticated from vanilla socket PLAIN security"); + + Socket sock = new Socket("127.0.0.1", port); + // send anonymous ZMTP/1.0 greeting + OutputStream out = sock.getOutputStream(); + out.write(new StringBuilder().append(0x01).append(0x00).toString().getBytes(ZMQ.CHARSET)); + // send sneaky message that shouldn't be received + out.write( + new StringBuilder().append(0x08).append(0x00).append("sneaky").append(0x00).toString() + .getBytes(ZMQ.CHARSET)); + int timeout = 250; + ZMQ.setSocketOption(server, ZMQ.ZMQ_RCVTIMEO, timeout); + + Msg msg = ZMQ.recv(server, 0); + assertThat(msg, nullValue()); + + sock.close(); + ZMQ.closeZeroLinger(server); + + // Shutdown + ZMQ.term(ctx); + // Wait until ZAP handler terminates + thread.join(); + } +} diff --git a/src/test/java/zmq/io/net/BindSrcAddressTest.java b/src/test/java/zmq/io/net/BindSrcAddressTest.java new file mode 100644 index 000000000..ea0300d8e --- /dev/null +++ b/src/test/java/zmq/io/net/BindSrcAddressTest.java @@ -0,0 +1,39 @@ +package zmq.io.net; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class BindSrcAddressTest +{ + @Test + public void test() throws IOException + { + Ctx ctx = ZMQ.createContext(); + assert (ctx != null); + + SocketBase socket = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); + assert (socket != null); + + int port1 = Utils.findOpenPort(); + int port2 = Utils.findOpenPort(); + int port3 = Utils.findOpenPort(); + boolean rc = ZMQ.connect(socket, "tcp://127.0.0.1:0;localhost:" + port1); + assert (rc); + + rc = ZMQ.connect(socket, "tcp://localhost:" + port3 + ";localhost:" + port2); + assert (rc); + + // rc = ZMQ.connect(socket, "tcp://lo:" + port3 + ";localhost:" + port2); + // assert (rc); + + ZMQ.close(socket); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/TcpAddressTest.java b/src/test/java/zmq/io/net/TcpAddressTest.java similarity index 84% rename from src/test/java/zmq/TcpAddressTest.java rename to src/test/java/zmq/io/net/TcpAddressTest.java index ab7e207cf..346f1ceef 100644 --- a/src/test/java/zmq/TcpAddressTest.java +++ b/src/test/java/zmq/io/net/TcpAddressTest.java @@ -1,11 +1,14 @@ -package zmq; +package zmq.io.net; -import org.junit.Test; +import static org.junit.Assert.assertEquals; import java.io.IOException; import java.net.InetSocketAddress; -import static org.junit.Assert.assertEquals; +import org.junit.Test; + +import zmq.io.net.tcp.TcpAddress; +import zmq.util.Utils; public class TcpAddressTest { @@ -14,7 +17,7 @@ public void parsesIpv6Address() throws IOException { String addressString = "2000::a1"; int port = Utils.findOpenPort(); - TcpAddress address = new TcpAddress("[" + addressString + "]:" + port); + TcpAddress address = new TcpAddress("[" + addressString + "]:" + port, true); InetSocketAddress expected = new InetSocketAddress(addressString, port); assertEquals(expected, address.address()); diff --git a/src/test/java/zmq/TestAddress.java b/src/test/java/zmq/io/net/TestAddress.java similarity index 72% rename from src/test/java/zmq/TestAddress.java rename to src/test/java/zmq/io/net/TestAddress.java index e2cc9c40e..8b5f3892e 100644 --- a/src/test/java/zmq/TestAddress.java +++ b/src/test/java/zmq/io/net/TestAddress.java @@ -1,16 +1,17 @@ -package zmq; +package zmq.io.net; -import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.hamcrest.CoreMatchers.is; + +import org.junit.Test; public class TestAddress { @Test public void testToNotResolvedToString() { - Address addr = new Address("tcp", "google.com:90", false); + Address addr = new Address("tcp", "google.com:90"); String saddr = addr.toString(); assertThat(saddr, is("tcp://google.com:90")); } @@ -18,8 +19,8 @@ public void testToNotResolvedToString() @Test public void testResolvedToString() { - Address addr = new Address("tcp", "google.com:90", false); - addr.resolve(); + Address addr = new Address("tcp", "google.com:90"); + addr.resolve(false); String resolved = addr.toString(); assertTrue(resolved.matches("tcp://\\d+\\.\\d+\\.\\d+\\.\\d+:90")); } @@ -27,6 +28,6 @@ public void testResolvedToString() @Test(expected = IllegalArgumentException.class) public void testInvaid() { - new Address("tcp", "ggglocalhostxxx:90", false).resolve(); + new Address("tcp", "ggglocalhostxxx:90").resolve(false); } } diff --git a/src/test/java/zmq/pipe/ConflateTest.java b/src/test/java/zmq/pipe/ConflateTest.java new file mode 100644 index 000000000..d3b400086 --- /dev/null +++ b/src/test/java/zmq/pipe/ConflateTest.java @@ -0,0 +1,55 @@ +package zmq.pipe; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class ConflateTest +{ + @Test + public void test() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.init(1); + assert (ctx != null); + + SocketBase in = ZMQ.socket(ctx, ZMQ.ZMQ_PULL); + assert (in != null); + + int port = Utils.findOpenPort(); + String host = "tcp://localhost:" + port; + int conflate = 1; + + ZMQ.setSocketOption(in, ZMQ.ZMQ_CONFLATE, conflate); + + boolean rc = ZMQ.bind(in, host); + assert (rc); + + SocketBase out = ZMQ.socket(ctx, ZMQ.ZMQ_PUSH); + assert (out != null); + + rc = ZMQ.connect(out, host); + assert (rc); + + int messageCount = 20; + for (int j = 0; j < messageCount; ++j) { + int count = Helper.send(out, Integer.toString(j)); + assert (count > 0); + } + Thread.sleep(200); + + String recvd = Helper.recv(in); + Assert.assertEquals("19", recvd); + + ZMQ.close(in); + ZMQ.close(out); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/TestYQueue.java b/src/test/java/zmq/pipe/YQueueTest.java similarity index 92% rename from src/test/java/zmq/TestYQueue.java rename to src/test/java/zmq/pipe/YQueueTest.java index 3b5198a71..7c9ea3d3b 100644 --- a/src/test/java/zmq/TestYQueue.java +++ b/src/test/java/zmq/pipe/YQueueTest.java @@ -1,10 +1,14 @@ -package zmq; +package zmq.pipe; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Msg; +import zmq.ZMQ; -public class TestYQueue +public class YQueueTest { @Test public void testReuse() diff --git a/src/test/java/zmq/proxy/ProxySingleSocketTest.java b/src/test/java/zmq/proxy/ProxySingleSocketTest.java new file mode 100644 index 000000000..6cb28b411 --- /dev/null +++ b/src/test/java/zmq/proxy/ProxySingleSocketTest.java @@ -0,0 +1,113 @@ +package zmq.proxy; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class ProxySingleSocketTest +{ + private static class ServerTask implements Runnable + { + private final Ctx ctx; + private final String host; + + public ServerTask(Ctx ctx, String host) + { + this.ctx = ctx; + this.host = host; + } + + @Override + public void run() + { + SocketBase rep = ZMQ.socket(ctx, ZMQ.ZMQ_REP); + assertThat(rep, notNullValue()); + + boolean rc = ZMQ.bind(rep, host); + assertThat(rc, is(true)); + + // Control socket receives terminate command from main over inproc + SocketBase control = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); + ZMQ.setSocketOption(control, ZMQ.ZMQ_SUBSCRIBE, ""); + + rc = ZMQ.connect(control, "inproc://control"); + assertThat(rc, is(true)); + + // Use rep as both frontend and backend + ZMQ.proxy(rep, rep, null, control); + + ZMQ.close(rep); + ZMQ.close(control); + } + + } + + @Test + public void testProxySingleSocket() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://127.0.0.1:" + port; + + // The main thread simply starts several clients and a server, and then + // waits for the server to finish. + Ctx ctx = ZMQ.createContext(); + + SocketBase req = ZMQ.socket(ctx, ZMQ.ZMQ_REQ); + assertThat(req, notNullValue()); + + boolean rc = ZMQ.connect(req, host); + assertThat(rc, is(true)); + + // Control socket receives terminate command from main over inproc + SocketBase control = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); + + rc = ZMQ.bind(control, "inproc://control"); + assertThat(rc, is(true)); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(new ServerTask(ctx, host)); + + int ret = ZMQ.send(req, "msg1", 0); + assertThat(ret, is(4)); + + System.out.print("."); + Msg msg = ZMQ.recv(req, 0); + System.out.print("."); + assertThat(msg, notNullValue()); + assertThat(new String(msg.data(), ZMQ.CHARSET), is("msg1")); + + ret = ZMQ.send(req, "msg22", 0); + assertThat(ret, is(5)); + + System.out.print("."); + msg = ZMQ.recv(req, 0); + System.out.print("."); + assertThat(msg, notNullValue()); + assertThat(new String(msg.data(), ZMQ.CHARSET), is("msg22")); + + ret = ZMQ.send(control, "TERMINATE", 0); + assertThat(ret, is(9)); + + System.out.println("."); + ZMQ.close(control); + ZMQ.close(req); + + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/proxy/ProxyTcpTest.java b/src/test/java/zmq/proxy/ProxyTcpTest.java new file mode 100644 index 000000000..63831d05d --- /dev/null +++ b/src/test/java/zmq/proxy/ProxyTcpTest.java @@ -0,0 +1,302 @@ +package zmq.proxy; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; + +import org.junit.Ignore; +import org.junit.Test; + +import zmq.Config; +import zmq.Ctx; +import zmq.Helper; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.io.coder.Decoder; +import zmq.io.coder.EncoderBase; +import zmq.util.Errno; +import zmq.util.Utils; + +@Ignore +public class ProxyTcpTest +{ + static class Client extends Thread + { + private int port; + + public Client(int port) + { + this.port = port; + } + + @Override + public void run() + { + System.out.println("Start client thread"); + try { + Socket s = new Socket("127.0.0.1", port); + Helper.send(s, "helo"); + Helper.send(s, "1234567890abcdefghizklmnopqrstuvwxyz"); + Helper.send(s, "end"); + Helper.send(s, "end"); + s.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + System.out.println("Stop client thread"); + } + } + + static class Dealer extends Thread + { + private final SocketBase s; + private final String name; + private final int port; + + public Dealer(Ctx ctx, String name, int port) + { + this.s = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + this.name = name; + this.port = port; + } + + @Override + public void run() + { + System.out.println("Start dealer " + name); + + ZMQ.connect(s, "tcp://127.0.0.1:" + port); + int i = 0; + while (true) { + Msg msg = s.recv(0); + if (msg == null) { + throw new RuntimeException("hello"); + } + System.out.println("DEALER " + name + " received " + msg); + String data = new String(msg.data(), 0, msg.size(), ZMQ.CHARSET); + + Msg response = null; + if ((i % 3) == 2) { + response = new Msg(msg.size() + 3); + response.put("OK ".getBytes(ZMQ.CHARSET)).put(msg.data()); + } + else { + response = new Msg(msg.data()); + } + + s.send(response, (i % 3) == 2 ? 0 : ZMQ.ZMQ_SNDMORE); + i++; + if (data.equals("end")) { + break; + } + } + s.close(); + System.out.println("Stop dealer " + name); + } + } + + public static class ProxyDecoder extends Decoder + { + private final Step readHeader = new Step() + { + @Override + public Step.Result apply() + { + return readHeader(); + } + }; + private final Step readBody = new Step() + { + @Override + public Step.Result apply() + { + return readBody(); + } + }; + + byte[] header = new byte[4]; + Msg msg; + int size = -1; + boolean identitySent = false; + Msg bottom; + + public ProxyDecoder(int bufsize, long maxmsgsize) + { + super(new Errno(), bufsize, maxmsgsize, Config.MSG_ALLOCATION_HEAP_THRESHOLD.getValue()); + nextStep(header, 4, readHeader); + + bottom = new Msg(); + bottom.setFlags(Msg.MORE); + } + + private Step.Result readHeader() + { + size = Integer.parseInt(new String(header, ZMQ.CHARSET)); + System.out.println("Received " + size); + msg = new Msg(size); + nextStep(msg, readBody); + + return Step.Result.MORE_DATA; + } + + private Step.Result readBody() + { + System.out.println("Received body " + new String(msg.data(), ZMQ.CHARSET)); + + if (!identitySent) { + Msg identity = new Msg(); + // push(identity); + identitySent = true; + } + + // push(bottom); + // push(msg); + + nextStep(header, 4, readHeader); + return Step.Result.DECODED; + } + + @Override + protected long binarySize(Msg msg) + { + return msg.size(); + } + } + + public static class ProxyEncoder extends EncoderBase + { + private final Runnable writeHeader = new Runnable() + { + @Override + public void run() + { + writeHeader(); + } + }; + private final Runnable writeBody = new Runnable() + { + @Override + public void run() + { + writeBody(); + } + }; + + ByteBuffer header = ByteBuffer.allocate(4); + Msg msg; + int size = -1; + boolean messageReady; + boolean identityReceived; + + public ProxyEncoder(int bufsize, long unused) + { + super(new Errno(), bufsize); + nextStep((Msg) null, writeHeader, true); + messageReady = false; + identityReceived = false; + } + + private void writeBody() + { + System.out.println("write body " + msg); + nextStep(msg, writeHeader, !msg.hasMore()); + + } + + private void writeHeader() + { + msg = inProgress; + if (msg == null) { + return; + } + if (!identityReceived) { + identityReceived = true; + nextStep(header, msg.size() < 255 ? 2 : 10, writeBody, true); + return; + } + else if (!messageReady) { + messageReady = true; + msg = inProgress; + if (msg == null) { + return; + } + } + messageReady = false; + System.out.println("write header " + msg.size()); + + header.clear(); + header.put(String.format("%04d", msg.size()).getBytes(ZMQ.CHARSET)); + header.flip(); + nextStep(header, header.limit(), writeBody, false); + return; + } + } + + static class Proxy extends Thread + { + private Ctx ctx; + private int routerPort; + private int dealerPort; + + Proxy(Ctx ctx, int routerPort, int dealerPort) + { + this.ctx = ctx; + this.routerPort = routerPort; + this.dealerPort = dealerPort; + } + + @Override + public void run() + { + boolean rc; + SocketBase routerBind = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); + assertThat(routerBind, notNullValue()); + + routerBind.setSocketOpt(ZMQ.ZMQ_DECODER, ProxyDecoder.class); + routerBind.setSocketOpt(ZMQ.ZMQ_ENCODER, ProxyEncoder.class); + + rc = ZMQ.bind(routerBind, "tcp://127.0.0.1:" + routerPort); + assertThat(rc, is(true)); + + SocketBase dealerBind = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(dealerBind, notNullValue()); + rc = ZMQ.bind(dealerBind, "tcp://127.0.0.1:" + dealerPort); + assertThat(rc, is(true)); + + ZMQ.proxy(routerBind, dealerBind, null); + + ZMQ.close(routerBind); + ZMQ.close(dealerBind); + } + } + + @Test + public void testProxyTcp() throws Exception + { + int routerPort = Utils.findOpenPort(); + int dealerPort = Utils.findOpenPort(); + + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + + Proxy mt = new Proxy(ctx, routerPort, dealerPort); + mt.start(); + + new Dealer(ctx, "A", dealerPort).start(); + // new Dealer(ctx, "B", dealerPort).start(); + + ZMQ.sleep(1); + Thread client = new Client(routerPort); + client.start(); + + client.join(); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/proxy/ProxyTerminateTest.java b/src/test/java/zmq/proxy/ProxyTerminateTest.java new file mode 100644 index 000000000..5b0c9f327 --- /dev/null +++ b/src/test/java/zmq/proxy/ProxyTerminateTest.java @@ -0,0 +1,121 @@ +package zmq.proxy; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class ProxyTerminateTest +{ + private static class ServerTask implements Runnable + { + private final Ctx ctx; + private final String hostFrontend; + private String hostBackend; + + public ServerTask(Ctx ctx, String hostFrontend, String hostBackend) + { + this.ctx = ctx; + this.hostFrontend = hostFrontend; + this.hostBackend = hostBackend; + } + + @Override + public void run() + { + SocketBase frontend = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); + assertThat(frontend, notNullValue()); + ZMQ.setSocketOption(frontend, ZMQ.ZMQ_SUBSCRIBE, ""); + + boolean rc = ZMQ.bind(frontend, hostFrontend); + assertThat(rc, is(true)); + + // Nice socket which is never read + SocketBase backend = ZMQ.socket(ctx, ZMQ.ZMQ_PUSH); + assertThat(backend, notNullValue()); + + rc = ZMQ.bind(frontend, hostBackend); + assertThat(rc, is(true)); + + // Control socket receives terminate command from main over inproc + SocketBase control = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); + ZMQ.setSocketOption(control, ZMQ.ZMQ_SUBSCRIBE, ""); + + rc = ZMQ.connect(control, "inproc://control"); + assertThat(rc, is(true)); + + // Connect backend to frontend via a proxy + ZMQ.proxy(frontend, backend, null, control); + + ZMQ.close(frontend); + ZMQ.close(backend); + ZMQ.close(control); + } + + } + + @Test + public void testProxyTerminate() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String frontend = "tcp://127.0.0.1:" + port; + port = Utils.findOpenPort(); + String backend = "tcp://127.0.0.1:" + port; + + // The main thread simply starts a basic steerable proxy server, publishes some messages, and then + // waits for the server to terminate. + Ctx ctx = ZMQ.createContext(); + + // Control socket receives terminate command from main over inproc + SocketBase control = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); + + boolean rc = ZMQ.bind(control, "inproc://control"); + assertThat(rc, is(true)); + + Thread thread = new Thread(new ServerTask(ctx, frontend, backend)); + thread.start(); + + Thread.sleep(500); + + // Start a secondary publisher which writes data to the SUB-PUSH server socket + SocketBase publisher = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); + assertThat(publisher, notNullValue()); + + rc = ZMQ.connect(publisher, frontend); + assertThat(rc, is(true)); + + Thread.sleep(50); + + int ret = ZMQ.send(publisher, "This is a test", 0); + assertThat(ret, is(14)); + + Thread.sleep(50); + + ret = ZMQ.send(publisher, "This is a test", 0); + assertThat(ret, is(14)); + + Thread.sleep(50); + + ret = ZMQ.send(publisher, "This is a test", 0); + assertThat(ret, is(14)); + + ret = ZMQ.send(control, "TERMINATE", 0); + assertThat(ret, is(9)); + + ZMQ.close(publisher); + ZMQ.close(control); + + thread.join(); + + ZMQ.term(ctx); + + } +} diff --git a/src/test/java/zmq/socket/AbstractSpecTest.java b/src/test/java/zmq/socket/AbstractSpecTest.java new file mode 100644 index 000000000..1cfe8b862 --- /dev/null +++ b/src/test/java/zmq/socket/AbstractSpecTest.java @@ -0,0 +1,59 @@ +package zmq.socket; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; + +public class AbstractSpecTest +{ + public boolean sendSeq(SocketBase socket, String... data) + { + int rc = 0; + if (data.length == 0) { + return false; + } + for (int idx = 0; idx < data.length; ++idx) { + String payload = data[idx]; + + if (payload == null) { + rc = ZMQ.send(socket, new Msg(), idx == data.length - 1 ? 0 : ZMQ.ZMQ_SNDMORE); + assertThat(rc < 0, is(false)); + } + else { + Msg msg = new Msg(payload.getBytes(ZMQ.CHARSET)); + rc = ZMQ.send(socket, msg, idx == data.length - 1 ? 0 : ZMQ.ZMQ_SNDMORE); + assertThat(rc < 0, is(false)); + } + } + return rc >= 0; + } + + public void recvSeq(SocketBase socket, String... data) + { + for (int idx = 0; idx < data.length; ++idx) { + Msg msg = ZMQ.recv(socket, 0); + String payload = data[idx]; + + if (payload == null) { + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(0)); + } + else { + assertThat(msg, notNullValue()); + assertThat(msg.data(), is(payload.getBytes(ZMQ.CHARSET))); + } + + int rc = ZMQ.getSocketOption(socket, ZMQ.ZMQ_RCVMORE); + if (idx == data.length - 1) { + assertThat(rc, is(0)); + } + else { + assertThat(rc, is(1)); + } + } + } +} diff --git a/src/test/java/zmq/TestPairInproc.java b/src/test/java/zmq/socket/pair/TestPairInproc.java similarity index 82% rename from src/test/java/zmq/TestPairInproc.java rename to src/test/java/zmq/socket/pair/TestPairInproc.java index 8c83096b9..5181fc65d 100644 --- a/src/test/java/zmq/TestPairInproc.java +++ b/src/test/java/zmq/socket/pair/TestPairInproc.java @@ -1,9 +1,15 @@ -package zmq; +package zmq.socket.pair; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.SocketBase; +import zmq.ZMQ; public class TestPairInproc { @@ -17,12 +23,12 @@ public void testPairInproc() SocketBase sb = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); assertThat(sb, notNullValue()); boolean brc = ZMQ.bind(sb, "inproc://a"); - assertThat(brc , is(true)); + assertThat(brc, is(true)); SocketBase sc = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); assertThat(sc, notNullValue()); brc = ZMQ.connect(sc, "inproc://a"); - assertThat(brc , is(true)); + assertThat(brc, is(true)); Helper.bounce(sb, sc); diff --git a/src/test/java/zmq/socket/pair/TestPairIpc.java b/src/test/java/zmq/socket/pair/TestPairIpc.java new file mode 100644 index 000000000..9ffacca33 --- /dev/null +++ b/src/test/java/zmq/socket/pair/TestPairIpc.java @@ -0,0 +1,46 @@ +package zmq.socket.pair; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.UUID; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.SocketBase; +import zmq.ZMQ; + +public class TestPairIpc +{ + // Create REQ/ROUTER wiring. + + @Test + public void testPairIpc() + { + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + SocketBase pairBind = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); + assertThat(pairBind, notNullValue()); + + UUID random = UUID.randomUUID(); + + boolean brc = ZMQ.bind(pairBind, "ipc:///tmp/tester/" + random.toString()); + assertThat(brc, is(true)); + + SocketBase pairConnect = ZMQ.socket(ctx, ZMQ.ZMQ_PAIR); + assertThat(pairConnect, notNullValue()); + + brc = ZMQ.connect(pairConnect, "ipc:///tmp/tester/" + random.toString()); + assertThat(brc, is(true)); + + Helper.bounce(pairBind, pairConnect); + + // Tear down the wiring. + ZMQ.close(pairBind); + ZMQ.close(pairConnect); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/TestPairTcp.java b/src/test/java/zmq/socket/pair/TestPairTcp.java similarity index 88% rename from src/test/java/zmq/TestPairTcp.java rename to src/test/java/zmq/socket/pair/TestPairTcp.java index 5eb15356e..59aba750d 100644 --- a/src/test/java/zmq/TestPairTcp.java +++ b/src/test/java/zmq/socket/pair/TestPairTcp.java @@ -1,16 +1,21 @@ -package zmq; +package zmq.socket.pair; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; import java.io.IOException; import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; + +import zmq.Ctx; +import zmq.Helper; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; public class TestPairTcp { - // Create REQ/ROUTER wiring. - @Test public void testPairTcp() throws IOException { @@ -33,6 +38,5 @@ public void testPairTcp() throws IOException ZMQ.close(sb); ZMQ.close(sc); ZMQ.term(ctx); - } } diff --git a/src/test/java/zmq/socket/pipeline/PushPullSpecTest.java b/src/test/java/zmq/socket/pipeline/PushPullSpecTest.java new file mode 100644 index 000000000..9a1edf78b --- /dev/null +++ b/src/test/java/zmq/socket/pipeline/PushPullSpecTest.java @@ -0,0 +1,240 @@ +package zmq.socket.pipeline; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Ignore; +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.socket.AbstractSpecTest; +import zmq.util.Utils; + +public class PushPullSpecTest extends AbstractSpecTest +{ + @Test + public void testSpecPullFairQueueIn() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // PULL: SHALL receive incoming messages from its peers using a fair-queuing + // strategy. + fairQueueIn(ctx, bindAddress, ZMQ.ZMQ_PULL, ZMQ.ZMQ_PUSH); + } + + ZMQ.term(ctx); + } + + @Test + public void testSpecPushRoundRobinOut() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // PUSH: SHALL route outgoing messages to connected peers using a + // round-robin strategy. + roundRobinOut(ctx, bindAddress, ZMQ.ZMQ_PUSH, ZMQ.ZMQ_PULL); + } + + ZMQ.term(ctx); + } + + @Test + public void testSpecPushBlockOnSendNoPeers() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // PUSH: SHALL block on sending, or return a suitable error, when it has no + // available peers. + blockOnSendNoPeers(ctx, bindAddress, ZMQ.ZMQ_PUSH); + } + + ZMQ.term(ctx); + } + + @Test + @Ignore + public void testSpecDestroyQueueOnDisconnect() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // PUSH and PULL: SHALL create this queue when a peer connects to it. If + // this peer disconnects, the socket SHALL destroy its queue and SHALL + // discard any messages it contains. + // *** Test disabled until libzmq does this properly *** + } + + ZMQ.term(ctx); + } + + private void blockOnSendNoPeers(Ctx ctx, String address, int bindType) throws IOException, InterruptedException + { + SocketBase push = ZMQ.socket(ctx, bindType); + + int timeout = 250; + boolean rc = ZMQ.setSocketOption(push, ZMQ.ZMQ_SNDTIMEO, timeout); + assertThat(rc, is(true)); + + int ret = ZMQ.send(push, "", ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(-1)); + assertThat(push.errno(), is(ZError.EAGAIN)); + + ret = ZMQ.send(push, "", 0); + assertThat(ret, is(-1)); + assertThat(push.errno(), is(ZError.EAGAIN)); + + rc = ZMQ.bind(push, address); + assertThat(rc, is(true)); + + ret = ZMQ.send(push, "", ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(-1)); + assertThat(push.errno(), is(ZError.EAGAIN)); + + ret = ZMQ.send(push, "", 0); + assertThat(ret, is(-1)); + assertThat(push.errno(), is(ZError.EAGAIN)); + + ZMQ.close(push); + } + + private void roundRobinOut(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + SocketBase push = ZMQ.socket(ctx, bindType); + boolean rc = ZMQ.bind(push, address); + assertThat(rc, is(true)); + + int timeout = 250; + int services = 5; + List senders = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase reps = ZMQ.socket(ctx, connectType); + assertThat(reps, notNullValue()); + + senders.add(reps); + + rc = ZMQ.setSocketOption(reps, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.connect(reps, address); + assertThat(rc, is(true)); + } + + // Wait for connections. + ZMQ.msleep(100); + + // Send 2N messages + for (int peer = 0; peer < services; ++peer) { + rc = sendSeq(push, "ABC"); + assertThat(rc, is(true)); + } + for (int peer = 0; peer < services; ++peer) { + rc = sendSeq(push, "DEF"); + assertThat(rc, is(true)); + } + + // Expect every PULL got one of each + for (int peer = 0; peer < services; ++peer) { + recvSeq(senders.get(peer), "ABC"); + recvSeq(senders.get(peer), "DEF"); + } + + ZMQ.closeZeroLinger(push); + for (SocketBase sender : senders) { + ZMQ.closeZeroLinger(sender); + } + + // Wait for disconnects. + ZMQ.msleep(100); + } + + private void fairQueueIn(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + // Server socket will accept connections + SocketBase pull = ZMQ.socket(ctx, bindType); + assertThat(pull, notNullValue()); + + boolean rc = ZMQ.bind(pull, address); + assertThat(rc, is(true)); + + int services = 5; + List senders = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase sender = ZMQ.socket(ctx, connectType); + assertThat(sender, notNullValue()); + + senders.add(sender); + + rc = ZMQ.connect(sender, address); + assertThat(rc, is(true)); + } + + // Wait for connections. + ZMQ.msleep(100); + + Set firstHalf = new HashSet<>(); + Set secondHalf = new HashSet<>(); + + // Send 2N messages + for (int peer = 0; peer < services; ++peer) { + sendSeq(senders.get(peer), "A" + peer); + firstHalf.add("A" + peer); + + sendSeq(senders.get(peer), "B" + peer); + secondHalf.add("B" + peer); + } + + // Wait for data. + ZMQ.msleep(100); + + // Expect to pull one from each first + for (int peer = 0; peer < services; ++peer) { + Msg msg = ZMQ.recv(pull, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(2)); + firstHalf.remove(new String(msg.data(), ZMQ.CHARSET)); + } + assertThat(firstHalf.size(), is(0)); + + // And then get the second batch + for (int peer = 0; peer < services; ++peer) { + Msg msg = ZMQ.recv(pull, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(2)); + secondHalf.remove(new String(msg.data(), ZMQ.CHARSET)); + } + assertThat(secondHalf.size(), is(0)); + + ZMQ.closeZeroLinger(pull); + for (SocketBase sender : senders) { + ZMQ.closeZeroLinger(sender); + + } + // Wait for disconnects. + ZMQ.msleep(100); + } +} diff --git a/src/test/java/zmq/socket/pipeline/TestPushPullTcp.java b/src/test/java/zmq/socket/pipeline/TestPushPullTcp.java new file mode 100644 index 000000000..a7a100a5a --- /dev/null +++ b/src/test/java/zmq/socket/pipeline/TestPushPullTcp.java @@ -0,0 +1,60 @@ +package zmq.socket.pipeline; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class TestPushPullTcp +{ + @Test + public void testPushPullTcp() throws IOException + { + int port = Utils.findOpenPort(); + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + SocketBase push = ZMQ.socket(ctx, ZMQ.ZMQ_PUSH); + assertThat(push, notNullValue()); + boolean brc = ZMQ.bind(push, "tcp://127.0.0.1:" + port); + assertThat(brc, is(true)); + + SocketBase pull = ZMQ.socket(ctx, ZMQ.ZMQ_PULL); + assertThat(pull, notNullValue()); + brc = ZMQ.connect(pull, "tcp://127.0.0.1:" + port); + assertThat(brc, is(true)); + + byte[] content = "12345678ABCDEFGH12345678abcdefgh".getBytes(ZMQ.CHARSET); + + // Send the message. + int rc = ZMQ.send(push, content, 32, ZMQ.ZMQ_SNDMORE); + assert (rc == 32); + rc = ZMQ.send(push, content, 32, 0); + assertThat(rc, is(32)); + + // Bounce the message back. + Msg msg; + msg = ZMQ.recv(pull, 0); + assert (msg.size() == 32); + int rcvmore = ZMQ.getSocketOption(pull, ZMQ.ZMQ_RCVMORE); + assertThat(rcvmore, is(1)); + + msg = ZMQ.recv(pull, 0); + assert (rc == 32); + rcvmore = ZMQ.getSocketOption(pull, ZMQ.ZMQ_RCVMORE); + assertThat(rcvmore, is(0)); + + // Tear down the wiring. + ZMQ.close(push); + ZMQ.close(pull); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/socket/pubsub/PubSubHwmTest.java b/src/test/java/zmq/socket/pubsub/PubSubHwmTest.java new file mode 100644 index 000000000..acccd663b --- /dev/null +++ b/src/test/java/zmq/socket/pubsub/PubSubHwmTest.java @@ -0,0 +1,210 @@ +package zmq.socket.pubsub; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.util.Utils; + +public class PubSubHwmTest +{ + @Test + public void testDefaults() + { + // send 1000 msg on hwm 1000, receive 1000 + int count = testDefaults(1000, 1000); + assertThat(count, is(1000)); + } + + @Test + public void testBlocking() + { + // send 6000 msg on hwm 2000, drops above hwm, only receive hwm + int count = testBlocking(2000, 6000); + assertThat(count, is(6000)); + } + + private int testDefaults(int sendHwm, int msgCnt) + { + Ctx ctx = ZMQ.createContext(); + + // Set up bind socket + SocketBase pub = ctx.createSocket(ZMQ.ZMQ_PUB); + boolean rc = ZMQ.bind(pub, "inproc://a"); + assertThat(rc, is(true)); + + // Set up connect socket + SocketBase sub = ctx.createSocket(ZMQ.ZMQ_SUB); + rc = ZMQ.connect(sub, "inproc://a"); + assertThat(rc, is(true)); + + //set a hwm on publisher + rc = ZMQ.setSocketOption(pub, ZMQ.ZMQ_SNDHWM, sendHwm); + assertThat(rc, is(true)); + + rc = ZMQ.setSocketOption(sub, ZMQ.ZMQ_SUBSCRIBE, new byte[0]); + assertThat(rc, is(true)); + + // Send until we block + int sendCount = 0; + while (sendCount < msgCnt && ZMQ.send(pub, "", ZMQ.ZMQ_DONTWAIT) == 0) { + ++sendCount; + } + + // Now receive all sent messages + int recvCount = 0; + while (null != ZMQ.recv(sub, ZMQ.ZMQ_DONTWAIT)) { + ++recvCount; + } + assertThat(sendCount, is(recvCount)); + + // Clean up + ZMQ.close(sub); + ZMQ.close(pub); + ZMQ.term(ctx); + + return recvCount; + } + + private int receive(SocketBase socket) + { + int recvCount = 0; + // Now receive all sent messages + while (null != ZMQ.recv(socket, ZMQ.ZMQ_DONTWAIT)) { + ++recvCount; + } + + return recvCount; + } + + private int testBlocking(int sendHwm, int msgCnt) + { + Ctx ctx = ZMQ.createContext(); + + // Set up bind socket + SocketBase pub = ctx.createSocket(ZMQ.ZMQ_PUB); + boolean rc = ZMQ.bind(pub, "inproc://a"); + assertThat(rc, is(true)); + + // Set up connect socket + SocketBase sub = ctx.createSocket(ZMQ.ZMQ_SUB); + rc = ZMQ.connect(sub, "inproc://a"); + assertThat(rc, is(true)); + + //set a hwm on publisher + rc = ZMQ.setSocketOption(pub, ZMQ.ZMQ_SNDHWM, sendHwm); + assertThat(rc, is(true)); + + rc = ZMQ.setSocketOption(pub, ZMQ.ZMQ_XPUB_NODROP, true); + assertThat(rc, is(true)); + + rc = ZMQ.setSocketOption(sub, ZMQ.ZMQ_SUBSCRIBE, new byte[0]); + assertThat(rc, is(true)); + + // Send until we block + int sendCount = 0; + int recvCount = 0; + while (sendCount < msgCnt) { + int ret = ZMQ.send(pub, "", ZMQ.ZMQ_DONTWAIT); + if (ret == 0) { + ++sendCount; + } + else if (ret == -1) { + assertThat(pub.errno(), is(ZError.EAGAIN)); + recvCount += receive(sub); + + assertThat(sendCount, is(recvCount)); + } + } + + recvCount += receive(sub); + + // Clean up + ZMQ.close(sub); + ZMQ.close(pub); + ZMQ.term(ctx); + + return recvCount; + } + + @Test + public void testResetHwm() throws IOException + { + // hwm should apply to the messages that have already been received + // with hwm 11024: send 9999 msg, receive 9999, send 1100, receive 1100 + + int firstCount = 9999; + int secondCount = 1100; + int hwm = 11024; + + int port = Utils.findOpenPort(); + Ctx ctx = ZMQ.createContext(); + + // Set up bind socket + SocketBase pub = ctx.createSocket(ZMQ.ZMQ_PUB); + boolean rc = ZMQ.setSocketOption(pub, ZMQ.ZMQ_SNDHWM, hwm); + assertThat(rc, is(true)); + + rc = ZMQ.bind(pub, "tcp://localhost:" + port); + assertThat(rc, is(true)); + + // Set up connect socket + SocketBase sub = ctx.createSocket(ZMQ.ZMQ_SUB); + + rc = ZMQ.setSocketOption(sub, ZMQ.ZMQ_RCVHWM, hwm); + assertThat(rc, is(true)); + + rc = ZMQ.connect(sub, "tcp://localhost:" + port); + assertThat(rc, is(true)); + + rc = ZMQ.setSocketOption(sub, ZMQ.ZMQ_SUBSCRIBE, new byte[0]); + assertThat(rc, is(true)); + + ZMQ.sleep(1); + + // Send messages + int sendCount = 0; + while (sendCount < firstCount && ZMQ.send(pub, "1", ZMQ.ZMQ_DONTWAIT) == 1) { + ++sendCount; + } + assertThat(sendCount, is(firstCount)); + + ZMQ.msleep(500); + + // Now receive all sent messages + int recvCount = 0; + while (null != ZMQ.recv(sub, ZMQ.ZMQ_DONTWAIT)) { + ++recvCount; + } + assertThat(recvCount, is(firstCount)); + + ZMQ.msleep(100); + + sendCount = 0; + while (sendCount < secondCount && ZMQ.send(pub, "2", ZMQ.ZMQ_DONTWAIT) == 1) { + ++sendCount; + } + assertThat(sendCount, is(secondCount)); + + ZMQ.msleep(200); + + // Now receive all sent messages + recvCount = 0; + while (null != ZMQ.recv(sub, ZMQ.ZMQ_DONTWAIT)) { + ++recvCount; + } + assertThat(recvCount, is(secondCount)); + + // Clean up + ZMQ.close(sub); + ZMQ.close(pub); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/socket/pubsub/TestPubsubTcp.java b/src/test/java/zmq/socket/pubsub/TestPubsubTcp.java new file mode 100644 index 000000000..27d23179c --- /dev/null +++ b/src/test/java/zmq/socket/pubsub/TestPubsubTcp.java @@ -0,0 +1,60 @@ +package zmq.socket.pubsub; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class TestPubsubTcp +{ + @Test + public void testPubsubTcp() throws Exception + { + int port = Utils.findOpenPort(); + Ctx ctx = ZMQ.createContext(); + assertThat(ctx, notNullValue()); + + SocketBase pubBind = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); + assertThat(pubBind, notNullValue()); + ZMQ.setSocketOption(pubBind, ZMQ.ZMQ_XPUB_NODROP, true); + + boolean rc = ZMQ.bind(pubBind, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + SocketBase subConnect = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); + assertThat(subConnect, notNullValue()); + + subConnect.setSocketOpt(ZMQ.ZMQ_SUBSCRIBE, "topic"); + rc = ZMQ.connect(subConnect, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + ZMQ.sleep(1); + + System.out.print("Send"); + pubBind.send(new Msg("topic abc".getBytes(ZMQ.CHARSET)), 0); + pubBind.send(new Msg("topix defg".getBytes(ZMQ.CHARSET)), 0); + pubBind.send(new Msg("topic defgh".getBytes(ZMQ.CHARSET)), 0); + + System.out.print(".Recv."); + Msg msg = subConnect.recv(0); + System.out.print("1."); + assertThat(msg.size(), is(9)); + + msg = subConnect.recv(0); + System.out.print("2."); + assertThat(msg.size(), is(11)); + + System.out.print(".End."); + ZMQ.close(subConnect); + ZMQ.close(pubBind); + ZMQ.term(ctx); + System.out.println("Done."); + } +} diff --git a/src/test/java/zmq/socket/pubsub/TestSubForward.java b/src/test/java/zmq/socket/pubsub/TestSubForward.java new file mode 100644 index 000000000..9e46bc24c --- /dev/null +++ b/src/test/java/zmq/socket/pubsub/TestSubForward.java @@ -0,0 +1,92 @@ +package zmq.socket.pubsub; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class TestSubForward +{ + // Create REQ/ROUTER wiring. + + @Test + public void testSubForward() throws IOException + { + int port1 = Utils.findOpenPort(); + int port2 = Utils.findOpenPort(); + + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + + // First, create an intermediate device + SocketBase xpubBind = ZMQ.socket(ctx, ZMQ.ZMQ_XPUB); + assertThat(xpubBind, notNullValue()); + + boolean rc = ZMQ.bind(xpubBind, "tcp://127.0.0.1:" + port1); + + SocketBase xsubBind = ZMQ.socket(ctx, ZMQ.ZMQ_XSUB); + assertThat(xsubBind, notNullValue()); + + rc = ZMQ.bind(xsubBind, "tcp://127.0.0.1:" + port2); + assertThat(rc, is(true)); + + // Create a publisher + SocketBase pubConnect = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); + assertThat(pubConnect, notNullValue()); + + rc = ZMQ.connect(pubConnect, "tcp://127.0.0.1:" + port2); + assertThat(rc, is(true)); + + // Create a subscriber + SocketBase subConnect = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); + assertThat(subConnect, notNullValue()); + + rc = ZMQ.connect(subConnect, "tcp://127.0.0.1:" + port1); + assertThat(rc, is(true)); + + // Subscribe for all messages. + ZMQ.setSocketOption(subConnect, ZMQ.ZMQ_SUBSCRIBE, ""); + + ZMQ.sleep(1); + + // Pass the subscription upstream through the device + Msg msg = ZMQ.recv(xpubBind, 0); + assertThat(msg, notNullValue()); + int n = ZMQ.send(xsubBind, msg, 0); + assertThat(n, not(0)); + + // Wait a bit till the subscription gets to the publisher + ZMQ.sleep(1); + + // Send an empty message + n = ZMQ.send(pubConnect, null, 0, 0); + assertThat(n, is(0)); + + // Pass the message downstream through the device + msg = ZMQ.recv(xsubBind, 0); + assertThat(msg, notNullValue()); + n = ZMQ.send(xpubBind, msg, 0); + assertThat(n, is(0)); + + // Receive the message in the subscriber + msg = ZMQ.recv(subConnect, 0); + assertThat(msg, notNullValue()); + + // Tear down the wiring. + ZMQ.close(xpubBind); + ZMQ.close(xsubBind); + ZMQ.close(pubConnect); + ZMQ.close(subConnect); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/socket/pubsub/XPubNodropTest.java b/src/test/java/zmq/socket/pubsub/XPubNodropTest.java new file mode 100644 index 000000000..b1e762055 --- /dev/null +++ b/src/test/java/zmq/socket/pubsub/XPubNodropTest.java @@ -0,0 +1,93 @@ +package zmq.socket.pubsub; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; + +public class XPubNodropTest +{ + // Create REQ/ROUTER wiring. + + @Test + public void testXpubNoDrop() throws IOException + { + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + + // Create a publisher + SocketBase pubBind = ZMQ.socket(ctx, ZMQ.ZMQ_PUB); + assertThat(pubBind, notNullValue()); + + boolean rc = ZMQ.bind(pubBind, "inproc://soname"); + assertThat(rc, is(true)); + + // set pub socket options + ZMQ.setSocketOption(pubBind, ZMQ.ZMQ_XPUB_NODROP, 1); + int hwm = 2000; + ZMQ.setSocketOption(pubBind, ZMQ.ZMQ_SNDHWM, hwm); + + // Create a subscriber + SocketBase subConnect = ZMQ.socket(ctx, ZMQ.ZMQ_SUB); + assertThat(subConnect, notNullValue()); + + rc = ZMQ.connect(subConnect, "inproc://soname"); + assertThat(rc, is(true)); + + // Subscribe for all messages. + ZMQ.setSocketOption(subConnect, ZMQ.ZMQ_SUBSCRIBE, ""); + + int hwmlimit = hwm - 1; + int sendCount = 0; + + // Send an empty message + for (int i = 0; i < hwmlimit; i++) { + int ret = ZMQ.send(pubBind, "", 0); + assert (ret == 0); + sendCount++; + } + + int recvCount = 0; + Msg msg = null; + do { + // Receive the message in the subscriber + msg = ZMQ.recv(subConnect, ZMQ.ZMQ_DONTWAIT); + if (msg != null) { + recvCount++; + } + } while (msg != null); + + assertThat(sendCount, is(recvCount)); + + // Now test real blocking behavior + // Set a timeout, default is infinite + int timeout = 0; + ZMQ.setSocketOption(pubBind, ZMQ.ZMQ_SNDTIMEO, timeout); + recvCount = 0; + sendCount = 0; + hwmlimit = hwm; + + while (ZMQ.send(pubBind, "", 0) == 0) { + sendCount++; + } + assertThat(pubBind.errno(), is(ZError.EAGAIN)); + while (ZMQ.recv(subConnect, ZMQ.ZMQ_DONTWAIT) != null) { + recvCount++; + } + assertThat(sendCount, is(recvCount)); + + // Tear down the wiring. + ZMQ.close(pubBind); + ZMQ.close(subConnect); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/socket/reqrep/DealerSpecTest.java b/src/test/java/zmq/socket/reqrep/DealerSpecTest.java new file mode 100644 index 000000000..28574eb10 --- /dev/null +++ b/src/test/java/zmq/socket/reqrep/DealerSpecTest.java @@ -0,0 +1,224 @@ +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; + +import zmq.Ctx; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.socket.AbstractSpecTest; +import zmq.util.Utils; + +public class DealerSpecTest extends AbstractSpecTest +{ + @Test + public void testSpecFairQueueIn() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL receive incoming messages from its peers using a fair-queuing + // strategy. + fairQueueIn(ctx, bindAddress, ZMQ.ZMQ_DEALER, ZMQ.ZMQ_DEALER); + } + + ZMQ.term(ctx); + } + + @Test + public void testSpecRoundRobinOut() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL route outgoing messages to available peers using a round-robin + // strategy. + roundRobinOut(ctx, bindAddress, ZMQ.ZMQ_DEALER, ZMQ.ZMQ_REP); + } + + ZMQ.term(ctx); + } + + @Test + public void testSpecBlockOnSendNoPeers() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL block on sending, or return a suitable error, when it has no connected peers. + blockOnSendNoPeers(ctx, bindAddress, ZMQ.ZMQ_DEALER); + } + + ZMQ.term(ctx); + } + + @Test + @Ignore + public void testSpecDestroyQueueOnDisconnect() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL create a double queue when a peer connects to it. If this peer + // disconnects, the DEALER socket SHALL destroy its double queue and SHALL + // discard any messages it contains. + // *** Test disabled until libzmq does this properly *** + // test_destroy_queue_on_disconnect (ctx); + } + + ZMQ.term(ctx); + } + + private void blockOnSendNoPeers(Ctx ctx, String address, int bindType) throws IOException, InterruptedException + { + SocketBase dealer = ZMQ.socket(ctx, bindType); + + int timeout = 250; + boolean rc = ZMQ.setSocketOption(dealer, ZMQ.ZMQ_SNDTIMEO, timeout); + assertThat(rc, is(true)); + + int ret = ZMQ.send(dealer, "", ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(-1)); + assertThat(dealer.errno(), is(ZError.EAGAIN)); + + ret = ZMQ.send(dealer, "", 0); + assertThat(ret, is(-1)); + assertThat(dealer.errno(), is(ZError.EAGAIN)); + + rc = ZMQ.bind(dealer, address); + assertThat(rc, is(true)); + + ret = ZMQ.send(dealer, "", ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(-1)); + assertThat(dealer.errno(), is(ZError.EAGAIN)); + + ret = ZMQ.send(dealer, "", 0); + assertThat(ret, is(-1)); + assertThat(dealer.errno(), is(ZError.EAGAIN)); + + ZMQ.close(dealer); + } + + private void roundRobinOut(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + SocketBase dealer = ZMQ.socket(ctx, bindType); + boolean rc = ZMQ.bind(dealer, address); + assertThat(rc, is(true)); + + int timeout = 250; + int services = 5; + List senders = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase reps = ZMQ.socket(ctx, connectType); + assertThat(reps, notNullValue()); + + senders.add(reps); + + rc = ZMQ.setSocketOption(reps, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.connect(reps, address); + assertThat(rc, is(true)); + } + + // Wait for connections. + ZMQ.msleep(100); + + // Send all requests + for (int peer = 0; peer < services; ++peer) { + rc = sendSeq(dealer, null, "ABC"); + assertThat(rc, is(true)); + } + + // Expect every REP got one message + for (int peer = 0; peer < services; ++peer) { + recvSeq(senders.get(peer), "ABC"); + } + + ZMQ.closeZeroLinger(dealer); + for (SocketBase sender : senders) { + ZMQ.closeZeroLinger(sender); + } + + // Wait for disconnects. + ZMQ.msleep(100); + } + + private void fairQueueIn(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + // Server socket will accept connections + SocketBase receiver = ZMQ.socket(ctx, bindType); + assertThat(receiver, notNullValue()); + + int timeout = 250; + boolean rc = ZMQ.setSocketOption(receiver, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.bind(receiver, address); + assertThat(rc, is(true)); + + int services = 5; + List senders = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase sender = ZMQ.socket(ctx, connectType); + assertThat(sender, notNullValue()); + + senders.add(sender); + + rc = ZMQ.setSocketOption(sender, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.connect(sender, address); + assertThat(rc, is(true)); + } + + rc = sendSeq(senders.get(0), "A"); + assertThat(rc, is(true)); + recvSeq(receiver, "A"); + + rc = sendSeq(senders.get(0), "A"); + assertThat(rc, is(true)); + recvSeq(receiver, "A"); + + // send N requests + for (int peer = 0; peer < services; ++peer) { + sendSeq(senders.get(peer), "B"); + } + + // Wait for data. + ZMQ.msleep(50); + + // handle N requests + for (int peer = 0; peer < services; ++peer) { + recvSeq(receiver, "B"); + } + + ZMQ.closeZeroLinger(receiver); + for (SocketBase sender : senders) { + ZMQ.closeZeroLinger(sender); + + } + // Wait for disconnects. + ZMQ.msleep(100); + } +} diff --git a/src/test/java/zmq/socket/reqrep/RepSpecTest.java b/src/test/java/zmq/socket/reqrep/RepSpecTest.java new file mode 100644 index 000000000..87fd5b0ee --- /dev/null +++ b/src/test/java/zmq/socket/reqrep/RepSpecTest.java @@ -0,0 +1,172 @@ +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.socket.AbstractSpecTest; +import zmq.util.Utils; + +public class RepSpecTest extends AbstractSpecTest +{ + @Test + public void testSpecFairQueueIn() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL receive incoming messages from its peers using a fair-queuing + // strategy. + fairQueueIn(ctx, bindAddress, ZMQ.ZMQ_REP, ZMQ.ZMQ_REQ); + } + + ZMQ.term(ctx); + } + + @Test + public void testSpecEnvelope() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // For an incoming message: + // SHALL remove and store the address envelope, including the delimiter. + // SHALL pass the remaining data frames to its calling application. + // SHALL wait for a single reply message from its calling application. + // SHALL prepend the address envelope and delimiter. + // SHALL deliver this message back to the originating peer. + envelope(ctx, bindAddress, ZMQ.ZMQ_REP, ZMQ.ZMQ_DEALER); + } + + ZMQ.term(ctx); + } + + private void envelope(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + SocketBase rep = ZMQ.socket(ctx, bindType); + boolean rc = ZMQ.bind(rep, address); + assertThat(rc, is(true)); + + SocketBase dealer = ZMQ.socket(ctx, connectType); + assertThat(dealer, notNullValue()); + + rc = ZMQ.connect(dealer, address); + assertThat(rc, is(true)); + + // minimal envelope + sendSeq(dealer, null, "A"); + recvSeq(rep, "A"); + sendSeq(rep, "A"); + recvSeq(dealer, null, "A"); + + // big envelope + sendSeq(dealer, "X", "Y", null, "A"); + recvSeq(rep, "A"); + sendSeq(rep, "A"); + recvSeq(dealer, "X", "Y", null, "A"); + + ZMQ.closeZeroLinger(rep); + ZMQ.closeZeroLinger(dealer); + + // Wait for disconnects. + ZMQ.msleep(100); + } + + private void fairQueueIn(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + // Server socket will accept connections + SocketBase rep = ZMQ.socket(ctx, bindType); + assertThat(rep, notNullValue()); + + int timeout = 250; + boolean rc = ZMQ.setSocketOption(rep, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.bind(rep, address); + assertThat(rc, is(true)); + + int services = 5; + List reqs = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase sender = ZMQ.socket(ctx, connectType); + assertThat(sender, notNullValue()); + + reqs.add(sender); + + rc = ZMQ.setSocketOption(sender, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.connect(sender, address); + assertThat(rc, is(true)); + } + + rc = sendSeq(reqs.get(0), "A"); + assertThat(rc, is(true)); + recvSeq(rep, "A"); + rc = sendSeq(rep, "A"); + assertThat(rc, is(true)); + recvSeq(reqs.get(0), "A"); + + rc = sendSeq(reqs.get(0), "A"); + assertThat(rc, is(true)); + recvSeq(rep, "A"); + rc = sendSeq(rep, "A"); + assertThat(rc, is(true)); + recvSeq(reqs.get(0), "A"); + + boolean someoneFixThis = false; + // TODO V4 review this test (breaking in libzmq): there is no guarantee about the order of the replies. + if (someoneFixThis) { + // send N requests + for (int peer = 0; peer < services; ++peer) { + sendSeq(reqs.get(peer), "B" + peer); + } + + Set replies = new HashSet<>(); + // handle N requests + for (int peer = 0; peer < services; ++peer) { + Msg msg = ZMQ.recv(rep, 0); + assertThat(msg, notNullValue()); + + String reply = new String(msg.data(), ZMQ.CHARSET); + replies.add(reply); + sendSeq(rep, reply); + } + for (int peer = 0; peer < services; ++peer) { + Msg msg = ZMQ.recv(reqs.get(peer), 0); + assertThat(msg, notNullValue()); + + String reply = new String(msg.data(), ZMQ.CHARSET); + replies.remove(reply); + } + assertThat(replies.size(), is(0)); + } + + ZMQ.closeZeroLinger(rep); + for (SocketBase sender : reqs) { + ZMQ.closeZeroLinger(sender); + + } + // Wait for disconnects. + ZMQ.msleep(100); + } +} diff --git a/src/test/java/zmq/socket/reqrep/ReqSpecTest.java b/src/test/java/zmq/socket/reqrep/ReqSpecTest.java new file mode 100644 index 000000000..ae3120d77 --- /dev/null +++ b/src/test/java/zmq/socket/reqrep/ReqSpecTest.java @@ -0,0 +1,283 @@ +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.socket.AbstractSpecTest; +import zmq.util.Utils; + +public class ReqSpecTest extends AbstractSpecTest +{ + @Test + public void testSpecMessageFormat() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // The request and reply messages SHALL have this format on the wire: + // * A delimiter, consisting of an empty frame, added by the REQ socket. + // * One or more data frames, comprising the message visible to the + // application. + messageFormat(ctx, bindAddress, ZMQ.ZMQ_REQ, ZMQ.ZMQ_ROUTER); + } + + ZMQ.term(ctx); + } + + @Test + public void testSpecRoundRobinOut() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL route outgoing messages to connected peers using a round-robin + // strategy. + roundRobinOut(ctx, bindAddress, ZMQ.ZMQ_REQ, ZMQ.ZMQ_REP); + } + + ZMQ.term(ctx); + } + + @Test + public void testSpecBlockOnSendNoPeers() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL block on sending, or return a suitable error, when it has no + // connected peers. + blockOnSendNoPeers(ctx, bindAddress, ZMQ.ZMQ_REQ); + } + + ZMQ.term(ctx); + } + + @Test + @Ignore + public void testSpecOnlyListensToCurrentPeer() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL accept an incoming message only from the last peer that it sent a + // request to. + // SHALL discard silently any messages received from other peers. + // PH: this test is still failing; disabled for now to allow build to + // complete. + onlyListensToCurrentPeer(ctx, bindAddress, ZMQ.ZMQ_REQ, ZMQ.ZMQ_ROUTER); + } + + ZMQ.term(ctx); + } + + private void onlyListensToCurrentPeer(Ctx ctx, String bindAddress, int bindType, int connectType) + { + SocketBase socket = ZMQ.socket(ctx, bindType); + + boolean rc = ZMQ.setSocketOption(socket, ZMQ.ZMQ_IDENTITY, "A"); + assertThat(rc, is(true)); + + rc = ZMQ.bind(socket, bindAddress); + assertThat(rc, is(true)); + + int timeout = 250; + int services = 3; + List senders = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase connect = ZMQ.socket(ctx, connectType); + assertThat(connect, notNullValue()); + + senders.add(connect); + + rc = ZMQ.setSocketOption(connect, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.setSocketOption(connect, ZMQ.ZMQ_ROUTER_MANDATORY, true); + assertThat(rc, is(true)); + + rc = ZMQ.connect(connect, bindAddress); + assertThat(rc, is(true)); + } + + ZMQ.msleep(100); + + for (int peer = 0; peer < services; ++peer) { + // There still is a race condition when a stale peer's message + // arrives at the REQ just after a request was sent to that peer. + // To avoid that happening in the test, sleep for a bit. + ZMQ.msleep(10); + + sendSeq(socket, "ABC"); + + // Receive on router i + recvSeq(senders.get(peer), "A", null, "ABC"); + + // Send back replies on all routers + for (int j = 0; j < services; ++j) { + List replies = Arrays.asList("WRONG", "GOOD"); + String reply = peer == j ? replies.get(1) : replies.get(0); + sendSeq(senders.get(j), "A", null, reply); + } + + // Receive only the good reply + recvSeq(socket, "GOOD"); + } + + ZMQ.closeZeroLinger(socket); + for (SocketBase sender : senders) { + ZMQ.closeZeroLinger(sender); + } + + // Wait for disconnects. + ZMQ.msleep(100); + } + + private void blockOnSendNoPeers(Ctx ctx, String address, int bindType) throws IOException, InterruptedException + { + SocketBase socket = ZMQ.socket(ctx, bindType); + + int timeout = 250; + boolean rc = ZMQ.setSocketOption(socket, ZMQ.ZMQ_SNDTIMEO, timeout); + assertThat(rc, is(true)); + + int ret = ZMQ.send(socket, "", ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(-1)); + assertThat(socket.errno(), is(ZError.EAGAIN)); + + ret = ZMQ.send(socket, "", 0); + assertThat(ret, is(-1)); + assertThat(socket.errno(), is(ZError.EAGAIN)); + + rc = ZMQ.bind(socket, address); + assertThat(rc, is(true)); + + ret = ZMQ.send(socket, "", ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(-1)); + assertThat(socket.errno(), is(ZError.EAGAIN)); + + ret = ZMQ.send(socket, "", 0); + assertThat(ret, is(-1)); + assertThat(socket.errno(), is(ZError.EAGAIN)); + + ZMQ.close(socket); + } + + private void roundRobinOut(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + SocketBase req = ZMQ.socket(ctx, bindType); + boolean rc = ZMQ.bind(req, address); + assertThat(rc, is(true)); + + int timeout = 250; + int services = 5; + List senders = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase reps = ZMQ.socket(ctx, connectType); + assertThat(reps, notNullValue()); + + senders.add(reps); + + rc = ZMQ.setSocketOption(reps, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.connect(reps, address); + assertThat(rc, is(true)); + } + + // We have to give the connects time to finish otherwise the requests + // will not properly round-robin. We could alternatively connect the + // REQ sockets to the REP sockets. + ZMQ.msleep(200); + + // Send our peer-replies, and expect every REP it used once in order + for (int peer = 0; peer < services; ++peer) { + rc = sendSeq(req, "ABC"); + assertThat(rc, is(true)); + + recvSeq(senders.get(peer), "ABC"); + rc = sendSeq(senders.get(peer), "DEF"); + assertThat(rc, is(true)); + + recvSeq(req, "DEF"); + } + + ZMQ.closeZeroLinger(req); + for (SocketBase sender : senders) { + ZMQ.closeZeroLinger(sender); + } + + // Wait for disconnects. + ZMQ.msleep(100); + } + + private void messageFormat(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + // Server socket will accept connections + SocketBase req = ZMQ.socket(ctx, bindType); + assertThat(req, notNullValue()); + + boolean rc = ZMQ.bind(req, address); + assertThat(rc, is(true)); + + SocketBase router = ZMQ.socket(ctx, connectType); + assertThat(router, notNullValue()); + + rc = ZMQ.connect(router, address); + assertThat(rc, is(true)); + + // Send a multi-part request. + sendSeq(req, "ABC", "DEF"); + + // Receive peer identity + Msg msg = ZMQ.recv(router, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size() > 0, is(true)); + + Msg peerId = msg; + + int more = ZMQ.getSocketOption(router, ZMQ.ZMQ_RCVMORE); + assertThat(more, is(1)); + + // Receive the rest. + recvSeq(router, null, "ABC", "DEF"); + + // Send back a single-part reply. + int ret = ZMQ.send(router, peerId, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(peerId.size())); + + sendSeq(router, null, "GHI"); + + // Receive reply. + recvSeq(req, "GHI"); + + ZMQ.closeZeroLinger(req); + ZMQ.closeZeroLinger(router); + + // Wait for disconnects. + ZMQ.msleep(100); + } +} diff --git a/src/test/java/zmq/TestRouterHandover.java b/src/test/java/zmq/socket/reqrep/RouterHandoverTest.java similarity index 87% rename from src/test/java/zmq/TestRouterHandover.java rename to src/test/java/zmq/socket/reqrep/RouterHandoverTest.java index 744a75bf9..e4f448f95 100644 --- a/src/test/java/zmq/TestRouterHandover.java +++ b/src/test/java/zmq/socket/reqrep/RouterHandoverTest.java @@ -1,13 +1,19 @@ -package zmq; - -import org.junit.Test; +package zmq.socket.reqrep; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; -public class TestRouterHandover +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class RouterHandoverTest { @Test public void testRouterHandover() throws Exception @@ -22,7 +28,7 @@ public void testRouterHandover() throws Exception SocketBase router = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); brc = ZMQ.bind(router, "tcp://127.0.0.1:" + port); - assertThat(brc , is(true)); + assertThat(brc, is(true)); // Enable the handover flag ZMQ.setSocketOption(router, ZMQ.ZMQ_ROUTER_HANDOVER, 1); @@ -42,8 +48,8 @@ public void testRouterHandover() throws Exception assertThat(rc, is(5)); Msg msg = ZMQ.recv(router, 0); - assertThat(msg.size() , is(1)); - assertThat(new String(msg.data()) , is("X")); + assertThat(msg.size(), is(1)); + assertThat(new String(msg.data()), is("X")); msg = ZMQ.recv(router, 0); assertThat(msg.size(), is(5)); @@ -62,8 +68,8 @@ public void testRouterHandover() throws Exception assertThat(rc, is(5)); msg = ZMQ.recv(router, 0); - assertThat(msg.size() , is(1)); - assertThat(new String(msg.data()) , is("X")); + assertThat(msg.size(), is(1)); + assertThat(new String(msg.data()), is("X")); msg = ZMQ.recv(router, 0); assertThat(msg.size(), is(5)); diff --git a/src/test/java/zmq/socket/reqrep/RouterMandatoryTest.java b/src/test/java/zmq/socket/reqrep/RouterMandatoryTest.java new file mode 100644 index 000000000..0994b5c68 --- /dev/null +++ b/src/test/java/zmq/socket/reqrep/RouterMandatoryTest.java @@ -0,0 +1,165 @@ +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.util.Utils; + +public class RouterMandatoryTest +{ + @Test + public void testRouterMandatory() throws Exception + { + int sent; + boolean rc; + + int port = Utils.findOpenPort(); + + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + + SocketBase router = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); + assertThat(router, notNullValue()); + + rc = ZMQ.bind(router, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + // Sending a message to an unknown peer with the default setting + // This will not report any error + sent = ZMQ.send(router, "UNKNOWN", ZMQ.ZMQ_SNDMORE); + assertThat(sent, is(7)); + + sent = ZMQ.send(router, "DATA", 0); + assertThat(sent, is(4)); + + // Send a message to an unknown peer with mandatory routing + // This will fail + int mandatory = 1; + ZMQ.setSocketOption(router, ZMQ.ZMQ_ROUTER_MANDATORY, mandatory); + + // Send a message and check that it fails + sent = ZMQ.send(router, "UNKNOWN", ZMQ.ZMQ_SNDMORE | ZMQ.ZMQ_DONTWAIT); + assertThat(sent, is(-1)); + assertThat(router.errno(), is(ZError.EHOSTUNREACH)); + + // Create dealer called "X" and connect it to our router + SocketBase dealer = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(dealer, notNullValue()); + + ZMQ.setSocketOption(dealer, ZMQ.ZMQ_IDENTITY, "X"); + + rc = ZMQ.connect(dealer, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + // Get message from dealer to know when connection is ready + int ret = ZMQ.send(dealer, "Hello", 0); + assertThat(ret, is(5)); + + Msg msg = ZMQ.recv(router, 0); + assertThat(msg, notNullValue()); + assertThat(msg.data()[0], is((byte) 'X')); + + // Send a message to connected dealer now + // It should work + sent = ZMQ.send(router, "X", ZMQ.ZMQ_SNDMORE); + assertThat(sent, is(1)); + sent = ZMQ.send(router, "Hello", 0); + assertThat(sent, is(5)); + + // Clean up. + ZMQ.close(router); + ZMQ.close(dealer); + ZMQ.term(ctx); + } + + private static final int BUF_SIZE = 65636; + + @Test + public void testRouterMandatoryHwm() throws Exception + { + boolean rc; + + System.out.print("Starting router mandatory HWM test"); + + int port = Utils.findOpenPort(); + + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + + SocketBase router = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); + assertThat(router, notNullValue()); + + ZMQ.setSocketOption(router, ZMQ.ZMQ_ROUTER_MANDATORY, true); + ZMQ.setSocketOption(router, ZMQ.ZMQ_SNDHWM, 1); + ZMQ.setSocketOption(router, ZMQ.ZMQ_LINGER, 1); + + rc = ZMQ.bind(router, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + // Create dealer called "X" and connect it to our router, configure HWM + SocketBase dealer = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assertThat(dealer, notNullValue()); + + ZMQ.setSocketOption(dealer, ZMQ.ZMQ_RCVHWM, 1); + ZMQ.setSocketOption(dealer, ZMQ.ZMQ_IDENTITY, "X"); + + rc = ZMQ.connect(dealer, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + System.out.print("."); + // Get message from dealer to know when connection is ready + int ret = ZMQ.send(dealer, "Hello", 0); + assertThat(ret, is(5)); + + System.out.print("."); + Msg msg = ZMQ.recv(router, 0); + System.out.print("."); + assertThat(msg, notNullValue()); + assertThat(msg.data()[0], is((byte) 'X')); + + int i = 0; + for (; i < 100000; ++i) { + ret = ZMQ.send(router, "X", ZMQ.ZMQ_DONTWAIT | ZMQ.ZMQ_SNDMORE); + if (ret == -1 && router.errno() == ZError.EAGAIN) { + break; + } + assertThat(ret, is(1)); + ret = ZMQ.send(router, new byte[BUF_SIZE], BUF_SIZE, ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(BUF_SIZE)); + } + System.out.print("."); + + // This should fail after one message but kernel buffering could + // skew results + assertThat(i < 10, is(true)); + + ZMQ.sleep(1); + // Send second batch of messages + for (; i < 100000; ++i) { + ret = ZMQ.send(router, "X", ZMQ.ZMQ_DONTWAIT | ZMQ.ZMQ_SNDMORE); + if (ret == -1 && router.errno() == ZError.EAGAIN) { + break; + } + assertThat(ret, is(1)); + ret = ZMQ.send(router, new byte[BUF_SIZE], BUF_SIZE, ZMQ.ZMQ_DONTWAIT); + assertThat(ret, is(BUF_SIZE)); + } + System.out.print("."); + // This should fail after two messages but kernel buffering could + // skew results + assertThat(i < 20, is(true)); + System.out.println("Done sending messages."); + // Clean up. + ZMQ.close(router); + ZMQ.close(dealer); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/socket/reqrep/RouterProbeTest.java b/src/test/java/zmq/socket/reqrep/RouterProbeTest.java new file mode 100644 index 000000000..bb2c2640d --- /dev/null +++ b/src/test/java/zmq/socket/reqrep/RouterProbeTest.java @@ -0,0 +1,79 @@ +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class RouterProbeTest +{ + @Test + public void testProbeRouter() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://127.0.0.1:" + port; + + Ctx ctx = ZMQ.createContext(); + + // Server socket will accept connections + SocketBase server = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); + assertThat(server, notNullValue()); + + boolean rc = ZMQ.bind(server, host); + assertThat(rc, is(true)); + + // Create client and connect to server, doing a probe + + SocketBase client = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); + assertThat(client, notNullValue()); + + rc = ZMQ.setSocketOption(client, ZMQ.ZMQ_IDENTITY, "X"); + assertThat(rc, is(true)); + + rc = ZMQ.setSocketOption(client, ZMQ.ZMQ_PROBE_ROUTER, true); + assertThat(rc, is(true)); + + rc = ZMQ.connect(client, host); + assertThat(rc, is(true)); + + // We expect an identity=X + empty message from client + Msg msg = ZMQ.recv(server, 0); + assertThat(msg, notNullValue()); + assertThat(msg.get(0), is((byte) 'X')); + + msg = ZMQ.recv(server, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(0)); + + // Send a message to client now + int ret = ZMQ.send(server, "X", ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(1)); + + ret = ZMQ.send(server, "Hello", 0); + assertThat(ret, is(5)); + + msg = ZMQ.recv(client, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(5)); + + // TODO DIFF V4 test should stop here, check the logic if we should receive payload in the previous message. + msg = ZMQ.recv(client, 0); + assertThat(msg, notNullValue()); + assertThat(new String(msg.data(), ZMQ.CHARSET), is("Hello")); + + ZMQ.closeZeroLinger(server); + ZMQ.closeZeroLinger(client); + + // Shutdown + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/socket/reqrep/RouterSpecTest.java b/src/test/java/zmq/socket/reqrep/RouterSpecTest.java new file mode 100644 index 000000000..c9f773026 --- /dev/null +++ b/src/test/java/zmq/socket/reqrep/RouterSpecTest.java @@ -0,0 +1,127 @@ +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Ignore; +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.socket.AbstractSpecTest; +import zmq.util.Utils; + +public class RouterSpecTest extends AbstractSpecTest +{ + @Test + public void testFairQueueIn() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL receive incoming messages from its peers using a fair-queuing + // strategy. + fairQueueIn(ctx, bindAddress, ZMQ.ZMQ_ROUTER, ZMQ.ZMQ_DEALER); + } + + ZMQ.term(ctx); + } + + @Test + @Ignore + public void testDestroyQueueOnDisconnect() throws IOException, InterruptedException + { + Ctx ctx = ZMQ.createContext(); + int port = Utils.findOpenPort(); + List binds = Arrays.asList("inproc://a", "tcp://127.0.0.1:" + port); + + for (String bindAddress : binds) { + // SHALL create a double queue when a peer connects to it. If this peer + // disconnects, the ROUTER socket SHALL destroy its double queue and SHALL + // discard any messages it contains. + // *** Test disabled until libzmq does this properly *** + // test_destroy_queue_on_disconnect (ctx); + } + + ZMQ.term(ctx); + } + + private void fairQueueIn(Ctx ctx, String address, int bindType, int connectType) + throws IOException, InterruptedException + { + // Server socket will accept connections + SocketBase receiver = ZMQ.socket(ctx, bindType); + assertThat(receiver, notNullValue()); + + int timeout = 250; + boolean rc = ZMQ.setSocketOption(receiver, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.bind(receiver, address); + assertThat(rc, is(true)); + + int services = 5; + List senders = new ArrayList<>(); + for (int peer = 0; peer < services; ++peer) { + SocketBase sender = ZMQ.socket(ctx, connectType); + assertThat(sender, notNullValue()); + + senders.add(sender); + + rc = ZMQ.setSocketOption(sender, ZMQ.ZMQ_RCVTIMEO, timeout); + assertThat(rc, is(true)); + + rc = ZMQ.setSocketOption(sender, ZMQ.ZMQ_IDENTITY, "A" + peer); + assertThat(rc, is(true)); + + rc = ZMQ.connect(sender, address); + assertThat(rc, is(true)); + } + + rc = sendSeq(senders.get(0), "M"); + assertThat(rc, is(true)); + recvSeq(receiver, "A0", "M"); + + rc = sendSeq(senders.get(0), "M"); + assertThat(rc, is(true)); + recvSeq(receiver, "A0", "M"); + + Set sum = new HashSet<>(); + + // send N requests + for (int peer = 0; peer < services; ++peer) { + sendSeq(senders.get(peer), "M"); + sum.add("A" + peer); + } + + // handle N requests + for (int peer = 0; peer < services; ++peer) { + Msg msg = ZMQ.recv(receiver, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(2)); + sum.remove(new String(msg.data(), ZMQ.CHARSET)); + recvSeq(receiver, "M"); + } + assertThat(sum.size(), is(0)); + + ZMQ.closeZeroLinger(receiver); + for (SocketBase sender : senders) { + ZMQ.closeZeroLinger(sender); + + } + // Wait for disconnects. + ZMQ.msleep(100); + } +} diff --git a/src/test/java/zmq/TestInvalidRep.java b/src/test/java/zmq/socket/reqrep/TestInvalidRep.java similarity index 89% rename from src/test/java/zmq/TestInvalidRep.java rename to src/test/java/zmq/socket/reqrep/TestInvalidRep.java index 679149427..1d60cc894 100644 --- a/src/test/java/zmq/TestInvalidRep.java +++ b/src/test/java/zmq/socket/reqrep/TestInvalidRep.java @@ -1,9 +1,15 @@ -package zmq; +package zmq.socket.reqrep; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; public class TestInvalidRep { @@ -39,9 +45,9 @@ public void testInvalidRep() int addrSize = addr.size(); System.out.println("addrSize: " + addr.size()); assertThat(addr.size() > 0, is(true)); - bottom = ZMQ.recv(routerSocket, 0); + bottom = ZMQ.recv(routerSocket, 0); assertThat(bottom.size(), is(0)); - body = ZMQ.recv(routerSocket, 0); + body = ZMQ.recv(routerSocket, 0); assertThat(body.size(), is(1)); assertThat(body.data()[0], is((byte) 'r')); @@ -59,7 +65,7 @@ public void testInvalidRep() // Check whether we've got the valid reply. body = ZMQ.recv(reqSocket, 0); assertThat(body.size(), is(1)); - assertThat(body.data()[0] , is((byte) 'b')); + assertThat(body.data()[0], is((byte) 'b')); // Tear down the wiring. ZMQ.close(routerSocket); diff --git a/src/test/java/zmq/TestReqCorrelateRelaxed.java b/src/test/java/zmq/socket/reqrep/TestReqCorrelateRelaxed.java similarity index 91% rename from src/test/java/zmq/TestReqCorrelateRelaxed.java rename to src/test/java/zmq/socket/reqrep/TestReqCorrelateRelaxed.java index a44e70a7e..cceb734b1 100644 --- a/src/test/java/zmq/TestReqCorrelateRelaxed.java +++ b/src/test/java/zmq/socket/reqrep/TestReqCorrelateRelaxed.java @@ -1,18 +1,26 @@ -package zmq; +package zmq.socket.reqrep; -import java.util.Arrays; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; + +import java.util.Arrays; + import org.junit.Test; +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; + /** * This test does one setup, and runs a few tests on that setup. */ public class TestReqCorrelateRelaxed { - private static final String PAYLOAD = "Payload"; + static final int REQUEST_ID_LENGTH = 4; + private static final String PAYLOAD = "Payload"; /** * Prepares sockets and runs actual tests. @@ -71,14 +79,14 @@ public byte[] testReqSentFrames(SocketBase dealer, SocketBase reqClient) throws Msg receivedReqId = ZMQ.recv(dealer, 0); assertThat(receivedReqId, notNullValue()); - assertThat(receivedReqId.size(), is(Req.REQUEST_ID_LENGTH)); + assertThat(receivedReqId.size(), is(REQUEST_ID_LENGTH)); assertThat(receivedReqId.flags() & ZMQ.ZMQ_MORE, not(0)); byte[] buf = new byte[128]; int requestIdLen = receivedReqId.getBytes(0, buf, 0, 128); - assertThat(requestIdLen, is(Req.REQUEST_ID_LENGTH)); + assertThat(requestIdLen, is(REQUEST_ID_LENGTH)); - byte[] requestId = Arrays.copyOf(buf, Req.REQUEST_ID_LENGTH); + byte[] requestId = Arrays.copyOf(buf, REQUEST_ID_LENGTH); // 2. empty frame Msg receivedEmpty = ZMQ.recv(dealer, 0); @@ -97,9 +105,7 @@ public byte[] testReqSentFrames(SocketBase dealer, SocketBase reqClient) throws int receivedPayloadLen = receivedPayload.getBytes(0, buf, 0, 128); assertThat(receivedPayloadLen, is(PAYLOAD.getBytes().length)); - for (int i = 0; i < receivedPayloadLen; i++) { - assertThat(PAYLOAD.getBytes()[i], is(PAYLOAD.getBytes()[i])); - } + assertThat(Arrays.equals(receivedPayload.data(), PAYLOAD.getBytes()), is(true)); return requestId; } @@ -121,7 +127,7 @@ public void testReqRecvGoodRequestId(SocketBase dealer, SocketBase reqClient, by Msg responsePayload = new Msg(PAYLOAD.getBytes()); // Send response - assertThat(ZMQ.send(dealer, requestId, ZMQ.ZMQ_SNDMORE), is(6)); + assertThat(ZMQ.send(dealer, requestId, ZMQ.ZMQ_SNDMORE), is(REQUEST_ID_LENGTH)); assertThat(ZMQ.send(dealer, empty, ZMQ.ZMQ_SNDMORE), is(0)); assertThat(ZMQ.send(dealer, responsePayload, 0), is(responsePayload.size())); @@ -158,7 +164,7 @@ public void testReqRecvBadRequestId(SocketBase dealer, SocketBase reqClient, byt assertThat(ZMQ.send(dealer, badResponsePayload, 0), is(badResponsePayload.size())); // Send response with good request ID - assertThat(ZMQ.send(dealer, goodRequestId, ZMQ.ZMQ_SNDMORE), is(6)); + assertThat(ZMQ.send(dealer, goodRequestId, ZMQ.ZMQ_SNDMORE), is(REQUEST_ID_LENGTH)); assertThat(ZMQ.send(dealer, empty, ZMQ.ZMQ_SNDMORE), is(0)); assertThat(ZMQ.send(dealer, goodResponsePayload, 0), is(goodResponsePayload.size())); diff --git a/src/test/java/zmq/TestReqrepDevice.java b/src/test/java/zmq/socket/reqrep/TestReqrepDevice.java similarity index 91% rename from src/test/java/zmq/TestReqrepDevice.java rename to src/test/java/zmq/socket/reqrep/TestReqrepDevice.java index f2d771d4d..e3e587ebe 100644 --- a/src/test/java/zmq/TestReqrepDevice.java +++ b/src/test/java/zmq/socket/reqrep/TestReqrepDevice.java @@ -1,11 +1,18 @@ -package zmq; +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; import java.io.IOException; import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; public class TestReqrepDevice { @@ -26,20 +33,20 @@ public void testReprepDevice() throws IOException assertThat(dealer, notNullValue()); brc = ZMQ.bind(dealer, "tcp://127.0.0.1:" + dealerPort); - assertThat(brc , is(true)); + assertThat(brc, is(true)); SocketBase router = ZMQ.socket(ctx, ZMQ.ZMQ_ROUTER); assertThat(router, notNullValue()); brc = ZMQ.bind(router, "tcp://127.0.0.1:" + routerPort); - assertThat(brc , is(true)); + assertThat(brc, is(true)); // Create a worker. SocketBase rep = ZMQ.socket(ctx, ZMQ.ZMQ_REP); assertThat(rep, notNullValue()); brc = ZMQ.connect(rep, "tcp://127.0.0.1:" + dealerPort); - assertThat(brc , is(true)); + assertThat(brc, is(true)); SocketBase req = ZMQ.socket(ctx, ZMQ.ZMQ_REQ); assertThat(req, notNullValue()); @@ -69,9 +76,9 @@ public void testReprepDevice() throws IOException // Receive the request. msg = ZMQ.recv(rep, 0); - assertThat(msg.size() , is(3)); + assertThat(msg.size(), is(3)); buff = new String(msg.data(), ZMQ.CHARSET); - assertThat(buff , is("ABC")); + assertThat(buff, is("ABC")); rcvmore = ZMQ.getSocketOption(rep, ZMQ.ZMQ_RCVMORE); assertThat(rcvmore > 0, is(true)); msg = ZMQ.recv(rep, 0); @@ -85,7 +92,7 @@ public void testReprepDevice() throws IOException rc = ZMQ.send(rep, "GHIJKL", ZMQ.ZMQ_SNDMORE); assertThat(rc, is(6)); rc = ZMQ.send(rep, "MN", 0); - assertThat(rc , is(2)); + assertThat(rc, is(2)); // Pass the reply through the device. for (int i = 0; i != 4; i++) { diff --git a/src/test/java/zmq/TestReqrepInproc.java b/src/test/java/zmq/socket/reqrep/TestReqrepInproc.java similarity index 81% rename from src/test/java/zmq/TestReqrepInproc.java rename to src/test/java/zmq/socket/reqrep/TestReqrepInproc.java index 0f79a8dd9..7a1ed6866 100644 --- a/src/test/java/zmq/TestReqrepInproc.java +++ b/src/test/java/zmq/socket/reqrep/TestReqrepInproc.java @@ -1,9 +1,15 @@ -package zmq; +package zmq.socket.reqrep; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.SocketBase; +import zmq.ZMQ; public class TestReqrepInproc { @@ -17,12 +23,12 @@ public void testReqrepInproc() SocketBase sb = ZMQ.socket(ctx, ZMQ.ZMQ_REP); assertThat(sb, notNullValue()); boolean brc = ZMQ.bind(sb, "inproc://a"); - assertThat(brc , is(true)); + assertThat(brc, is(true)); SocketBase sc = ZMQ.socket(ctx, ZMQ.ZMQ_REQ); assertThat(sc, notNullValue()); brc = ZMQ.connect(sc, "inproc://a"); - assertThat(brc , is(true)); + assertThat(brc, is(true)); Helper.bounce(sb, sc); diff --git a/src/test/java/zmq/TestReqrepIpc.java b/src/test/java/zmq/socket/reqrep/TestReqrepIpc.java similarity index 81% rename from src/test/java/zmq/TestReqrepIpc.java rename to src/test/java/zmq/socket/reqrep/TestReqrepIpc.java index 790518049..039f87298 100644 --- a/src/test/java/zmq/TestReqrepIpc.java +++ b/src/test/java/zmq/socket/reqrep/TestReqrepIpc.java @@ -1,9 +1,15 @@ -package zmq; +package zmq.socket.reqrep; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.SocketBase; +import zmq.ZMQ; public class TestReqrepIpc { @@ -15,12 +21,12 @@ public void testReqrepIpc() SocketBase sb = ZMQ.socket(ctx, ZMQ.ZMQ_REP); assertThat(sb, notNullValue()); boolean brc = ZMQ.bind(sb, "ipc:///tmp/tester2"); - assertThat(brc , is(true)); + assertThat(brc, is(true)); SocketBase sc = ZMQ.socket(ctx, ZMQ.ZMQ_REQ); assertThat(sc, notNullValue()); brc = ZMQ.connect(sc, "ipc:///tmp/tester2"); - assertThat(brc , is(true)); + assertThat(brc, is(true)); Helper.bounce(sb, sc); diff --git a/src/test/java/zmq/socket/reqrep/TestReqrepTcp.java b/src/test/java/zmq/socket/reqrep/TestReqrepTcp.java new file mode 100644 index 000000000..2440cd59e --- /dev/null +++ b/src/test/java/zmq/socket/reqrep/TestReqrepTcp.java @@ -0,0 +1,41 @@ +package zmq.socket.reqrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Helper; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class TestReqrepTcp +{ + @Test + public void testReqrepTcp() throws Exception + { + int port = Utils.findOpenPort(); + System.out.println("Starting with port " + port); + Ctx ctx = ZMQ.init(1); + assertThat(ctx, notNullValue()); + + SocketBase repBind = ZMQ.socket(ctx, ZMQ.ZMQ_REP); + assertThat(repBind, notNullValue()); + boolean rc = ZMQ.bind(repBind, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + SocketBase reqConnect = ZMQ.socket(ctx, ZMQ.ZMQ_REQ); + assertThat(reqConnect, notNullValue()); + rc = ZMQ.connect(reqConnect, "tcp://127.0.0.1:" + port); + assertThat(rc, is(true)); + + Helper.bounce(repBind, reqConnect); + + ZMQ.close(reqConnect); + ZMQ.close(repBind); + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/TestRouterMandatory.java b/src/test/java/zmq/socket/reqrep/TestRouterMandatory.java similarity index 93% rename from src/test/java/zmq/TestRouterMandatory.java rename to src/test/java/zmq/socket/reqrep/TestRouterMandatory.java index bafe750fa..8e2604b63 100644 --- a/src/test/java/zmq/TestRouterMandatory.java +++ b/src/test/java/zmq/socket/reqrep/TestRouterMandatory.java @@ -1,9 +1,16 @@ -package zmq; +package zmq.socket.reqrep; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.SocketBase; +import zmq.ZError; +import zmq.ZMQ; +import zmq.util.Utils; public class TestRouterMandatory { @@ -23,7 +30,7 @@ public void testRouterMandatory() throws Exception assertThat(sa, notNullValue()); brc = ZMQ.bind(sa, "tcp://127.0.0.1:" + port); - assertThat(brc , is(true)); + assertThat(brc, is(true)); // Sending a message to an unknown peer with the default setting rc = ZMQ.send(sa, "UNKNOWN", ZMQ.ZMQ_SNDMORE); diff --git a/src/test/java/zmq/socket/stream/StreamEmptyTest.java b/src/test/java/zmq/socket/stream/StreamEmptyTest.java new file mode 100644 index 000000000..58f33e9df --- /dev/null +++ b/src/test/java/zmq/socket/stream/StreamEmptyTest.java @@ -0,0 +1,66 @@ +package zmq.socket.stream; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class StreamEmptyTest +{ + @Test + public void testStreamEmpty() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://localhost:" + port; + + Ctx ctx = ZMQ.init(1); + assert (ctx != null); + + // Set up listener STREAM. + SocketBase bind = ZMQ.socket(ctx, ZMQ.ZMQ_STREAM); + assert (bind != null); + + boolean rc = ZMQ.bind(bind, host); + assert (rc); + + // Set up connection stream. + SocketBase connect = ZMQ.socket(ctx, ZMQ.ZMQ_DEALER); + assert (connect != null); + + // Do the connection. + rc = ZMQ.connect(connect, host); + assert (rc); + + ZMQ.sleep(1); + + int ret = ZMQ.send(connect, "", 0); + assertThat(ret, is(0)); + + Msg msg = ZMQ.recv(bind, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(5)); + + ret = ZMQ.send(bind, msg, ZMQ.ZMQ_SNDMORE); + assertThat(ret, is(5)); + + ret = ZMQ.send(bind, new Msg(), 0); + assertThat(ret, is(0)); + + ZMQ.setSocketOption(bind, ZMQ.ZMQ_LINGER, 0); + ZMQ.setSocketOption(connect, ZMQ.ZMQ_LINGER, 0); + + ZMQ.close(bind); + ZMQ.close(connect); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/socket/stream/StreamTest.java b/src/test/java/zmq/socket/stream/StreamTest.java new file mode 100644 index 000000000..2d5d86537 --- /dev/null +++ b/src/test/java/zmq/socket/stream/StreamTest.java @@ -0,0 +1,61 @@ +package zmq.socket.stream; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import zmq.Ctx; +import zmq.Msg; +import zmq.SocketBase; +import zmq.ZMQ; +import zmq.util.Utils; + +public class StreamTest +{ + @Test + public void testStream2stream() throws IOException, InterruptedException + { + int port = Utils.findOpenPort(); + String host = "tcp://localhost:" + port; + + Ctx ctx = ZMQ.init(1); + assert (ctx != null); + + // Set up listener STREAM. + SocketBase bind = ZMQ.socket(ctx, ZMQ.ZMQ_STREAM); + assert (bind != null); + + boolean rc = ZMQ.bind(bind, host); + assert (rc); + + // Set up connection stream. + SocketBase connect = ZMQ.socket(ctx, ZMQ.ZMQ_STREAM); + assert (connect != null); + + // Do the connection. + rc = ZMQ.connect(connect, host); + assert (rc); + + ZMQ.sleep(1); + + // Connecting sends a zero message + // Server: First frame is identity, second frame is zero + + Msg msg = ZMQ.recv(bind, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size() > 0, is(true)); + + msg = ZMQ.recv(bind, 0); + assertThat(msg, notNullValue()); + assertThat(msg.size(), is(0)); + + ZMQ.close(bind); + ZMQ.close(connect); + + ZMQ.term(ctx); + } +} diff --git a/src/test/java/zmq/TestBlob.java b/src/test/java/zmq/util/TestBlob.java similarity index 82% rename from src/test/java/zmq/TestBlob.java rename to src/test/java/zmq/util/TestBlob.java index 8ac33b29b..d1b2d9e28 100644 --- a/src/test/java/zmq/TestBlob.java +++ b/src/test/java/zmq/util/TestBlob.java @@ -1,11 +1,14 @@ -package zmq; +package zmq.util; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; import java.util.HashMap; import org.junit.Test; -import static org.junit.Assert.assertThat; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; + +import zmq.ZMQ; public class TestBlob { @@ -14,7 +17,7 @@ public void testBlobMap() { HashMap map = new HashMap(); - Blob b = Blob.createBlob("a".getBytes(ZMQ.CHARSET), false); + Blob b = Blob.createBlob("a".getBytes(ZMQ.CHARSET)); map.put(b, "aa"); assertThat(map.remove(b), notNullValue()); diff --git a/src/test/java/zmq/TestUtils.java b/src/test/java/zmq/util/TestUtils.java similarity index 92% rename from src/test/java/zmq/TestUtils.java rename to src/test/java/zmq/util/TestUtils.java index 2e19e12c8..a7978f497 100644 --- a/src/test/java/zmq/TestUtils.java +++ b/src/test/java/zmq/util/TestUtils.java @@ -1,16 +1,17 @@ -package zmq; +package zmq.util; -import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; public class TestUtils { @Test public void testRealloc() { - Integer[] src = new Integer[] {1, 3, 5}; + Integer[] src = new Integer[] { 1, 3, 5 }; Integer[] dest = Utils.realloc(Integer.class, src, 3, true); assertThat(src.length, is(3)); @@ -33,7 +34,7 @@ public void testRealloc() assertThat(dest[4], is(3)); assertThat(dest[5], is(5)); - src = new Integer[] {1, 3, 5, 7, 9, 11}; + src = new Integer[] { 1, 3, 5, 7, 9, 11 }; dest = Utils.realloc(Integer.class, src, 4, false); assertThat(dest.length, is(4)); assertThat(dest[0], is(1)); diff --git a/src/test/java/zmq/util/WireTest.java b/src/test/java/zmq/util/WireTest.java new file mode 100644 index 000000000..dce08fb1e --- /dev/null +++ b/src/test/java/zmq/util/WireTest.java @@ -0,0 +1,72 @@ +package zmq.util; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.nio.ByteBuffer; + +import org.junit.Test; + +public class WireTest +{ + @Test + public void testUint32() + { + testUint32(0); + testUint32(0xff); + testUint32(0xff + 1); + testUint32(0xff - 1); + testUint32(811233478); + testUint32(12345678); + testUint32(2048 * 2000); + testUint32(2048 * 20000); + testUint32(2048 * 2048 * 2048); + testUint32(2048 * 2048 * 2048 * 2048); + testUint32(2048 * 2048 * 2048 * 2048 * 2048); + testUint32(2048 * 2048 * 2048 * 2048 * 2048 * 2048); + testUint32(2048 * 2048 * 2048 * 2048 * 2048 * 2048 * 2048); + testUint32(2048 * 2048 * 2048 * 2048 * 2048 * 2048 * 2048 * 2048); + testUint32(Integer.MAX_VALUE); + + testUint32(-811233478); + testUint32(Integer.MIN_VALUE); + } + + private void testUint32(int expected) + { + ByteBuffer buf = ByteBuffer.allocate(8); + Wire.putUInt32(buf, expected); + int actual = Wire.getUInt32(buf); + assertThat(actual, is(expected)); + } + + @Test + public void testUnsignedInteger64() + { + testUnsignedLong(0); + testUnsignedLong(0xff); + testUnsignedLong(0xff + 1); + testUnsignedLong(0xff - 1); + testUnsignedLong(811233478); + testUnsignedLong(12345678); + testUnsignedLong(2048 * 2000); + testUnsignedLong(2048 * 20000); + testUnsignedLong(2048 * 2048 * 2048); + testUnsignedLong(2048 * 2048 * 2048 * 2048); + testUnsignedLong(Integer.MAX_VALUE); + testUnsignedLong(Long.MAX_VALUE); + + testUnsignedLong(-1); + testUnsignedLong(-811233478); + testUnsignedLong(Integer.MIN_VALUE); + testUnsignedLong(Long.MIN_VALUE); + } + + private void testUnsignedLong(long expected) + { + ByteBuffer buf = ByteBuffer.allocate(8); + Wire.putUInt64(buf, expected); + long actual = Wire.getUInt64(buf, 0); + assertThat(actual, is(expected)); + } +}