Package org.deftserver.io

Source Code of org.deftserver.io.AsynchronousSocket

package org.deftserver.io;

import java.io.EOFException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.UnresolvedAddressException;

import org.deftserver.util.Closeables;
import org.deftserver.util.NopAsyncResult;
import org.deftserver.web.AsyncCallback;
import org.deftserver.web.AsyncResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Charsets;

public class AsynchronousSocket implements IOHandler {
 
  private static final Logger logger = LoggerFactory.getLogger(AsynchronousSocket.class);
 
  private final int DEFAULT_BYTEBUFFER_SIZE = 1024;
 
  private final AsyncResult<String> nopAsyncStringResult = NopAsyncResult.of(String.class).nopAsyncResult;
  private final AsyncResult<Boolean> nopAsyncBooleanResult = NopAsyncResult.of(Boolean.class).nopAsyncResult;
 
  private final SelectableChannel channel;
  private int interestOps;
 
  private String readDelimiter = "";
  private int readBytes = Integer.MAX_VALUE;
 
  private AsyncResult<Boolean> connectCallback = nopAsyncBooleanResult;
  private AsyncCallback closeCallback = AsyncCallback.nopCb;
  private AsyncResult<String> readCallback = nopAsyncStringResult;
  private AsyncCallback writeCallback = AsyncCallback.nopCb;
 
  private final StringBuilder readBuffer = new StringBuilder();
  private final StringBuilder writeBuffer = new StringBuilder();
 
  private boolean reachedEOF = false;
 
  /**
   * Create a new {@code AsynchronousSocket} that will delegate its io operations the given {@code SelectableChannel}.
   */
  public AsynchronousSocket(SelectableChannel channel) {
    this.channel = channel;
    interestOps = SelectionKey.OP_CONNECT;
    if (channel instanceof SocketChannel && (((SocketChannel) channel).isConnected())) {
      interestOps |= SelectionKey.OP_READ;
    }
    IOLoop.INSTANCE.addHandler(channel, this, interestOps, null);
  }
 
  /**
   * Connects to the given host port tuple and invokes the given callback when a successful connection is established.
   */
  public void connect(String host, int port, AsyncResult<Boolean> ccb) {
    IOLoop.INSTANCE.updateHandler(channel, interestOps |= SelectionKey.OP_CONNECT);
    connectCallback = ccb;
    if (channel instanceof SocketChannel) {
      try {
        ((SocketChannel) channel).connect(new InetSocketAddress(host, port));
      } catch (IOException e) {
        logger.error("Failed to connect to: {}, message: {} ", host, e.getMessage());
        invokeConnectFailureCallback(e);
      } catch (UnresolvedAddressException e) {
        logger.warn("Unresolvable host: {}", host);
        invokeConnectFailureCallback(e);
      }
    }
  }
 
  /**
   * Close the socket.
   */
  public void close() {
    Closeables.closeQuietly(channel);
    invokeCloseCallback();
  }
 
  /**
   * The given callback will invoked when the underlaying {@code SelectableChannel} is closed.
   */
  public void setCloseCallback(AsyncCallback ccb) {
    closeCallback = ccb;
  }
 
  /**
   * Should only be invoked by the IOLoop
   */
  @Override
  public void handleAccept(SelectionKey key) throws IOException {
    logger.debug("handle accept...");
  }

  /**
   * Should only be invoked by the IOLoop
   */
  @Override
  public void handleConnect(SelectionKey key) throws IOException {
    logger.debug("handle connect...");
    SocketChannel sc = (SocketChannel) channel;
    if (sc.isConnectionPending()) {
      try {
        sc.finishConnect();
        invokeConnectSuccessfulCallback();
        interestOps &= ~SelectionKey.OP_CONNECT;
        IOLoop.INSTANCE.updateHandler(channel, interestOps |= SelectionKey.OP_READ);
      } catch (ConnectException e) {
        logger.warn("Connect failed: {}", e.getMessage());
        invokeConnectFailureCallback(e);
      }
    }
  }
 
