package erjang.epmd;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
public abstract class PacketConnection implements Connection {
static final Logger log = Logger.getLogger("erjang.epmd");
protected SelectionKey sk;
private int state;
private ByteBuffer recvBuffer = null;
private String name = "";
ArrayList<ByteBuffer> outbuf = new ArrayList<ByteBuffer>();
protected void receivePacket(ByteBuffer buf) {
// ignore //
}
protected void connectionClosed() {
// do nothing //
}
/**
* @param buf byte buffer of packet (will be flip'ed)
* @param includeSize
*/
void sendPacket(ByteBuffer buf, boolean includeSize) {
if (includeSize) {
ByteBuffer size = ByteBuffer.allocate(2);
size.putShort(0, (short) buf.position());
outbuf.add(size);
}
buf.flip();
outbuf.add(buf);
sk.interestOps(SelectionKey.OP_WRITE | sk.interestOps());
}
/**
* construct a NIOConnection from a selection key
*/
PacketConnection(SelectionKey sk) {
SocketChannel sch = (SocketChannel) sk.channel();
if (sch.isConnected()) // connected immediatedly if local on *nix
{
sk.interestOps(SelectionKey.OP_READ);
state = Connection.OPEN;
} else if (sch.isConnectionPending()) {
sk.interestOps(SelectionKey.OP_CONNECT);
state = Connection.OPENING;
}
this.sk = sk; // link this to the key
sk.attach(this);
recvBuffer = ByteBuffer.allocate(8196);
}
/**
* process a connect complete selection
*/
public void doConnect() {
SocketChannel sc = (SocketChannel) sk.channel();
try {
sc.finishConnect();
sk.interestOps(sk.interestOps() & ~SelectionKey.OP_CONNECT);
log.fine("connect complete");
state = Connection.OPEN;
} catch (IOException e) {
e.printStackTrace();
closeComplete();
}
}
ByteBuffer rcvBuf;
protected boolean keep;
/**
* process a read ready selection
*/
public void doRead() {
SocketChannel sc = (SocketChannel) sk.channel();
if (sc.isOpen()) {
int len;
recvBuffer.clear();
try {
len = sc.read(recvBuffer);
} catch (IOException e) {
//e.printStackTrace();
len = -1;
} // error look like eof
log.finer("read len=" + len);
if (len == -1) {
close();
return;
}
int start = 0;
while (recvBuffer.position() > start + 2) {
int pklen = recvBuffer.getShort(start) & 0xffff;
if (recvBuffer.position() >= start + 2 + pklen) {
// got a complete package!
ByteBuffer buf = slice(recvBuffer, start + 2, pklen);
receivePacket(buf);
if (!this.keep && outbuf.isEmpty()) {
close();
return;
}
start += 2 + pklen;
} else if (recvBuffer.position() == recvBuffer.limit()) {
// receive buffer is full; reallocate it!
int available = recvBuffer.position() - start;
byte[] data = new byte[pklen + 2];
recvBuffer.position(start);
recvBuffer.get(data, 0, available);
recvBuffer = ByteBuffer.wrap(data);
return;
}
}
// move contents of recvBuffer from
// start ... position
// down to
// 0 .. (position - start)
if (start > 0 && recvBuffer.position() > 0) {
if (recvBuffer.position() == start) {
recvBuffer.clear();
} else {
int left = recvBuffer.position() - start;
byte[] data = new byte[left];
recvBuffer.position(start);
recvBuffer.get(data);
recvBuffer.clear();
recvBuffer.put(data);
}
}
} else {
close();
}
}
private ByteBuffer slice(ByteBuffer buf, int start, int len) {
int savePos = buf.position();
int saveLim = buf.limit();
buf.position(start);
buf.limit(start + len);
ByteBuffer res = buf.slice();
buf.position(savePos);
buf.limit(saveLim);
return res;
}
/**
* process a write ready selection
*/
public void doWrite() {
log.finer("write ready");
if (!outbuf.isEmpty()) {
GatheringByteChannel bc = (GatheringByteChannel) sk.channel();
ByteBuffer[] out = outbuf.toArray(new ByteBuffer[outbuf.size()]);
// do the vector write
try {
bc.write(out);
} catch (IOException e) {
e.printStackTrace();
}
for (int i = 0; i < out.length && !out[i].hasRemaining(); i++) {
outbuf.remove(0);
}
}
if (outbuf.isEmpty()) {
sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
}
if (!this.keep && outbuf.isEmpty()) {
close();
}
}
/*
* close the connection and its socket
*/
public void close() {
if (state != Connection.CLOSED) {
SocketChannel sc = (SocketChannel) sk.channel();
if (sc.isOpen()) {
if (state == Connection.OPEN) // open attempt graceful
// shutdown
{
log.fine("shutting down");
state = Connection.CLOSING;
Socket sock = sc.socket();
try {
sock.shutdownOutput();
} catch (IOException se) {
log.severe("shutdown failed: " + se.getMessage());
log.log(Level.FINE, "details: ", se);
}
} else
closeComplete();
} else
log.warning("already closed");
}
}
// called internally if already closing or closed by partner
private void closeComplete() {
log.fine("closing channel");
try {
sk.interestOps(0);
SocketChannel sc = (SocketChannel) sk.channel();
if (sc != null && sc.isOpen())
sc.close();
sk.selector().wakeup();
sk.attach(null);
} catch (IOException ce) {
log.severe("close failed: " + ce.getMessage());
log.log(Level.FINE, "details: ", ce);
}
state = Connection.CLOSED;
connectionClosed();
}
public String getName() {
return name;
}
public void setName(String nm) {
name = nm;
}
public int getState() {
return state;
}
}