package com.koushikdutta.async; import android.util.Log; import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.callback.WritableCallback; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; public class AsyncNetworkSocket implements AsyncSocket { AsyncNetworkSocket() { } @Override public void end() { mChannel.shutdownOutput(); } public boolean isChunked() { return mChannel.isChunked(); } InetSocketAddress socketAddress; void attach(SocketChannel channel, InetSocketAddress socketAddress) throws IOException { this.socketAddress = socketAddress; maxAlloc = 256 * 1024; // 256K mChannel = new SocketChannelWrapper(channel); } void attach(DatagramChannel channel) throws IOException { mChannel = new DatagramChannelWrapper(channel); // keep udp at roughly the mtu, which is 1540 or something // letting it grow freaks out nio apparently. maxAlloc = 8192; } ChannelWrapper getChannel() { return mChannel; } public void onDataWritable() { assert mWriteableHandler != null; 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 !mChannel.isChunked(); return; } try { ByteBuffer[] arr = list.getAllArray(); mChannel.write(arr); list.addAll(arr); handleRemaining(list.remaining()); } catch (IOException e) { closeInternal(); reportEndPending(e); reportClose(e); } } private void handleRemaining(int remaining) { if (remaining > 0) { // chunked channels should not fail assert !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 !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) { closeInternal(); reportEndPending(ex); reportClose(ex); } } private ByteBufferList pending = new ByteBufferList(); // private ByteBuffer[] buffers = new ByteBuffer[8]; int maxAlloc; 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; // ByteBufferList.obtainArray(buffers, Math.min(Math.max(mToAlloc, 2 << 11), maxAlloc)); ByteBuffer b = ByteBufferList.obtain(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. long read = mChannel.read(b); if (read < 0) { closeInternal(); closed = true; } else { total += read; } if (read > 0) { mToAlloc = (int)read * 2; b.flip(); // for (int i = 0; i < buffers.length; i++) { // ByteBuffer b = buffers[i]; // buffers[i] = null; // b.flip(); // pending.add(b); // } pending.add(b); Util.emitAllData(this, pending); } 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) { Log.e("NIO", "Unhandled exception", e); } } boolean mEndReported; Exception mPendingEndException; void reportEndPending(Exception e) { if (pending.hasRemaining()) { 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.hasRemaining()) { Util.emitAllData(this, pending); } } @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 InetSocketAddress getRemoteAddress() { return socketAddress; } public int getLocalPort() { return mChannel.getLocalPort(); } public Object getSocket() { return getChannel().getSocket(); } }