/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you 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 org.jboss.netty.channel.socket.nio;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import nliveroid.nlr.main.ClientHandler;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipelineException;
import org.jboss.netty.channel.ChannelSink;
import org.jboss.netty.channel.ChannelState;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.DefaultExceptionEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SucceededChannelFuture;
import org.jboss.netty.util.ThreadRenamingRunnable;
import org.jboss.netty.util.internal.DeadLockProofWorker;
import org.jboss.netty.util.internal.LinkedTransferQueue;
import android.util.Log;
import com.flazr.rtmp.RtmpDecoder;
import com.flazr.rtmp.RtmpEncoder;
import com.flazr.rtmp.client.ClientHandshakeHandler;
import com.flazr.rtmp.client.RtmpPublisher;
/**
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
*
* @version $Rev: 2144 $, $Date: 2010-02-09 12:41:12 +0900 (Tue, 09 Feb 2010) $
*
*/
public class NioClientSocketPipelineSink implements ChannelSink {
private static final AtomicInteger nextId = new AtomicInteger();
final int id = nextId.incrementAndGet();
final Executor bossExecutor;
private final Boss[] bosses;
private final NioWorker[] workers;
private final AtomicInteger bossIndex = new AtomicInteger();
private final AtomicInteger workerIndex = new AtomicInteger();
private ClientHandler handler;
public NioClientSocketPipelineSink(
Executor bossExecutor, Executor workerExecutor,
int bossCount, int workerCount,ClientHandshakeHandler handshaker,RtmpDecoder decoder,RtmpEncoder encoder,ClientHandler handler,RtmpPublisher publisher) {
this.bossExecutor = bossExecutor;
this.handler = handler;
//ここでBossとWorkerがnewされる
bosses = new Boss[bossCount];
for (int i = 0; i < bosses.length; i ++) {
bosses[i] = new Boss(i + 1);
}
workers = new NioWorker[workerCount];
for (int i = 0; i < workers.length; i ++) {
workers[i] = new NioWorker(id, i + 1, workerExecutor,handshaker,decoder,encoder,handler,publisher);
}
}
public void eventSunkStateEvent(ChannelEvent e) throws Exception {
ChannelStateEvent event = (ChannelStateEvent) e;
NioSocketChannel channel =
(NioSocketChannel) event.getChannel();
ChannelFuture future = event.getFuture();
ChannelState state = event.getState();
Object value = event.getValue();
Log.d("NioClientSocketPipelineSink","ChannelStateEvent " + state + " " + value);
switch (state) {
case CONNECTED:
if (value != null) {
connect(channel, future, (SocketAddress) value);
} else {
channel.worker.close(channel, future);
}
break;
case INTEREST_OPS:
channel.worker.setInterestOps(channel, future, ((Integer) value).intValue());
break;
}
}
public void eventSunkMessageEvent(MessageEvent e) throws Exception {
Log.d("NioClientSocketPipelineSink","OFFER --- ");
NioSocketChannel channel = (NioSocketChannel) e.getChannel();
boolean offered = channel.writeBuffer.offer(e);//書き込み予約をセットする
assert offered;
channel.worker.writeFromUserCode(channel);//ここが実際にNioWorkerのwrite0からSendBufferPoolに渡されて送信される
}
public void offerWrite(ChannelEvent e){
// Log.d("NioClientSocketPipelineSink","offerWrite --- " +e.getChannel().toString() +" " + e.toString() );
MessageEvent event = (MessageEvent) e;
NioSocketChannel channel = (NioSocketChannel) event.getChannel();
boolean offered = channel.writeBuffer.offer(event);
assert offered;
channel.worker.writeFromUserCode(channel);//ここが実際にNioWorkerのwrite0からSendBufferPoolに渡されて送信される
}
public void connect(
final NioSocketChannel channel, final ChannelFuture future,
SocketAddress remoteAddress) {
try {
if (channel.socket.connect(remoteAddress)) {
Log.d("NioClientSocketPipelineSink","Sockeet connect success");
channel.worker.register(channel, future);//スレッドをスタートする
} else {
channel.getCloseFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture f)
throws Exception {//正常終了処置
Log.d("NioClientSocketPipelineSink","Socket close operationComplete");
if (!future.isDone()) {
future.setFailure(new ClosedChannelException());
}
}
});
future.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
channel.connectFuture = future;
nextBoss().register(channel);
}
} catch (Throwable t) {
future.setFailure(t);
handler.exceptionCaught(new DefaultExceptionEvent(channel, t));
channel.worker.close(channel, new SucceededChannelFuture(channel));
}
}
public ChannelFuture close(ChannelEvent e){
ChannelStateEvent event = (ChannelStateEvent) e;
Object value = event.getValue();
NioSocketChannel channel =
(NioSocketChannel) e.getChannel();
ChannelFuture future = e.getFuture();
if (Boolean.FALSE.equals(value)) {//CLOSEが作られていなく、なぜかこの値のFALSEでCLOSEするみたい→このクラスのcloseに変更
channel.worker.close(channel, future);
}
return future;
}
Boss nextBoss() {
return bosses[Math.abs(
bossIndex.getAndIncrement() % bosses.length)];
}
public NioWorker nextWorker() {
return workers[Math.abs(
workerIndex.getAndIncrement() % workers.length)];
}
private final class Boss implements Runnable {
volatile Selector selector;
private boolean started;
private final int subId;
private final AtomicBoolean wakenUp = new AtomicBoolean();
private final Object startStopLock = new Object();
private final Queue<Runnable> registerTaskQueue = new LinkedTransferQueue<Runnable>();
Boss(int subId) {
this.subId = subId;
}
void register(NioSocketChannel channel) {
Runnable registerTask = new RegisterTask(this, channel);
Selector selector;
synchronized (startStopLock) {
if (!started) {//ド頭のストリームと接続するロジック
// Open a selector if this worker didn't start yet.
try {
Log.d("NioClientSocketPipelineSink","SelectorOpen");
this.selector = selector = Selector.open();
} catch (Throwable t) {
throw new ChannelException(
"Failed to create a selector.", t);
}
// Start the worker thread with the new Selector.
boolean success = false;
try {
DeadLockProofWorker.start(
bossExecutor,
new ThreadRenamingRunnable(
this, "New I/O client boss #" + id + '-' + subId));
success = true;
} finally {
if (!success) {
// Release the Selector if the execution fails.
try {
selector.close();
} catch (Throwable t) {
Log.d("","Failed to close a selector."+ t);
}
this.selector = selector = null;
// The method will return to the caller at this point.
}
}
} else {
// Use the existing selector if this worker has been started.
selector = this.selector;
}
assert selector != null && selector.isOpen();
started = true;
boolean offered = registerTaskQueue.offer(registerTask);
assert offered;
}
if (wakenUp.compareAndSet(false, true)) {
selector.wakeup();
}
}
public void run() {
boolean shutdown = false;
Selector selector = this.selector;
long lastConnectTimeoutCheckTimeNanos = System.nanoTime();
for (;;) {
wakenUp.set(false);
try {
int selectedKeyCount = selector.select(500);
// 'wakenUp.compareAndSet(false, true)' is always evaluated
// before calling 'selector.wakeup()' to reduce the wake-up
// overhead. (Selector.wakeup() is an expensive operation.)
//
// However, there is a race condition in this approach.
// The race condition is triggered when 'wakenUp' is set to
// true too early.
//
// 'wakenUp' is set to true too early if:
// 1) Selector is waken up between 'wakenUp.set(false)' and
// 'selector.select(...)'. (BAD)
// 2) Selector is waken up between 'selector.select(...)' and
// 'if (wakenUp.get()) { ... }'. (OK)
//
// In the first case, 'wakenUp' is set to true and the
// following 'selector.select(...)' will wake up immediately.
// Until 'wakenUp' is set to false again in the next round,
// 'wakenUp.compareAndSet(false, true)' will fail, and therefore
// any attempt to wake up the Selector will fail, too, causing
// the following 'selector.select(...)' call to block
// unnecessarily.
//
// To fix this problem, we wake up the selector again if wakenUp
// is true immediately after selector.select(...).
// It is inefficient in that it wakes up the selector for both
// the first case (BAD - wake-up required) and the second case
// (OK - no wake-up required).
if (wakenUp.get()) {
selector.wakeup();
}
processRegisterTaskQueue();
if (selectedKeyCount > 0) {
processSelectedKeys(selector.selectedKeys());
}
// Handle connection timeout every 0.5 seconds approximately.
long currentTimeNanos = System.nanoTime();
if (currentTimeNanos - lastConnectTimeoutCheckTimeNanos >= 500 * 1000000L) {
lastConnectTimeoutCheckTimeNanos = currentTimeNanos;
processConnectTimeout(selector.keys(), currentTimeNanos);
}
// Exit the loop when there's nothing to handle.
// The shutdown flag is used to delay the shutdown of this
// loop to avoid excessive Selector creation when
// connection attempts are made in a one-by-one manner
// instead of concurrent manner.
if (selector.keys().isEmpty()) {
if (shutdown ||
bossExecutor instanceof ExecutorService && ((ExecutorService) bossExecutor).isShutdown()) {
synchronized (startStopLock) {
if (registerTaskQueue.isEmpty() && selector.keys().isEmpty()) {
started = false;
try {
selector.close();
} catch (IOException e) {
Log.d("IOException at Nio ClientSocketPipeline",
"Failed to close a selector."+ e);
} finally {
this.selector = null;
}
break;
} else {
shutdown = false;
}
}
} else {
// Give one more second.
shutdown = true;
}
} else {
shutdown = false;
}
} catch (Throwable t) {
Log.d("ERROR",
"Unexpected exception in the selector loop."+t);
// Prevent possible consecutive immediate failures.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore.
}
}
}
}
private void processRegisterTaskQueue() {
for (;;) {
final Runnable task = registerTaskQueue.poll();
if (task == null) {
break;
}
task.run();
}
}
private void processSelectedKeys(Set<SelectionKey> selectedKeys) {
for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {
SelectionKey k = i.next();
i.remove();
if (!k.isValid()) {
close(k);
continue;
}
if (k.isConnectable()) {
connect(k);
}
}
}
private void processConnectTimeout(Set<SelectionKey> keys, long currentTimeNanos) {
ConnectException cause = null;
for (SelectionKey k: keys) {
if (!k.isValid()) {
close(k);
continue;
}
NioSocketChannel ch = (NioSocketChannel) k.attachment();
if (ch.connectDeadlineNanos > 0 &&
currentTimeNanos >= ch.connectDeadlineNanos) {
if (cause == null) {
cause = new ConnectException("connection timed out");
}
ch.connectFuture.setFailure(cause);
handler.exceptionCaught(new DefaultExceptionEvent(ch, cause));
ch.worker.close(ch, new SucceededChannelFuture(ch));
}
}
}
private void connect(SelectionKey k) {
NioSocketChannel ch = (NioSocketChannel) k.attachment();
try {
if (ch.socket.finishConnect()) {
k.cancel();
ch.worker.register(ch, ch.connectFuture);
}
} catch (Throwable t) {
ch.connectFuture.setFailure(t);
handler.exceptionCaught(new DefaultExceptionEvent(ch, t));
k.cancel(); // Some JDK implementations run into an infinite loop without this.
ch.worker.close(ch, new SucceededChannelFuture(ch));
}
}
private void close(SelectionKey k) {
NioSocketChannel ch = (NioSocketChannel) k.attachment();
ch.worker.close(ch, new SucceededChannelFuture(ch));
}
}
private static final class RegisterTask implements Runnable {
private final Boss boss;
private final NioSocketChannel channel;
RegisterTask(Boss boss, NioSocketChannel channel) {
this.boss = boss;
this.channel = channel;
}
public void run() {
try {//実際の接続はここ
channel.socket.register(
boss.selector, SelectionKey.OP_CONNECT, channel);
} catch (ClosedChannelException e) {
channel.worker.close(channel, new SucceededChannelFuture(channel));
}
int connectTimeout = channel.getConfig().getConnectTimeoutMillis();
if (connectTimeout > 0) {
channel.connectDeadlineNanos = System.nanoTime() + connectTimeout * 1000000L;
}
}
}
/**
* Sends an {@link ExceptionEvent} upstream with the specified
* {@code cause}.
*
* @param event the {@link ChannelEvent} which caused a
* {@link ChannelHandler} to raise an exception
* @param cause the exception raised by a {@link ChannelHandler}
*/
public void exceptionCaught(
ChannelEvent event, ChannelPipelineException cause) throws Exception {
Throwable actualCause = cause.getCause();
if (actualCause == null) {
actualCause = cause;
}
handler.exceptionCaught(new DefaultExceptionEvent(event.getChannel(), actualCause));
}
}