/* $Id$ */
package ibis.ipl.impl.nio;
import ibis.ipl.ConnectionRefusedException;
import ibis.ipl.ConnectionTimedOutException;
import ibis.ipl.PortType;
import ibis.ipl.impl.ReceivePort;
import ibis.ipl.impl.ReceivePortIdentifier;
import ibis.ipl.impl.SendPortIdentifier;
import ibis.util.IPUtils;
import ibis.util.ThreadPool;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* implements a channelfactory using the tcp implementation of nio
*/
class TcpChannelFactory implements ChannelFactory, Protocol {
private static Logger logger = LoggerFactory.getLogger(TcpChannelFactory.class);
// Server socket Channel we listen for new connection on
private ServerSocketChannel ssc;
// Address ssc is bound to
private InetSocketAddress address;
private NioIbis ibis;
TcpChannelFactory(NioIbis ibis) throws IOException {
int port = 0;
InetAddress localAddress = IPUtils.getLocalHostAddress();
this.ibis = ibis;
// init server socket channel
ssc = ServerSocketChannel.open();
address = new InetSocketAddress(localAddress, port);
ssc.socket().bind(address);
// just in case it binded to some other port
localAddress = ssc.socket().getInetAddress();
port = ssc.socket().getLocalPort();
address = new InetSocketAddress(localAddress, port);
ThreadPool.createNew(this, "TcpChannelFactory");
}
public InetSocketAddress getAddress() {
return address;
}
public void quit() throws IOException {
// this will make the accept() throw an AsynchronusCloseException
// or an ClosedChannelException and make the thread exit
ssc.close();
}
/**
* Tries to connect the sendport to the receiveport for the given time.
* Returns the resulting channel.
*/
public Channel connect(NioSendPort spi,
ReceivePortIdentifier rpi, long timeoutMillis)
throws IOException {
int reply;
SocketChannel channel;
long deadline = 0;
long time;
if (logger.isDebugEnabled()) {
logger.debug("connecting \"" + spi + "\" to \"" + rpi + "\"");
}
if (timeoutMillis > 0) {
deadline = System.currentTimeMillis() + timeoutMillis;
}
InetSocketAddress addr = ibis.getAddress(rpi.ibis);
while (true) {
if (deadline == 0) {
// do a blocking connect
channel = SocketChannel.open();
channel.connect(addr);
} else {
time = System.currentTimeMillis();
if (time >= deadline) {
logger.error("timeout on connecting");
throw new IOException("timeout on connecting");
}
channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(addr);
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT);
if (selector.select(deadline - time) == 0) {
// nothing selected, so we had a timeout
logger.error("timed out while connecting socket "
+ "to receiver");
throw new ConnectionTimedOutException("timed out while"
+ " connecting socket to receiver", rpi);
}
if (!channel.finishConnect()) {
throw new IOException("finish connect failed while we made sure"
+ " it would work");
}
selector.close();
channel.configureBlocking(true);
}
channel.socket().setTcpNoDelay(true);
// channel.socket().setSendBufferSize(0x8000);
// channel.socket().setReceiveBufferSize(0x8000);
// write out rpi name, spi identifier and spi capabilities.
ChannelAccumulator accumulator = new ChannelAccumulator(channel);
accumulator.writeByte(CONNECTION_REQUEST);
DataOutputStream d = new DataOutputStream(accumulator);
d.writeUTF(rpi.name());
spi.ident.writeTo(d);
spi.type.writeTo(d);
d.flush();
if (logger.isDebugEnabled()) {
logger.debug("waiting for reply on connect");
}
if (timeoutMillis > 0) {
time = System.currentTimeMillis();
if (time >= deadline) {
logger.warn("timeout on waiting for reply on connecting");
throw new IOException("timeout on waiting for reply");
}
channel.configureBlocking(false);
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ);
if (selector.select(deadline - time) == 0) {
// nothing selected, so we had a timeout
try {
channel.close();
} catch (IOException e) {
// IGNORE
}
logger.error("timed out while for reply from receiver");
throw new ConnectionTimedOutException("timed out while"
+ " waiting for reply from receiver", rpi);
}
selector.close();
channel.configureBlocking(true);
}
// see what he thinks about it
ChannelDissipator dissipator = new ChannelDissipator(channel);
reply = dissipator.readByte();
if (reply == ReceivePort.DENIED) {
logger.error("Receiver denied connection");
channel.close();
throw new ConnectionRefusedException("Receiver denied connection", rpi);
} else if (reply == ReceivePort.ACCEPTED) {
if (logger.isDebugEnabled()) {
logger.debug("made new connection from \"" + spi
+ "\" to \"" + rpi + "\"");
}
channel.configureBlocking(true);
return channel;
} else if (reply == ReceivePort.DISABLED) {
// receiveport not (yet) enabled, wait for a while
try {
channel.close();
} catch (Exception e) {
// IGNORE
}
try {
Thread.sleep(100);
} catch (Exception e) {
// IGNORE
}
// and retry
continue;
} else {
logger.error("illegal opcode in ChannelFactory.connect()");
throw new IOException("illegal opcode in ChannelFactory.connect()");
}
}
}
/**
* Handles incoming requests
*/
private void handleRequest(SocketChannel channel) {
byte request;
String name;
SendPortIdentifier spi = null;
PortType capabilities = null;
ReceivePortIdentifier rpi;
NioReceivePort rp = null;
ChannelDissipator dissipator = new ChannelDissipator(channel);
ChannelAccumulator accumulator = new ChannelAccumulator(channel);
if (logger.isDebugEnabled()) {
logger.debug("got new connection from "
+ channel.socket().getInetAddress() + ":"
+ channel.socket().getPort());
}
try {
request = dissipator.readByte();
if (request != CONNECTION_REQUEST) {
logger.error("received unknown request");
try {
dissipator.close();
accumulator.close();
channel.close();
} catch (IOException e) {
// IGNORE
}
return;
}
DataInputStream d = new DataInputStream(dissipator);
name = d.readUTF();
spi = new SendPortIdentifier(d);
capabilities = new PortType(d);
rpi = new ReceivePortIdentifier(name, ibis.ident);
rp = (NioReceivePort) ibis.findReceivePort(name);
if (rp == null) {
logger.error("could not find receiveport, connection denied");
accumulator.writeByte(ReceivePort.DENIED);
accumulator.flush();
channel.close();
return;
}
if (logger.isDebugEnabled()) {
logger.debug("giving new connection to receiveport " + rpi);
}
// register connection with receiveport
byte reply = rp.connectionRequested(spi, capabilities, channel);
// send reply
accumulator.writeByte(reply);
accumulator.flush();
if (reply != ReceivePort.ACCEPTED) {
channel.close();
if (logger.isInfoEnabled()) {
logger.info("receiveport rejected connection");
}
return;
}
} catch (IOException e) {
logger.error("got an exception on handling an incoming request"
+ ", closing channel" + e);
try {
channel.close();
} catch (IOException e2) {
// IGNORE
}
return;
}
if (logger.isDebugEnabled()) {
logger.debug("set up new connection");
}
}
/**
* Accepts connections on the server socket channel
*/
public void run() {
SocketChannel channel = null;
Thread.currentThread().setName("ChannelFactory");
logger.info("ChannelFactory running on " + ssc);
while (true) {
try {
channel = ssc.accept();
channel.socket().setTcpNoDelay(true);
// channel.socket().setSendBufferSize(0x8000);
// channel.socket().setReceiveBufferSize(0x8000);
channel.configureBlocking(true);
} catch (ClosedChannelException e) {
// the channel was closed before we started the accept
// OR while we were doing the accept
// take the hint, and exit
ssc = null;
return;
} catch (Exception e3) {
try {
ssc.close();
if (channel != null) {
channel.close();
}
} catch (IOException e4) {
// IGNORE
}
logger.error("could not do accept");
return;
}
handleRequest(channel);
}
}
}