package com.koushikdutta.async;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import junit.framework.Assert;
import android.util.Log;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.WritableCallback;
public class AsyncNetworkSocket implements AsyncSocket {
AsyncNetworkSocket() {
}
public boolean isChunked() {
return mChannel.isChunked();
}
void attach(SocketChannel channel) throws IOException {
mChannel = new SocketChannelWrapper(channel);
}
void attach(DatagramChannel channel) throws IOException {
mChannel = new DatagramChannelWrapper(channel);
}
ChannelWrapper getChannel() {
return mChannel;
}
public void onDataWritable() {
Assert.assertNotNull(mWriteableHandler);
mWriteableHandler.onWriteable();
}
private ChannelWrapper mChannel;
private SelectionKey mKey;
private AsyncServer mServer;
void setup(AsyncServer server, SelectionKey key) {
mServer = server;
mKey = key;
}
@Override
public void write(final ByteBufferList list) {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
write(list);
}
});
return;
}
if (!mChannel.isConnected()) {
Assert.assertFalse(mChannel.isChunked());
return;
}
try {
mChannel.write(list.toArray());
handleRemaining(list.remaining());
}
catch (IOException e) {
close();
reportEndPending(e);
reportClose(e);
}
}
private void handleRemaining(int remaining) {
if (remaining > 0) {
// chunked channels should not fail
Assert.assertFalse(mChannel.isChunked());
// register for a write notification if a write fails
mKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
else {
mKey.interestOps(SelectionKey.OP_READ);
}
}
@Override
public void write(final ByteBuffer b) {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
write(b);
}
});
return;
}
try {
if (!mChannel.isConnected()) {
Assert.assertFalse(mChannel.isChunked());
return;
}
// keep writing until the the socket can't write any more, or the
// data is exhausted.
mChannel.write(b);
handleRemaining(b.remaining());
}
catch (IOException ex) {
close();
reportEndPending(ex);
reportClose(ex);
}
}
private ByteBufferList pending;
int mToAlloc = 0;
int onReadable() {
spitPending();
// even if the socket is paused,
// it may end up getting a queued readable event if it is
// already in the selector's ready queue.
if (mPaused)
return 0;
int total = 0;
try {
boolean closed = false;
int maxAlloc = 256 * 1024; // 256K
// keep udp at roughly the mtu, which is 1540 or something
// letting it grow freaks out nio apparently.
if (mChannel.isChunked())
maxAlloc = 8192;
ByteBuffer b = ByteBuffer.allocate(Math.min(Math.max(mToAlloc, 2 << 11), maxAlloc));
// keep track of the max mount read during this read cycle
// so we can be quicker about allocations during the next
// time this socket reads.
int read = mChannel.read(b);
if (read < 0) {
closeInternal();
closed = true;
}
else {
total += read;
}
if (read > 0) {
mToAlloc = read * 2;
b.limit(b.position());
b.position(0);
ByteBufferList list = new ByteBufferList(b);
Util.emitAllData(this, list);
if (b.remaining() != 0) {
Assert.assertTrue(pending == null);
pending = list;
}
}
if (closed) {
reportEndPending(null);
reportClose(null);
}
}
catch (Exception e) {
closeInternal();
reportEndPending(e);
reportClose(e);
}
return total;
}
boolean closeReported;
protected void reportClose(Exception e) {
if (closeReported)
return;
closeReported = true;
if (mClosedHander != null) {
mClosedHander.onCompleted(e);
mClosedHander = null;
}
}
@Override
public void close() {
closeInternal();
reportClose(null);
}
public void closeInternal() {
mKey.cancel();
try {
mChannel.close();
}
catch (IOException e) {
}
}
WritableCallback mWriteableHandler;
@Override
public void setWriteableCallback(WritableCallback handler) {
mWriteableHandler = handler;
}
DataCallback mDataHandler;
@Override
public void setDataCallback(DataCallback callback) {
mDataHandler = callback;
}
@Override
public DataCallback getDataCallback() {
return mDataHandler;
}
CompletedCallback mClosedHander;
@Override
public void setClosedCallback(CompletedCallback handler) {
mClosedHander = handler;
}
@Override
public CompletedCallback getClosedCallback() {
return mClosedHander;
}
@Override
public WritableCallback getWriteableCallback() {
return mWriteableHandler;
}
void reportEnd(Exception e) {
if (mEndReported)
return;
mEndReported = true;
if (mCompletedCallback != null)
mCompletedCallback.onCompleted(e);
else if (e != null)
e.printStackTrace();
}
boolean mEndReported;
Exception mPendingEndException;
void reportEndPending(Exception e) {
if (pending != null) {
mPendingEndException = e;
return;
}
reportEnd(e);
}
private CompletedCallback mCompletedCallback;
@Override
public void setEndCallback(CompletedCallback callback) {
mCompletedCallback = callback;
}
@Override
public CompletedCallback getEndCallback() {
return mCompletedCallback;
}
@Override
public boolean isOpen() {
return mChannel.isConnected() && mKey.isValid();
}
boolean mPaused = false;
@Override
public void pause() {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
pause();
}
});
return;
}
if (mPaused)
return;
mPaused = true;
try {
mKey.interestOps(~SelectionKey.OP_READ & mKey.interestOps());
}
catch (Exception ex) {
}
}
private void spitPending() {
if (pending != null) {
Util.emitAllData(this, pending);
if (pending.remaining() == 0) {
pending = null;
}
}
}
@Override
public void resume() {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
resume();
}
});
return;
}
if (!mPaused)
return;
mPaused = false;
try {
mKey.interestOps(SelectionKey.OP_READ | mKey.interestOps());
}
catch (Exception ex) {
}
spitPending();
if (!isOpen())
reportEndPending(mPendingEndException);
}
@Override
public boolean isPaused() {
return mPaused;
}
@Override
public AsyncServer getServer() {
return mServer;
}
public int getLocalPort() {
return mChannel.getLocalPort();
}
}