package org.red5.server.net.rtmpt; /* * RED5 Open Source Flash Server - http://www.osflash.org/red5 * * Copyright (c) 2006-2009 by respective authors (see below). All rights reserved. * * This library is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any later * version. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.apache.mina.common.ByteBuffer; import org.red5.server.api.Red5; import org.red5.server.net.protocol.SimpleProtocolDecoder; import org.red5.server.net.protocol.SimpleProtocolEncoder; import org.red5.server.net.rtmp.IRTMPHandler; import org.red5.server.net.rtmp.RTMPConnection; import org.red5.server.net.rtmp.codec.RTMP; import org.red5.server.net.rtmp.message.Packet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class BaseRTMPTConnection extends RTMPConnection { private static final Logger log = LoggerFactory.getLogger(BaseRTMPTConnection.class); /** * Protocol decoder */ private SimpleProtocolDecoder decoder; /** * Protocol encoder */ private SimpleProtocolEncoder encoder; private static class PendingData { private ByteBuffer buffer; private Packet packet; private PendingData(ByteBuffer buffer, Packet packet) { this.buffer = buffer; this.packet = packet; } private PendingData(ByteBuffer buffer) { this.buffer = buffer; } public ByteBuffer getBuffer() { return buffer; } public Packet getPacket() { return packet; } public String toString() { return getClass().getName() + "(buffer=" + buffer + "; packet=" + packet + ")"; } } /** * List of pending messages */ private LinkedList<PendingData> pendingMessages = new LinkedList<PendingData>(); /** * Closing flag */ private volatile boolean closing; /** * Number of read bytes */ private AtomicLong readBytes = new AtomicLong(0); /** * Number of written bytes */ private AtomicLong writtenBytes = new AtomicLong(0); /** * Byte buffer */ private ByteBuffer buffer; /** * RTMP events handler */ private volatile IRTMPHandler handler; public BaseRTMPTConnection(String type) { super(type); this.buffer = ByteBuffer.allocate(2048); this.buffer.setAutoExpand(true); } /** * Return any pending messages up to a given size. * * @param targetSize the size the resulting buffer should have * @return a buffer containing the data to send or null if no messages are * pending */ abstract public ByteBuffer getPendingMessages(int targetSize); /** {@inheritDoc} */ @Override public void close() { // Defer actual closing so we can send back pending messages to the client. closing = true; } /** * Getter for property 'closing'. * * @return Value for property 'closing'. */ public boolean isClosing() { return closing; } /** * Real close */ public void realClose() { if (!isClosing()) { return; } getWriteLock().lock(); try { if (buffer != null) { buffer.release(); buffer = null; } state.setState(RTMP.STATE_DISCONNECTED); pendingMessages.clear(); } finally { getWriteLock().unlock(); } super.close(); } /** * Send raw data down the connection. * * @param packet the buffer containing the raw data */ @Override public void rawWrite(ByteBuffer packet) { getWriteLock().lock(); try { pendingMessages.add(new PendingData(packet)); } finally { getWriteLock().unlock(); } } /** {@inheritDoc} */ @Override public long getReadBytes() { return readBytes.get(); } /** {@inheritDoc} */ @Override public long getWrittenBytes() { return writtenBytes.get(); } /** {@inheritDoc} */ @Override public long getPendingMessages() { getReadLock().lock(); try { return pendingMessages.size(); } finally { getReadLock().unlock(); } } /** * Decode data sent by the client. * * @param data the data to decode * @return a list of decoded objects */ public List<?> decode(ByteBuffer data) { getWriteLock().lock(); try { if (closing || state.getState() == RTMP.STATE_DISCONNECTED) { // Connection is being closed, don't decode any new packets return Collections.EMPTY_LIST; } Red5.setConnectionLocal(this); readBytes.addAndGet(data.limit()); this.buffer.put(data); this.buffer.flip(); return this.decoder.decodeBuffer(this.state, this.buffer); } finally { getWriteLock().unlock(); } } /** * Send RTMP packet down the connection. * * @param packet the packet to send */ @Override public void write(final Packet packet) { getWriteLock().lock(); try { if (closing || state.getState() == RTMP.STATE_DISCONNECTED) { // Connection is being closed, don't send any new packets return; } ByteBuffer data; try { data = this.encoder.encode(this.state, packet); } catch (Exception e) { log.error("Could not encode message {}", packet, e); return; } // Mark packet as being written writingMessage(packet); pendingMessages.add(new PendingData(data, packet)); } finally { getWriteLock().unlock(); } } protected ByteBuffer foldPendingMessages(int targetSize) { ByteBuffer result = ByteBuffer.allocate(2048); result.setAutoExpand(true); // We'll have to create a copy here to avoid endless recursion List<Object> toNotify = new LinkedList<Object>(); getWriteLock().lock(); try { if (pendingMessages.isEmpty()) { return null; } while (!pendingMessages.isEmpty()) { PendingData pendingMessage = pendingMessages.remove(); result.put(pendingMessage.getBuffer()); if (pendingMessage.getPacket() != null) toNotify.add(pendingMessage.getPacket()); if ((result.position() > targetSize)) break; } } finally { getWriteLock().unlock(); } for (Object message : toNotify) { try { handler.messageSent(this, message); } catch (Exception e) { log.error("Could not notify stream subsystem about sent message.", e); } } result.flip(); writtenBytes.addAndGet(result.limit()); return result; } public void setHandler(IRTMPHandler handler) { this.handler = handler; } public void setDecoder(SimpleProtocolDecoder decoder) { this.decoder = decoder; } public void setEncoder(SimpleProtocolEncoder encoder) { this.encoder = encoder; } }