org.jgroups.stack.GossipRouter Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
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
log.warn("dest %s in cluster %s not found", dest, group);
}
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