How to transfer files (SIMON 1.0.0)

This tutorial describes how to transfer files from A to B with the help of SIMON.

Introduction

Normally SIMON is used for calling methods on remote objects. Such a remote method call includes a lot of reflection, proxying and serialization which is normally not noticed by the user regarding the time that is used to do all that magic internally. The calls seem to be very fast transferred to server and return value back to client. The time that is used to transfer the method call to server and back isn't critical at all.

But if you transfer files, this would have an impact on transfer-speed. So it's not an good idea to transfer the byte packages of a file via a normal remote method call. Say we have an 100MBit LAN and want to use the complete bandwidth (about 10MBytes/sec), then it would be not acceptable to have only 9, 8 or ever less MB/sec because of such internal magic ...

To overcome this, SIMON has a special feature called RawChannel. This transfers your data without involving reflection or proxys.
But before we start, you should know how the things work...

We assume you want to transfer file "MyGreatHolidayMovie.mpg" from your client to your server. The server then needs to be aware of your whish to transfer the file. So he has to be prepared. At a first step, you tell the server: I want to transfer a file. Maybe the name is also important and so you attach the filename to your transfer-request.
Then you transfer the file block by block (it's always a good idea not to load the complete file in memory...) and finally you tel the server: All data transfered, all done.

So in short:

  1. Request the receiving of a file
  2. Transfer data
  3. close the transfer

This can be mapped to SIMONs API like this:

Shared Code

First we setup the server's interface

package de.root1.simon.samples.rawchannel.shared;

import de.root1.simon.SimonRemote;
import de.root1.simon.exceptions.SimonRemoteException;

public interface RawChannelServer extends SimonRemote {

    public static final String BIND_NAME = "RawChannelFileTransfer";

    public int openFileChannel(String filename) throws SimonRemoteException;

    public byte[] getFileBytes(String filename) throws SimonRemoteException;

}

Server Code

Then we setup the server's implementation:

package de.root1.simon.samples.rawchannel.server;

import de.root1.simon.Simon;
import de.root1.simon.exceptions.SimonRemoteException;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import de.root1.simon.samples.rawchannel.shared.RawChannelServer;

public class RawChannelServerImpl implements RawChannelServer {

    public int openFileChannel(String filename) throws SimonRemoteException {
        int token = Simon.prepareRawChannel(new FileReceiver(filename),this);
        return token;      
    }

    public byte[] getFileBytes(String filename) throws SimonRemoteException {

        File f = new File(filename);

        byte[] data = new byte[(int)f.length()];

        DataInputStream dis;
        try {
            dis = new DataInputStream(new FileInputStream(f));
            dis.readFully(data);
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        return data;
    }

}

As you can see, we need a method on the server which returns a token, provided by the SIMON class. This token is needed by the client to open the transfer channel.
Also also we need to setup a listener - here called FileReceiver - that receives all the data:

package de.root1.simon.samples.rawchannel.server;

import de.root1.simon.RawChannelDataListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileReceiver implements RawChannelDataListener {

    private FileChannel fc;

    public FileReceiver(String filename) {
        try {
            fc = new FileOutputStream(new File(filename)).getChannel();
        } catch (FileNotFoundException ex) {
            // cannot really occur, because we wanto CREATE the file.
            ex.printStackTrace();
        }
    }

    public void write(ByteBuffer data) {
        try {
            fc.write(data);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public void close() {
        try {
            fc.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

}

Okay, now the only missing server part is the SIMON Registry to which we bind the server:

package de.root1.simon.samples.rawchannel.server;

import de.root1.simon.Registry;
import de.root1.simon.Simon;
import de.root1.simon.exceptions.NameBindingException;
import de.root1.simon.samples.rawchannel.shared.RawChannelServer;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class Server {

    public static void main(String[] args) {

        try {

            Registry registry = Simon.createRegistry(InetAddress.getByName("0.0.0.0"), 2000);

            RawChannelServerImpl rcsi = new RawChannelServerImpl() ;
            registry.bind( RawChannelServer.BIND_NAME, rcsi);

            // Server is now running. If you whish to shutdown, call this lines:
            //registry.stop();
            //registry.unbind(RawChannelServer.BIND_NAME);

        } catch (UnknownHostException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (NameBindingException ex) {
            ex.printStackTrace();
        }

    }

}

Client Code

There's less code for the client... Let's have a look:

package de.root1.simon.samples.rawchannel.client;

import de.root1.simon.RawChannel;
import de.root1.simon.Simon;
import de.root1.simon.exceptions.LookupFailedException;
import de.root1.simon.exceptions.SimonRemoteException;
import de.root1.simon.samples.rawchannel.shared.RawChannelServer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Random;

public class Client {

    private static final String TEMPDIR = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator");
    private static final String TESTFILE_RECEIVER = TEMPDIR + "TestFileForReceiver.dat";
    private static final String TESTFILE_SENDER = TEMPDIR + "TestFile.dat";

    public static void main(String[] args) {

        // create some testfiles
        createTestFile();

        try {

            // Get connection to server
            RawChannelServer rcs = (RawChannelServer) Simon.lookup(InetAddress.getLocalHost(), 2000, RawChannelServer.BIND_NAME);

            // get a RawChannel Token from server. This is needed to open the
            // RawChannel
            int token = rcs.openFileChannel(TESTFILE_RECEIVER);

            // with the remote object and token, tell SIMON that you need a
            // RawChannel
            RawChannel rawChannel = Simon.openRawChannel(token, rcs);

            // first, we open a FileChannel. This is thanks to Java NIO faster
            // than normal file operation
            File file = new File(TESTFILE_SENDER);
            FileChannel fileChannel = new FileInputStream(file).getChannel();

            // we send the file in 512byte packages through the RawChannel
            ByteBuffer data = ByteBuffer.allocate(512);
            while (fileChannel.read(data) != -1) {
                rawChannel.write(data);
                data.clear();
            }

            // all data written. Now we can close the FileChannel
            fileChannel.close();

            // ... and also th RawChannel
            rawChannel.close();

            // Another way to send/get files is to pass them as return-values of
            // method arguments. But if transfering files is the main purpose of
            // your program, I really recommend to use the RawChannel approach
            byte[] fileBytesReceived = rcs.getFileBytes(TESTFILE_RECEIVER);
            FileOutputStream fileOutputStream = new FileOutputStream(new File(TESTFILE_RECEIVER));
            fileOutputStream.write(fileBytesReceived);
            fileOutputStream.close();

            Simon.release(rcs);

        } catch (UnknownHostException ex) {
            ex.printStackTrace();
        } catch (LookupFailedException ex) {
            ex.printStackTrace();
        } catch (SimonRemoteException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        // finally we clean up our temporary testfiles
        cleanUpFiles();
    }

    /**
     * Generates a test file
     */
    private static void createTestFile() {
        try {
            FileChannel fc = new FileOutputStream(new File(TESTFILE_SENDER)).getChannel();
            Random r = new Random(System.currentTimeMillis());
            byte[] data = new byte[1024];
            for (int i = 0; i < 10; i++) {
                r.nextBytes(data);
                fc.write(ByteBuffer.wrap(data));
            }
            fc.close();
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Cleans up testfiles
     */
    private static void cleanUpFiles() {
        File f1 = new File(TESTFILE_SENDER);
        f1.delete();
        File f2 = new File(TESTFILE_RECEIVER);
        f2.delete();
    }
}