/**
* Fortika - Robust Group Communication
* Copyright (C) 2002-2006 Sergio Mena de la Cruz (EPFL) (sergio.mena@epfl.ch)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package framework.libraries.tcp;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import uka.transport.DeepClone;
import uka.transport.MarshalStream;
import uka.transport.Transportable;
import uka.transport.UnmarshalStream;
import framework.Constants;
import framework.PID;
//import lse.net.net.RSocket;
/**
* This class implements a TCP connection to a remote process.
* It has several threads for sending/receiving/connecting. It has
* been designed to be used by the protocols as a <i>framework
* library </i>.
*
* See interfaces <i>NonBlockingTCP</i> and <i>TCPStackInterface</i>
* for more details about the way this class interacts with the protocols
* in the stack
*/
public class Connection implements Comparable, Transportable {
//TODO: remove "implements Transportable"
/**
* Reference to the local process. It is passes as a parameter to this
* class's constructor
*/
private PID myself;
/**
* Boolean set to <i>true</i> if this is connection towards the local process.
* The behavior of a self-connected Connection is slightly different from
* that of a normal Connection. A self-connected Connection does not have a socket
* (since it is not necessary), and it has special thread for sending and receiving.
*/
private boolean selfConnected;
/*3
* The socket used to send/receive data
*/
Socket socket;
/**
* PID of the remote process.
*/
PID remote = null;
/**
* The socket's OutputStream
*/
OutputStream os = null;
/**
* The socket's InputStream
*/
InputStream is = null;
/**
* The parent object that will insert the events into the stack. It is
* typically the wrapping micro-protocol of a low level <i>common code</i>
* protocol.
*/
TCPStackInterface parent = null;
/**
* Tells whether the active threads must exit
*/
boolean exit = false;
/**
* Tells whether the connection protocol has been carried out
*/
boolean connected = false;
/**
* The message waiting to be sent by the sending thread
*/
byte[] messageToSend = null;
/**
* Tells whether the sending thread is active
*/
boolean sender = false;
/**
* Tells whether the receiving thread is active
*/
boolean receiver = false;
/**
* Constructor called when the socket has already been created. Normally,
* this constructor will be called by the Server class upon an incomming
* connection from a remote process.
*
* @param myself The PID of the local process
* @param sock Socket already created
* @param sock parent The object that inserts event into the stack
*/
public Connection(PID myself, Socket sock, TCPStackInterface parent) {
this.myself = myself;
this.parent = parent;
socket = sock;
selfConnected = false;
startThread(acceptThread, "acceptThread");
startThread(senderThread, "senderThread2");
startThread(receiverThread, "receiverThread2");
}
/**
* Constructor called when there is no created socket yet. Normally,
* this constructor will be called by a protcol that wants to start
* a client connection with a remote process.
*
* @param myself The PID of the local process
* @param remote PID of the process to connect to
* @param sock parent The object that inserts event into the stack
*/
public Connection(PID myself, PID remote, TCPStackInterface parent) {
this.myself = myself;
this.remote = remote;
this.parent = parent;
selfConnected = myself.equals(remote);
if (selfConnected) {
startThread(selfSendReceiveThread, "selfSendReceiveThread");
} else {
startThread(connectThread, "connectThread");
startThread(senderThread, "senderThread1");
startThread(receiverThread, "receiverThread1");
}
}
private void startThread(Runnable r, String name) {
Thread t = new Thread(Constants.THREADGROUP, r, name);
t.setDaemon(true);
t.start();
}
/**
* Disconnect the socket and finish all active threads
*/
public synchronized void disconnect() {
exit = true;
notifyAll();
}
/**
* Start the sender thread. From this moment on, the Connection
* notifies <i>parent</i> whenever it is ready to accept a new
* message to send. This is done with the parent's
* <i>readyForNextMessage</i> method.
*/
public synchronized void startSender() {
sender = true;
notifyAll();
}
/**
* Stop the sender thread
*/
public synchronized void stopSender() {
sender = false;
}
/**
* Start the receiver thread. From this moment on, the Connection
* notifies <i>parent</i> whenever a message is received from the
* socket. This is done with the parent's <i>recv</i> method.
*/
public synchronized void startReceiver() {
receiver = true;
notifyAll();
}
/**
* Stop the receiver thread
*/
public synchronized void stopReceiver() {
receiver = false;
}
/**
* Accepts a message to be asynchronously sent by the sending thread.
* The caller returns immedaitely, without waiting for the message
* to be actually written to the socket.
*
* @param message The message to be asynchronously sent
*/
public synchronized void setMessageToSend(byte[] message) {
if (messageToSend != null)
throw new RuntimeException("messageToSend != null!!!!");
messageToSend = message;
notifyAll();
}
/**
* Sends a message synchronously. The called does not return immedaitely.
* It returns after the message is actually written to the socket. If
* the buffers at TCP level are full, this method will block the caller
* until there is again space for the message at the TCP level.
*
* This method is normally used by the sender thread, And sometimes by
* protocol that are 100% sure that TCP buffers will not be full by the
* time this method is called.
*
* @param message The message to be synchronously sent
*/
public void sendMessage(byte[] b) {
try {
if (selfConnected) {
parent.recv(b, moi);
} else {
os.write(intToBytes(b.length));
os.write(b);
os.flush();
}
} catch (IOException ioe) {
disconnect();
parent.broken(this);
}
}
/**
* Returns the PID of the remote process
*/
public PID getRemotePID() {
return remote;
}
Connection moi = this;
private Runnable connectThread = new Runnable() {
public void run() {
//Protocol for the connection initator
try {
socket = new Socket(remote.ip, remote.port);
// Good!! We got a connection... but
// DON'T notify it with parent.opened() yet
is = socket.getInputStream();
os = socket.getOutputStream();
// Deactivate Nagle algorithm
socket.setTcpNoDelay(true);
} catch (IOException ioe) {
//Connection failed
parent.connected(remote, null);
disconnect();
return;
}
try {
//Wait for the real PID
PID realPID = readPID();
if (exit) {
shutdownOutput();
return;
}
if (realPID == null
|| //Connection remotely closed
!realPID.equals(remote)) {
//Ooops, different incarnation
disconnect();
parent.broken(moi);
//parent.closed(moi);
//shutdownOutput();
return;
}
//Send local PID
sendPID();
connected = true;
synchronized (moi) {
moi.notifyAll();
}
parent.connected(remote, moi);
} catch (IOException ioe) {
disconnect();
parent.broken(moi);
}
}
};
private Runnable acceptThread = new Runnable() {
public void run() {
try {
// Deactivate Nagle algorithm
socket.setTcpNoDelay(true);
//Protocol for the connection receiver
os = socket.getOutputStream();
//Send local PID
sendPID();
is = socket.getInputStream();
//Wait for remote PID
PID remPID = readPID();
if (remPID == null) {
//Connection closed
shutdownOutput();
return;
}
remote = remPID;
connected = true;
synchronized (moi) {
moi.notifyAll();
}
parent.accepted(moi);
} catch (IOException ioe) {
disconnect();
}
}
};
private Runnable receiverThread = new Runnable() {
public void run() {
try {
synchronized (moi) {
while (!connected && !exit) {
try {
moi.wait();
} catch (InterruptedException ie) {
}
}
}
if (exit)
return;
while (true) {
// Normal message reception
byte[] m = readMessage();
// if(cauterized){
// return;
// }
if (exit)
return;
synchronized (moi) {
while (!receiver && !exit) {
try {
moi.wait();
} catch (InterruptedException ie) {
}
}
if (exit)
return;
}
//Deliver message to the transport
parent.recv(m, moi);
}
} catch (EOFException eofe) {
parent.closed(moi);
} catch (IOException ioe) {
disconnect();
parent.broken(moi);
}
}
};
private Runnable senderThread = new Runnable() {
public void run() {
try {
synchronized (moi) {
while (!connected && !exit) {
try {
moi.wait();
} catch (InterruptedException ie) {
}
}
}
if (exit) {
shutdownOutput();
return;
}
while (true) {
synchronized (moi) {
while (!sender && !exit) {
try {
moi.wait();
} catch (InterruptedException ie) {
}
}
if (exit) {
shutdownOutput();
return;
}
}
parent.readyForNextMessage(moi);
synchronized (moi) {
while (messageToSend == null && !exit) {
try {
moi.wait();
} catch (InterruptedException ie) {
}
}
if (exit) {
shutdownOutput();
return;
}
}
sendMessage(messageToSend);
messageToSend = null;
}
} catch (IOException ioe) {
System.out.println(
"Connection: IOException while shutting down output");
}
}
};
private Runnable selfSendReceiveThread = new Runnable() {
public void run() {
byte[] m;
connected = true;
parent.connected(remote, moi);
//parent.accepted(moi);
while (true) {
synchronized (moi) {
while (!sender && !exit) {
try {
moi.wait();
} catch (InterruptedException ie) {
}
}
if (exit) {
return;
}
}
parent.readyForNextMessage(moi);
synchronized (moi) {
while ((messageToSend == null || !receiver) && !exit) {
try {
moi.wait();
} catch (InterruptedException ie) {
}
}
if (exit) {
return;
}
m = messageToSend;
messageToSend = null;
}
//Deliver message to the transport
parent.recv(m, moi);
}
}
};
/**
* Send the local process' PID to the remote process. This is used when
* connecting to verify the incarnation number.
*/
void sendPID() throws IOException {
//PID p = PID.LOCALHOST;
//send inetAddress
byte[] inetBytes = myself.ip.getHostName().getBytes();
os.write(intToBytes(inetBytes.length));
os.write(inetBytes);
//send port
os.write(intToBytes(myself.port));
//send incarnation
os.write(intToBytes(myself.incarnation));
os.flush();
}
/**
* Attempt to treat a message (of length nextLength).
*
* @exception EOFException Throws anexception if th end of the inputstream has
* been reached.
*/
byte[] readMessage() throws EOFException, IOException {
int length = treatInt();
byte[] t = new byte[length];
readIS(t);
return t;
}
/**
* @return the read {@link GroupComm.common.PID PID} from the inputStream
* or null if the inputStream has been closed.
*/
/*private*/
PID readPID() throws IOException, EOFException {
InetAddress inet = null;
int port = -1;
int incarnation = -1;
//Read the inetAddress
inet = treatInetAddress();
//Read the port number
port = treatInt();
//Read and format the incarnation number
incarnation = treatInt();
return new PID(inet, port, incarnation);
}
/*private*/
void shutdownOutput() throws IOException {
if (socket != null)
socket.shutdownOutput();
}
private InetAddress treatInetAddress() throws EOFException, IOException {
InetAddress inet = null;
//Read and format the length of the InetAddress
int lengthInet = treatInt();
//Read and format the InetAddress
byte[] t = new byte[lengthInet];
readIS(t);
try {
inet = InetAddress.getByName(new String(t));
} catch (UnknownHostException ex) {
throw new IOException();
}
return inet;
}
/**
* Attempt to rebuild an int from the inputStream.
*
* @exception EOFException Throws anexception if th end of the inputstream has
* been reached.
*/
private int treatInt() throws EOFException, IOException {
byte[] t = new byte[4];
readIS(t);
return bytesToInt(t);
}
/**
* Reads <b>exactly</b> <i>length</i> bytes from the inputStream, stores it
* into <i>b</i>, and returns the number of read bytes.</br>
* If the method returns -1, it means that the inputstream has been closed.
*/
private int readIS(byte[] b) throws EOFException, IOException {
int i = 0, n;
for (n = 0; n < b.length && i != -1; n += i) {
i = is.read(b, n, b.length - n);
}
if (i == -1)
throw new EOFException();
return n;
}
//TODO: Migrate this to DataXXput Stream!!!
/**
* Transforms an <i>int</i> into a <i>bytearray</i>
*/
private static byte[] intToBytes(int i) {
//Creates byte array
byte[] header = new byte[4];
header[3] = (byte) (0xff & (i >> 24));
header[2] = (byte) (0xff & (i >> 16));
header[1] = (byte) (0xff & (i >> 8));
header[0] = (byte) (0xff & i);
return header;
}
/**
* Transfoms a <i>bytearray</i> into an <i>int</i>
*/
private static int bytesToInt(byte[] b) {
int i =
(((b[3] & 0xff) << 24)
| ((b[2] & 0xff) << 16)
| ((b[1] & 0xff) << 8)
| (b[0] & 0xff));
return i;
}
/**
* Method for comparing two Connections
*/
public int compareTo(Object o) {
Connection c = (Connection) o;
if (c == null) {
System.err.println(
"Connection: compareTo: remote connectin is null!!");
return +1;
}
if (selfConnected || c.selfConnected)
return 0;
IpAndPort[] pairs = {
//Me Remote
new IpAndPort(socket.getInetAddress(), socket.getPort()),
//Me Local
new IpAndPort(socket.getLocalAddress(), socket.getLocalPort()),
//Other Remote
new IpAndPort(c.socket.getInetAddress(), c.socket.getPort()),
//Other Local
new IpAndPort(c.socket.getLocalAddress(), c.socket.getLocalPort())};
int min = 0;
for (int i = 1; i < pairs.length; i++) {
if (pairs[i].compareTo(pairs[min]) < 0)
min = i;
}
IpAndPort aux = null;
if ((min % 2) == 1) {
aux = pairs[0];
pairs[0] = pairs[1];
pairs[1] = aux;
aux = pairs[2];
pairs[2] = pairs[3];
pairs[3] = aux;
}
if (pairs[0].compareTo(pairs[2]) < 0) {
return -1;
} else if (pairs[0].compareTo(pairs[2]) > 0) {
return +1;
} else { //The're the same... let's see the local
if (pairs[1].compareTo(pairs[3]) < 0) {
return -1;
} else if (pairs[1].compareTo(pairs[3]) > 0) {
return +1;
} else { //Completely equal
return 0;
}
}
}
public String toString() {
String st = new String(//"ConnectionID:"+
//super.toString() +
"PID="
+ remote
+ "; exit="
+ exit
+ "; connected="
+ connected
+ "; sender="
+ sender
+ "; receiver="
+ receiver);
if (socket != null)
st =
st
+ "; remote=("
+ socket.getInetAddress()
+ ":"
+ socket.getPort()
+ "); local=("
+ socket.getLocalAddress()
+ ":"
+ socket.getLocalPort()
+ ")";
return st + ")";
}
public void marshal(MarshalStream arg0) throws IOException {
throw new IOException("unimplemented!");
}
public void unmarshalReferences(UnmarshalStream arg0) throws IOException, ClassNotFoundException {
throw new IOException("unimplemented!");
}
public Object deepClone(DeepClone arg0) throws CloneNotSupportedException {
throw new CloneNotSupportedException("unimplemented!");
}
}