/*
* Copyright 1999-2012 Alibaba Group.
*
* 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.
*/
package fm.liu.timo.net;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.pmw.tinylog.Logger;
import fm.liu.timo.mysql.connection.MySQLConnection;
import fm.liu.timo.net.connection.AbstractConnection;
import fm.liu.timo.net.connection.BackendConnection;
/**
* @author xianmao.hexm
*/
public final class NIOConnector extends Thread {
private static final ConnectIdGenerator ID_GENERATOR = new ConnectIdGenerator();
private final String name;
private final Selector selector;
private final BlockingQueue<BackendConnection> connectQueue;
private long connectCount;
public NIOConnector(String name) throws IOException {
super.setName(name);
this.name = name;
this.selector = Selector.open();
this.connectQueue = new LinkedBlockingQueue<BackendConnection>();
}
public long getConnectCount() {
return connectCount;
}
public void postConnect(BackendConnection c) {
connectQueue.offer(c);
selector.wakeup();
}
@Override
public void run() {
final Selector selector = this.selector;
for (;;) {
++connectCount;
try {
selector.select(1000L);
connect(selector);
Set<SelectionKey> keys = selector.selectedKeys();
try {
for (SelectionKey key : keys) {
Object att = key.attachment();
if (att != null && key.isValid() && key.isConnectable()) {
finishConnect(key, att);
} else {
key.cancel();
}
}
} finally {
keys.clear();
}
} catch (Throwable e) {
Logger.warn("Thread {} exception:{}", name, e);
}
}
}
private void connect(Selector selector) {
AbstractConnection c = null;
while ((c = connectQueue.poll()) != null) {
try {
SocketChannel channel = (SocketChannel) c.getChannel();
channel.register(selector, SelectionKey.OP_CONNECT, c);
channel.connect(new InetSocketAddress(c.getHost(), c.getPort()));
} catch (Throwable e) {
c.close(e.getMessage());
}
}
}
private void finishConnect(SelectionKey key, Object att) {
BackendConnection c = (BackendConnection) att;
try {
if (finishConnect(c, (SocketChannel) c.getChannel())) {
clearSelectionKey(key);
c.setID(ID_GENERATOR.getID());
c.getProcessor().addBackend(c);
MySQLConnection msqlCon = (MySQLConnection) c;
msqlCon.getDatasource().add(c);
NIOProcessor processor = (NIOProcessor) c.getProcessor();
NIOReactor reactor = processor.getReactor();
reactor.postRegister(c);
}
} catch (Throwable e) {
clearSelectionKey(key);
c.onConnectFailed(e);
c.close(e.getMessage());
}
}
private boolean finishConnect(BackendConnection c, SocketChannel channel) throws IOException {
if (channel.isConnectionPending()) {
channel.finishConnect();
c.finishConnect();
return true;
} else {
return false;
}
}
private void clearSelectionKey(SelectionKey key) {
if (key.isValid()) {
key.attach(null);
key.cancel();
}
}
/**
* 后端连接ID生成器
*
* @author xianmao.hexm
*/
private static class ConnectIdGenerator {
private static final long MAX_VALUE = Long.MAX_VALUE;
private long connectId = 0L;
private final Object lock = new Object();
private long getID() {
synchronized (lock) {
if (connectId >= MAX_VALUE) {
connectId = 0L;
}
return ++connectId;
}
}
}
}