org.jgroups.stack.GossipRouter Maven / Gradle / Ivy
package org.jgroups.stack;
import org.jgroups.Address;
import org.jgroups.Message;
import org.jgroups.PhysicalAddress;
import org.jgroups.Version;
import org.jgroups.annotations.Component;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.blocks.cs.*;
import org.jgroups.conf.AttributeType;
import org.jgroups.jmx.JmxConfigurator;
import org.jgroups.jmx.ReflectUtils;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.PingData;
import org.jgroups.util.*;
import javax.net.ssl.*;
import java.io.DataInput;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* 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, DiagnosticsHandler.ProbeHandler {
@ManagedAttribute(description="The bind address which should be used by the GossipRouter.")
protected InetAddress 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 idle client connections are closed. 0 disables expiration.",
writable=true,type=AttributeType.TIME)
protected long expiry_time;
@ManagedAttribute(description="Interval (in msecs) to check for expired connections and close them. 0 disables reaping.",
writable=true,type=AttributeType.TIME)
protected long reaper_interval;
@ManagedAttribute(description="Time (in ms) for setting SO_LINGER on sockets returned from accept(). 0 means do not set SO_LINGER"
,type=AttributeType.TIME)
protected int linger_timeout=-1;
protected ThreadFactory thread_factory=new DefaultThreadFactory("gossip", false, true);
protected SocketFactory socket_factory=new DefaultSocketFactory();
@ManagedAttribute(description="Initial size of the TCP/NIO receive buffer (in bytes)")
protected int recv_buf_size;
@ManagedAttribute(description="Expose GossipRouter via JMX")
protected boolean jmx;
@ManagedAttribute(description="Use non-blocking IO (true) or blocking IO (false). Cannot be changed at runtime")
protected boolean use_nio;
@ManagedAttribute(description="Handles client disconnects: sends SUSPECT message to all other members of that group")
protected boolean emit_suspect_events=true;
@ManagedAttribute(description="Dumps messages (dest/src/length/headers) to stdout")
protected DumpMessages dump_msgs;
@ManagedAttribute(description="The max number of bytes a message can have. If greater, an exception will be " +
"thrown. 0 disables this", type=AttributeType.BYTES)
protected int max_length;
protected BaseServer server;
protected final AtomicBoolean running=new AtomicBoolean(false);
protected final Log log=LogFactory.getLog(this.getClass());
@Component(name="diag",description="DiagnosticsHandler listening for probe requests")
protected DiagnosticsHandler diag;
@Component(name="tls",description="Contains the attributes for TLS (SSL sockets) when enabled=true")
protected TLS tls=new TLS();
@ManagedAttribute(description="Use bounded queues for sending (https://issues.redhat.com/browse/JGRP-2759)) in TCP (use_nio=false)")
protected boolean non_blocking_sends;
@ManagedAttribute(description="When sending and non_blocking, how many messages to queue max")
protected int max_send_queue=128;
// mapping between groups and - pairs
protected final Map> address_mappings=new ConcurrentHashMap<>();
// to cache output streams for serialization (https://issues.redhat.com/browse/JGRP-2576)
protected final Map output_streams=new ConcurrentHashMap<>();
protected static final BiConsumer MSG_CONSUMER=(version,msg)
-> System.out.printf("dst=%s src=%s (%d bytes): hdrs= %s\n", msg.dest(), msg.src(), msg.getLength(), msg.printHeaders());
public GossipRouter(String bind_addr, int local_port) throws Exception {
this.port=local_port;
this.bind_addr=bind_addr != null? InetAddress.getByName(bind_addr) : null;
init();
}
public GossipRouter(InetAddress bind_addr, int local_port) throws Exception {
this.port=local_port;
this.bind_addr=bind_addr;
init();
}
public Address localAddress() {return server.localAddress();}
public String bindAddress() {return bind_addr != null? bind_addr.toString() : null;}
public GossipRouter bindAddress(InetAddress 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 reaperInterval() {return reaper_interval;}
public GossipRouter reaperInterval(long t) {this.reaper_interval=t; return this;}
public int lingerTimeout() {return linger_timeout;}
public GossipRouter lingerTimeout(int t) {this.linger_timeout=t; return this;}
public int recvBufferSize() {return recv_buf_size;}
public GossipRouter recvBufferSize(int s) {recv_buf_size=s; 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 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 DumpMessages dumpMessages() {return dump_msgs;}
public GossipRouter dumpMessages(DumpMessages flag) {dump_msgs=flag; return this;}
public GossipRouter dumpMessages(boolean d) {dump_msgs=d? DumpMessages.ALL : DumpMessages.NONE; return this;}
public int maxLength() {return max_length;}
public GossipRouter maxLength(int len) {max_length=len; if(server != null) server.setMaxLength(len);
return this;}
public DiagnosticsHandler diagHandler() {return diag;}
public TLS tls() {return tls;}
public GossipRouter tls(TLS t) {this.tls=t; return this;}
public boolean nonBlockingSends() {return non_blocking_sends;}
public GossipRouter nonBlockingSends(boolean b) {this.non_blocking_sends=b; return this;}
public int maxSendQueue() {return max_send_queue;}
public GossipRouter maxSendQueue(int s) {this.max_send_queue=s; return this;}
@ManagedAttribute(description="operational status", name="running")
public boolean running() {return running.get();}
@ManagedAttribute(description="The number of different clusters registered")
public int numRegisteredClusters() {
return address_mappings.size();
}
@ManagedAttribute(description="The number of registered client (all clusters)")
public int numRegisteredClients() {
return (int)address_mappings.values().stream().mapToLong(s -> s.keySet().size()).sum();
}
public GossipRouter init() throws Exception {
diag=new DiagnosticsHandler(log, socket_factory, thread_factory)
.registerProbeHandler(this)
.printHeaders(b -> String.format("GossipRouter [addr=%s, cluster=GossipRouter, version=%s]\n",
localAddress(), Version.description));
return this;
}
/**
* 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 GossipRouter start() throws Exception {
if(!running.compareAndSet(false, true))
return this;
if(jmx)
JmxConfigurator.register(this, Util.getMBeanServer(), "jgroups:name=GossipRouter");
// Creating the DiagnosticsHandler _before_ the TLS socket factory makes the former use regular sockets;
// if this was not the case, Probe would have to be extended to use SSLSockets, too
if(tls.enabled()) {
SocketFactory factory=tls.createSocketFactory();
socketFactory(factory);
}
server=use_nio? new NioServer(thread_factory, socket_factory, bind_addr, port, port, null, 0,
recv_buf_size, "jgroups.nio.gossiprouter")
: new TcpServer(thread_factory, socket_factory, bind_addr, port, port, null, 0, recv_buf_size,
"jgroups.tcp.gossiprouter").nonBlockingSends(non_blocking_sends).maxSendQueue(max_send_queue);
server.receiver(this).setMaxLength(max_length)
.addConnectionListener(this)
.connExpireTimeout(expiry_time).reaperInterval(reaper_interval).linger(linger_timeout);
server.start();
if(diag.isEnabled()) {
StackType ip_version=bind_addr instanceof Inet6Address? StackType.IPv6 : StackType.IPv4;
Configurator.setDefaultAddressValues(diag, ip_version);
diag.start();
}
Runtime.getRuntime().addShutdownHook(new Thread(GossipRouter.this::stop));
return this;
}
/**
* Always called before destroy(). Close 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(diag, 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 dumpAddressMappings() {
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) {
receive(sender, ByteBuffer.wrap(buf, offset, length));
}
@Override
public void receive(Address sender, ByteBuffer buf) {
int original_pos=buf.position();
GossipType type;
try {
type=GossipType.values()[buf.get()];
}
catch(Exception ex) {
log.error("failed reading data from %s: %s", sender, ex);
return;
}
switch(type) {
case REGISTER:
handleRegister(sender, new ByteArrayDataInputStream(buf));
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 {
DataInput in=new ByteArrayDataInputStream(buf);
String group=Bits.readString(in);
Address dest=Util.readAddress(in);
route(group, dest, buf.position(original_pos));
if(dump_msgs == DumpMessages.ALL) {
ByteArrayDataInputStream input=new ByteArrayDataInputStream(buf);
GossipData data=new GossipData();
data.readFrom(input);
dump(data);
}
}
catch(Throwable t) {
log.error(Util.getMessage("FailedReadingRequest"), t);
return;
}
break;
case HEARTBEAT:
handleHeartbeat(sender);
break;
case GET_MBRS:
handleGetMembersRequest(sender, new ByteArrayDataInputStream(buf));
break;
case UNREGISTER:
handleUnregister(new ByteArrayDataInputStream(buf));
break;
}
}
public void receive(Address sender, DataInput in, int length) 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 that is not
// available natively
if((request=readRequest(in, type)) != null) {
ByteArrayDataOutputStream out=getOutputStream(request.sender, request.serializedSize());
// we might be concurrent traffic from *different* (senders) TcpConnections for the
// *same* target address, so we have to synchronized below in order to avoid corruption
// of data (https://issues.redhat.com/browse/JGRP-2722)
synchronized(out) {
out.position(0);
request.writeTo(out);
route(request.group, request.addr, out.buffer(), 0, out.position());
}
if(dump_msgs == DumpMessages.ALL)
dump(request);
}
}
catch(Throwable t) {
log.error(Util.getMessage("FailedReadingRequest"), t);
return;
}
break;
case HEARTBEAT:
request=readRequest(in, type);
handleHeartbeat(sender);
break;
case GET_MBRS:
handleGetMembersRequest(sender, in);
break;
case UNREGISTER:
handleUnregister(in);
break;
}
}
public Map handleProbe(String... keys) {
Map map=new TreeMap<>();
for(String key: keys) {
if(key.startsWith("ops")) {
map.put(key, ReflectUtils.listOperations(getClass()));
continue;
}
if(key.startsWith("op") || key.startsWith("invoke")) {
int index=key.indexOf('=');
if(index == -1)
throw new IllegalArgumentException(String.format("\\= not found in operation %s", key));
String op=key.substring(index + 1).trim();
try {
ReflectUtils.invokeOperation(map, op, this);
}
catch(Exception ex) {
throw new IllegalArgumentException(String.format("operation %s failed: %s", op, ex));
}
continue;
}
if(key.startsWith("jmx")) {
int index=key.indexOf('=');
String tmp;
if(index == -1)
tmp=null;
else
tmp=key.substring(index+1);
ReflectUtils.handleAttributeAccess(map, tmp, this);
continue;
}
if(key.startsWith("keys")) {
StringBuilder sb=new StringBuilder();
for(DiagnosticsHandler.ProbeHandler handler : diag.getProbeHandlers()) {
String[] tmp=handler.supportedKeys();
if(tmp != null) {
for(String s: tmp)
sb.append(s).append(" ");
}
}
map.put(key, sb.toString());
continue;
}
if(key.startsWith("member-addrs")) {
InetSocketAddress sa=(InetSocketAddress)diag.getLocalAddress();
if(sa != null) {
Set physical_addrs=Set.of(new IpAddress(sa.getAddress(), sa.getPort()));
// Set physical_addrs=diag.getLocalAddress();
String list=Util.print(physical_addrs);
map.put(key, list);
}
continue;
}
if(key.startsWith("dump")) {
map.put(key, Util.dumpThreads());
}
}
return map;
}
public String[] supportedKeys() {
return new String[]{"ops", "op", "invoke", "keys", "member-addrs", "dump"};
}
protected ByteArrayDataOutputStream getOutputStream(Address mbr, int size) {
return output_streams.computeIfAbsent(mbr, __ -> new ByteArrayDataOutputStream(size));
}
protected void handleHeartbeat(Address sender) {
GossipData rsp=new GossipData(GossipType.HEARTBEAT);
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 %s to %s: %s", GossipType.HEARTBEAT, sender, ex);
}
}
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);
if(log.isDebugEnabled())
log.debug("added %s (%s) to group %s", logical_name, phys_addr, group);
if(dump_msgs == DumpMessages.REGISTRATION || dump_msgs == DumpMessages.ALL)
System.out.printf("added %s (%s) to group %s\n", logical_name, phys_addr, group);
}
}
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);
}
}
if(dump_msgs == DumpMessages.ALL || log.isDebugEnabled()) {
String rsps=rsp.ping_data == null? null
: rsp.ping_data.stream().map(r -> String.format("%s (%s)", r.getLogicalName(), r.getPhysicalAddr()))
.collect(Collectors.joining(", "));
if(rsps != null) {
if(log.isDebugEnabled())
log.debug("get(%s) -> %s", req.getGroup(), rsps);
if(dump_msgs == DumpMessages.ALL)
System.out.printf("get(%s) -> %s\n", req.getGroup(), rsps);
}
}
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 %s to %s: %s", GossipType.GET_MBRS_RSP, sender, ex);
}
}
protected static void dump(GossipData data) {
Util.parse(data.buffer, data.offset, data.length, MSG_CONSUMER, null, null, false, false);
}
@Override
public void connectionClosed(Connection conn) {
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) {
NameCache.add(addr, 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;
Entry e=m.get(addr);
if(e != null) {
if(log.isDebugEnabled())
log.debug("removed %s (%s) from group %s", e.logical_name, e.phys_addr, group);
if(dump_msgs == DumpMessages.REGISTRATION || dump_msgs == DumpMessages.ALL)
System.out.printf("removed %s (%s) from group %s\n", e.logical_name, e.phys_addr, group);
}
if(m.remove(addr) != null && m.isEmpty())
address_mappings.remove(group);
output_streams.remove(addr);
}
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());
output_streams.remove(entry2.getKey());
log.debug("connection to %s closed", client_addr);
if(log.isDebugEnabled())
log.debug("removed %s (%s) from group %s", e.logical_name, e.phys_addr, entry.getKey());
if(dump_msgs == DumpMessages.REGISTRATION || dump_msgs == DumpMessages.ALL)
System.out.printf("removed %s (%s) from group %s\n", e.logical_name, e.phys_addr, entry.getKey());
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 route(String group, Address dest, ByteBuffer buf) {
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, buf);
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, buf);
}
}
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 sendToAllMembersInGroup(Set> dests, ByteBuffer buf) {
for(Map.Entry entry: dests) {
Entry e=entry.getValue();
if(e == null /* || e.phys_addr == null */)
continue;
try {
server.send(e.client_addr, buf.duplicate());
}
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, ByteBuffer buf) {
try {
server.send(dest, buf);
}
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);}
}
public static void main(String[] args) throws Exception {
int port=12001;
int recv_buf_size=0, max_length=0;
long expiry_time=0, reaper_interval=0;
boolean diag_enabled=true, diag_enable_udp=true, diag_enable_tcp=false;
InetAddress diag_mcast_addr=null, diag_bind_addr=null;
int diag_port=7500, diag_port_range=50, diag_ttl=8, soLinger=-1;
List diag_bind_interfaces=null;
String diag_passcode=null;
// Use bounded queues for sending (https://issues.redhat.com/browse/JGRP-2759)") in TCP (use_nio=false)
boolean non_blocking_sends=false;
// When sending and non_blocking, how many messages to queue max
int max_send_queue=128;
TLS tls=new TLS();
long start=System.currentTimeMillis();
String bind_addr=null;
boolean jmx=false, nio=false, suspects=true;
DumpMessages dump_msgs = DumpMessages.NONE;
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("-recv_buf_size".equals(args[i])) {
recv_buf_size=Integer.parseInt(args[++i]);
continue;
}
if("-expiry".equals(arg)) {
expiry_time=Long.parseLong(args[++i]);
continue;
}
if("-reaper_interval".equals(arg)) {
reaper_interval=Long.parseLong(args[++i]);
continue;
}
if("-jmx".equals(arg)) {
jmx=Boolean.parseBoolean(args[++i]);
continue;
}
if("-solinger".equals(arg)) {
soLinger=Integer.parseInt(args[++i]);
continue;
}
if("-nio".equals(arg)) {
nio=Boolean.parseBoolean(args[++i]);
continue;
}
if("-non_blocking_sends".equals(arg)) {
non_blocking_sends=Boolean.parseBoolean(args[++i]);
continue;
}
if("max_send_queue".equals(arg)) {
max_send_queue=Integer.parseInt(args[++i]);
continue;
}
if("-suspect".equals(arg)) {
suspects=Boolean.parseBoolean(args[++i]);
continue;
}
if("-dump_msgs".equals(arg)) {
dump_msgs=DumpMessages.parse(args[++i]);
continue;
}
if("-max_length".equals(arg)) {
max_length=Integer.parseInt(args[++i]);
continue;
}
if("-diag_enabled".equals(arg)) {
diag_enabled=Boolean.parseBoolean(args[++i]);
continue;
}
if("-diag_enable_udp".equals(arg)) {
diag_enable_udp=Boolean.parseBoolean(args[++i]);
continue;
}
if("-diag_enable_tcp".equals(arg)) {
diag_enable_tcp=Boolean.parseBoolean(args[++i]);
continue;
}
if("-diag_mcast_addr".equals(arg)) {
diag_mcast_addr=InetAddress.getByName(args[++i]);
continue;
}
if("-diag_bind_addr".equals(arg)) {
diag_bind_addr=InetAddress.getByName(args[++i]);
continue;
}
if("-diag_port".equals(arg)) {
diag_port=Integer.parseInt(args[++i]);
continue;
}
if("-diag_port_range".equals(arg)) {
diag_port_range=Integer.parseInt(args[++i]);
continue;
}
if("-diag_ttl".equals(arg)) {
diag_ttl=Integer.parseInt(args[++i]);
continue;
}
if("-diag_bind_interfaces".equals(arg)) {
diag_bind_interfaces=Util.parseInterfaceList(args[++i]);
continue;
}
if("-diag_passcode".equals(arg)) {
diag_passcode=args[++i];
continue;
}
if("-tls_protocol".equals(arg)) {
tls.enabled(true).setProtocols(args[++i].split(","));
continue;
}
if("-tls_cipher_suites".equals(arg)) {
tls.enabled(true).setCipherSuites(args[++i].split(","));
continue;
}
if("-tls_provider".equals(arg)) {
tls.enabled(true).setProvider(args[++i]);
continue;
}
if("-tls_keystore_path".equals(arg)) {
tls.enabled(true).setKeystorePath(args[++i]);
continue;
}
if("-tls_keystore_password".equals(arg)) {
tls.enabled(true).setKeystorePassword(args[++i]);
continue;
}
if("-tls_keystore_type".equals(arg)) {
tls.enabled(true).setKeystoreType(args[++i]);
continue;
}
if("-tls_keystore_alias".equals(arg)) {
tls.enabled(true).setKeystoreAlias(args[++i]);
continue;
}
if("-tls_truststore_path".equals(arg)) {
tls.enabled(true).setTruststorePath(args[++i]);
continue;
}
if("-tls_truststore_password".equals(arg)) {
tls.enabled(true).setTruststorePassword(args[++i]);
continue;
}
if("-tls_truststore_type".equals(arg)) {
tls.enabled(true).setTruststoreType(args[++i]);
continue;
}
if("-tls_client_auth".equals(arg)) {
tls.enabled(true).setClientAuth(TLSClientAuth.valueOf(args[++i].toUpperCase()));
continue;
}
if("-tls_sni_matcher".equals(arg)) {
tls.enabled(true).getSniMatchers().add(SNIHostName.createSNIMatcher(args[++i]));
continue;
}
help();
return;
}
if(tls.enabled() && nio)
throw new IllegalArgumentException("Cannot use NIO with TLS");
GossipRouter router=new GossipRouter(bind_addr, port)
.jmx(jmx).expiryTime(expiry_time).reaperInterval(reaper_interval)
.useNio(nio)
.recvBufferSize(recv_buf_size)
.lingerTimeout(soLinger)
.emitSuspectEvents(suspects)
.dumpMessages(dump_msgs)
.maxLength(max_length)
.tls(tls).nonBlockingSends(non_blocking_sends).maxSendQueue(max_send_queue);
router.diagHandler().setEnabled(diag_enabled)
.enableUdp(diag_enable_udp)
.enableTcp(diag_enable_tcp)
.setMcastAddress(diag_mcast_addr)
.setBindAddress(diag_bind_addr)
.setPort(diag_port)
.setPortRange(diag_port_range)
.setTtl(diag_ttl)
.setBindInterfaces(diag_bind_interfaces)
.setPasscode(diag_passcode);
String type="";
if(tls.enabled()) {
tls.init();
tls.setWatcher(new FileWatcher());
SSLContext context=tls.createContext();
SocketFactory socket_factory=tls.createSocketFactory(context);
router.socketFactory(socket_factory);
type=String.format(" (%s/%s)", tls.getProtocols()!=null?String.join(",",tls.getProtocols()):"TLS",
context.getProvider());
}
router.start();
long time=System.currentTimeMillis()-start;
IpAddress local=(IpAddress)router.localAddress();
System.out.printf("\nGossipRouter started in %d ms listening on %s:%s%s\n",
time, bind_addr != null? bind_addr : "0.0.0.0", local.getPort(), type);
}
public enum DumpMessages {
NONE,
REGISTRATION,
ALL;
static DumpMessages parse(String s) {
s = s.trim();
if (s.isEmpty() || s.equalsIgnoreCase("false")) {
return NONE;
} else if (s.equalsIgnoreCase("true")) {
return ALL;
} else {
return valueOf(s.toUpperCase());
}
}
}
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(" -jmx - Expose attributes and operations via JMX.\n");
System.out.println(" -recv_buf_size - Sets the receive buffer");
System.out.println();
System.out.println(" -solinger - Time for setting SO_LINGER on connections");
System.out.println();
System.out.println(" -expiry - Time for closing idle connections. 0 means don't expire.");
System.out.println();
System.out.println(" -reaper_interval - Time for check for expired connections. 0 means don't check.");
System.out.println();
System.out.println(" -nio - Whether or not to use non-blocking connections (NIO)");
System.out.println();
System.out.println(" -non_blocking_sends - Use bounded queues for sending (https://issues.redhat.com/browse/JGRP-2759))");
System.out.println();
System.out.println(" -max_send_queue - When sending and non_blocking, how many messages to queue max");
System.out.println();
System.out.println(" -max_length - The max size (in bytes) of a message");
System.out.println();
System.out.println(" -suspect - Whether or not to use send SUSPECT events when a conn is closed");
System.out.println();
System.out.println(" -dump_msgs
© 2015 - 2025 Weber Informatics LLC | Privacy Policy