package org.jgroups.stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jgroups.Address;
import org.jgroups.Message;
import org.jgroups.protocols.TunnelHeader;
import org.jgroups.util.Buffer;
import org.jgroups.util.ExposedByteArrayOutputStream;
import org.jgroups.util.Util;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.net.InetAddress;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.List;
/**
* Client stub that talks to a remote GossipRouter
* @author Bela Ban
* @version $Id: RouterStub.java,v 1.22.2.1 2009/01/06 21:52:41 jiwils Exp $
*/
public class RouterStub {
String router_host=null; // name of the router host
int router_port=0; // port on which router listens on router_host
Socket sock=null; // socket connecting to the router
private ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(512);
DataOutputStream output=null; // output stream associated with sock
DataInputStream input=null; // input stream associated with sock
DatagramSocket my_sock=null; // needed to generate an ID that's unqique across this host
Address local_addr=null; // addr of group mbr. Once assigned, remains the same
static final long RECONNECT_TIMEOUT=5000; // msecs to wait until next connection retry attempt
private volatile boolean connected=false;
private volatile boolean reconnect=false; // controls reconnect() loop
protected static final Log log=LogFactory.getLog(RouterStub.class);
protected ConnectionListener conn_listener;
private String groupname=null;
private InetAddress bind_addr=null;
public interface ConnectionListener {
void connectionStatusChange(boolean connected);
}
public RouterStub() {
}
/**
Creates a stub for a remote Router object.
@param router_host The name of the router's host
@param router_port The router's port
*/
public RouterStub(String router_host, int router_port) {
this.router_host=router_host != null? router_host : "localhost";
this.router_port=router_port;
}
public InetAddress getBindAddress() {
return bind_addr;
}
public void setBindAddress(InetAddress bind_addr) {
this.bind_addr=bind_addr;
}
public String getRouterHost() {
return router_host;
}
public void setRouterHost(String router_host) {
this.router_host=router_host;
}
public int getRouterPort() {
return router_port;
}
public void setRouterPort(int router_port) {
this.router_port=router_port;
}
public boolean isConnected() {
return connected;
}
public void setConnectionListener(ConnectionListener conn_listener) {
this.conn_listener=conn_listener;
}
public synchronized Address getLocalAddress() throws SocketException {
if(local_addr == null)
local_addr=generateLocalAddress();
return local_addr;
}
private synchronized Address generateLocalAddress() throws SocketException {
my_sock=new DatagramSocket(0, bind_addr);
local_addr=new IpAddress(bind_addr, my_sock.getLocalPort());
return local_addr;
}
// private synchronized long generateUniquePort() {
// long ret=System.currentTimeMillis();
// if(ret <= last_port) {
// ret=++last_port;
// }
// else {
// last_port=ret;
// }
// return ret;
// }
/**
Register this process with the router under <code>groupname</code>.
@param groupname The name of the group under which to register
*/
public synchronized void connect(String groupname) throws Exception {
if(groupname == null || groupname.length() == 0)
throw new Exception("groupname is null");
this.groupname=groupname;
if(local_addr == null)
local_addr=generateLocalAddress();
try {
sock=new Socket(router_host, router_port, bind_addr, 0);
sock.setSoLinger(true, 5); // 5 seconds!
output=new DataOutputStream(sock.getOutputStream());
GossipData req=new GossipData(GossipRouter.CONNECT, groupname, local_addr, null);
req.writeTo(output);
output.flush();
input=new DataInputStream(sock.getInputStream()); // retrieve our own address by reading it from the socket
setConnected(true);
}
catch(Exception e) {
if(log.isWarnEnabled())
log.warn("failed connecting to " + router_host + ":" + router_port);
Util.close(sock); Util.close(input); Util.close(output);
setConnected(false);
throw e;
}
}
public void connect(String groupname, String router_host, int router_port) throws Exception {
setRouterHost(router_host);
setRouterPort(router_port);
connect(groupname);
}
/** Closes the socket and the input and output streams associated with it */
public synchronized void disconnect() {
disconnect(false);
}
public synchronized void disconnect(boolean is_reconnect) {
try {
if(sock == null || output == null || input == null) {
setConnected(false);
return;
}
if(groupname == null || groupname.length() == 0) {
if(log.isErrorEnabled()) log.error("groupname is null");
return;
}
if(local_addr == null) {
if(log.isErrorEnabled()) log.error("local_addr is null");
return;
}
GossipData req=new GossipData(GossipRouter.DISCONNECT, groupname, local_addr, null);
req.writeTo(output);
setConnected(false);
}
catch(Exception e) {
// if(log.isErrorEnabled()) log.error("failed unregistering " + local_addr, e);
}
finally {
Util.close(output);
Util.close(input);
Util.close(sock); sock=null;
setConnected(false);
// stop the TUNNEL receiver thread
reconnect=false;
if(is_reconnect) {
Util.close(my_sock);
local_addr=null;
}
}
}
/**
Retrieves the membership (list of Addresses) for a given group. This is mainly used by the PING
protocol to obtain its initial membership. This is used infrequently, so don't maintain socket
for the entire time, but create/delete it on demand.
*/
public List get(String groupname) {
List ret=null;
Socket tmpsock=null;
DataOutputStream tmpOutput=null;
DataInputStream tmpInput=null;
if(groupname == null || groupname.length() == 0) {
if(log.isErrorEnabled()) log.error("groupname is null");
return null;
}
try {
tmpsock=new Socket(router_host, router_port);
tmpsock.setSoLinger(true, 5); // 5 seconds!
// request membership for groupname
tmpOutput=new DataOutputStream(tmpsock.getOutputStream());
GossipData request=new GossipData(GossipRouter.ROUTER_GET, groupname, null, null);
request.writeTo(tmpOutput);
tmpInput=new DataInputStream(tmpsock.getInputStream());
GossipData response=new GossipData();
response.readFrom(tmpInput);
return response.getMembers();
}
catch(Exception e) {
if(log.isErrorEnabled()) log.error("exception=" + e);
}
finally {
Util.close(tmpOutput);
Util.close(tmpInput);
Util.close(tmpsock);
}
return ret;
}
/** Sends a message to the router. Returns false if message cannot be sent (e.g. no connection to
router, true otherwise. */
public boolean send(Message msg, String groupname) {
Address dst_addr=null;
if(sock == null || output == null || input == null) {
if(log.isErrorEnabled()) log.error("no connection to router (groupname=" + groupname + ')');
setConnected(false);
return false;
}
if(msg == null) {
if(log.isErrorEnabled()) log.error("message is null");
return false;
}
try {
dst_addr=msg.getDest(); // could be null in case of mcast
try {
out_stream.reset();
}
catch (Exception ex) {
out_stream=new ExposedByteArrayOutputStream(512);
}
// at this point out_stream is always valid and non-null
DataOutputStream tmp=new DataOutputStream(out_stream);
msg.writeTo(tmp);
tmp.close();
Buffer buf=new Buffer(out_stream.getRawBuffer(), 0, out_stream.size());
// 1. Group name
output.writeUTF(groupname);
// 2. Destination address
Util.writeAddress(dst_addr, output);
// 3. Length of byte buffer
output.writeInt(buf.getLength());
// 4. Byte buffer
output.write(buf.getBuf(), 0, buf.getLength());
}
catch(Exception e) {
if(log.isErrorEnabled()) log.error("failed sending message to " + dst_addr, e);
setConnected(false);
return false;
}
return true;
}
/** Receives a message from the router (blocking mode). If the connection is down,
false is returned, otherwise true */
public Message receive() throws Exception {
Message ret=null;
byte[] buf=null;
int len;
if(sock == null || output == null || input == null) {
// if(log.isErrorEnabled()) log.error("no connection to router");
setConnected(false);
return null;
}
Address dest;
try {
dest=Util.readAddress(input);
len=input.readInt();
if(len == 0) {
ret=null;
}
else {
buf=new byte[len];
input.readFully(buf, 0, len);
ret=new Message(false);
ByteArrayInputStream tmp=new ByteArrayInputStream(buf);
DataInputStream in=new DataInputStream(tmp);
ret.readFrom(in);
ret.setDest(dest);
in.close();
}
if(log.isTraceEnabled()) log.trace("received "+ret);
return ret;
}
catch(Exception e) {
setConnected(false);
throw e;
}
}
/** Tries to establish connection to router. Tries until router is up again. */
public void reconnect(int max_attempts) throws Exception {
int num_atttempts=0;
if(connected) return;
disconnect();
reconnect=true;
while(reconnect && (num_atttempts++ < max_attempts || max_attempts == -1)) {
try {
connect(groupname);
break;
}
catch(Exception ex) { // this is a normal case
if(log.isTraceEnabled()) log.trace("failed reconnecting", ex);
}
if(max_attempts == -1)
Util.sleep(RECONNECT_TIMEOUT);
}
if(!connected)
throw new Exception("reconnect failed");
if(log.isTraceEnabled()) log.trace("client reconnected");
}
public void reconnect() throws Exception {
reconnect(-1);
}
private void notifyConnectionListener(boolean connected) {
if(conn_listener != null) {
conn_listener.connectionStatusChange(connected);
}
}
private void setConnected(boolean connected) {
boolean notify=this.connected != connected;
this.connected=connected;
if(notify) {
try {
notifyConnectionListener(this.connected);
}
catch(Throwable t) {
log.error("failed notifying ConnectionListener " + conn_listener, t);
}
}
}
public static void main(String[] args) {
if(args.length != 2) {
System.out.println("RouterStub <host> <port>");
return;
}
RouterStub stub=new RouterStub(args[0], Integer.parseInt(args[1])),
stub2=new RouterStub(args[0], Integer.parseInt(args[1]));
Address my_addr;
boolean rc;
final String groupname="BelaGroup";
Message msg;
List mbrs;
try {
System.out.println("Registering under " + groupname);
stub.connect(groupname);
System.out.println("My address is " + stub.getLocalAddress());
my_addr=stub2.getLocalAddress();
stub2.connect(groupname);
System.out.println("Getting members of " + groupname + ": ");
mbrs=stub.get(groupname);
System.out.println("Done, mbrs are " + mbrs);
for(int i=1; i <= 10; i++) {
msg=new Message(null, my_addr, "Bela #" + i);
msg.putHeader("TUNNEL", new TunnelHeader(groupname));
rc=stub2.send(msg, groupname);
System.out.println("Sent msg #" + i + ", rc=" + rc);
}
for(int i=0; i < 10; i++) {
msg=stub.receive();
System.out.println("Received msg " + msg.getObject());
}
}
catch(Exception ex) {
log.error(ex);
}
finally {
stub.disconnect();
}
}
}