  /**
   * Should only be invoked by the IOLoop
   */
  @Override
  public void handleRead(SelectionKey key) throws IOException {
    logger.debug("handle read...");
    ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BYTEBUFFER_SIZE);
    int read = ((SocketChannel) key.channel()).read(buffer);
    if (read == -1) {  // EOF
      reachedEOF = true;
      IOLoop.INSTANCE.updateHandler(channel, interestOps &= ~SelectionKey.OP_READ);
      return;
    }
    readBuffer.append(new String(buffer.array(), 0, buffer.position(), Charsets.ISO_8859_1));
    logger.debug("readBuffer size: {}", readBuffer.length());
    checkReadState();
  }

  /**
   * Should only be invoked by the IOLoop
   */
  @Override
  public void handleWrite(SelectionKey key) {
    logger.debug("handle write...");
    doWrite();
  }

  /**
   * Reads from the underlaying SelectableChannel until delimiter is reached. When it its, the given
   * AsyncResult will be invoked.
   */
  public void readUntil(String delimiter, AsyncResult<String> rcb) {
    logger.debug("readUntil delimiter: {}", delimiter);
    readDelimiter = delimiter;
    readCallback = rcb;
    checkReadState();
  }

  /**
   * Reads from the underlaying SelectableChannel until n bytes are read. When it its, the given
   * AsyncResult will be invoked.
   */
  public void readBytes(int n, AsyncResult<String> rcb) {
    logger.debug("readBytes #bytes: {}", n);
    readBytes = n;
    readCallback = rcb;
    checkReadState();
  }
 
  /**
   *  If readBuffer contains readDelimiter, client read is finished => invoke readCallback (onSuccess)
   *  Or if readBytes bytes are read, client read is finished => invoke readCallback (onSuccess)
   *  Of if end-of-stream is reached => invoke readCallback (onFailure)
   */
  private void checkReadState() {
    if (reachedEOF) {
      invokeReadFailureCallback(new EOFException("Reached end-of-stream"));
      return;
    }
    int index = readBuffer.indexOf(readDelimiter);
    if (index != -1 && !readDelimiter.isEmpty()) {
      String result = readBuffer.substring(0, index /*+ readDelimiter.length()*/);
      readBuffer.delete(0, index + readDelimiter.length());
      logger.debug("readBuffer size: {}", readBuffer.length());
      readDelimiter = "";
      invokeReadSuccessfulCallback(result);
    } else if (readBuffer.length() >= readBytes) {
      String result = readBuffer.substring(0, readBytes);
      readBuffer.delete(0, readBytes);
      logger.debug("readBuffer size: {}", readBuffer.length());
      readBytes = Integer.MAX_VALUE;
      invokeReadSuccessfulCallback(result);
    }
  }

  private void invokeReadSuccessfulCallback(String result) {
    AsyncResult<String> cb = readCallback;
    readCallback = nopAsyncStringResult;
    cb.onSuccess(result);
  }
 
  private void invokeReadFailureCallback(Exception e) {
    AsyncResult<String> cb = readCallback;
    readCallback = nopAsyncStringResult;
    cb.onFailure(e);
  }
 
  private void invokeWriteCallback() {
    AsyncCallback cb = writeCallback;
    writeCallback = AsyncCallback.nopCb;
    cb.onCallback();
  }
 
  private void invokeCloseCallback() {
    AsyncCallback cb = closeCallback;
    closeCallback = AsyncCallback.nopCb;
    cb.onCallback();
  }
 
  private void invokeConnectSuccessfulCallback() {
    AsyncResult<Boolean> cb = connectCallback;
    connectCallback = nopAsyncBooleanResult;
    cb.onSuccess(true);
  }
 
  private void invokeConnectFailureCallback(Exception e) {
    AsyncResult<Boolean> cb = connectCallback;
    connectCallback = nopAsyncBooleanResult;
    cb.onFailure(e);;
  }

  /**
   * Writes the given data to the underlaying SelectableChannel. When all data is successfully transmitted, the given
   * AsyncCallback will be invoked
   */
  public void write(String data, AsyncCallback wcb) {
    logger.debug("write data: {}", data);
    writeBuffer.append(data);
    logger.debug("writeBuffer size: {}", writeBuffer.length());
    writeCallback = wcb;
    doWrite();
  }
 
  /**
   * If we succeed to write everything in writeBuffer, client write is finished => invoke writeCallback
   */
  private void doWrite() {
    int written = 0;
    try {
      if (((SocketChannel)channel).isConnected()) {
        written = ((SocketChannel) channel).write(ByteBuffer.wrap(writeBuffer.toString().getBytes()));
      }
    } catch (IOException e) {
      logger.error("IOException during write: {}", e.getMessage());
      invokeCloseCallback();
      Closeables.closeQuietly(channel);
    }
    writeBuffer.delete(0, written);
    logger.debug("wrote: {} bytes", written);
    logger.debug("writeBuffer size: {}", writeBuffer.length());
    if (writeBuffer.length() > 0) {
      IOLoop.INSTANCE.updateHandler(channel, interestOps |= SelectionKey.OP_WRITE);
    } else {
      IOLoop.INSTANCE.updateHandler(channel, interestOps &= ~SelectionKey.OP_WRITE);
      invokeWriteCallback();
    }
  }

}
TOP

Related Classes of org.deftserver.io.AsynchronousSocket

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.