First Java

         First Java

A. First(Second...) Edition
This is the first assignment of comp6231 which is concentrated on distributed system design and this assignment is required
to use RMI. And believe it or not, this is actually my first java program because in comp346 the java program is not written
by me but it is given by professor. So, this is the first one.
¡¡
{I should say this is actually the last version which I can only make it right long after my assignment is submitted. Indeed
I am now struggling with assginment3. However, after correctly solve synchronization problem in assignment2, assignment1 
becomes quite easy. The most important thing I learned from assignment2(CORBA) is the exact picture of thread model at 
server side. Yesterday morning, I quickly browse the UnicastremoteObject in sun.com and it confirms what I expect. By 
inheritating from  this class which implements some interfaces for multi-threading, you are able to allow java server to
create multiple instance of your implementation class.}
B.The problem
Programming Problem:
3. Java RMI
In the programming assignments, you are going to develop a simple distributed banking system (DBS). Our bank comprises a set of branches. Each branch manages a disjoint subset of the bank's accounts. Associated with each account is a unique account number¡ªseven digit integer (the first three digits indicate the branch and the next four digits indicate the unique account within that branch), a balance¡ªfloating point number, as well as other information (like name, address and contact information of the account holder) that will be of less concern to us. Customers may invoke the following operations on accounts:
• Open(acnt): Opens a new account and returns the (randomly generated) account number acnt.
• Deposit(acnt, amt): Cause the balance of account number acnt to be increased by the specified amount amt. Returns the new account balance.
• Withdraw(acnt, amt): Cause the balance of account number acnt to be decreased by specified amount amt. Returns the new (possibly negative) account balance.
• Balance(acnt): Returns the balance of account number acnt.
This application basically has a BankServer implementing the above operations and a BankClient invoking these operations as necessary. For simplicity, assume that the account information of a branch (that is, all the accounts and their balances) is stored in a table accessed by the account number and stored in persistent storage.
In this assignment you are going to develop this application using Java RMI. Specifically, do the following.
• Write the Java RMI interface definition for the BankServer with the specified operations.
• Implement the BankServer.
• Design and implement a BankClient which invokes the server to test the correct operation of the DBS.
¡¡
C.The idea of program
¡¡

Design Choices:

1.     Design of packages

package

files included

class or interface

Super

BankInterface

BankInterface.java,

BankInterface

Remote

BankException.java

BankException

RemoteException

BankServer

BankInterfaceImplement.java

BankInterfaceImplement

UnicastRemoteObject

DataBase

 

BranchManager

 

Server.java

Server

 

BankClient

Client.java

Client

 

 2.     Design of interface

a) Originally I want to mimic "DCOM" which always uses return value of SUCESS or FAILURE to indicate whether a remote procedure is successful or not. However, java doesn't allow me to return a parameter by passing by reference. So, I have to preserve return value for each function call in interface. And in order to uniformly handle both system error and logical error, I use remote exception to indicate any error or exception at remote server. (see details in design of exception)

b) As both client and server needs to reference interface during compilation, it is very natural for me to put some constants inside interface like RMI port number, number of branches etc. 

3. Design of Database

a)  In order to speed up searching and simplify synchronization, I store data of one branch in one file. i.e. there are ten branches in my design and there are ten database files for them.

b)  Usually we store each record linearly in database file. However, unless we implement an index file there is no way for fast access to records in searching. Therefore, I used a RandomAccessFile object to speed up searching. This comes from the observation of a simple fact that there are 0 to 9999 account number in each branch. Therefore I store the balance of each account in file like a big array. Initially the balance for each account is initialized to -1 to indicate unopened account. When opening a new account, my file pointer uses "seek" method to move to offset of accountNumber*SizeOfAccount from beginning of file. Then write a new value of 0 to the balance of that account. So, by wasting a little space, my database file can achieve highest searching performance. Of course there is some overhead to initialize all accounts when database starts its first run but it will only do it once.

c) Since file opening operation is very expensive, my database class allocate each branch file with one RandomAccessFile object to handle file operations.

 3.     Design of Synchronization

a)          Originally I mistakenly think it is enough to add "synchronized" key word to each method of interface like "open", "deposit", "withdraw", and "balance". Later I realized that it will only guarantee synchronization within that method itself, but there is no synchronization between these methods. For example, even though we are sure there is always only one thread calling "open" method one by one, but when "open" is called, "deposit" can also be called by another thread. So, I use class "Semaphore" to handle synchronization.

b)         Between ¡°acquire¡± and ¡°release¡± method of Semaphore, there is possible exception throwing and in order to make server robust I force ¡°release¡± at ¡°finally¡± part of ¡°try¡­catch¡± statement.

c)          Since my synchronization is at level of file and each branch has its own database file, concurrence of operation between different branch is guaranteed. 

4.      Design of Exception

a)          My exception ¡°BankException¡± extends from ¡°RemoteException¡± and  mainly acts as a message displaying at client-side. That is whatever error or exception will be displayed at client-side. i.e. withdraw operation with unopened account or withdraw amount exceeds balance of the account will all throw this exception.

b)     All other system exception at server-side will be re-thrown when intercepted.

5.     Design of Testing

a)    Manual testing is made by an interactive menu at client side so that all methods in interface will be tested by command line.

b)   Automated testing is made by a loop of 3000 random actions of all methods in interface and user can even run more than one client process at the same time.

c)    In automated testing, there is an overflow of port number and connection of RMI is lost.

d)     In order to test mutual exclusion cases, I have designed a thread testing method which creates equal number of threads either depositing or withdrawing same amount from same account concurrently. If the server is not properly synchronized with deposit and withdraw operations, obviously there is a very big opportunity that the final balance would not be the same as that before testing. For example, in my testing, I created twenty depositing and twenty withdrawing threads to deposit and withdraw respectively 50 from the same account. And in order to allow them to start at the same time, I used a "CyclicBarrier" to barrier them until all forty threads are created. After the testing, the balance remains exactly same which proves that my server is properly synchronized.

 

¡¡

D.The major functions
Q:
Hi,

1. As for the question I asked you the other day, what is the difference of UDP and TCP in the size of packet? 2. In Java, is static method thread safe? For example, class Float has a method "static float parseFloat(String s);" which is static. And if in my class multi-thread may call this method, is there any problem for this static method? Because I remember in Linux, static API has this kind of thread problem. Can you justify this?

thank you for your time,

¡¡

A: Unfortunately I lost this important email and I just write down by my memory as a brief account.
1. The packet size of UDP and TCP is depending on the network environment and should be no difference to lower level. That is to say, the only
difference of two protocol is whether there is connection or not.
2. Static method will cause trouble for thread unless it has global variable otherwise those temporary variable is stored in its independent 
stack which will not be affected by context switch. As for the share code, it is just an instruction pointer which is read only.
However, I restored the message from "Google Desktop Search"! What amazing!
A:
1.
2. 
Q:
Thanks for your explanation which is clear and convincing!
I have run into another problem. In RMI, I want to pass parameter by reference. That is to say, to make parameter as "out" parameter. At 
beginning, I thought a class object is enough. Then I realized it must implement "Serializable" interface. So, I try to use "String" which 
implements the interface. However, it doesn't work. It only pass parameter by value, or by copy. I read a little explanation in "sun.com" 
and get an impression that I need to pass by "RemoteObject". Is it true?  If yes, how to create my own "remoteObject"?  By inheritance from 
which one?
A: 
I think you can just take a look at the RMI example code given by the 
course homepage, as follows:

http://www.cs.concordia.ca/~comp6231_4/CodeSample/RMI/hello/

The example given by sun.com is complicated because they are trying to 
demonstrate advanced features called dynamic code loading. Also it is a 
complicated application by itself. The example "Hello" is much simpler 
and easier to understand.

Regarding the problem you raised, I think there is some confusion here, 
which should be made clear. First, if you try to call a remote method 
whose implementation is residing in a remote object (i.e. the server 
side), the only way to pass parameters is to pass either (a copy of) 
the value (for primary data types like integer, float, ...) or another 
copy (for objects). You cannot pass a reference because it is actually 
pointing to a local object (i.e. on the client side) which is not 
accessable by the remote object (running on the server side). Just 
imagine, these two (client and server) could be running on different 
machines! How could the client tell the server to find out its local 
object and do something with it?

However, if you claim your local object is accessable globally, then it 
becomes a remote object from the perspective of the server. In other 
words, the server can then allocate it just like the client allocating 
the server's remote object. Certainly you need to the same thing for it,
such as claiming a remote interface, and having the client's object to 
implement that interface and so on. If you read into the example given 
by sun.com, you will find it is working like this. There are actually 
two remote objects implementing two remote interfaces: one is 
ComputeEngine implementing interface Compute and is running on the 
server side, hence is a remote object accessable by the client, and the 
other is class Pi implementing interface Task and is running on the 
client side, hence is a remote object accessable by the server. The 
latter one is the thing I am talking about regarding your problem.

I don't know about your design, but I don't think you have to follow 
the sun.com example and make it that complicated. A simple 
implementation like Hello will do, which is what I am suggesting.
¡¡
Q & A:
> hi,
>
> Thanks for your detailed message.
> 1. For this particular assignment, there is of course simple 
> solution. However, my concern is originated from observation of IDL 
> which defines each parameter either as "in", "out", or "inout". In 
> c++, we have a one-to-one correspondance of translatation to either 
> passing by value, or by reference. However, I cannot see any easy way 
> to translate them into java unless using the way described by you as 
> "RemoteObject" which is not intuitive to programmer at all. By the 
> way, how to compile IDL into java stub class? Or what is the IDL 
> compiler in java?

Java is different from C++. Therefore what I can suggest is, don't try 
to put your mind of C++ programming into a Java design and try to look 
for the correspondence. Just use what-ever you can find in Java. If you 
want to pass something back (i.e. "out" in C++), use the return 
statement. To resolve your non-intuitive feeling here, you have to 
understand they are REMOATE objects rather than local ones, hence there 
is no way to make local references just like you did in C++.

Java IDL compiler is idltojava, or in up-to-date versions, idlj. The 
link of this is as follows:

http://java.sun.com/docs/books/tutorial/idl/hello/idltojava.html

> 2. I tried to implement thread synchronization by using "Semaphore" 
> in java. However, there is one problem here. See following:
> Semaphore availabel;
> ...
> try{
>    available.acquire();
>    ...//doing my work here with possible exception
>    available.release();
> }
> catch(Exception er){
>     ...//handle exception
> }
> finally{
>    available.release(); //here is the problem
> }
>
> In order to make my server robust, I need to force any thread 
> "finally" release the counted Semaphore at block "finally". This 
> seems OK. However, in sun.com, it is said that "release()" can throw 
> "InterruptedException". This will create an infinite loop. Even 
> though I can simply use an "if (!(er instanceof InterruptException)) 
> available.release();" to differenciate this situation, still I am not 
> sure this is the right way for general exception handling. What is 
> your suggestion? Thanks.

In this situation, I don't think you have to guarantee release in case 
of exceptions. This is rather a design issue. If you do catch 
exceptions, the first thing is to handle it properly, and in some cases 
it might be impossible to continue the release (depends on the nature 
of exception). On the other hand, you might have a boolean indicator of 
exception and turn it on in your catch block, so that in your finally 
block you can check it before release. This possibly avoids loop.

> 3. As this is first time for me to use java to write program, I need 
> you a little help on this question. However, it is rather a design 
> decision than a technique question. As you see, in all interface we 
> can return a value to indicate success or failure of this operation. 
> (i.e. boolean value like that in "DCOM".) However, remote exception 
> gives an alternative. My question is what is usual way to do it? By 
> using a return boolean value to indicate success or failure of 
> interface or using remote exception which I suspect to be very 
> expensive, right?

Certainly not. Remote object as return parameter is complicated in both 
design and implementation, and is difficult to debug/test. If you are 
familar or experienced with these stuffs, you might give an advanced 
design with such things, otherwise my suggestion is, make it as simple 
as you can, as long as it works.

> 4. Another insane observation is that when I declare a variable 
> "float f =100.0;", javac always gives error message "expect float 
> find double" which puzzles me a lot. What is going on here?

By default such a statement is a claim of double. To claim a float, you 
might put it like this: "float f = 100.0f". In other words, you might 
explicitly indicate you have a float value.

> 5. And this last question is quite tough for me. As I did automated 
> testing with three clients which try random open, deposit, 
> withdraw,balance operation in a long loop simultaneously, I found out 
> that sooner or later all three clients will lose connection to server 
> by giving exception message of "java.net.BindException: Address 
> already in use: connect". What I can guess is that there is some kind 
> of deadlock happened in server or connection. Is there any clue of 
> this kind of problem?  I attach my code as a reference.
> explanation:
> a) three packages: BankServer, BankInterface, BankClient
> b) to run server: java BankServer.Server
>    to run client: java BankClient.Client localhost auto
>    //for client I designed to give both interactive human testing and 
> automated testing, the parameter is "host" "runningchoice".
> c) There is one database file for each branch and in order to random 
> access balance for each account, I treat file as a big array with 
> length of "sizeof(float)*TotalAccountNumber". At first run, server 
> creates files and initializes all location to -1 to indicate unopened 
> account. When open an account, use randomAccessFile operation "seek" 
> to go to offset of "account*sizeof(float)" and initialize datat to 0. 
> Deposit and withdraw also "seek" to offset of "account*sizeof(float)" 
> and write new balance.

I took a glance of your code and I am not seeing problems. This is 
actually a performance problem: you are out of TCP/IP connections on 
your machine(s). Taking the fact that you are running a test of 3000 
connections for each client, it is understandable that all the 
available ports will be consumed up soon after you launch the 
application. So it is not your fault.

My suggestion for testing mutual exclusion is, create several test 
cases where mutual exclusion does happen. So you can demonstrate how 
your solusion works in order to resolve such problem.

Besides the theory assignment is a bit of interesting since it reveals the necessity of asynchronized RPC.
2. Remote Procedure Call
A client makes remote procedure calls to a server. The client takes 5 ms to compute the arguments for each request, and the server takes 10 ms to process each request. The local operating system processing time for each send and receive operation is 0.5 ms, and the network time to transmit each request or reply message is 3 ms. Marshalling or demarshalling takes 0.5 ms per message.
(a) [10%] Ignoring context switching time, calculate the time taken by the client to generate and return from two requests (i) if it single-threaded, and (ii) if it has two threads that can make requests concurrently on a single processor.
(b) [5%] Is there a need for asynchronous RPC if client and server processes are threaded? Why or why not?
  1. a)   Let's label each action in two requests operation as following:

a1) client computing arguments of first request = 5ms

b1) marshalling taken at client-side of first request=0.5ms

c1) os processing time at client-side for sending first request=0.5ms

d1) network transmitting time for first request=3ms

e1) os processing time at server-side for receiving first request=0.5ms

f1)  demarshalling for first request at server-side=0.5ms

g1) server processing first request=10ms

h1) marshalling first reply at server-side =0.5ms

i1)  os processing time for sending first reply at server-side=0.5ms

j1) network transmitting time for first reply=3ms

k1) os processing time for receiving first reply at client=0.5ms

l1) demarshalling at client-side for first reply=0.5ms

 

a2) client computing arguments of second request = 5ms

b2) marshalling taken at client-side of second request=0.5ms

c2) os processing time at client-side for sending second request=0.5ms

d2) network transmitting time for second request=3ms

e2) os processing time at server-side for receiving second request=0.5ms

f2)  demarshalling for second request at server-side=0.5ms

g2) server processing second request=10ms

h2) marshalling second reply at server-side =0.5ms

i2)  os processing time for sending second reply at server-side=0.5ms

j2) network transmitting time for second reply=3ms

k2) os processing time for receiving second reply at client=0.5ms

l2) demarshalling at client-side for second reply=0.5ms

 

 

i)  If single thread and synchronized RPC, the total time for two request is just     

two time of single request.  The first request takes total time of  a1+b1+c1+d1+e1+f1+g1+h1+i1+j1+k1+l1

=5+0.5+0.5+3+0.5+0.5+10+0.5+0.5+3+ 0.5+0.5

=25ms

So, the two requests takes total 25x2=50ms

 

ii) For two threads, we can imagine one thread finishes all its job until there is context switch which is the best situation. (because if the second thread switch in before first thread finishes its request sending will only make total time longer. )

client

a1

b1

c1

a2

b2

c2

 

 

 

 

k1

l1

 

 

 

k2

l2

time

5

0.5

0.5

5

0.5

0.5

 

 

 

 

0.5

0.5

 

 

 

0.5

0.5

server

 

 

 

 

e1

f1

g1

h1

i1

e2

f2

g2

h2

i2

 

 

 

time

 

 

 

 

0.5

0.5

10

0.5

0.5

0.5

0.5

10

0.5

0.5

 

 

 

network

 

 

 

d1

 

 

 

 

 

j1

 

 

 

 

j2

 

 

time

 

 

 

3

 

 

 

 

 

3

 

 

 

 

3

 

 

 

 

total time

= a1+b1+c1+d1+e1+f1+g1+h1+i1+e2+f2+g2+h2+i2+j2+k2+l2

=5+0.5+0.5+3+0.5+0.5+10+0.5+0.5+0.5+0.5+10+0.5+0.5+3+0.5+0.5

=37ms

So, the total time for two threads for two requests is 37ms.

b) 

Yes, there is need for asynchronized RPC because we can view question ii of a) as asynchronized RPC as at both client and server all jobs are done sequentially. That is to say, if in asynchronized RPC for single thread to process two requests, the total time is 37ms instead of 50ms in synchronized RPC in question i) of a).  The speedup is (50-37)/50=26% which is quite significant.

{According to solution, this conclusion should be opposite because by using multi-thread model we achieve same

effect of asynchronization. Then why do we need it?}

 


E.Further improvement
RMI is implemented by TCP and the automated testing which use a loop of 3000 requests to run the client will cause TCP 
connection breakdown. The tutor suggested to design some mutual exclusive cases to test. However, I can only think about
creating some threads to try to access same data which is even more complicated than the current code. So, I give it up.
To Compile:
1. setup classpath to your working directory.
2. create BankInterface, BankClient, BankServer directory in working directory.
3. place source files into directory according to its package (directory name).
4. at command line, 
5. to compile server: javac .\BankServer\Server.java
6. to compile client: javac .\BankClient\Client.java
To Run:
to run server: java BankServer.Server
to run client for interactive testing: java BankClient.Client localhost manual
to run client for automated testing: java BankClient.Client localhost automated
to run client for thread testing:    java BankClient.Client localhost t
¡¡
¡¡
{ The buggy part of previous version is quite a mixed style. First there is some subtle thing about "static" and non-static.
If you are going to synchronize, you have to make something static such as the global file pointer, global semaphore. So,
for you have to declare those in your "implementation class". This is the first mistake I made. And I removed the wrapper
class "database" for debugging. And the major mistake is my misunderstanding of java syntax of "finally" which will be 
executed no matter whether there is exception or not. In order to maintain a correct state for a robust server, at beginning
I add extra "semaphore release" statement in "finally" block which doubles the "release count" of semaphore. This of course
invalidate functionality of semaphore.}
¡¡
¡¡
F.File listing
¡¡
package BankInterface:
1. BankInterface.java
2. BankException.java
¡¡
package BankClient:
1. BankClient.java
¡¡
package BankServer:
1. BankServer.java
2. BankInterfaceImplement.java
file name: BankInterface.java
package BankInterface;

import java.rmi.*;


public interface BankInterface extends Remote {

	public final int RMIPort=1099;
	public static final int BranchIDOffset=100;
	public static final int MaxBranchNumber=1;
	//the contact maybe telephone number

	public int Open() 
		throws java.rmi.RemoteException;


	public float Deposit(int acnt, float amt) 
		throws java.rmi.RemoteException;

	public float Withdraw(int acnt, float amt)
		throws java.rmi.RemoteException;

	//amt out
	public float Balance(int acnt)
		throws java.rmi.RemoteException;

} //end interface
¡¡
		 
		


file name: BankException.java
package BankInterface;

import java.rmi.*;


public class BankException extends java.rmi.RemoteException
{	
	public BankException()
	{
		super();
	}
	public BankException(String msg)
	{
		super(msg);
	}
}

			 
file name: Client.java			
	
package BankClient;

import java.io.*;
import java.rmi.*;
import java.util.*;
import java.util.concurrent.*;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

import BankInterface.*;


		

public class Client 
{
	
	//protected static DataOutputStream bufOutput;//= new DataOutputStream(System.out);

	protected static int RunningThreadNumber=200;
	protected static RunningThread deposits[];
	protected static RunningThread withdraws[];
	protected static CyclicBarrier barrier=new CyclicBarrier(RunningThreadNumber*2); 


	protected static BufferedReader bufInput;//= new BufferedReader(System.in);
	//protected static DataInputStream bufInput= new DataInputStream(new InputStreamReader(System.in));

	protected static final String menuStr
		="Please input your choice:\n Open(1), Deposit(2), Withdraw(3), Balance(4), Exit(5)\n";

	protected static BankInterface h;
 
	public static void main(String args[]) 
	{
		try 
		{			      
			String hostName;
			String portNum;
			String registryURL;	
			
			//bufOutput=new DataOutputStream(System.out);
			bufInput = new BufferedReader(new InputStreamReader(System.in));
			
			//the first argument must be machine name
			if (args.length>0)
			{
				hostName=args[0];
			}
			else
			{
				hostName="localhost";
			}
			portNum=Integer.toString(BankInterface.RMIPort);

			registryURL = "rmi://"+hostName +":" + portNum + "/BankInterface";  
			h =	(BankInterface)Naming.lookup(registryURL);

			// invoke the remote method(s)
			if (args.length>0&&args[1].charAt(0)=='a')
			{			
				autoTester();									
			}
			else
			{
				if (args.length>0&&args[1].charAt(0)=='t')
				{
					threadTester();
				}
				else
				{
					manualTester();
				}
			}
	
		} // end try 
		catch (Exception ex) 
		{
			//ex.printStackTrace();
			System.out.println(ex.getMessage());
		} 
	} 
	
	//a helper class of thread to test mutual exclusion in server
	static class RunningThread extends Thread 
	{
		protected float amount, newAmount;
		protected int account;
		//protected BankInterface bankInterface;
		public RunningThread(int acnt,  float amt)
		{
			amount=amt;
			account=acnt;			
		}

		public void run()
		{			
			try
			{
				//System.out.println(Integer.toString(barrier.getNumberWaiting()));
				barrier.await();
				if (amount>0)
				{	
									
					System.out.println(getName()+ " reports to deposit "+Float.toString(amount) +
						" to account " + Integer.toString(account) +" and balance is " + 
						Float.toString(h.Deposit(account, amount)));					
					
					h.Deposit(account, amount);
				}
				else
				{	
									
					System.out.println(getName()+ " reports to withdraw "+Float.toString(-amount) +
						" to account " + Integer.toString(account)+ " and balance is " +
						Float.toString(h.Withdraw(account, -amount)));					
					
					h.Withdraw(account, -amount);
				}
			}
			catch (Exception er)
			{
				System.out.println(er.getMessage());
			}
		}
	}

	protected static void threadTester()
	{
		int account;		
		float amount;
		try
		{
			account=h.Open();//open a new account;
			//account=1037202;
			amount=h.Deposit(account, 3000);
			deposits=new RunningThread[RunningThreadNumber];
			withdraws=new RunningThread[RunningThreadNumber];
			System.out.println("thread test begins and we deposit 3000.0 to account " 
				+ Integer.toString(account)
				+ " and we are going to run equal number of deposit and withdraw actions," +
				"if server is not properly synchronized, the balance will not be the same probably");

			System.out.println("the balance of account "+ Integer.toString(account) + " is "
				+ Float.toString(amount));


			for (int i=0; i<RunningThreadNumber; i++)
			{
				deposits[i]=new RunningThread(account, 50);
				deposits[i].start();
				withdraws[i]=new RunningThread(account, -50);
				withdraws[i].start();
			}

			for (int i=0; i<RunningThreadNumber; i++)
			{
				deposits[i].join();
				withdraws[i].join();
			}

			System.out.println("thread test is over and let's check if the balance of account remains same as before");
			
			System.out.println("the balance of "+Integer.toString(account) + " is " +
				Float.toString(h.Balance(account)));
		}
		catch (Exception er)
		{
			System.out.println(er.getMessage());
		}
	}


	protected static void manualTester()
	{
		//now do the test
		int choice=0;
		int account;
		float amount;

		do
		{			
			//bufOutput.writeBytes(menuStr);
			System.out.println(menuStr);
			
			try
			{
				choice = Integer.parseInt(bufInput.readLine());
				switch(choice)
				{
					case 1:
						account=h.Open();
						//bufOutput.writeBytes("new opened account is" + Integer.toString(account)+"\n");
						System.out.println("new opened account is " + Integer.toString(account));
						break;
					case 2:
						//bufOutput.writeBytes("input account no.\n");
						System.out.println("input account no.");
						account=Integer.parseInt(bufInput.readLine());
						//bufOutput.writeBytes("input amount to deposit:\n");
						System.out.println("input amount to deposit:");
						amount=Float.parseFloat(bufInput.readLine());
						System.out.println("new balance is "+Float.toString(h.Deposit(account, amount)));
						break;
					case 3:
						//bufOutput.writeBytes("input account no.\n");
						System.out.println("input account no");
						account=Integer.parseInt(bufInput.readLine());
						//bufOutput.writeBytes("input amount to withdraw:\n");
						System.out.println("input amount to withdraw:");
						amount=Float.parseFloat(bufInput.readLine());
						System.out.println("new balance is "+ Float.toString(h.Withdraw(account, amount)));
						break;
					case 4:
						//bufOutput.writeBytes("input account no.\n");
						System.out.println("input account no");
						account=Integer.parseInt(bufInput.readLine());
						//bufOutput.writeBytes("balance of account " + Integer.toString(account)+
						//	" is " + Float.toString(h.Balance(account))+ "\n");
						System.out.println("balance of account " + Integer.toString(account)+
							" is " + Float.toString(h.Balance(account)));
						break;
					case 5:
						//bufOutput.writeBytes("exiting test\n");
						System.out.println("exiting test\n");
						break;
					default:
						//bufOutput.writeBytes("please input choice between 1 to 5.\n");
						System.out.println("please input choice between 1 to 5.");
						break;
				}
			}
			catch (Exception er)
			{
				System.out.println(er.getMessage());
			}

		}
		while (choice!=5);
	}


	protected static void autoTester()
	{
		final int MaxAmount=1000;
		Random rand=new Random();
		int choice, account;
		float amount;
		
		for (int i=0; i<3000; ++i)
		{
			try
			{
				//purely for testing
				//choice=0;
				choice=rand.nextInt(4);
				switch (choice)
				{
				case 0:
					//open
					System.out.println("try to open a new account");
					System.out.println("a new opened account is " +Integer.toString(h.Open()));
					break;
				case 1:
					//deposit
					account=(rand.nextInt(BankInterface.MaxBranchNumber)+BankInterface.BranchIDOffset)*10000
						+rand.nextInt(10000);
					amount=rand.nextInt(MaxAmount)+rand.nextInt(100)/(float)100.0;
					System.out.println("try to deposit account "+Integer.toString(account)+" amount of " 
						+ Float.toString(amount));
					
					System.out.println("account "+Integer.toString(account)+" is deposited with "+Float.toString(amount)
						+ " and new balance is "+ Float.toString(h.Deposit(account, amount)));
					break;
				case 2:
					//withdraw
					account=(rand.nextInt(BankInterface.MaxBranchNumber)+BankInterface.BranchIDOffset)*10000
						+rand.nextInt(10000);
					//i want to use float with two digits after point
					amount=rand.nextInt(MaxAmount)+rand.nextInt(100)/(float)100.0;
					System.out.println("try to withdraw account "+Integer.toString(account)+" amount of " 
						+ Float.toString(amount));
					
					System.out.println("account "+Integer.toString(account)+" is withdraw with "+Float.toString(amount)
						+" and new balance is " + Float.toString(h.Withdraw(account, amount)));
					break;
				case 3:
					//balance
					account=(rand.nextInt(BankInterface.MaxBranchNumber)+BankInterface.BranchIDOffset)*10000
						+rand.nextInt(10000);
					System.out.println("try to get balance of account "+Integer.toString(account));
					amount=h.Balance(account);
					System.out.println("account "+Integer.toString(account)+" has balance of "+Float.toString(amount));
					break;
				}
			}
			catch(Exception er)
			{
				System.out.println(er.getMessage());
			}
		}
	}
}
¡¡
file name: Server.java	

package BankServer;

import java.rmi.*;
import java.rmi.server.*;

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.net.*;
import java.io.*;

import BankInterface.*;

/**
 * This class represents the object server for a distributed
 * object of class SomeImpl, which implements the remote 
 * interface SomeInterface.
 */

public class Server  {
	public static void main(String args[]) 
	{
		String portNum = Integer.toString(BankInterface.RMIPort), registryURL;
		String host;
		try
		{   
			// code for obtaining RMI port number value omitted
			BankInterfaceImplement exportedObj = new BankInterfaceImplement();
			startRegistry(BankInterface.RMIPort);
			// register the object under the name "some"
			if (args.length>0)
			{
				host=args[0];
			}
			else
			{
				ServerSocket serverSocket=new ServerSocket(9000);
				InetAddress inetAddress=serverSocket.getInetAddress();				
				host=inetAddress.getLocalHost().getHostName();
				serverSocket.close();
			}
				
			registryURL = "rmi://"+ host+ ":" + portNum + "/BankInterface";
			Naming.rebind(registryURL, exportedObj);
			System.out.println("Bank Server ready.");
		}// end try
		catch (Exception re) 
		{
			System.out.println("Exception in BankServer.main: " + re);
		} // end catch
	} // end main

   // This method starts a RMI registry on the local host, if it
   // does not already exist at the specified port number.
   private static void startRegistry(int RMIPortNum)
      throws RemoteException
	{
		try 
		{
			Registry registry = LocateRegistry.getRegistry(RMIPortNum);
			registry.list( );  
			// The above call will throw an exception
			
		}// if the registry does not already exist
		catch (RemoteException ex) 
		{
			// No valid registry at that port.
			System.out.println(
			"RMI registry cannot be located at port " 
			+ RMIPortNum);
			Registry registry = LocateRegistry.createRegistry(RMIPortNum);
			System.out.println(
			"RMI registry created at port " + RMIPortNum);
		}
   } // end startRegistry
     
} // end class
¡¡

file name: BankInterfaceImplement.java					

		
package BankServer;
import java.lang.*;
import java.rmi.*;
import java.util.*;
import java.io.*;
import java.util.concurrent.Semaphore;

import java.rmi.server.*;
import BankInterface.*;

/**
 * This class implements the remote interface BankInterface.
 */

//the big trap is to declare both branchID and account as a data member of this 
//class, because RMI may invoke multi-thread and these data is not thread safe
//so, keep them as temporary variable in each function call.
public class BankInterfaceImplement 
	extends UnicastRemoteObject
	implements BankInterface 
{  
	protected static final int MAX_AVAILABLE = 1;
	//protected static DataBase dataBase;
	protected static Random rand;
	protected static boolean isFirst=true;	
	public static Semaphore available;

	
	//even though we are sure there is only one instance of "DataBase" in server, still to make semanctically clear,
	//make branchmananger static too.
	public static BranchManager branches[];


	//constructor
	public BankInterfaceImplement() //throws RemoteException
		throws java.rmi.RemoteException
	{
		super();
		try
		{
			if (isFirst)
			{
				isFirst=false;
				//dataBase=new DataBase();
				rand=new Random();
				available=new Semaphore(MAX_AVAILABLE, true);
				
				branches=new BranchManager[BankInterface.MaxBranchNumber];

				for (int i=0; i<BankInterface.MaxBranchNumber; i++)
				{
					branches[i]=new BranchManager(i);			
				}

			}
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}
	public void finalize() throws java.rmi.RemoteException
	{
		if (!isFirst)
		{
			isFirst=true;//because in constructor, it is already set to be false
			//dataBase.finalize();
		}
	}

	//the branchID has to be shifted to make 7 digit
   	public int Open() //throws java.io.IOException
		throws java.rmi.RemoteException
	{		
		int branchID;
		int account;
		try
		{
			branchID=rand.nextInt(BankInterface.MaxBranchNumber);
			account=branches[branchID].Open();
			return SetBranchID(branchID, account);
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}	
	}

	public float Deposit(int acnt, float amount) //throws java.io.IOException
		throws java.rmi.RemoteException
	{
		int branchID, account;
		branchID=GetBranchID(acnt);
		account=acnt%10000;
		//check range of branch id

		//to prevent deposit negative amount
		if (amount<=0)
		{
			throw new BankException("negative amount");
		}
		if (!CheckBranchID(branchID))
		{
			throw new BankException("invalid branch id");
		}
		try
		{				
			return branches[branchID].Deposit(account, amount);
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
		
	}

	public float Withdraw(int acnt, float amount)  //throws java.io.IOException
		throws java.rmi.RemoteException
	{
				
		int branchID, account;

		branchID=GetBranchID(acnt);
		account=acnt%10000;
		//check range of branch id
		//and make sure amount is positive
		if (amount<=0)
		{
			throw new BankException("negative amount");
		}
		if (!CheckBranchID(branchID))
		{
			throw new BankException("invalid branch id");
		}

		try
		{
			return branches[branchID].Withdraw(account, amount);
		}
		catch (Exception er)
		{				
			throw new BankException(er.getMessage());			
		}
	}

	//amt out this is not synchronized
	public float Balance(int acnt)  //throws java.io.IOException
		throws java.rmi.RemoteException
	{
		int branchID, account;	
	
		branchID=GetBranchID(acnt);
		account=acnt%10000;
		//check range of branch id

		if (!CheckBranchID(branchID))
		{
			throw new BankException("invalid branch id");
		}

		try
		{
			return branches[branchID].Balance(account);
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());	
		}		
	}

	//you may have specific method in future
	protected boolean CheckBranchID(int branchID)
	{
		return (branchID>=0&&branchID<BankInterface.MaxBranchNumber+BankInterface.BranchIDOffset);	
	}
	protected int SetBranchID(int id, int account)
	{
		return (id+BranchIDOffset)*10000 + account;
	}

	protected int GetBranchID(int account)
	{
		return account/10000-BankInterface.BranchIDOffset;
	}
} 


//declare a helper class


class BranchManager
{

	private int branchID;
	private static final float NegativeInfinite= -10000;
	private Random rand;
	//static boolean flag=true;
	public static final int AccountSize=4;
	

	public RandomAccessFile branchFile;

	public BranchManager(int id)   throws java.rmi.RemoteException
	{
		branchID=id;
		rand=new Random(id);
	
		initialize();
	}

	public void finalize() throws java.rmi.RemoteException
	{
		try 
		{			
			branchFile.close();
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}

	}

	void initialize()   throws java.rmi.RemoteException
	{
		String fileName=Integer.toString(branchID+1)+".data";
		try 
		{
			branchFile=new RandomAccessFile(fileName, "rw");
			if (branchFile.length()==0)//this is the new file 
			{
				branchFile.setLength(AccountSize*10000);
				branchFile.seek(0);//starting 
				for (int j=0; j<10000; j++)
				{					
					//branchFiles[i].writeInt(0);//initialize to 0;
					branchFile.writeFloat(NegativeInfinite);// indicating unused
				}
			}
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}

	

	protected boolean findAccount(int account) throws java.rmi.RemoteException
	{
		try
		{
			branchFile.seek(account*AccountSize);
			return branchFile.readFloat()!=NegativeInfinite;
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}

	public int Open()	throws java.rmi.RemoteException
	{
		int result;
		try
		{
			//entering critical section
			//purely for testing
			//System.out.println("open before acquire");
			BankInterfaceImplement.available.acquireUninterruptibly();
			//purely for testing
			//this is purely for testing
			if (BankInterfaceImplement.available.availablePermits()>0)
			{
				throw new BankException("during open, semaphore is corrupted");
			}

			//System.out.println("open after acquire");
			do
			{
				//allow account starting from 0
				result=rand.nextInt(10000);
				//System.out.println("now it is "+Integer.toString(result));
			}
			while (findAccount(result));
			branchFile.seek(result*AccountSize);
			branchFile.writeFloat(0);
			

			//purely for testing
			//System.out.println("open before release new account is " +Integer.toString(result));
			//BankInterfaceImplement.available.release();
			//purely for testing
			//System.out.println("open after release new account is " +Integer.toString(result));
			//leaving critical section
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}		
		finally
		{
			BankInterfaceImplement.available.release();
		}	
		return result;
	}
	
	
	public float Deposit(int account, float amount) throws java.rmi.RemoteException
	{
		
		//entering critical section
		try
		{
			float oldValue;
		

			BankInterfaceImplement.available.acquireUninterruptibly();
			//this is purely for testing
			if (BankInterfaceImplement.available.availablePermits()>0)
			{
				throw new BankException("during deposit, semaphore is corrupted");
			}
			branchFile.seek(account*AccountSize);
			oldValue=branchFile.readFloat();
			//account not opened
			if (oldValue==NegativeInfinite)
			{
				//BankInterfaceImplement.available.release();
				throw new BankException("during deposit, account is not opened");
			}
			else
			{
				//it is pity there is no backward moving
				branchFile.seek(account*AccountSize);
				branchFile.writeFloat(amount+oldValue);
			}

			//BankInterfaceImplement.available.release();
			return amount+oldValue;
			//leaving critical section
		}
		catch(Exception er)
		{
			throw new BankException(er.getMessage());
		}
		finally
		{			
			BankInterfaceImplement.available.release();
		}		
		
	}

	//assume amount is positive
	public float Withdraw(int account, float amount) throws java.rmi.RemoteException
	{		
		try
		{
			float oldValue=NegativeInfinite;
			//entering critical section
			BankInterfaceImplement.available.acquireUninterruptibly();
			
			//this is purely for testing
			if (BankInterfaceImplement.available.availablePermits()>0)
			{
				throw new BankException("during withdraw, semaphore is corrupted");
			}

			branchFile.seek(account*AccountSize);
			oldValue=branchFile.readFloat();
			//account cannot have negative balance
			//including the case of unopened account
			if (oldValue==NegativeInfinite)
			{
				throw new BankException("during withdraw, account is not opened");
			}
		
			//it is pity there is no backward moving
			branchFile.seek(account*AccountSize);
			branchFile.writeFloat(oldValue-amount);
			

			//BankInterfaceImplement.available.release();
			//leaving critical section
			return oldValue-amount;
			
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
		finally
		{
			BankInterfaceImplement.available.release();
		}		
	}
	
	//even reading may not need to be synchronized, but 
	//file pointer has to be! So make it simple by synchronized!
	public float Balance(int account)   throws java.rmi.RemoteException
	{
		float oldValue;
		try
		{
			//entering critical section
			BankInterfaceImplement.available.acquireUninterruptibly();
			//this is purely for testing
			if (BankInterfaceImplement.available.availablePermits()>0)
			{
				throw new BankException("during balance, semaphore is corrupted");
			}

			branchFile.seek(account*AccountSize);
			oldValue=branchFile.readFloat();
		
			if (oldValue==NegativeInfinite)
			{
				throw new BankException("druing open, account has not opened");
			}
			return oldValue;
			//leaving critical section
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
		finally
		{			
			BankInterfaceImplement.available.release();
			
		}

	
	}				
}

/*	
class DataBase
{
	public static final int AccountSize=4;
	//even though we are sure there is only one instance of "DataBase" in server, still to make semanctically clear,
	//make branchmananger static too.
	public static BranchManager branches[];

	public DataBase()  throws java.rmi.RemoteException
	{
		try
		{
			branches=new BranchManager[BankInterface.MaxBranchNumber];

			for (int i=0; i<BankInterface.MaxBranchNumber; i++)
			{
				branches[i]=new BranchManager(i);			
			}
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}

	public void finalize() throws java.rmi.RemoteException
	{
		try
		{
			for (int i=0; i<BankInterface.MaxBranchNumber; i++)
			{
				branches[i].finalize();		
			}
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}

	public int Open(int branchID) throws java.rmi.RemoteException
	{
		try
		{
			return branches[branchID].Open();
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}

	public float Deposit(int branchID, int account, float amount) throws java.rmi.RemoteException
	{
		try
		{
			return branches[branchID].Deposit(account, amount);
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}

	public float Withdraw(int branchID, int account, float amount) throws java.rmi.RemoteException
	{
		try
		{
			return branches[branchID].Withdraw(account, amount);
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}

	public float Balance(int branchID, int account) throws java.rmi.RemoteException
	{
		try
		{
			return branches[branchID].Balance(account);
		}
		catch (Exception er)
		{
			throw new BankException(er.getMessage());
		}
	}
}

*/
			
		
			
A snapshot of running automated testing:

¡­.

try to get balance of account 1006177

RemoteException occurred in server thread; nested exception is:

BankInterface.BankException: account has not opened

try to deposit account 1018599 amount of 953.47

RemoteException occurred in server thread; nested exception is:

BankInterface.BankException: account is not opened

try to withdraw account 1026943 amount of 462.89

RemoteException occurred in server thread; nested exception is:

BankInterface.BankException: account is not opened or balance is less than withdraw amount

try to get balance of account 1012449

RemoteException occurred in server thread; nested exception is:

BankInterface.BankException: account has not opened

try to open a new account

a new opened account is 1017130

try to open a new account

a new opened account is 1082668

try to withdraw account 1014824 amount of 308.14

RemoteException occurred in server thread; nested exception is:

BankInterface.BankException: account is not opened or balance is less than withdraw amount

try to get balance of account 1085843

RemoteException occurred in server thread; nested exception is:

BankInterface.BankException: account has not opened

try to open a new account

a new opened account is 1007025

¡­..

 

A snapshot of running thread testing:

J:\MyJava>java BankClient.Client localhost t
thread test begins and we deposit 3000.0 to account 1078696 and we are going to
run equal number of deposit and withdraw actions,if server is not properly synch
ronized, the balance will not be the same probably
the balance of account 1078696 is 3000.0
Thread-2 reports to deposit 50.0 to account 1078696
Thread-9 successfully withdraw 50.0 to account 1078696
Thread-13 successfully withdraw 50.0 to account 1078696
Thread-32 reports to deposit 50.0 to account 1078696
Thread-33 successfully withdraw 50.0 to account 1078696
Thread-34 reports to deposit 50.0 to account 1078696
Thread-11 successfully withdraw 50.0 to account 1078696
Thread-3 successfully withdraw 50.0 to account 1078696
Thread-14 reports to deposit 50.0 to account 1078696
Thread-4 reports to deposit 50.0 to account 1078696
Thread-5 successfully withdraw 50.0 to account 1078696
Thread-6 reports to deposit 50.0 to account 1078696
Thread-36 reports to deposit 50.0 to account 1078696
Thread-37 successfully withdraw 50.0 to account 1078696
Thread-15 successfully withdraw 50.0 to account 1078696
Thread-38 reports to deposit 50.0 to account 1078696
Thread-16 reports to deposit 50.0 to account 1078696
Thread-39 successfully withdraw 50.0 to account 1078696
Thread-41 successfully withdraw 50.0 to account 1078696
Thread-40 reports to deposit 50.0 to account 1078696
Thread-17 successfully withdraw 50.0 to account 1078696
Thread-35 successfully withdraw 50.0 to account 1078696
Thread-7 successfully withdraw 50.0 to account 1078696
Thread-8 reports to deposit 50.0 to account 1078696
Thread-18 reports to deposit 50.0 to account 1078696
Thread-19 successfully withdraw 50.0 to account 1078696
Thread-22 reports to deposit 50.0 to account 1078696
Thread-26 reports to deposit 50.0 to account 1078696
Thread-2 reports the balance of account 1078696 is 3000.0
Thread-27 successfully withdraw 50.0 to account 1078696
Thread-28 reports to deposit 50.0 to account 1078696
Thread-29 successfully withdraw 50.0 to account 1078696
Thread-34 reports the balance of account 1078696 is 2950.0
Thread-32 reports the balance of account 1078696 is 2950.0
Thread-13 reports the balance of account 1078696 is 2950.0
Thread-9 reports the balance of account 1078696 is 2950.0
Thread-30 reports to deposit 50.0 to account 1078696
Thread-10 reports to deposit 50.0 to account 1078696
Thread-12 reports to deposit 50.0 to account 1078696
Thread-3 reports the balance of account 1078696 is 3100.0
Thread-4 reports the balance of account 1078696 is 3100.0
Thread-5 reports the balance of account 1078696 is 3100.0
Thread-6 reports the balance of account 1078696 is 3100.0
Thread-36 reports the balance of account 1078696 is 3100.0
Thread-15 reports the balance of account 1078696 is 3100.0
Thread-38 reports the balance of account 1078696 is 3100.0
Thread-39 reports the balance of account 1078696 is 3100.0
Thread-41 reports the balance of account 1078696 is 3100.0
Thread-40 reports the balance of account 1078696 is 3100.0
Thread-7 reports the balance of account 1078696 is 3100.0
Thread-8 reports the balance of account 1078696 is 3100.0
Thread-18 reports the balance of account 1078696 is 3100.0
Thread-19 reports the balance of account 1078696 is 3100.0
Thread-26 reports the balance of account 1078696 is 3100.0
Thread-28 reports the balance of account 1078696 is 3100.0
Thread-14 reports the balance of account 1078696 is 3100.0
Thread-33 reports the balance of account 1078696 is 2950.0
Thread-29 reports the balance of account 1078696 is 3100.0
Thread-35 reports the balance of account 1078696 is 3100.0
Thread-27 reports the balance of account 1078696 is 3100.0
Thread-16 reports the balance of account 1078696 is 3100.0
Thread-22 reports the balance of account 1078696 is 3100.0
Thread-30 reports the balance of account 1078696 is 3100.0
Thread-10 reports the balance of account 1078696 is 3100.0
Thread-37 reports the balance of account 1078696 is 3100.0
Thread-25 successfully withdraw 50.0 to account 1078696
Thread-11 reports the balance of account 1078696 is 3050.0
Thread-17 reports the balance of account 1078696 is 3050.0
Thread-23 successfully withdraw 50.0 to account 1078696
Thread-24 reports to deposit 50.0 to account 1078696
Thread-12 reports the balance of account 1078696 is 3050.0
Thread-31 successfully withdraw 50.0 to account 1078696
Thread-21 successfully withdraw 50.0 to account 1078696
Thread-20 reports to deposit 50.0 to account 1078696
Thread-25 reports the balance of account 1078696 is 3000.0
Thread-31 reports the balance of account 1078696 is 3000.0
Thread-23 reports the balance of account 1078696 is 3000.0
Thread-24 reports the balance of account 1078696 is 3000.0
Thread-20 reports the balance of account 1078696 is 3000.0
Thread-21 reports the balance of account 1078696 is 3000.0
thread test is over and let's check if the balance of account remains same as be
fore
the balance of 1078696 is 3000.0

J:\MyJava>

¡¡

 

¡¡
			
				 back.gif (341 bytes)       up.gif (335 bytes)         next.gif (337 bytes)