/* =========================================================================
fmq_server.c
Generated class for fmq_server protocol server
-------------------------------------------------------------------------
Copyright (c) 1991-2012 iMatix Corporation -- http://www.imatix.com
Copyright other contributors as noted in the AUTHORS file.
This file is part of FILEMQ, see http://filemq.org.
This is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
This software is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTA-
BILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
=========================================================================
*/
package org.filemq;
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import org.zeromq.ZMQ;
import org.zeromq.ZMQ.Socket;
import org.zeromq.ZMQ.Poller;
import org.zeromq.ZThread;
import org.zeromq.ZMsg;
import org.zeromq.ZContext;
import org.zeromq.ZFrame;
// ---------------------------------------------------------------------
// Structure of our front-end API class
public class FmqServer {
private ZContext ctx; // CZMQ context
private Socket pipe; // Pipe through to server
// --------------------------------------------------------------------------
// Create a new fmq_server and a new server instance
public FmqServer ()
{
ctx = new ZContext ();
pipe = ZThread.fork (ctx, new ServerThread ());
}
// --------------------------------------------------------------------------
// Destroy the fmq_server and stop the server
public void destroy ()
{
pipe.send ("STOP");
pipe.recvStr ();
ctx.destroy ();
}
// --------------------------------------------------------------------------
// Load server configuration data
public void configure (final String config_file)
{
pipe.sendMore ("CONFIG");
pipe.send (config_file);
}
// --------------------------------------------------------------------------
// Set one configuration key value
public void setoption (final String path, final String value)
{
pipe.sendMore ("SETOPTION");
pipe.sendMore (path);
pipe.send (value);
}
// --------------------------------------------------------------------------
public int bind (final String endpoint)
{
assert (endpoint != null);
pipe.sendMore ("BIND");
pipe.send (String.format("%s", endpoint));
String reply = pipe.recvStr ();
int rc = Integer.parseInt (reply);
return rc;
}
// --------------------------------------------------------------------------
public void publish (final String location,final String alias)
{
assert (location != null);
assert (alias != null);
pipe.sendMore ("PUBLISH");
pipe.sendMore (String.format("%s", location));
pipe.send (String.format("%s", alias));
}
// --------------------------------------------------------------------------
public void setAnonymous (long enabled)
{
pipe.sendMore ("SET ANONYMOUS");
pipe.send (String.format("%d", enabled));
}
// ---------------------------------------------------------------------
// State machine constants
private enum State {
start_state (1),
checking_client_state (2),
challenging_client_state (3),
ready_state (4),
dispatching_state (5);
@SuppressWarnings ("unused")
private final int state;
State (int state)
{
this.state = state;
}
};
private enum Event {
terminate_event (-1),
ohai_event (1),
heartbeat_event (2),
expired_event (3),
_other_event (4),
friend_event (5),
foe_event (6),
maybe_event (7),
yarly_event (8),
icanhaz_event (9),
nom_event (10),
hugz_event (11),
kthxbai_event (12),
dispatch_event (13),
send_chunk_event (14),
send_delete_event (15),
next_patch_event (16),
no_credit_event (17),
finished_event (18);
@SuppressWarnings ("unused")
private final int event;
Event (int event)
{
this.event = event;
}
};
// There's no point making these configurable
private static final int CHUNK_SIZE = 1000000;
// --------------------------------------------------------------------------
// Subscription object
private static class Sub {
private Client client; // Always refers to live client
private String path; // Path client is subscribed to
private Map <String, String> cache; // Client's cache list
// --------------------------------------------------------------------------
// Constructor
private Sub (Client client, String path, Map <String, String> cache)
{
this.client = client;
this.path = path;
this.cache = new HashMap <String, String> (cache);
resolveCachePath (this.cache, this);
}
// Cached filenames may be local, in which case prefix them with
// the subscription path so we can do a consistent match.
private static void resolveCachePath (Map <String, String> cache, Sub self)
{
for (String key : cache.keySet ()) {
if (!key.startsWith ("/")) {
String new_key = String.format ("%s/%s", self.path, key);
cache.put (new_key, cache.remove (key));
}
}
}
// --------------------------------------------------------------------------
// Destructor
private void destroy ()
{
}
// --------------------------------------------------------------------------
// Add patch to sub client patches list
private void addPatch (FmqPatch patch)
{
// Skip file creation if client already has identical file
patch.setDigest ();
if (patch.op () == FmqPatch.OP.patch_create) {
String digest = cache.get (patch.virtual ());
if (digest != null && digest.compareToIgnoreCase (patch.digest ()) == 0)
return; // Just skip patch for this client
}
// Remove any previous patches for the same file
Iterator <FmqPatch> it = client.patches.iterator ();
while (it.hasNext ()) {
FmqPatch existing = it.next ();
if (patch.virtual ().equals (existing.virtual ())) {
it.remove ();
existing.destroy ();
break;
}
}
// Track that we've queued patch for client, so we don't do it twice
cache.put (patch.digest (), patch.virtual ());
client.patches.add (patch.dup ());
}
}
// --------------------------------------------------------------------------
// Mount point in memory
private static class Mount {
private String location; // Physical location
private String alias; // Alias into our tree
private FmqDir dir; // Directory snapshot
private List <Sub> subs; // Client subscriptions
// --------------------------------------------------------------------------
// Constructor
// Loads directory tree if possible
private Mount (String location, String alias)
{
// Mount path must start with '/'
// We'll do better error handling later
assert (alias.startsWith ("/"));
this.location = location;
this.alias = alias;
dir = FmqDir.newFmqDir (location, null);
subs = new ArrayList <Sub> ();
}
// --------------------------------------------------------------------------
// Destructor
private void destroy ()
{
// Destroy subscriptions
for (Sub sub : subs) {
sub.destroy ();
}
if (dir != null)
dir.destroy ();
}
// --------------------------------------------------------------------------
// Reloads directory tree and returns true if activity, false if the same
private boolean refresh (Server server)
{
boolean activity = false;
// Get latest snapshot and build a patches list for any changes
FmqDir latest = FmqDir.newFmqDir (location, null);
List <FmqPatch> patches = FmqDir.diff (dir, latest, alias);
// Drop old directory and replace with latest version
if (dir != null)
dir.destroy ();
dir = latest;
// Copy new patches to clients' patches list
for (Sub sub : subs) {
for (FmqPatch patch : patches) {
sub.addPatch (patch);
activity = true;
}
}
// Destroy patches, they've all been copied
for (FmqPatch patch : patches) {
patch.destroy ();
}
return activity;
}
// --------------------------------------------------------------------------
// Store subscription for mount point
private void storeSub (Client client, FmqMsg request)
{
// Store subscription along with any previous ones
// Coalesce subscriptions that are on same path
String path = request.path ();
Iterator <Sub> it = subs.iterator ();
while (it.hasNext ()) {
Sub sub = it.next ();
if (client == sub.client) {
// If old subscription is superset/same as new, ignore new
if (path.startsWith (sub.path))
return;
else
// If new subscription is superset of old one, remove old
if (sub.path.startsWith (path)) {
it.remove ();
sub.destroy ();
}
}
}
// New subscription for this client, append to our list
Sub sub = new Sub (client, path, request.cache ());
subs.add (sub);
// If client requested resync, send full mount contents now
if (request.optionsNumber ("RESYNC", 0) == 1) {
List <FmqPatch> patches = FmqDir.resync (dir, alias);
for (FmqPatch patch : patches) {
sub.addPatch (patch);
patch.destroy ();
}
}
}
// --------------------------------------------------------------------------
// Purge subscriptions for a specified client
private void purgeSub (Client client)
{
Iterator <Sub> it = subs.iterator ();
while (it.hasNext ()) {
Sub sub = it.next ();
if (sub.client == client) {
it.remove ();
sub.destroy ();
}
}
}
}
// Client hash function that checks if client is alive
private static void clientDispatch (Map <String, Client> clients, Server server)
{
for (Client client : clients.values ())
server.clientExecute (client, Event.dispatch_event);
}
// ---------------------------------------------------------------------
// Context for each client connection
private static class Client {
// Properties accessible to client actions
private long heartbeat; // Heartbeat interval
private Event next_event; // Next event
private int credit; // Credit remaining
private List <FmqPatch> patches; // Patches to send
private FmqPatch patch; // Current patch
private FmqFile file; // Current file we're sending
private long offset; // Offset of next read in file
private long sequence; // Sequence number for chunk
// Properties you should NOT touch
private Socket router; // Socket to client
private long heartbeat_at; // Next heartbeat at this time
private long expires_at; // Expires at this time
private State state; // Current state
private Event event; // Current event
private String hashkey; // Key into clients hash
private ZFrame address; // Client address identity
private FmqMsg request; // Last received request
private FmqMsg reply; // Reply to send out, if any
// Client methods
private Client (String hashkey, ZFrame address)
{
this.hashkey = hashkey;
this.state = State.start_state;
this.address = address.duplicate ();
reply = new FmqMsg (0);
reply.setAddress (this.address);
patches = new ArrayList <FmqPatch> ();
}
private void destroy ()
{
if (address != null)
address.destroy ();
if (request != null)
request.destroy ();
if (reply != null)
reply.destroy ();
for (FmqPatch patch : patches)
patch.destroy ();
}
private void setRequest (FmqMsg request)
{
if (this.request != null)
this.request.destroy ();
this.request = request;
// Any input from client counts as heartbeat
heartbeat_at = System.currentTimeMillis () + heartbeat;
// Any input from client counts as activity
expires_at = System.currentTimeMillis () + heartbeat * 3;
}
}
// Client hash function that calculates tickless timer
private static long clientTickless (Map <String, Client> clients , long tickless)
{
for (Client self: clients.values ()) {
if (tickless > self.heartbeat_at)
tickless = self.heartbeat_at;
}
return tickless;
}
// Client hash function that checks if client is alive
private static void clientPing (Map <String, Client> clients , Server server)
{
Iterator <Map.Entry <String, Client>> it = clients.entrySet ().iterator ();
while (it.hasNext ()) {
Client self = it.next ().getValue ();
// Expire client if it's not answered us in a while
if (System.currentTimeMillis () >= self.expires_at && self.expires_at > 0) {
// In case dialog doesn't handle expired_event by destroying
// client, set expires_at to zero to prevent busy looping
self.expires_at = 0;
if (server.clientExecute (self, Event.expired_event, true))
it.remove ();
}
else
// Check whether to send heartbeat to client
if (System.currentTimeMillis () >= self.heartbeat_at) {
server.clientExecute (self, Event.heartbeat_event);
self.heartbeat_at = System.currentTimeMillis () + self.heartbeat;
}
}
}
// ---------------------------------------------------------------------
// Context for the server thread
private static class Server {
// Properties accessible to client actions
private List <Mount> mounts; // Mount points
private int port; // Server port
// Properties you should NOT touch
private ZContext ctx; // Own CZMQ context
private Socket pipe; // Socket to back to caller
private Socket router; // Socket to talk to clients
private Map <String, Client> clients; // Clients we've connected to
private boolean stopped; // Has server stopped?
private FmqConfig config; // Configuration tree
private int monitor; // Monitor interval
private long monitor_at; // Next monitor at this time
private int heartbeat; // Heartbeat for clients
// Server methods
private void config ()
{
// Get standard server configuration
monitor = Integer.parseInt (
config.resolve ("server/monitor", "1")) * 1000;
heartbeat = Integer.parseInt (
config.resolve ("server/heartbeat", "1")) * 1000;
monitor_at = System.currentTimeMillis () + monitor;
}
private Server (ZContext ctx, Socket pipe)
{
this.ctx = ctx;
this.pipe = pipe;
router = ctx.createSocket (ZMQ.ROUTER);
clients = new HashMap <String, Client> ();
config = new FmqConfig ("root", null);
config ();
mounts = new ArrayList <Mount> ();
}
private void destroy ()
{
ctx.destroySocket (router);
config.destroy ();
for (Client c: clients.values ())
c.destroy ();
// Destroy mount points
for (Mount mount : mounts)
mount.destroy ();
}
// Apply configuration tree:
// * apply server configuration
// * print any echo items in top-level sections
// * apply sections that match methods
private void applyConfig ()
{
// Apply echo commands and class methods
FmqConfig section = config.child ();
while (section != null) {
FmqConfig entry = section.child ();
while (entry != null) {
if (entry.name ().equals ("echo"))
zclock_log (entry.value ());
entry = entry.next ();
}
if (section.name ().equals ("bind")) {
String endpoint = section.resolve ("endpoint", "?");
port = bind (router, endpoint);
}
else
if (section.name ().equals ("publish")) {
String location = section.resolve ("location", "?");
String alias = section.resolve ("alias", "?");
Mount mount = new Mount (location, alias);
mounts.add (mount);
}
else
if (section.name ().equals ("set_anonymous")) {
long enabled = Long.parseLong (section.resolve ("enabled", ""));
// Enable anonymous access without a config file
config.setPath ("security/anonymous", enabled > 0 ? "1" :"0");
}
section = section.next ();
}
config ();
}
private void controlMessage ()
{
ZMsg msg = ZMsg.recvMsg (pipe);
String method = msg.popString ();
if (method.equals ("BIND")) {
String endpoint = msg.popString ();
port = bind (router, endpoint);
pipe.send (String.format ("%d", port));
}
else
if (method.equals ("PUBLISH")) {
String location = msg.popString ();
String alias = msg.popString ();
Mount mount = new Mount (location, alias);
mounts.add (mount);
}
else
if (method.equals ("SET ANONYMOUS")) {
long enabled = Long.parseLong (msg.popString ());
// Enable anonymous access without a config file
config.setPath ("security/anonymous", enabled > 0 ? "1" :"0");
}
else
if (method.equals ("CONFIG")) {
String config_file = msg.popString ();
config.destroy ();
config = FmqConfig.load (config_file);
if (config != null)
applyConfig ();
else {
System.out.printf ("E: cannot load config file '%s'\n", config_file);
config = new FmqConfig ("root", null);
}
}
else
if (method.equals ("SETOPTION")) {
String path = msg.popString ();
String value = msg.popString ();
config.setPath (path, value);
config ();
}
else
if (method.equals ("STOP")) {
pipe.send ("OK");
stopped = true;
}
msg.destroy ();
msg = null;
}
// Custom actions for state machine
private void terminateTheClient (Client client)
{
for (Mount mount : mounts) {
mount.purgeSub (client);
}
client.next_event = Event.terminate_event;
}
private void tryAnonymousAccess (Client client)
{
if (Integer.parseInt (config.resolve ("security/anonymous", "0")) == 1)
client.next_event = Event.friend_event;
else
if (Integer.parseInt (config.resolve ("security/plain", "0")) == 1)
client.next_event = Event.maybe_event;
else
client.next_event = Event.foe_event;
}
private void listSecurityMechanisms (Client client)
{
if (Integer.parseInt (config.resolve ("security/anonymous", "0")) == 1)
client.reply.appendMechanisms ("ANONYMOUS");
if (Integer.parseInt (config.resolve ("security/plain", "0")) == 1)
client.reply.appendMechanisms ("PLAIN");
}
private void trySecurityMechanism (Client client)
{
client.next_event = Event.foe_event;
String [] result = new String [2];
String login, password;
if (client.request.mechanism ().equals ("PLAIN")
&& FmqSasl.plainDecode (client.request.response (), result)) {
login = result [0];
password = result [1];
FmqConfig account = config.locate ("security/plain/account");
while (account != null) {
if (account.resolve ("login", "").equals (login)
&& account.resolve ("password", "").equals (password)) {
client.next_event = Event.friend_event;
break;
}
account = account.next ();
}
}
}
private void storeClientSubscription (Client client)
{
// Find mount point with longest match to subscription
String path = client.request.path ();
Mount mount = mounts.isEmpty () ? null : mounts.get (0);
for (Mount check : mounts) {
// If check->alias is prefix of path and alias is
// longer than current mount then we have a new mount
if (path.startsWith (check.alias)
&& check.alias.length () > mount.alias.length ())
mount = check;
}
mount.storeSub (client, client.request);
}
private void storeClientCredit (Client client)
{
client.credit += client.request.credit ();
}
private void monitorTheServer (Client client)
{
boolean activity = false;
for (Mount mount : mounts) {
if (mount.refresh (this))
activity = true;
}
if (activity)
clientDispatch (clients, this);
}
private void getNextPatchForClient (Client client)
{
// Get next patch for client if we're not doing one already
if (client.patch == null)
client.patch = client.patches.isEmpty () ? null : client.patches.remove (0);
if (client.patch == null) {
client.next_event = Event.finished_event;
return;
}
// Get virtual filename from patch
client.reply.setFilename (client.patch.virtual ());
// We can process a delete patch right away
if (client.patch.op () == FmqPatch.OP.patch_delete) {
client.reply.setSequence (client.sequence++);
client.reply.setOperation (FmqMsg.FMQ_MSG_FILE_DELETE);
client.next_event = Event.send_delete_event;
// No reliability in this version, assume patch delivered safely
client.patch.destroy ();
client.patch = null;
}
else
if (client.patch.op () == FmqPatch.OP.patch_create) {
// Create patch refers to file, open that for input if needed
if (client.file == null) {
client.file = client.patch.file ().dup ();
if (client.file.input () == false) {
// File no longer available, skip it
client.patch.destroy ();
client.file.destroy ();
client.patch = null;
client.file = null;
client.next_event = Event.next_patch_event;
return;
}
client.offset = 0;
}
// Get next chunk for file
FmqChunk chunk = client.file.read (CHUNK_SIZE, client.offset);
assert (chunk != null);
// Check if we have the credit to send chunk
if (chunk.size () <= client.credit) {
client.reply.setSequence (client.sequence++);
client.reply.setOperation (FmqMsg.FMQ_MSG_FILE_CREATE);
client.reply.setOffset (client.offset);
client.reply.setChunk (new ZFrame (chunk.data ()));
client.offset += chunk.size ();
client.credit -= chunk.size ();
client.next_event = Event.send_chunk_event;
// Zero-sized chunk means end of file
if (chunk.size () == 0) {
client.file.destroy ();
client.patch.destroy ();
client.patch = null;
client.file = null;
}
}
else
client.next_event = Event.no_credit_event;
chunk.destroy ();
}
}
// Execute state machine as long as we have events
// Returns true, if it requires remove on iteration
private boolean clientExecute (Client client, Event event)
{
return clientExecute (client, event, false);
}
private boolean clientExecute (Client client, Event event, boolean oniter)
{
client.next_event = event;
while (client.next_event != null) {
client.event = client.next_event;
client.next_event = null;
switch (client.state) {
case start_state:
if (client.event == Event.ohai_event) {
tryAnonymousAccess (client);
client.state = State.checking_client_state;
}
else
if (client.event == Event.heartbeat_event) {
}
else
if (client.event == Event.expired_event) {
terminateTheClient (client);
}
else {
// Process all other events
client.reply.setId (FmqMsg.RTFM);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
terminateTheClient (client);
}
break;
case checking_client_state:
if (client.event == Event.friend_event) {
client.reply.setId (FmqMsg.OHAI_OK);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
client.state = State.ready_state;
}
else
if (client.event == Event.foe_event) {
client.reply.setId (FmqMsg.SRSLY);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
terminateTheClient (client);
}
else
if (client.event == Event.maybe_event) {
listSecurityMechanisms (client);
client.reply.setId (FmqMsg.ORLY);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
client.state = State.challenging_client_state;
}
else
if (client.event == Event.heartbeat_event) {
}
else
if (client.event == Event.expired_event) {
terminateTheClient (client);
}
else
if (client.event == Event.ohai_event) {
tryAnonymousAccess (client);
client.state = State.checking_client_state;
}
else {
// Process all other events
client.reply.setId (FmqMsg.RTFM);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
terminateTheClient (client);
}
break;
case challenging_client_state:
if (client.event == Event.yarly_event) {
trySecurityMechanism (client);
client.state = State.checking_client_state;
}
else
if (client.event == Event.heartbeat_event) {
}
else
if (client.event == Event.expired_event) {
terminateTheClient (client);
}
else
if (client.event == Event.ohai_event) {
tryAnonymousAccess (client);
client.state = State.checking_client_state;
}
else {
// Process all other events
client.reply.setId (FmqMsg.RTFM);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
terminateTheClient (client);
}
break;
case ready_state:
if (client.event == Event.icanhaz_event) {
storeClientSubscription (client);
client.reply.setId (FmqMsg.ICANHAZ_OK);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
}
else
if (client.event == Event.nom_event) {
storeClientCredit (client);
getNextPatchForClient (client);
client.state = State.dispatching_state;
}
else
if (client.event == Event.hugz_event) {
client.reply.setId (FmqMsg.HUGZ_OK);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
}
else
if (client.event == Event.kthxbai_event) {
terminateTheClient (client);
}
else
if (client.event == Event.dispatch_event) {
getNextPatchForClient (client);
client.state = State.dispatching_state;
}
else
if (client.event == Event.heartbeat_event) {
client.reply.setId (FmqMsg.HUGZ);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
}
else
if (client.event == Event.expired_event) {
terminateTheClient (client);
}
else
if (client.event == Event.ohai_event) {
tryAnonymousAccess (client);
client.state = State.checking_client_state;
}
else {
// Process all other events
client.reply.setId (FmqMsg.RTFM);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
terminateTheClient (client);
}
break;
case dispatching_state:
if (client.event == Event.send_chunk_event) {
client.reply.setId (FmqMsg.CHEEZBURGER);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
getNextPatchForClient (client);
}
else
if (client.event == Event.send_delete_event) {
client.reply.setId (FmqMsg.CHEEZBURGER);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
getNextPatchForClient (client);
}
else
if (client.event == Event.next_patch_event) {
getNextPatchForClient (client);
}
else
if (client.event == Event.no_credit_event) {
client.state = State.ready_state;
}
else
if (client.event == Event.finished_event) {
client.state = State.ready_state;
}
else
if (client.event == Event.heartbeat_event) {
}
else
if (client.event == Event.expired_event) {
terminateTheClient (client);
}
else
if (client.event == Event.ohai_event) {
tryAnonymousAccess (client);
client.state = State.checking_client_state;
}
else {
// Process all other events
client.reply.setId (FmqMsg.RTFM);
client.reply.send (client.router);
client.reply = new FmqMsg (0);
client.reply.setAddress (client.address);
terminateTheClient (client);
}
break;
}
if (client.next_event == Event.terminate_event) {
// Automatically calls client_destroy
client.destroy ();
if (oniter)
return true;
clients.remove (client.hashkey);
break;
}
}
return false;
}
private void clientMessage ()
{
FmqMsg request = FmqMsg.recv (router);
if (request == null)
return; // Interrupted; do nothing
String hashkey = request.address ().strhex ();
Client client = clients.get (hashkey);
if (client == null) {
client = new Client (hashkey, request.address ());
client.heartbeat = heartbeat;
client.router = router;
clients.put (hashkey, client);
}
client.setRequest (request);
if (request.id () == FmqMsg.OHAI)
clientExecute (client, Event.ohai_event);
else
if (request.id () == FmqMsg.YARLY)
clientExecute (client, Event.yarly_event);
else
if (request.id () == FmqMsg.ICANHAZ)
clientExecute (client, Event.icanhaz_event);
else
if (request.id () == FmqMsg.NOM)
clientExecute (client, Event.nom_event);
else
if (request.id () == FmqMsg.HUGZ)
clientExecute (client, Event.hugz_event);
else
if (request.id () == FmqMsg.KTHXBAI)
clientExecute (client, Event.kthxbai_event);
}
}
// The server runs as a background thread so that we can run multiple
// engines at once. The API talks to the server thread over an inproc
// pipe.
// Finally here's the server thread itself, which polls its two
// sockets and processes incoming messages
private static class ServerThread
implements ZThread.IAttachedRunnable {
@Override
public void run (Object [] args, ZContext ctx, Socket pipe)
{
Server self = new Server (ctx, pipe);
Poller items = ctx.getContext ().poller ();
items.register (self.pipe, Poller.POLLIN);
items.register (self.router, Poller.POLLIN);
self.monitor_at = System.currentTimeMillis () + self.monitor;
while (!self.stopped && !Thread.currentThread ().isInterrupted ()) {
// Calculate tickless timer, up to interval seconds
long tickless = System.currentTimeMillis () + self.monitor;
tickless = clientTickless (self.clients, tickless);
// Poll until at most next timer event
long rc = items.poll (tickless - System.currentTimeMillis ());
if (rc == -1)
break; // Context has been shut down
// Process incoming message from either socket
if (items.pollin (0))
self.controlMessage ();
if (items.pollin (1))
self.clientMessage ();
// Send heartbeats to idle clients as needed
clientPing (self.clients, self);
// If clock went past timeout, then monitor server
if (System.currentTimeMillis () >= self.monitor_at) {
self.monitorTheServer (null);
self.monitor_at = System.currentTimeMillis () + self.monitor;
}
}
self.destroy ();
}
}
public static void zclock_log (String fmt, Object ... args)
{
System.out.println (String.format (fmt, args));
}
public static int bind (Socket sock, String addr)
{
int pos = addr.lastIndexOf (':');
String port = addr.substring (pos + 1);
if (port.equals ("*"))
return sock.bindToRandomPort (addr.substring (0, pos));
else {
sock.bind (addr);
return Integer.parseInt (port);
}
}
}