package kg.apc.io; import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketOption; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Set; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; // IMPORTANT: we have troubles with Java 6 to 7 compatibility here // SEE: http://www.oracle.com/technetwork/java/javase/compatibility-417013.html#incompatibilities // maybe should get rid of NIO here... /** * SocketChannel with timeouts. This class performs blocking operations for * connect and IO. Make note that some of methods are not implemeted yet. Also * selector usage kills scalability */ public class SocketChannelWithTimeouts extends SocketChannel { protected SocketChannel socketChannel; protected Selector selector; private long connectTimeout = 5000; private long readTimeout = 10000; protected SelectionKey channelKey; private static final Logger log = LoggingManager.getLoggerForClass(); private boolean fastFirstPacketRead; protected SocketChannelWithTimeouts() throws IOException { super(null); log.debug("Creating socketChannel"); selector = Selector.open(); socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); channelKey = socketChannel.register(selector, SelectionKey.OP_CONNECT); } public static SocketChannel open() throws IOException { return new SocketChannelWithTimeouts(); } @Override public SocketChannel bind(SocketAddress socketAddress) throws IOException { return socketChannel.bind(socketAddress); } @Override public SocketAddress getLocalAddress() throws IOException { return socketChannel.getLocalAddress(); } @Override public <T> SocketChannel setOption(SocketOption<T> socketOption, T t) throws IOException { return socketChannel.setOption(socketOption, t); } @Override public <T> T getOption(SocketOption<T> socketOption) throws IOException { return socketChannel.getOption(socketOption); } @Override public Set<SocketOption<?>> supportedOptions() { return socketChannel.supportedOptions(); } @Override public SocketChannel shutdownInput() throws IOException { return socketChannel.shutdownInput(); } @Override public SocketChannel shutdownOutput() throws IOException { return socketChannel.shutdownOutput(); } @Override public boolean connect(SocketAddress remote) throws IOException { long start = System.currentTimeMillis(); //log.debug("trying to connect"); socketChannel.connect(remote); if (selector.select(connectTimeout) > 0) { selector.selectedKeys().remove(channelKey); //log.debug("selected connect"); //log.debug("Spent " + (System.currentTimeMillis() - start)); if (!channelKey.isConnectable()) { throw new IllegalStateException("Socket channel is in not connectable state"); } socketChannel.finishConnect(); channelKey = socketChannel.register(selector, SelectionKey.OP_READ); if (log.isDebugEnabled()) { log.debug("Connected socket in " + (System.currentTimeMillis() - start)); } if (!socketChannel.isConnected()) { throw new SocketException("SocketChannel not connected on some reason"); } return true; } //log.debug("Spent " + (System.currentTimeMillis() - start)); throw new SocketTimeoutException("Failed to connect to " + remote.toString()); } @Override public int read(ByteBuffer dst) throws IOException { int bytesRead = 0; while (selector.select(readTimeout) > 0) { selector.selectedKeys().remove(channelKey); int cnt = socketChannel.read(dst); if (cnt < 1) { if (bytesRead < 1) { bytesRead = -1; } //log.info("Bytes read: "+bytesRead); return bytesRead; } else { bytesRead += cnt; if (!fastFirstPacketRead) { fastFirstPacketRead = true; return bytesRead; } } } throw new SocketTimeoutException("Timeout exceeded while reading from socket"); } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { throw new UnsupportedOperationException("Not supported yet."); } @Override public int write(ByteBuffer src) throws IOException { fastFirstPacketRead = false; int res = 0; int size = src.remaining(); while (res < size) { res += socketChannel.write(src); } return res; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { throw new UnsupportedOperationException("Not supported yet."); } @Override protected void implCloseSelectableChannel() throws IOException { socketChannel.close(); selector.close(); } @Override protected void implConfigureBlocking(boolean block) throws IOException { throw new UnsupportedOperationException("This class is blocking implementation of SocketChannel"); } @Override public Socket socket() { return socketChannel.socket(); } @Override public boolean isConnected() { return socketChannel.isConnected(); } @Override public boolean isConnectionPending() { return socketChannel.isConnectionPending(); } @Override public boolean finishConnect() throws IOException { return socketChannel.finishConnect(); } public void setConnectTimeout(int t) { connectTimeout = t; } public void setReadTimeout(int t) { readTimeout = t; } public SocketAddress getRemoteAddress() throws IOException { return null; } }