package com.google.code.hs4j.network.nio.impl;
/**
*Copyright [2008-2009] [dennis zhuang]
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*Unless required by applicable law or agreed to in writing,
*software distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
*either express or implied. See the License for the specific language governing permissions and limitations under the License
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Future;
import com.google.code.hs4j.network.buffer.IoBuffer;
import com.google.code.hs4j.network.config.Configuration;
import com.google.code.hs4j.network.core.EventType;
import com.google.code.hs4j.network.core.WriteMessage;
import com.google.code.hs4j.network.core.impl.FutureImpl;
import com.google.code.hs4j.network.core.impl.WriteMessageImpl;
import com.google.code.hs4j.network.nio.NioSessionConfig;
import com.google.code.hs4j.network.util.ByteBufferUtils;
import com.google.code.hs4j.network.util.SelectorFactory;
/**
* Nio tcp connection
*
* @author dennis
*
*/
public class NioTCPSession extends AbstractNioSession {
private InetSocketAddress remoteAddress;
@Override
public final boolean isExpired() {
if (log.isDebugEnabled()) {
log.debug("sessionTimeout=" + sessionTimeout + ",this.timestamp="
+ lastOperationTimeStamp.get() + ",current="
+ System.currentTimeMillis());
}
return sessionTimeout <= 0 ? false : System.currentTimeMillis()
- lastOperationTimeStamp.get() >= sessionTimeout;
}
public NioTCPSession(NioSessionConfig sessionConfig, int readRecvBufferSize) {
super(sessionConfig);
if (selectableChannel != null && getRemoteSocketAddress() != null) {
loopback = getRemoteSocketAddress().getAddress()
.isLoopbackAddress();
}
setReadBuffer(IoBuffer.allocate(readRecvBufferSize));
onCreated();
}
@Override
protected Object writeToChannel(WriteMessage message) throws IOException {
if (message.getWriteFuture() != null && !message.isWriting()
&& message.getWriteFuture().isCancelled()) {
return message.getMessage();
}
if (message.getWriteBuffer() == null) {
if (message.getWriteFuture() != null) {
message.getWriteFuture().setResult(Boolean.TRUE);
}
return message.getMessage();
}
IoBuffer writeBuffer = message.getWriteBuffer();
// begin writing
message.writing();
if (useBlockingWrite) {
return blockingWrite(selectableChannel, message, writeBuffer);
} else {
while (true) {
long n = doRealWrite(selectableChannel, writeBuffer);
if (n > 0) {
statistics.statisticsWrite(n);
scheduleWritenBytes.addAndGet(0 - n);
}
if (writeBuffer == null || !writeBuffer.hasRemaining()) {
if (message.getWriteFuture() != null) {
message.getWriteFuture().setResult(Boolean.TRUE);
}
return message.getMessage();
} else if (n == 0) {
// have more data, but the buffer is full,
// wait next time to write
return null;
}
}
}
}
public InetSocketAddress getRemoteSocketAddress() {
if (remoteAddress == null) {
remoteAddress = (InetSocketAddress) ((SocketChannel) selectableChannel)
.socket().getRemoteSocketAddress();
}
return remoteAddress;
}
/**
* Blocking write using temp selector
*
* @param channel
* @param message
* @param writeBuffer
* @return
* @throws IOException
* @throws ClosedChannelException
*/
protected final Object blockingWrite(SelectableChannel channel,
WriteMessage message, IoBuffer writeBuffer) throws IOException,
ClosedChannelException {
SelectionKey tmpKey = null;
Selector writeSelector = null;
int attempts = 0;
int bytesProduced = 0;
try {
while (writeBuffer.hasRemaining()) {
long len = doRealWrite(channel, writeBuffer);
if (len > 0) {
attempts = 0;
bytesProduced += len;
statistics.statisticsWrite(len);
} else {
attempts++;
if (writeSelector == null) {
writeSelector = SelectorFactory.getSelector();
if (writeSelector == null) {
// Continue using the main one.
continue;
}
tmpKey = channel.register(writeSelector,
SelectionKey.OP_WRITE);
}
if (writeSelector.select(1000) == 0) {
if (attempts > 2) {
throw new IOException("Client disconnected");
}
}
}
}
if (!writeBuffer.hasRemaining() && message.getWriteFuture() != null) {
message.getWriteFuture().setResult(Boolean.TRUE);
}
} finally {
if (tmpKey != null) {
tmpKey.cancel();
tmpKey = null;
}
if (writeSelector != null) {
// Cancel the key.
writeSelector.selectNow();
SelectorFactory.returnSelector(writeSelector);
}
}
scheduleWritenBytes.addAndGet(0 - bytesProduced);
return message.getMessage();
}
@Override
protected WriteMessage wrapMessage(Object msg, Future<Boolean> writeFuture) {
WriteMessage message = new WriteMessageImpl(msg,
(FutureImpl<Boolean>) writeFuture);
if (message.getWriteBuffer() == null) {
message.setWriteBuffer(encoder.encode(message.getMessage(), this));
}
return message;
}
@Override
protected void readFromBuffer() {
if (!readBuffer.hasRemaining()) {
if (readBuffer.capacity() < Configuration.MAX_READ_BUFFER_SIZE) {
readBuffer = IoBuffer.wrap(ByteBufferUtils
.increaseBufferCapatity(readBuffer.buf()));
} else {
// buffer's capacity is greater than maxium
return;
}
}
if (closed) {
return;
}
int n = -1;
int readCount = 0;
try {
while ((n = ((ReadableByteChannel) selectableChannel)
.read(readBuffer.buf())) > 0) {
readCount += n;
}
if (readCount > 0) {
readBuffer.flip();
decode();
readBuffer.compact();
} else if (readCount == 0
&& !((SocketChannel) selectableChannel).socket()
.isInputShutdown() && useBlockingRead) {
n = blockingRead();
if (n > 0) {
readCount += n;
}
}
if (n < 0) { // Connection closed
close();
} else {
selectorManager.registerSession(this, EventType.ENABLE_READ);
}
if (log.isDebugEnabled()) {
log.debug("read " + readCount + " bytes from channel");
}
} catch (ClosedChannelException e) {
// ignore exception
close();
} catch (Throwable e) {
onException(e);
close();
}
}
/**
* Blocking read using temp selector
*
* @return
* @throws ClosedChannelException
* @throws IOException
*/
protected final int blockingRead() throws ClosedChannelException,
IOException {
int n = 0;
Selector readSelector = SelectorFactory.getSelector();
SelectionKey tmpKey = null;
try {
if (selectableChannel.isOpen()) {
tmpKey = selectableChannel.register(readSelector, 0);
tmpKey.interestOps(tmpKey.interestOps() | SelectionKey.OP_READ);
int code = readSelector.select(500);
tmpKey
.interestOps(tmpKey.interestOps()
& ~SelectionKey.OP_READ);
if (code > 0) {
do {
n = ((ReadableByteChannel) selectableChannel)
.read(readBuffer.buf());
if (log.isDebugEnabled()) {
log.debug("use temp selector read " + n + " bytes");
}
} while (n > 0 && readBuffer.hasRemaining());
readBuffer.flip();
decode();
readBuffer.compact();
}
}
} finally {
if (tmpKey != null) {
tmpKey.cancel();
tmpKey = null;
}
if (readSelector != null) {
// Cancel the key.
readSelector.selectNow();
SelectorFactory.returnSelector(readSelector);
}
}
return n;
}
/**
* Decode buffer
*/
@Override
public void decode() {
Object message;
int size = readBuffer.remaining();
while (readBuffer.hasRemaining()) {
try {
message = decoder.decode(readBuffer, this);
if (message == null) {
break;
} else {
if (statistics.isStatistics()) {
statistics
.statisticsRead(size - readBuffer.remaining());
size = readBuffer.remaining();
}
}
dispatchReceivedMessage(message);
} catch (Exception e) {
onException(e);
log.error("Decode error", e);
super.close();
break;
}
}
}
public Socket socket() {
return ((SocketChannel) selectableChannel).socket();
}
@Override
protected final void closeChannel() throws IOException {
flush0();
// try to close output first
Socket socket = ((SocketChannel) selectableChannel).socket();
try {
if (!socket.isClosed() && !socket.isOutputShutdown()) {
socket.shutdownOutput();
}
if (!socket.isClosed() && !socket.isInputShutdown()) {
socket.shutdownInput();
}
} catch (IOException e) {
}
try {
socket.close();
} catch (IOException e) {
}
unregisterSession();
unregisterChannel();
}
}