
zmq.socket.pubsub.XPub Maven / Gradle / Ivy
package zmq.socket.pubsub;
import java.util.ArrayDeque;
import java.util.Deque;
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
{
private static final class SendUnsubscription implements IMtrieHandler
{
@Override
public void invoke(Pipe pipe, byte[] data, int size, XPub self)
{
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.
private final Mtrie subscriptions;
// Distributor of messages holding the list of outbound pipes.
private final Dist dist;
// If true, send all subscription messages upstream, not just
// unique ones
private boolean verbose;
// True if we are in the middle of sending a multi-part message.
private boolean more;
// Drop messages if HWM reached, otherwise return with false
private boolean lossy;
// 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 pendingFlags;
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;
verbose = false;
more = false;
lossy = true;
subscriptions = new Mtrie();
dist = new Dist();
pendingData = new ArrayDeque<>();
pendingFlags = new ArrayDeque<>();
}
@Override
protected void xattachPipe(Pipe pipe, boolean subscribeToAll)
{
assert (pipe != null);
dist.attach(pipe);
// 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);
}
// The pipe is active when attached. Let's read the subscriptions from
// it, if any.
xreadActivated(pipe);
}
@Override
protected void xreadActivated(Pipe pipe)
{
// There are some subscriptions waiting. Let's process them.
Msg sub = null;
while ((sub = pipe.read()) != null) {
// Apply the subscription to the trie
byte[] data = sub.data();
int size = sub.size();
if (size > 0 && (data[0] == 0 || data[0] == 1)) {
boolean unique;
if (data[0] == 0) {
unique = subscriptions.rm(data, size, pipe);
}
else {
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 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(sub));
pendingFlags.add(sub.flags());
}
}
}
@Override
protected void xwriteActivated(Pipe pipe)
{
dist.activated(pipe);
}
@Override
public boolean xsetsockopt(int option, Object optval)
{
if (option == ZMQ.ZMQ_XPUB_VERBOSE) {
verbose = Options.parseBoolean(option, optval);
}
else if (option == ZMQ.ZMQ_XPUB_NODROP) {
lossy = !Options.parseBoolean(option, optval);
}
else {
errno.set(ZError.EINVAL);
return false;
}
return true;
}
@Override
protected void xpipeTerminated(Pipe pipe)
{
// Remove the pipe from the trie. If there are topics that nobody
// is interested in anymore, send corresponding unsubscriptions
// upstream.
subscriptions.rm(pipe, sendUnsubscription, this);
dist.terminated(pipe);
}
private void markAsMatching(Pipe pipe)
{
dist.match(pipe);
}
@Override
protected boolean xsend(Msg msg)
{
boolean msgMore = msg.hasMore();
// For the first part of multi-part message, find the matching pipes.
if (!more) {
subscriptions.match(msg.buf(), msg.size(), markAsMatching, this);
}
if (lossy || dist.checkHwm()) {
// Send the message to all the pipes that were marked as matching
// in the previous step.
if (dist.sendToMatching(msg)) {
// If we are at the end of multi-part message we can mark all the pipes
// as non-matching.
if (!msgMore) {
dist.unmatch();
}
more = msgMore;
return true; // Yay, sent successfully
}
}
else {
errno.set(ZError.EAGAIN);
}
return false;
}
@Override
protected boolean xhasOut()
{
return dist.hasOut();
}
@Override
protected Msg xrecv()
{
// If there is at least one
if (pendingData.isEmpty()) {
errno.set(ZError.EAGAIN);
return null;
}
Blob first = pendingData.pollFirst();
Msg msg = new Msg(first.data());
int flags = pendingFlags.pollFirst();
msg.setFlags(flags);
return msg;
}
@Override
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);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy