package org.red5.server.net.udp; /* * 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.HashSet; import java.util.LinkedList; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.IoHandlerAdapter; import org.apache.mina.common.IoSession; import org.red5.io.utils.HexDump; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * => client send to server * <= server send to client * << server broadcast * * Connecting to the server * * => byte(join) * << byte(join) int(id) * => byte(list) * <= byte(list) int(count) int(id) int(id) ... * * Sending a message to all * * => byte(send) [..anything..] * << byte(send) [..anything..] * * Server ping client to keep alive, every second * * <= byte(noop) * => byte(noop) * * Timeouts (after 10s no reply) * * << byte(exit) int(id) * * @author luke */ public class BasicHandler extends IoHandlerAdapter { protected static Logger log = LoggerFactory.getLogger(BasicHandler.class); static final int TICK = 1000; static final int TIMEOUT = 10000; static final byte NOOP = 0x00; static final byte JOIN = 0x01; static final byte LIST = 0x02; static final byte SEND = 0x03; static final byte EXIT = 0x04; final ByteBuffer NOOP_MSG = ByteBuffer.wrap(new byte[]{NOOP}).asReadOnlyBuffer(); protected Timer timer = new Timer("Timer", true); protected Set<IoSession> sessions = Collections.synchronizedSet(new HashSet<IoSession>()); protected boolean showInfo = false; public BasicHandler(){ timer.scheduleAtFixedRate(new TimeoutTask(), 0, TICK); showInfo = log.isInfoEnabled(); } @Override public void exceptionCaught(IoSession session, Throwable ex) throws Exception { if(showInfo) log.info("Exception: "+session.getRemoteAddress().toString(), ex); sessions.remove(session); } @Override public void messageReceived(IoSession session, Object message) throws Exception { if(showInfo) log.info("Incomming: "+session.getRemoteAddress().toString()); ByteBuffer data = (ByteBuffer) message; // IN HEX DUMP log.info(HexDump.prettyPrintHex(data.asReadOnlyBuffer().array())); final byte type = data.get(); data.position(0); switch(type){ case NOOP: // drop break; case JOIN: if(!sessions.contains(session)){ sessions.add(session); join(session); } break; case LIST: list(session); break; case SEND: broadcast(session, data); break; case EXIT: if(sessions.contains(session)){ sessions.remove(session); session.close(); leave(session); } break; default: if(showInfo) log.info("Unknown (play echo): "+session.getRemoteAddress().toString()); echo(session, data); break; } } protected void echo(IoSession session, ByteBuffer data){ session.write(data); } protected void broadcast(IoSession exclude, ByteBuffer data){ for(IoSession session : sessions){ if(exclude != null && exclude.equals(session)) continue; if(showInfo) log.info("Sending: "+session.getRemoteAddress().toString()); data.acquire(); session.write(data); } } protected void list(IoSession to){ final int size = 1 + 4 + (sessions.size()*4); ByteBuffer msg = ByteBuffer.allocate(size); msg.put(LIST); msg.putInt(sessions.size()); for(IoSession session : sessions){ msg.putInt(session.getRemoteAddress().hashCode()); } msg.flip(); to.write(msg); } protected void leave(IoSession session){ final int size = 5; ByteBuffer msg = ByteBuffer.allocate(size); msg.put(EXIT); msg.putInt(session.getRemoteAddress().hashCode()); msg.flip(); broadcast(null, msg); } protected void join(IoSession session){ final int size = 5; ByteBuffer msg = ByteBuffer.allocate(size); msg.put(JOIN); msg.putInt(session.getRemoteAddress().hashCode()); msg.flip(); broadcast(null, msg); } @Override public void sessionCreated(IoSession session) throws Exception { if(showInfo) log.info("Created: {}", session.getRemoteAddress().toString()); } protected class TimeoutTask extends TimerTask { public void run(){ long kill = System.currentTimeMillis() - TIMEOUT; LinkedList<IoSession> remove = new LinkedList<IoSession>(); for(IoSession session : sessions){ if(session.getLastReadTime() < kill){ if(showInfo) log.info("Timout: {}", session.getRemoteAddress().toString()); remove.add(session); } else { session.write(NOOP_MSG.asReadOnlyBuffer()); } } if(remove.size() == 0) return; for(IoSession session : remove){ sessions.remove(session); session.close(); } for(IoSession session : remove){ leave(session); } } } }