/**
* Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.ctdp.rfdynhud.plugins.datasender;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.jagatoo.util.streams.LimitedInputStream;
/**
* Connects via a socket using UDP and sends/receives data (server side).
*
* @author Marvin Froehlich (CTDP)
*/
public abstract class AbstractUDPServerCommunicator extends AbstractServerCommunicator
{
private final int port;
private DatagramSocket socket = null;
private InetAddress clientAddress = null;
private int clientPort = -1;
private long datagramOrdinal = 0L;
private volatile boolean running = false;
private volatile boolean connected = false;
private volatile boolean restartSender = true;
private volatile boolean restartReceiver = true;
private volatile boolean closeRequested = false;
private final Stack<DatagramPacket> datagrams = new Stack<DatagramPacket>();
private boolean commandInProgress = false;
private short currentCommand = 0;
@Override
public final boolean isRunning()
{
return ( running );
}
@Override
protected void startCommandImpl( short code )
{
synchronized ( eventsBuffer )
{
if ( commandInProgress )
throw new IllegalStateException( "Another command (" + currentCommand + ") has been started, but not ended." );
currentCommand = code;
commandInProgress = true;
try
{
eventsBuffer.writeLong( datagramOrdinal++ );
eventsBuffer.writeShort( code );
}
catch ( IOException e )
{
log( e );
}
}
}
@Override
protected void endCommandImpl()
{
byte[] buffer = null;
int usedBufferLength = -1;
synchronized ( eventsBuffer )
{
if ( !commandInProgress )
throw new IllegalStateException( "No command had been started." );
buffer = eventsBuffer0.toByteArray(); // TODO: Optimize by reusing byte arrays!
eventsBuffer0.reset();
usedBufferLength = buffer.length;
currentCommand = 0;
commandInProgress = false;
}
//if ( buffer != null )
{
DatagramPacket datagram = new DatagramPacket( buffer, usedBufferLength );
synchronized ( datagrams )
{
datagrams.push( datagram );
}
}
}
private final Runnable sender = new Runnable()
{
private final List<DatagramPacket> datagramsCopy = new ArrayList<DatagramPacket>();
@Override
public void run()
{
datagramsCopy.clear();
while ( running )
{
if ( clientAddress != null )
{
DatagramPacket closeDatagram = null;
synchronized ( datagrams )
{
// If close requested, send a close datagram.
if ( closeRequested )
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream( baos );
try
{
dos.writeLong( datagramOrdinal++ );
dos.writeShort( CONNECTION_CLOSED );
dos.close();
closeDatagram = new DatagramPacket( baos.toByteArray(), baos.size(), clientAddress, clientPort );
running = false;
closeRequested = false;
// Forgit pending datagrams.
datagrams.clear();
datagramsCopy.add( closeDatagram );
}
catch ( IOException e )
{
log( e );
}
}
else if ( !datagrams.isEmpty() )
{
// Copy pending datagrams to a local buffer...
for ( int i = 0; i < datagrams.size(); i++ )
datagramsCopy.add( datagrams.get( i ) );
datagrams.clear();
}
}
// Send all pending datagrams...
if ( !datagramsCopy.isEmpty() )
{
for ( int i = 0; i < datagramsCopy.size(); i++ )
{
DatagramPacket datagram = datagramsCopy.get( i );
datagram.setAddress( clientAddress );
datagram.setPort( clientPort );
try
{
socket.send( datagram );
}
catch ( SocketException e )
{
if ( !closeRequested )
{
log( "Connection closed unexpectedly" );
log( e );
running = false;
close( true );
}
break;
}
catch ( IOException e )
{
log( e );
}
if ( datagram != closeDatagram )
{
// TODO: push used datagrams to a pool to reuse them.
}
}
datagramsCopy.clear();
}
}
try
{
Thread.sleep( 10L );
}
catch ( InterruptedException e )
{
log( e );
}
}
running = false;
connected = false;
closeRequested = false;
if ( socket != null )
{
socket.close();
socket = null;
}
synchronized ( eventsBuffer )
{
eventsBuffer0.reset();
}
onConnectionClosed();
if ( restartSender )
{
try
{
Thread.sleep( 200L );
}
catch ( InterruptedException e )
{
}
new Thread( this ).start();
}
}
};
private final Runnable receiver = new Runnable()
{
@Override
public void run()
{
byte[] buffer = new byte[ 1024 * 1024 ];
ByteArrayInputStream bais = new ByteArrayInputStream( buffer );
LimitedInputStream lin = new LimitedInputStream( bais, buffer.length );
DataInputStream din = new DataInputStream( lin );
DatagramPacket datagram = new DatagramPacket( buffer, buffer.length );
while ( running )
{
try
{
socket.receive( datagram );
if ( clientAddress == null )
{
clientAddress = datagram.getAddress();
clientPort = datagram.getPort();
}
bais.reset();
lin.resetLimit( datagram.getLength() );
while ( din.available() >= 10 )
{
@SuppressWarnings( "unused" )
long receivedDatagramOrdinal = din.readLong();
//plugin.debug( "in.available: ", din.available() );
readInput( din );
}
}
catch ( SocketException e )
{
if ( !closeRequested )
{
log( "Connection closed unexpectedly" );
log( e );
//close( true );
}
break;
}
catch ( IOException e )
{
log( e );
}
try
{
Thread.sleep( 10L );
}
catch ( InterruptedException e )
{
log( e );
}
}
running = false;
connected = false;
closeRequested = false;
if ( socket != null )
{
socket.close();
socket = null;
}
synchronized ( eventsBuffer )
{
eventsBuffer0.reset();
}
if ( restartReceiver )
{
try
{
Thread.sleep( 200L );
}
catch ( InterruptedException e )
{
}
new Thread( this ).start();
}
}
};
@Override
public void connect()
{
if ( running )
return;
try
{
if ( socket == null )
{
socket = new DatagramSocket( port );
socket.setReuseAddress( true );
}
datagramOrdinal = 0L;
}
catch ( Throwable t )
{
running = false;
log( t );
return;
}
connected = true;
running = true;
closeRequested = false;
new Thread( sender ).start();
new Thread( receiver ).start();
}
protected void beforeClosed()
{
}
@Override
protected final void close( boolean restart )
{
if ( connected )
{
beforeClosed();
closeRequested = true;
}
else
{
running = false;
}
this.restartSender = restart;
this.restartReceiver = restart;
if ( socket != null )
{
socket.close();
socket = null;
}
}
public AbstractUDPServerCommunicator( int port, String password )
{
super( password );
this.port = port;
}
}