package lcm.lcm;
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.nio.*;
/** LCM provider for the tcpq: URL. All messages are sent to a central
* "hub" process (that must be started separately), which will relay
* the messages to all other processes. TCPService is an
* implementation of the hub process.
*
* The tcpq:// protocol is NOT suitable for real-time or high-bandwidth
* traffic. It is specifically designed for playing back a log file in a
* post-processing context (i.e., play back the log as fast as possible, but
* without dropping anything).
**/
public class TCPProvider implements Provider
{
LCM lcm;
static final int DEFAULT_PORT = 7700;
static final String DEFAULT_NETWORK = "127.0.0.1:7700";
InetAddress inetAddr;
int inetPort;
TCPThread tcp;
public static final int MAGIC_SERVER = 0x287617fa; // first word sent by server
public static final int MAGIC_CLIENT = 0x287617fb; // first word sent by client
public static final int VERSION = 0x0100; // what version do we implement?
public static final int MESSAGE_TYPE_PUBLISH = 1;
public static final int MESSAGE_TYPE_SUBSCRIBE = 2;
public static final int MESSAGE_TYPE_UNSUBSCRIBE = 3;
HashSet<String> subscriptions = new HashSet<String>();
public TCPProvider(LCM lcm, URLParser up) throws IOException
{
this.lcm = lcm;
String addrport[] = up.get("network", DEFAULT_NETWORK).split(":");
if (addrport.length == 1) {
inetAddr = InetAddress.getByName(addrport[0]);
inetPort = 7700;
} else if (addrport.length == 2) {
inetAddr = InetAddress.getByName(addrport[0]);
inetPort = Integer.valueOf(addrport[1]);
} else {
System.err.println("TCPProvider: Don't know how to parse "+up.get("network", DEFAULT_NETWORK));
System.exit(-1);
}
tcp = new TCPThread();
tcp.start();
}
/** Publish a message synchronously. However, if the server is not
* available, it will return immediately.
**/
public synchronized void publish(String channel, byte data[], int offset, int length)
{
try {
publishEx(channel, data, offset, length);
} catch (Exception ex) {
System.err.println("TCPProvider ex: "+ex);
}
}
byte[] stringToBytes(String s)
{
try {
return s.getBytes("US-ASCII");
} catch(UnsupportedEncodingException ex) {
System.err.println("lcm.TCPProvider: Bad channel name" + s);
throw new RuntimeException("Don't know how to recover from this");
}
}
/* private void sockWriteAndFlush(byte[] b)
{
// try to send message on socket. If the socket is not
// connected, we'll simply fail. The tcpthread is
// responsible for maintaining a connection to the hub.
OutputStream sockOuts = tcp.getOutputStream();
if (sockOuts != null) {
try {
sockOuts.write(b);
sockOuts.flush();
} catch (IOException ex) {
}
}
}
*/
public synchronized void subscribe(String channel)
{
subscriptions.add(channel);
tcp.sendSubscribe(channel);
}
public synchronized void unsubscribe(String channel)
{
subscriptions.remove(channel);
tcp.sendUnsubscribe(channel);
}
public synchronized void close()
{
if (null != tcp) {
tcp.close();
try {
tcp.join();
} catch (InterruptedException ex) {
}
}
tcp = null;
}
static final void safeSleep(int ms)
{
try {
Thread.sleep(ms);
} catch (InterruptedException ex) {
}
}
void publishEx(String channel, byte data[], int offset, int length) throws Exception
{
byte[] channel_bytes = stringToBytes(channel);
int payload_size = channel_bytes.length + length;
ByteArrayOutputStream bouts = new ByteArrayOutputStream(length + channel.length() + 32);
DataOutputStream outs = new DataOutputStream(bouts);
outs.writeInt(MESSAGE_TYPE_PUBLISH);
outs.writeInt(channel_bytes.length);
outs.write(channel_bytes, 0, channel_bytes.length);
outs.writeInt(length);
outs.write(data, offset, length);
tcp.write(bouts.toByteArray());
}
// synchronize on writes and to changes in subscription state.
class TCPThread extends Thread
{
Socket sock;
DataInputStream ins;
OutputStream outs;
boolean exit = false;
int serverVersion;
TCPThread()
{
}
synchronized void write(byte b[]) throws IOException
{
// if our connection is dead or not yet up, we just drop
// this message. (subscribes will be setup again when the
// connection comes back up).
if (outs == null)
return;
outs.write(b);
outs.flush();
}
synchronized void sendSubscribe(String channel)
{
byte channel_bytes[] = stringToBytes(channel);
try {
ByteArrayOutputStream bouts = new ByteArrayOutputStream(channel.length() + 8);
DataOutputStream outs = new DataOutputStream(bouts);
outs.writeInt(MESSAGE_TYPE_SUBSCRIBE);
outs.writeInt(channel_bytes.length);
outs.write(channel_bytes, 0, channel_bytes.length);
write(bouts.toByteArray());
} catch (IOException ex) {
System.out.println("ex: "+ex);
}
}
synchronized void sendUnsubscribe(String channel)
{
byte channel_bytes[] = stringToBytes(channel);
try {
ByteArrayOutputStream bouts = new ByteArrayOutputStream(channel.length() + 8);
DataOutputStream outs = new DataOutputStream(bouts);
outs.writeInt(MESSAGE_TYPE_UNSUBSCRIBE);
outs.writeInt(channel_bytes.length);
outs.write(channel_bytes, 0, channel_bytes.length);
write(bouts.toByteArray());
} catch (IOException ex) {
}
}
public void run()
{
while (!exit) {
synchronized (this) {
//////////////////////////////////
// reconnect
try {
sock = new Socket(inetAddr, inetPort);
OutputStream _outs = sock.getOutputStream();
DataOutputStream _douts = new DataOutputStream(_outs);
_douts.writeInt(MAGIC_CLIENT);
_douts.writeInt(VERSION);
_douts.flush();
outs = _outs;
ins = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
int magic = ins.readInt();
if (magic != MAGIC_SERVER) {
sock.close();
continue;
}
serverVersion = ins.readInt();
} catch (IOException ex) {
System.err.println("lcm.TCPProvider: Unable to connect to "+inetAddr+":"+inetPort);
safeSleep(500);
// try connecting again.
continue;
}
for (String sub : subscriptions) {
System.out.println("resending subscription "+sub);
sendSubscribe(sub);
}
}
//////////////////////////////////
// read loop
try {
while (!exit) {
int type = ins.readInt();
int channellen = ins.readInt();
byte channel[] = new byte[channellen];
ins.readFully(channel);
int datalen = ins.readInt();
byte data[] = new byte[datalen];
ins.readFully(data);
lcm.receiveMessage(new String(channel), data, 0, data.length);
}
} catch (IOException ex) {
// exit read loop so we'll create a new connection.
}
}
}
void close()
{
try {
sock.close();
} catch (IOException ex) {
}
exit = true;
}
OutputStream getOutputStream()
{
return outs;
}
}
}