/* * 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.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.SocketChannel; import java.util.Queue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import nliveroid.nlr.main.ClientHandler; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelException; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelState; import org.jboss.netty.channel.DefaultChannelFuture; import org.jboss.netty.channel.DownstreamChannelStateEvent; import org.jboss.netty.channel.FailedChannelFuture; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SucceededChannelFuture; import org.jboss.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer; import org.jboss.netty.util.internal.ConcurrentHashMap; import org.jboss.netty.util.internal.LinkedTransferQueue; import org.jboss.netty.util.internal.ThreadLocalBoolean; import android.util.Log; 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: 2202 $, $Date: 2010-02-23 16:18:58 +0900 (Tue, 23 Feb 2010) $ * */ public class NioSocketChannel implements Channel{ private final ThreadLocalBoolean notifying = new ThreadLocalBoolean(); private static final int ST_OPEN = 0; private static final int ST_BOUND = 1; private static final int ST_CONNECTED = 2; private static final int ST_CLOSED = -1; volatile int state = ST_OPEN; final SocketChannel socket; public final NioWorker worker; private final DefaultNioSocketChannelConfig config; private volatile InetSocketAddress localAddress; private volatile InetSocketAddress remoteAddress; final Object interestOpsLock = new Object(); final Object writeLock = new Object(); final Runnable writeTask = new WriteTask(); final AtomicBoolean writeTaskInTaskQueue = new AtomicBoolean(); public final Queue<MessageEvent> writeBuffer = new WriteRequestQueue(); final AtomicInteger mWriteBufferSize = new AtomicInteger(); final AtomicInteger highWaterMarkCounter = new AtomicInteger(); boolean inWriteNowLoop; boolean writeSuspended; MessageEvent currentWriteEvent; SendBuffer currentWriteBuffer; private NioClientSocketPipelineSink sink; private RtmpPublisher publisher; private ClientHandler handler; public NioSocketChannel(ClientHandler handler,NioClientSocketPipelineSink sink, NioWorker worker) { this.socket = newSocket(); this.worker = worker; config = new DefaultNioSocketChannelConfig(socket.socket()); this.sink = sink; this.handler = handler; id = allocateId(this); } public DefaultNioSocketChannelConfig getConfig() { return config; } public InetSocketAddress getLocalAddress() { InetSocketAddress localAddress = this.localAddress; if (localAddress == null) { try { this.localAddress = localAddress = (InetSocketAddress) socket.socket().getLocalSocketAddress(); } catch (Throwable t) { // Sometimes fails on a closed socket in Windows. return null; } } return localAddress; } public InetSocketAddress getRemoteAddress() { InetSocketAddress remoteAddress = this.remoteAddress; if (remoteAddress == null) { try { this.remoteAddress = remoteAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress(); } catch (Throwable t) { // Sometimes fails on a closed socket in Windows. return null; } } return remoteAddress; } @Override public boolean isOpen() { return state >= ST_OPEN; } public boolean isBound() { return state >= ST_BOUND; } public boolean isConnected() { return state == ST_CONNECTED; } final void setBound() { assert state == ST_OPEN : "Invalid state: " + state; state = ST_BOUND; } final void setConnected() { if (state != ST_CLOSED) { state = ST_CONNECTED; } } protected boolean setClosed() { state = ST_CLOSED; // Deallocate the current channel's ID from allChannels so that other // new channels can use it. allChannels.remove(id);//チャンネルから return closeFuture.setClosed(); } @Override public int getInterestOps() { if (!isOpen()) { return Channel.OP_WRITE; } int interestOps = getRawInterestOps();//今のセレクターの状態を取得 int writeBufferSize = mWriteBufferSize.get(); if (writeBufferSize != 0) { int hiWater = highWaterMarkCounter.get(); Log.d("NioSocketChannel","highWaterMarkCounter :" + hiWater); if (hiWater > 0) {//カウンタが正の時 int lowWaterMark = getConfig().getWriteBufferLowWaterMark(); Log.d("NioSocketChannel","writeBufferSize" + writeBufferSize); Log.d("NioSocketChannel","lowWaterMark" + lowWaterMark); if (writeBufferSize >= lowWaterMark) { interestOps |= Channel.OP_WRITE; Log.d("NioSocketChannel","WRITE OK L----" + lowWaterMark); } else { interestOps &= ~Channel.OP_WRITE;//書き込みキーを取り消す Log.d("NioSocketChannel","TOOLOWDATA? --- " + lowWaterMark); // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // handler.exceptionCaught(new DefaultExceptionEvent(this, new Throwable("There are too few data."))); } } else { int highWaterMark = getConfig().getWriteBufferHighWaterMark(); Log.d("NioSocketChannel","writeBufferSize" + writeBufferSize); Log.d("NioSocketChannel","highWaterMark" + highWaterMark); if (writeBufferSize >= highWaterMark) { interestOps |= Channel.OP_WRITE; // if(publisher.isWaitDataSend()){ Log.d("NioSocketChannel","WRITE OK H---- " + writeBufferSize); // publisher.restartDataSend(this); // } } else { Log.d("NioSocketChannel","TOOHIGHDATA? ---- " + writeBufferSize); // if(!publisher.isWaitDataSend()){ // interestOps |= Channel.OP_WRITE; // publisher.waitDataSend(); // }else{ interestOps &= ~Channel.OP_WRITE;//書き込みキーを取り消す // } // handler.exceptionCaught(new DefaultExceptionEvent(this, new Throwable("There are too many data!!"))); } } } else { interestOps &= ~Channel.OP_WRITE;//書き込みキーを取り消す Log.d("NioSocketChannel","elseinterestOps" + interestOps); } return interestOps; } int getRawInterestOps() { return interestOps; } public ChannelFuture write(Object message, SocketAddress remoteAddress) { return getUnsupportedOperationFuture(); } private final class WriteRequestQueue extends LinkedTransferQueue<MessageEvent> { //private static final long serialVersionUID = 1L; WriteRequestQueue() { super(); } @Override public boolean offer(MessageEvent e) { boolean success = super.offer(e); assert success; int messageSize = getMessageSize(e); int newWriteBufferSize = mWriteBufferSize.addAndGet(messageSize);//サイズ分増やした値 int highWaterMark = getConfig().getWriteBufferHighWaterMark(); Log.d("NioSocketChannel","offer ---" +newWriteBufferSize ); if (newWriteBufferSize >= highWaterMark && newWriteBufferSize - messageSize < highWaterMark) { //今の(変えてる箇所はない)上限を、今のメッセージが超えたらカウンターをインクリメント highWaterMarkCounter.incrementAndGet(); Log.d("NioSocketChannel","highWaterMarkCounter incrementAndGet"); if (!notifying.get()) { Log.d("NioSocketChannel","offer notifying 0--------------" + notifying.get()); // notifying.set(Boolean.TRUE); // notifying.set(Boolean.FALSE); // Log.d("NioSocketChannel","offer notifying 1--------------" + notifying.get()); } } return true; } @Override public MessageEvent poll() { MessageEvent e = super.poll(); if (e != null) { int messageSize = getMessageSize(e); int newWriteBufferSize = mWriteBufferSize.addAndGet(-messageSize); int lowWaterMark = getConfig().getWriteBufferLowWaterMark(); if ((newWriteBufferSize == 0 || newWriteBufferSize < lowWaterMark) && newWriteBufferSize + messageSize >= lowWaterMark) { //メッセージサイズが0か、今のメッセージが最初に下限を下回ったらカウンターをデクリメント highWaterMarkCounter.decrementAndGet(); Log.d("NioSocketChannel","highWaterMarkCounter decrementAndGet messageSize " + messageSize + " lowWaterMark " + lowWaterMark +" newWriteBufferSize " +newWriteBufferSize); if (isConnected() && notifying.get()) { Log.d("NioSocketChannel","poll notifying --------------" + notifying.get()); // notifying.set(Boolean.FALSE); // Log.d("NioSocketChannel","poll notifying 1--------------" + notifying.get()); } } } return e; } /** * ChannelBufferだったら、readableBytes()を返す * それ以外は0を返す * @param e * @return */ private int getMessageSize(MessageEvent e) { Object m = e.getMessage(); if (m instanceof ChannelBuffer) { return ((ChannelBuffer) m).readableBytes(); } return 0; } } private final class WriteTask implements Runnable { WriteTask() { super(); } public void run() { writeTaskInTaskQueue.set(false); worker.writeFromTaskLoop(NioSocketChannel.this); } } //NioClientSocketChannelと上位のこのクラスを統合 private static SocketChannel newSocket() { SocketChannel socket; try { socket = SocketChannel.open(); } catch (IOException e) { throw new ChannelException("Failed to open a socket.", e); } boolean success = false; try { socket.configureBlocking(false); success = true; } catch (IOException e) { throw new ChannelException("Failed to enter non-blocking mode.", e); } finally { if (!success) { try { socket.close(); } catch (IOException e) { Log.d("ERROR", "Failed to close a partially initialized socket." +e); } } } return socket; } volatile ChannelFuture connectFuture; volatile boolean boundManually; // Does not need to be volatile as it's accessed by only one thread. long connectDeadlineNanos; //AbstractChannelと統合 static final ConcurrentMap<Integer, Channel> allChannels = new ConcurrentHashMap<Integer, Channel>(); private static Integer allocateId(Channel channel) { Integer id = Integer.valueOf(System.identityHashCode(channel)); for (;;) { // Loop until a unique ID is acquired. // It should be found in one loop practically. if (allChannels.putIfAbsent(id, channel) == null) { // Successfully acquired. return id; } else { // Taken by other channel at almost the same moment. id = Integer.valueOf(id.intValue() + 1); } } } private final Integer id; private final ChannelFuture succeededFuture = new SucceededChannelFuture(this); private final ChannelCloseFuture closeFuture = new ChannelCloseFuture(); private volatile int interestOps = OP_READ; /** Cache for the string representation of this channel */ private boolean strValConnected; private String strVal; public final Integer getId() { return id; } /** * Returns the cached {@link SucceededChannelFuture} instance. */ protected ChannelFuture getSucceededFuture() { return succeededFuture; } /** * Returns the {@link FailedChannelFuture} whose cause is an * {@link UnsupportedOperationException}. */ protected ChannelFuture getUnsupportedOperationFuture() { return new FailedChannelFuture(this, new UnsupportedOperationException()); } /** * Returns the {@linkplain System#identityHashCode(Object) identity hash code} * of this channel. */ @Override public final int hashCode() { return System.identityHashCode(this); } /** * Returns {@code true} if and only if the specified object is identical * with this channel (i.e: {@code this == o}). */ @Override public final boolean equals(Object o) { return this == o; } /** * Compares the {@linkplain #getId() ID} of the two channels. */ public final int compareTo(Channel o) { return getId().compareTo(o.getId()); } public ChannelFuture close() { try { Log.d("NLiveRoid","NioSocketChannel CLOSE"); sink.close( new DownstreamChannelStateEvent( this, this.getCloseFuture(), ChannelState.OPEN, Boolean.FALSE)); assert closeFuture == this.getCloseFuture(); } catch (Exception e) { e.printStackTrace(); this.closeFuture.setFailure(new Throwable()); } return closeFuture; } public ChannelFuture getCloseFuture() { return closeFuture; } /** * Sets the {@link #getInterestOps() interestOps} property of this channel * immediately. This method is intended to be called by an internal * component - please do not call it unless you know what you are doing. */ protected void setInterestOpsNow(int interestOps) { this.interestOps = interestOps; } //単に呼ばれてない // public boolean isReadable() { // return (getInterestOps() & OP_READ) != 0; // } public boolean isWritable() { int iOps = getInterestOps(); // Log.d("NioSocketChannel","SIZE " +writeBuffer.size()); // Log.d("NioSocketChannel","iOps " + Integer.toBinaryString(iOps) + " " + Integer.toBinaryString(OP_WRITE)); return (iOps & OP_WRITE) == 0; } /** * Returns the {@link String} representation of this channel. The returned * string contains the {@linkplain #getId() ID}, {@linkplain #getLocalAddress() local address}, * and {@linkplain #getRemoteAddress() remote address} of this channel for * easier identification. */ @Override public String toString() { boolean connected = isConnected(); if (strValConnected == connected && strVal != null) { return strVal; } StringBuilder buf = new StringBuilder(128); buf.append("[id: 0x"); buf.append(getIdString()); SocketAddress localAddress = getLocalAddress(); SocketAddress remoteAddress = getRemoteAddress(); if (remoteAddress != null) { buf.append(", "); buf.append(remoteAddress); buf.append(connected? " => " : " :> "); buf.append(localAddress); } else if (localAddress != null) { buf.append(", "); buf.append(localAddress); } buf.append(']'); String strVal = buf.toString(); this.strVal = strVal; strValConnected = connected; return strVal; } private String getIdString() { String answer = Integer.toHexString(id.intValue()); switch (answer.length()) { case 0: answer = "00000000"; break; case 1: answer = "0000000" + answer; break; case 2: answer = "000000" + answer; break; case 3: answer = "00000" + answer; break; case 4: answer = "0000" + answer; break; case 5: answer = "000" + answer; break; case 6: answer = "00" + answer; break; case 7: answer = "0" + answer; break; } return answer; } private final class ChannelCloseFuture extends DefaultChannelFuture { public ChannelCloseFuture() { super(NioSocketChannel.this, false); } @Override public boolean setSuccess() { // User is not supposed to call this method - ignore silently. return false; } @Override public boolean setFailure(Throwable cause) { // User is not supposed to call this method - ignore silently. return false; } boolean setClosed() { return setSuccess(); } } public void setPublisher(RtmpPublisher publisher) { this.publisher = publisher; } }