/* * Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software;Designed and Developed mainly by many Chinese * opensource volunteers. you can redistribute it and/or modify it under the * terms of the GNU General Public License version 2 only, as published by the * Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Any questions about this component can be directed to it's project Web address * https://code.google.com/p/opencloudb/. * */ package org.opencloudb.net; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.nio.channels.SelectionKey; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import org.opencloudb.buffer.BufferQueue; import org.opencloudb.config.ErrorCode; import org.opencloudb.util.TimeUtil; /** * @author mycat */ public abstract class AbstractConnection implements NIOConnection { protected static final Logger LOGGER = Logger .getLogger(AbstractConnection.class); protected final AsynchronousSocketChannel channel; protected NIOProcessor processor; protected NIOHandler handler; protected SelectionKey processKey; protected int packetHeaderSize; protected int maxPacketSize; private java.util.concurrent.locks.ReentrantLock writeLock = new ReentrantLock(); protected volatile int readBufferOffset; private volatile ByteBuffer readBuffer; private volatile ByteBuffer writeBuffer; private volatile boolean writing; // private volatile boolean writing = false; protected BufferQueue writeQueue; protected boolean isRegistered; protected final AtomicBoolean isClosed; protected boolean isSocketClosed; protected long startupTime; protected long lastReadTime; protected long lastWriteTime; protected long netInBytes; protected long netOutBytes; protected int writeAttempts; private long idleTimeout; private static AIOReadHandler aioReadHandler = new AIOReadHandler(); private static AIOWriteHandler aioWriteHandler = new AIOWriteHandler(); public AbstractConnection(AsynchronousSocketChannel channel) { this.channel = channel; this.isClosed = new AtomicBoolean(false); this.startupTime = TimeUtil.currentTimeMillis(); this.lastReadTime = startupTime; this.lastWriteTime = startupTime; } public long getIdleTimeout() { return idleTimeout; } public void setIdleTimeout(long idleTimeout) { this.idleTimeout = idleTimeout; } public boolean isIdleTimeout() { return TimeUtil.currentTimeMillis() > Math.max(lastWriteTime, lastReadTime) + idleTimeout; } public AsynchronousSocketChannel getChannel() { return channel; } public int getPacketHeaderSize() { return packetHeaderSize; } public void setPacketHeaderSize(int packetHeaderSize) { this.packetHeaderSize = packetHeaderSize; } public int getMaxPacketSize() { return maxPacketSize; } public void setMaxPacketSize(int maxPacketSize) { this.maxPacketSize = maxPacketSize; } public long getStartupTime() { return startupTime; } public long getLastReadTime() { return lastReadTime; } public void setProcessor(NIOProcessor processor) { this.processor = processor; this.readBuffer = processor.getBufferPool().allocate(); } public long getLastWriteTime() { return lastWriteTime; } public long getNetInBytes() { return netInBytes; } public long getNetOutBytes() { return netOutBytes; } public int getWriteAttempts() { return writeAttempts; } public NIOProcessor getProcessor() { return processor; } public ByteBuffer getReadBuffer() { return readBuffer; } public BufferQueue getWriteQueue() { return writeQueue; } public void setWriteQueue(BufferQueue writeQueue) { this.writeQueue = writeQueue; } public ByteBuffer allocate() { ByteBuffer buffer = this.processor.getBufferPool().allocate(); return buffer; } public final void recycle(ByteBuffer buffer) { this.processor.getBufferPool().recycle(buffer); } public final void recycleIfNeed(ByteBuffer buffer) { this.processor.getBufferPool().safeRecycle(buffer); } public void setHandler(NIOHandler handler) { this.handler = handler; } @Override public void handle(byte[] data) { try { handler.handle(data); } catch (Throwable e) { close("exeption:" + e.toString()); if (e instanceof ConnectionException) { error(ErrorCode.ERR_CONNECT_SOCKET, e); } else { error(ErrorCode.ERR_HANDLE_DATA, e); } } } @Override public void register() throws IOException { } public void asynRead() { ByteBuffer theBuffer = readBuffer; if (theBuffer == null) { theBuffer = ByteBuffer.allocate(1024); this.readBuffer = theBuffer; channel.read(theBuffer, this, aioReadHandler); } else if (theBuffer.hasRemaining()) { // theBuffer.compact(); channel.read(theBuffer, this, aioReadHandler); } else { throw new java.lang.IllegalArgumentException("full buffer to read "); } } public void onReadData(int got) throws IOException { if (isClosed.get()) { return; } ByteBuffer buffer = this.readBuffer; lastReadTime = TimeUtil.currentTimeMillis(); if (got < 0) { if (!this.isClosed()) { this.close("socket closed"); return; } } else if (got == 0) { return; } netInBytes += got; processor.addNetInBytes(got); // 澶勭悊鏁版嵁 int offset = readBufferOffset, length = 0, position = buffer.position(); for (;;) { length = getPacketLength(buffer, offset); if (length == -1) { if (!buffer.hasRemaining()) { buffer = checkReadBuffer(buffer, offset, position); } break; } if (position >= offset + length) { buffer.position(offset); byte[] data = new byte[length]; buffer.get(data, 0, length); handle(data); offset += length; if (position == offset) { if (readBufferOffset != 0) { readBufferOffset = 0; } buffer.clear(); break; } else { readBufferOffset = offset; buffer.position(position); continue; } } else { if (!buffer.hasRemaining()) { buffer = checkReadBuffer(buffer, offset, position); } break; } } } public void write(byte[] data) { ByteBuffer buffer = allocate(); buffer = writeToBuffer(data, buffer); write(buffer); } @Override public final void write(ByteBuffer buffer) { if (isClosed.get()) { recycle(buffer); aioWriteHandler.failed(new RuntimeException("socket already closed "), this); return; } try { writeLock.lock(); if (writing == false && writeQueue.isEmpty()) { writeBuffer = buffer; asynWrite(buffer); } else { writeQueue.put(buffer); } } catch (InterruptedException e) { error(ErrorCode.ERR_PUT_WRITE_QUEUE, e); return; } finally { writeLock.unlock(); } } private void asynWrite(ByteBuffer buffer) { writing = true; buffer.flip(); this.channel.write(buffer, this, aioWriteHandler); } public ByteBuffer checkWriteBuffer(ByteBuffer buffer, int capacity, boolean writeSocketIfFull) { if (capacity > buffer.remaining()) { if (writeSocketIfFull) { write(buffer); return allocate(); } else {// Relocate a larger buffer buffer.flip(); ByteBuffer newBuf = ByteBuffer.allocate(capacity); newBuf.put(buffer); this.recycle(buffer); return newBuf; } } else { return buffer; } } public ByteBuffer writeToBuffer(byte[] src, ByteBuffer buffer) { int offset = 0; int length = src.length; int remaining = buffer.remaining(); while (length > 0) { if (remaining >= length) { buffer.put(src, offset, length); break; } else { buffer.put(src, offset, remaining); write(buffer); buffer = allocate(); offset += remaining; length -= remaining; remaining = buffer.remaining(); continue; } } return buffer; } @Override public void close(String reason) { if (!isClosed.get()) { closeSocket(); isClosed.set(true); this.cleanup(); LOGGER.info("close connection,reason:" + reason + " " + this); } } public boolean isClosed() { return isClosed.get(); } public void idleCheck() { if (isIdleTimeout()) { LOGGER.info(toString() + " idle timeout"); close(" idle "); } } /** * 娓呯悊閬楃暀璧勬簮 */ protected void cleanup() { // 鍥炴敹鎺ユ敹缂撳瓨 if (readBuffer != null) { recycle(readBuffer); this.readBuffer = null; this.readBufferOffset = 0; } // 鍥炴敹鍙戦�缂撳瓨 if (writeQueue != null) { ByteBuffer buffer = null; while ((buffer = writeQueue.poll()) != null) { recycle(buffer); } writeQueue = null; } } protected int getPacketLength(ByteBuffer buffer, int offset) { if (buffer.position() < offset + packetHeaderSize) { return -1; } else { int length = buffer.get(offset) & 0xff; length |= (buffer.get(++offset) & 0xff) << 8; length |= (buffer.get(++offset) & 0xff) << 16; return length + packetHeaderSize; } } private ByteBuffer checkReadBuffer(ByteBuffer buffer, int offset, int position) { if (offset == 0) { if (buffer.capacity() >= maxPacketSize) { throw new IllegalArgumentException( "Packet size over the limit."); } int size = buffer.capacity() << 1; size = (size > maxPacketSize) ? maxPacketSize : size; ByteBuffer newBuffer = processor.getBufferPool().allocate(size); buffer.position(offset); newBuffer.put(buffer); readBuffer = newBuffer; recycle(buffer); return newBuffer; } else { buffer.position(offset); buffer.compact(); readBufferOffset = 0; return buffer; } } protected void onWriteFinished(int result) { if (isClosed.get()) { return; } netOutBytes += result; processor.addNetOutBytes(result); lastWriteTime = TimeUtil.currentTimeMillis(); try { writeLock.lock(); ByteBuffer theBuffer = writeBuffer; if (theBuffer.hasRemaining()) { theBuffer.compact(); asynWrite(theBuffer); } else {// write finished this.recycle(theBuffer); writeBuffer = null; writing = false; theBuffer = writeQueue.poll(); if (theBuffer != null) { this.writeBuffer = theBuffer; asynWrite(theBuffer); } else { writeBuffer = null; } } } finally { writeLock.unlock(); } } private void closeSocket() { if (channel != null) { boolean isSocketClosed = true; try { channel.close(); } catch (Throwable e) { } boolean closed = isSocketClosed && (!channel.isOpen()); if (closed == false) { LOGGER.warn("close socket of connnection failed " + this); } } } } class AIOWriteHandler implements CompletionHandler<Integer, AbstractConnection> { @Override public void completed(Integer result, AbstractConnection con) { if (result >= 0) { con.onWriteFinished(result); } else { con.close("write erro " + result); } } @Override public void failed(Throwable exc, AbstractConnection con) { con.close("write failed " + exc); } } class AIOReadHandler implements CompletionHandler<Integer, AbstractConnection> { @Override public void completed(Integer i, AbstractConnection con) { if (i > 0) { try { con.onReadData(i); con.asynRead(); } catch (IOException e) { con.close("handle err:" + e); } } else if (i == -1) { // System.out.println("read -1 xxxxxxxxx "+con); con.close("client closed"); } } @Override public void failed(Throwable exc, AbstractConnection con) { con.close(exc.toString()); } }