org.jgroups.stack.GossipRouter Maven / Gradle / Ivy
package org.jgroups.stack;
import org.jgroups.Address;
import org.jgroups.Global;
import org.jgroups.Message;
import org.jgroups.PhysicalAddress;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.blocks.cs.*;
import org.jgroups.jmx.JmxConfigurator;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.PingData;
import org.jgroups.util.*;
import java.io.DataInput;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Router for TCP based group comunication (using layer TCP instead of UDP). Instead of the TCP
* layer sending packets point-to-point to each other member, it sends the packet to the router
* which - depending on the target address - multicasts or unicasts it to the group / or single member.
*
* This class is especially interesting for applets which cannot directly make connections (neither
* UDP nor TCP) to a host different from the one they were loaded from. Therefore, an applet would
* create a normal channel plus protocol stack, but the bottom layer would have to be the TCP layer
* which sends all packets point-to-point (over a TCP connection) to the router, which in turn
* forwards them to their end location(s) (also over TCP). A centralized router would therefore have
* to be running on the host the applet was loaded from.
*
* An alternative for running JGroups in an applet (IP multicast is not allows in applets as of
* 1.2), is to use point-to-point UDP communication via the gossip server. However, then the appplet
* has to be signed which involves additional administrative effort on the part of the user.
*
* Note that a GossipRouter is also a good way of running JGroups in Amazon's EC2 environment which (as of summer 09)
* doesn't support IP multicasting.
* @author Bela Ban
* @author Vladimir Blagojevic
* @author Ovidiu Feodorov
* @since 2.1.1
*/
public class GossipRouter extends ReceiverAdapter implements ConnectionListener {
@ManagedAttribute(description="address to which the GossipRouter should bind", writable=true, name="bind_address")
protected String bind_addr;
@ManagedAttribute(description="server port on which the GossipRouter accepts client connections", writable=true)
protected int port=12001;
@ManagedAttribute(description="time (in msecs) until gossip entry expires. 0 disables expiration.", writable=true)
protected long expiry_time;
@Property(description="Time (in ms) for setting SO_LINGER on sockets returned from accept(). 0 means do not set SO_LINGER")
protected long linger_timeout=2000L;
@Property(description="Time (in ms) for SO_TIMEOUT on sockets returned from accept(). 0 means don't set SO_TIMEOUT")
protected long sock_read_timeout;
protected ThreadFactory thread_factory=new DefaultThreadFactory("gossip", false, true);
protected SocketFactory socket_factory=new DefaultSocketFactory();
@Property(description="The max queue size of backlogged connections")
protected int backlog=1000;
@Property(description="Expose GossipRouter via JMX",writable=false)
protected boolean jmx=true;
@Property(description="Use non-blocking IO (true) or blocking IO (false). Cannot be changed at runtime",writable=false)
protected boolean use_nio=true;
@Property(description="Handles client disconnects: sends SUSPECT message to all other members of that group")
protected boolean emit_suspect_events=true;
@Property(description="Dumps messages (dest/src/length/headers to stdout if enabled")
protected boolean dump_msgs;
protected BaseServer server;
protected final AtomicBoolean running=new AtomicBoolean(false);
protected Timer timer;
protected final Log log=LogFactory.getLog(this.getClass());
// mapping between groups and - pairs
protected final ConcurrentMap> address_mappings=new ConcurrentHashMap<>();
public GossipRouter(String bind_addr, int local_port) {
this.bind_addr=bind_addr;
this.port=local_port;
}
public Address localAddress() {return server.localAddress();}
public String bindAddress() {return bind_addr;}
public GossipRouter bindAddress(String addr) {this.bind_addr=addr; return this;}
public int port() {return port;}
public GossipRouter port(int port) {this.port=port; return this;}
public long expiryTime() {return expiry_time;}
public GossipRouter expiryTime(long t) {this.expiry_time=t; return this;}
public long lingerTimeout() {return linger_timeout;}
public GossipRouter lingerTimeout(long t) {this.linger_timeout=t; return this;}
public long socketReadTimeout() {return sock_read_timeout;}
public GossipRouter socketReadTimeout(long t) {this.sock_read_timeout=t; return this;}
public ThreadFactory threadPoolFactory() {return thread_factory;}
public GossipRouter threadPoolFactory(ThreadFactory f) {this.thread_factory=f; return this;}
public SocketFactory socketFactory() {return socket_factory;}
public GossipRouter socketFactory(SocketFactory sf) {this.socket_factory=sf; return this;}
public int backlog() {return backlog;}
public GossipRouter backlog(int backlog) {this.backlog=backlog; return this;}
public boolean jmx() {return jmx;}
public GossipRouter jmx(boolean flag) {jmx=flag; return this;}
public boolean useNio() {return use_nio;}
public GossipRouter useNio(boolean flag) {use_nio=flag; return this;}
public boolean emitSuspectEvents() {return emit_suspect_events;}
public GossipRouter emitSuspectEvents(boolean flag) {emit_suspect_events=flag; return this;}
public boolean dumpMessages() {return dump_msgs;}
public GossipRouter dumpMessages(boolean flag) {dump_msgs=flag; return this;}
@ManagedAttribute(description="operational status", name="running")
public boolean running() {return running.get();}
/**
* Lifecycle operation. Called after create(). When this method is called, the managed attributes
* have already been set.
* Brings the Router into a fully functional state.
*/
@ManagedOperation(description="Lifecycle operation. Called after create(). When this method is called, "
+ "the managed attributes have already been set. Brings the Router into a fully functional state.")
public void start() throws Exception {
if(!running.compareAndSet(false, true))
return;
if(jmx)
JmxConfigurator.register(this, Util.getMBeanServer(), "jgroups:name=GossipRouter");
InetAddress tmp=bind_addr != null? InetAddress.getByName(bind_addr) : null;
server=use_nio? new NioServer(thread_factory, socket_factory, tmp, port, port, null, 0)
: new TcpServer(thread_factory, socket_factory, tmp, port, port, null, 0);
server.receiver(this);
server.start();
server.addConnectionListener(this);
Runtime.getRuntime().addShutdownHook(new Thread(GossipRouter.this::stop));
}
/**
* Always called before destroy(). Close connections and frees resources.
*/
@ManagedOperation(description="Always called before destroy(). Closes connections and frees resources")
public void stop() {
if(!running.compareAndSet(true, false))
return;
try {
JmxConfigurator.unregister(this, Util.getMBeanServer(), "jgroups:name=GossipRouter");
}
catch(Exception ex) {
log.error(Util.getMessage("MBeanDeRegistrationFailed"), ex);
}
Util.close(server);
log.debug("router stopped");
}
@ManagedOperation(description="Dumps the contents of the routing table")
public String dumpRoutingTable() {
return server.printConnections();
}
@ManagedOperation(description="Dumps the address mappings")
public String dumpAddresssMappings() {
StringBuilder sb=new StringBuilder();
for(Map.Entry> entry: address_mappings.entrySet()) {
String group=entry.getKey();
Map val=entry.getValue();
if(val == null)
continue;
sb.append(group).append(":\n");
for(Map.Entry entry2: val.entrySet()) {
Address logical_addr=entry2.getKey();
Entry val2=entry2.getValue();
if(val2 == null)
continue;
sb.append(String.format(" %s: %s (client_addr: %s, uuid:%s)\n", val2.logical_name, val2.phys_addr, val2.client_addr, logical_addr));
}
}
return sb.toString();
}
@Override public void receive(Address sender, byte[] buf, int offset, int length) {
ByteArrayDataInputStream in=new ByteArrayDataInputStream(buf, offset, length);
GossipType type;
try {
type=GossipType.values()[in.readByte()];
in.position(Global.BYTE_SIZE);
}
catch(Exception ex) {
log.error("failed reading data from %s: %s", sender, ex);
return;
}
switch(type) {
case REGISTER:
handleRegister(sender, in);
break;
case MESSAGE:
// we already read the type, now read group and dest (minimal info required to route the message)
// this way, we don't need to copy the buffer
try {
String group=Bits.readString(in);
Address dest=Util.readAddress(in);
route(group, dest, buf, offset, length);
if(dump_msgs) {
ByteArrayDataInputStream input=new ByteArrayDataInputStream(buf, offset, length);
GossipData data=new GossipData();
data.readFrom(input);
dump(data);
}
}
catch(Throwable t) {
log.error(Util.getMessage("FailedReadingRequest"), t);
return;
}
break;
case GET_MBRS:
handleGetMembersRequest(sender, in);
break;
case UNREGISTER:
handleUnregister(in);
break;
}
}
public void receive(Address sender, DataInput in) throws Exception {
GossipType type=GossipType.values()[in.readByte()];
GossipData request=null;
switch(type) {
case REGISTER:
handleRegister(sender, in);
break;
case MESSAGE:
try {
// inefficient: we should transfer bytes from input stream to output stream, but Server currently
// doesn't provide a way of reading from an input stream
if((request=readRequest(in, type)) != null) {
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(request.serializedSize());
request.writeTo(out);
route(request.group, request.addr, out.buffer(), 0, out.position());
}
if(dump_msgs)
dump(request);
}
catch(Throwable t) {
log.error(Util.getMessage("FailedReadingRequest"), t);
return;
}
break;
case GET_MBRS:
handleGetMembersRequest(sender, in);
break;
case UNREGISTER:
handleUnregister(in);
break;
}
}
protected void handleRegister(Address sender, DataInput in) {
GossipData req=readRequest(in, GossipType.REGISTER);
if(req != null) {
String group=req.getGroup();
Address addr=req.getAddress();
PhysicalAddress phys_addr=req.getPhysicalAddress();
String logical_name=req.getLogicalName();
addAddressMapping(sender, group, addr, phys_addr, logical_name);
}
}
protected void handleUnregister(DataInput in) {
GossipData req=readRequest(in, GossipType.UNREGISTER);
if(req != null)
removeAddressMapping(req.getGroup(), req.getAddress());
}
protected void handleGetMembersRequest(Address sender, DataInput in) {
GossipData req=readRequest(in, GossipType.GET_MBRS);
if(req == null)
return;
GossipData rsp=new GossipData(GossipType.GET_MBRS_RSP, req.getGroup(), null);
Map members=address_mappings.get(req.getGroup());
if(members != null) {
for(Map.Entry entry : members.entrySet()) {
Address logical_addr=entry.getKey();
PhysicalAddress phys_addr=entry.getValue().phys_addr;
String logical_name=entry.getValue().logical_name;
PingData data=new PingData(logical_addr, true, logical_name, phys_addr);
rsp.addPingData(data);
}
}
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(rsp.serializedSize());
try {
rsp.writeTo(out);
server.send(sender, out.buffer(), 0, out.position());
}
catch(Exception ex) {
log.error("failed sending %d to %s: %s", GossipType.GET_MBRS_RSP, sender, ex);
}
}
protected static void dump(GossipData data) {
System.out.println("");
List messages=Util.parse(data.buffer, data.offset, data.length);
if(messages != null)
for(Message msg : messages)
System.out.printf("dst=%s src=%s (%d bytes): hdrs= %s\n", msg.dest(), msg.src(), msg.getLength(), msg.printHeaders());
}
@Override
public void connectionClosed(Connection conn, String reason) {
removeFromAddressMappings(conn.peerAddress());
}
@Override
public void connectionEstablished(Connection conn) {
log.debug("connection to %s established", conn.peerAddress());
}
protected GossipData readRequest(DataInput in) {
GossipData data=new GossipData();
try {
data.readFrom(in);
return data;
}
catch(Exception ex) {
log.error(Util.getMessage("FailedReadingRequest"), ex);
return null;
}
}
protected GossipData readRequest(DataInput in, GossipType type) {
GossipData data=new GossipData(type);
try {
data.readFrom(in, false);
return data;
}
catch(Exception ex) {
log.error(Util.getMessage("FailedReadingRequest"), ex);
return null;
}
}
protected void addAddressMapping(Address sender, String group, Address addr, PhysicalAddress phys_addr, String logical_name) {
ConcurrentMap m=address_mappings.get(group);
if(m == null) {
ConcurrentMap existing=this.address_mappings.putIfAbsent(group, m=new ConcurrentHashMap<>());
if(existing != null)
m=existing;
}
m.put(addr, new Entry(sender, phys_addr, logical_name));
}
protected void removeAddressMapping(String group, Address addr) {
Map m=address_mappings.get(group);
if(m == null)
return;
if(m.remove(addr) != null && m.isEmpty())
address_mappings.remove(group);
}
protected void removeFromAddressMappings(Address client_addr) {
if(client_addr == null) return;
Set> suspects=null; // group/address pairs
for(Map.Entry> entry: address_mappings.entrySet()) {
ConcurrentMap map=entry.getValue();
for(Map.Entry entry2: map.entrySet()) {
Entry e=entry2.getValue();
if(client_addr.equals(e.client_addr)) {
map.remove(entry2.getKey());
log.debug("connection to %s closed", client_addr);
if(map.isEmpty())
address_mappings.remove(entry.getKey());
if(suspects == null) suspects=new HashSet<>();
suspects.add(new Tuple<>(entry.getKey(), entry2.getKey()));
break;
}
}
}
if(emit_suspect_events && suspects != null && !suspects.isEmpty()) {
for(Tuple suspect: suspects) {
String group=suspect.getVal1();
Address addr=suspect.getVal2();
ConcurrentMap map=address_mappings.get(group);
if(map == null)
continue;
GossipData data=new GossipData(GossipType.SUSPECT, group, addr);
sendToAllMembersInGroup(map.entrySet(), data);
}
}
}
protected void route(String group, Address dest, byte[] msg, int offset, int length) {
ConcurrentMap map=address_mappings.get(group);
if(map == null)
return;
if(dest != null) { // unicast
Entry entry=map.get(dest);
if(entry != null)
sendToMember(entry.client_addr, msg, offset, length);
}
else { // multicast - send to all members in group
Set> dests=map.entrySet();
sendToAllMembersInGroup(dests, msg, offset, length);
}
}
protected void sendToAllMembersInGroup(Set> dests, GossipData request) {
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(request.serializedSize());
try {
request.writeTo(out);
}
catch(Exception ex) {
log.error("failed marshalling gossip data %s: %s; dropping request", request, ex);
return;
}
for(Map.Entry entry: dests) {
Entry e=entry.getValue();
if(e == null /* || e.phys_addr == null */)
continue;
try {
server.send(e.client_addr, out.buffer(), 0, out.position());
}
catch(Exception ex) {
log.error("failed sending message to %s (%s): %s", e.logical_name, e.phys_addr, ex);
}
}
}
protected void sendToAllMembersInGroup(Set> dests, byte[] buf, int offset, int len) {
for(Map.Entry entry: dests) {
Entry e=entry.getValue();
if(e == null /* || e.phys_addr == null */)
continue;
try {
server.send(e.client_addr, buf, offset, len);
}
catch(Exception ex) {
log.error("failed sending message to %s (%s): %s", e.logical_name, e.phys_addr, ex);
}
}
}
protected void sendToMember(Address dest, GossipData request) {
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(request.serializedSize());
try {
request.writeTo(out);
server.send(dest, out.buffer(), 0, out.position());
}
catch(Exception ex) {
log.error("failed sending unicast message to %s: %s", dest, ex);
}
}
protected void sendToMember(Address dest, byte[] buf, int offset, int len) {
try {
server.send(dest, buf, offset, len);
}
catch(Exception ex) {
log.error("failed sending unicast message to %s: %s", dest, ex);
}
}
protected static class Entry {
protected final PhysicalAddress phys_addr;
protected final String logical_name;
protected final Address client_addr; // address of the client which registered an item
public Entry(Address client_addr, PhysicalAddress phys_addr, String logical_name) {
this.phys_addr=phys_addr;
this.logical_name=logical_name;
this.client_addr=client_addr;
}
public String toString() {return String.format("client=%s, name=%s, addr=%s", client_addr, logical_name, phys_addr);}
}
/**
* Prints startup information.
*/
private void printStartupInfo() {
System.out.println("GossipRouter started at " + new Date());
System.out.print("Listening on port " + port);
System.out.println(" bound on address " + server.localAddress());
System.out.print("Backlog is " + backlog);
System.out.print(", linger timeout is " + linger_timeout);
System.out.println(", and read timeout is " + sock_read_timeout);
}
public static void main(String[] args) throws Exception {
int port=12001;
int backlog=0;
long soLinger=-1;
long soTimeout=-1;
long expiry_time=60000;
GossipRouter router=null;
String bind_addr=null;
boolean jmx=true, nio=true, suspects=true, dump_msgs=false;
for(int i=0; i < args.length; i++) {
String arg=args[i];
if("-port".equals(arg)) {
port=Integer.parseInt(args[++i]);
continue;
}
if("-bindaddress".equals(arg) || "-bind_addr".equals(arg)) {
bind_addr=args[++i];
continue;
}
if("-backlog".equals(arg)) {
backlog=Integer.parseInt(args[++i]);
continue;
}
if("-expiry".equals(arg)) {
expiry_time=Long.parseLong(args[++i]);
continue;
}
if("-jmx".equals(arg)) {
jmx=Boolean.valueOf(args[++i]);
continue;
}
if("-solinger".equals(arg)) {
soLinger=Long.parseLong(args[++i]);
continue;
}
if("-sotimeout".equals(arg)) {
soTimeout=Long.parseLong(args[++i]);
continue;
}
if("-nio".equals(arg)) {
nio=Boolean.parseBoolean(args[++i]);
continue;
}
if("-suspect".equals(arg)) {
suspects=Boolean.parseBoolean(args[++i]);
continue;
}
if("-dump_msgs".equals(arg)) {
dump_msgs=Boolean.parseBoolean(args[++i]);
continue;
}
help();
return;
}
router=new GossipRouter(bind_addr, port)
.jmx(jmx).expiryTime(expiry_time)
.useNio(nio)
.backlog(backlog)
.socketReadTimeout(soTimeout)
.lingerTimeout(soLinger)
.emitSuspectEvents(suspects)
.dumpMessages(dump_msgs);
router.start();
IpAddress local=(IpAddress)router.localAddress();
System.out.printf("\nGossipRouter listening on %s:%s\n", bind_addr != null? bind_addr : "0.0.0.0", local.getPort());
}
static void help() {
System.out.println();
System.out.println("GossipRouter [-port ] [-bind_addr ] [options]");
System.out.println();
System.out.println("Options:");
System.out.println();
System.out.println(" -backlog - Max queue size of backlogged connections. Must be");
System.out.println(" greater than zero or the default of 1000 will be");
System.out.println(" used.");
System.out.println();
System.out.println(" -jmx - Expose attributes and operations via JMX.");
System.out.println();
System.out.println(" -solinger - Time for setting SO_LINGER on connections. 0");
System.out.println(" means do not set SO_LINGER. Must be greater than");
System.out.println(" or equal to zero or the default of 2000 will be");
System.out.println(" used.");
System.out.println();
System.out.println(" -sotimeout - Time for setting SO_TIMEOUT on connections. 0");
System.out.println(" means don't set SO_TIMEOUT. Must be greater than");
System.out.println(" or equal to zero or the default of 3000 will be");
System.out.println(" used.");
System.out.println();
System.out.println(" -expiry - Time for closing idle connections. 0");
System.out.println(" means don't expire.");
System.out.println();
System.out.printf(" -nio - Whether or not to use non-blocking connections (NIO)\n");
System.out.println();
System.out.printf(" -suspect - Whether or not to use send SUSPECT events when a conn is closed\n");
System.out.println();
System.out.printf(" -dump_msgs - Dumps all messages to stdout after routing them\n");
System.out.println();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy