/*
* Copyright (C) 1996, 1997 By Radu Sion
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Radu Sion (radus@cs.pub.ro)
*
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* (radus@cs.pub.ro, http://smart.cs.pub.ro/~radus/)
*
*/
import java.net.*;
import java.io.*;
import Player;
import ClientMessageCreator;
import SimpleQueue;
import ClientConnectAAA;
import NullClientConnectAAA;
import dbg;
/**
* A class that implements the necesary connecting and messaging functions
* for connecting and communicating with the RoundRobinServerThread
* In order not to overload the server a simple CTS mechanism is implemented.
* The client should NOT SEND again, before it receives a CTS Message from the server.
* He is allowed to send, but the Message just gets queued up and the feedback becomes slower,
* The method sendOK() returns a value that tests if some CTS Message was received and no
* other Message was sent. You have the option to change if the control Messages reach the
* child class or not.
* Written: Radu Sion
* Version: 0.61
* Source: Client.java
* @see Player
* @see ClientMessageCreator
* @see Message
* @see RoundRobinServerThread
* @see ConnectServerThread
* @see SimpleQueue
* @see ClientConnectAAA
*/
public class
Client
extends Object
{
Player player;
SimpleQueue oq;
SimpleQueue iq;
ClientConnectAAA aaa;
boolean is_ok_to_send = true; /* is it ok to send ? */
boolean filter_ctrl = false; /* filtering control messages ? */
int group = MessageConstants.INVALID; /* group number */
int number = MessageConstants.INVALID; /* player number */
/**
* Creates a new client that will use the specified AAA object to
* negociate at connect with the connection server.
* (AAA = Auth & Auth & Accounting)
* @param a AAA object to use in talking with connect server
*/
public
Client(ClientConnectAAA a)
{
aaa = a;
if (aaa == null) aaa=new NullClientConnectAAA();
}
/**
* Returns the internal AAA object.
*/
public synchronized ClientConnectAAA
getAAA()
{
return aaa;
}
/**
* Changes the internal AAA object, that is the way the connection is
* to be done from now on. (Authentication, Authorisation, Accounting)
* No parameter checkings performed.
* @param a New internal AAA object to use
*/
public synchronized void
setAAA(ClientConnectAAA a)
{
aaa = a;
if (aaa == null) aaa=new NullClientConnectAAA();
}
/**
* Connects to the remote RoundRobinServerThread and creates
* all needed structures for connecting. Should be used at the beginning.
* @param serverhost The server name/address to which to connect to
* @param in_queue_len The maximum length of the incomming Message queue
* @param out_queue_len The maximum length of the outgoing Message queue
* @param rsleep Read thread sleep delay (arround 100)
* @param wsleep Write thread sleep delay (arround 100)
* @param timeout Timeout after which the connection is closed if no valid RESET message received
* @return Returns true if ok, false otherwise (timeout or others)
*/
public boolean
connect(String serverhost, int serverport, int in_queue_len, int out_queue_len, int rsleep, int wsleep, int timeout)
{
dbg.p(dbg.INFO,"C: Connecting ...");
Socket ss = null;
try
{
ss = new Socket(serverhost,serverport);
/** doing AAA with connect server **/
if (aaa.doAAA(ss))
{
player = new Player(ss,out_queue_len,in_queue_len,-1,-1,rsleep,wsleep);
player.setExitIfFullQueues(true, true);
iq = player.InMessageQueue;
oq = player.OutMessageQueue;
}
else
{
throw new Exception("AAA Error. Rejected from server or server down.");
}
/** waiting here for RESET or other first message
**/
Message m = readRaw(timeout);
if (!MessageParser.isValid(m))
{
throw new Exception("Message format error at connect.");
}
if (!MessageParser.isRESET(m))
{
dbg.p(dbg.WARN, "C: Weird first message != RESET, maybe old server.");
}
synchronized(this)
{
number = MessageParser.getDestination(m);
group = MessageParser.getDestinationGroup(m);
}
}
catch (Exception eee)
{
dbg.p(dbg.ERROR,"C: Unable to connect ... " + eee.getMessage());
util.closeSocket(ss);
return false;
}
dbg.p(dbg.INFO,"C: Done Connecting.");
return true;
}
/**
* Tests the status of the connection & others.
* The child class should shutdown if the return is false.
* @return Returns true if ok, false otherwise.
*/
public boolean
test()
{
return player.testOk();
}
/**
* Waits for a specified message a given timeinterval.
* The message is bypassing the messagefilter !!!
* Comparision is done by comparing the whole dataarea of the messages.
*
* DURING THIS CALL NO OTHER MESSAGES REACH recv() !!!!!!!!!!!!
* THIS METHOD SHOULD BE USED WITH CARE ONLY IN UNUSUAL CASES !
* IT IS VERY TRICKY INTERNALLY !!!
* NOT TO BE USED INSIDE A STOP !!!
*
*
NOT IMPLEMENTED YET !!!
*
* This method works only for small messages that don't exceed the
* systems socket buffer !!!
*
* @param msg Message to wait for
* @param timeout Timeout to wait for that message
* @return True if message received in that interval, false otherwise
*/
public synchronized boolean
waitForMessage(Message msg, long timeout)
{
long dummy;
boolean ret = false;
Message joker;
String gigi = msg.getString();
for (dummy = 0; dummy < timeout/1; dummy ++)
{
try
{
this.wait(1);
}
catch (InterruptedException ie)
{
}
try
{
joker = (Message)iq.fetchNext();
//dbg.p(dbg.JUNK, "joker="+joker.getString());
if (joker.getString().compareTo(gigi) == 0)
{
ret = true;
break;
}
}
catch (EmptyException ee)
{
//dbg.p(dbg.JUNK, "exception 1 ...");
}
}
return ret;
}
/**
* Waits for some message to appear and then returns it rightaway.
* The message is returned as it is, bypassing the messagefilter.
* If no message appears in the specified timeout then returns null.
*
* THIS METHOD SHOULD BE USED WITH CARE ONLY IN UNUSUAL CASES !
* FOR REGULAR MESSAGE READING USE recv() INSTEAD !
*
*
NOT IMPLEMENTED YET !!!
*
* This method works only for small messages that don't exceed the
* systems socket buffer !!!
*
* @param timeout Timeout to wait for message. (timeout*10ms !!!)
* @return A new received message or null in case of error
*/
public synchronized Message
readRaw(long timeout)
{
Message ret = null;
long dummy;
for (dummy=0; dummy < timeout; dummy++)
{
try
{
this.wait(1);
}
catch (InterruptedException ie)
{
}
try
{
ret = (Message)iq.fetchNext();
break;
}
catch (EmptyException ee)
{
//dbg.p(dbg.JUNK, "exception 2 ...");
}
}
return ret;
}
/**
* Sends a Message. It's internally synchronized !!!
* @param m The Message to send
* @exception FullException When the outgoing queue is full
*/
public void
send(Message m)
throws FullException
{
if ((oq == null) || (m==null)) dbg.p(dbg.INFO,"C: Internal Error in sending ... null Message object ?");
if (m==null) m = ClientMessageCreator.NOP();
//while (oq.isFull()) ; /* blocking if full ... is this alright ? */
oq.Insert(m);
synchronized(this)
{
is_ok_to_send = false;
}
//dbg.p(dbg.INFO,"C: message inserted");
return;
}
/**
* Gives a recommandation if it's ok to send, in order to maintain good feedback.
* The issue is not to fill the queues by the server. If they are full then the
* feedback will be very bad because the incoming Messages (from the clients) will
* be handled only after the ones in the queue. If the queue is not too long then it's
* ok. But who knows. Use it before you send any other Message.
* @return If true then it's ok to send, else wait
*/
public synchronized boolean
sendOK()
{
if (oq == null) return false;
return ((is_ok_to_send) && (oq.isEmpty()));
}
/**
* Sets the filter control Messages on/off. By default the control Messages (CTS,NOP,etc) will
* reach also the child class via recv. If you change this, recv() will more often throw EmptyException.
* @param filter If true sets the filtering on, else off
*/
public synchronized void
setMessageFilter(boolean how)
{
filter_ctrl = how;
}
/**
* Gets the clients group number from the server structures.
* @return Group number or MessageConstants.INVALID if error
*/
public synchronized int
getGroup()
{
return group;
}
/**
* Gets the internal Player object used.
* @return Player object
*/
public Player
getPlayer()
{
return player;
}
/**
* Tries to set the client's group in the server structures.
* The change will not be imediately and may not happen if the requested group is invalid.
* No return is made and no guaranties are given (in this version) that the request
* will be fulfilled. Generaly speaking everything should be ok.
* Some old queued messages may get lost during this process.
*/
public void
setGroup(int newgroup)
{
try
{
oq.Insert(ClientMessageCreator.CHGRP(newgroup));
}
catch (FullException eee)
{
dbg.p(dbg.INFO,"C: Exception " + eee.getMessage());
}
}
/**
* Gets the clients number in the server structures.
* @return The clients number
*/
public synchronized int
getNumber()
{
return number;
}
/**
* control message filter, used in recv(). changes also internal variables.
* returns true if Message was control Message (NOP,CTS)
* false if message invalid or not control message.
*/
private boolean
messagefilter(Message mm)
{
int dummy;
//dbg.p(dbg.INFO,"C: filtering message [" + mm + "]");
if (!MessageParser.isValid(mm))
{
dbg.p(dbg.INFO,"C: Received INVALID MESSAGE");
return false;
}
//dbg.p(dbg.INFO,"C: is ok");
if (MessageParser.isOK(mm))
{
dbg.p(dbg.INFO,"C: Received OK !!!");
synchronized(this)
{
number = MessageParser.getDestination(mm);
group = MessageParser.getDestinationGroup(mm);
is_ok_to_send = true;
}
return true;
}
if (MessageParser.isCTS(mm))
{
dbg.p(dbg.INFO,"C: Received CTS !!!");
synchronized(this)
{
is_ok_to_send = true;
}
//dbg.p(dbg.INFO,"C: Done recv CTS !!!");
return true;
}
if (MessageParser.isRESET(mm))
{
dbg.p(dbg.INFO,"C: Received RESET !!!");
synchronized(this)
{
number = MessageParser.getDestination(mm);
group = MessageParser.getDestinationGroup(mm);
is_ok_to_send = true;
}
return true;
}
if (MessageParser.isNOP(mm))
{
return true;
}
// if (MessageParser.isDNF(mm))
// {
// return true;
// }
// if (MessageParser.isINVRQ(mm))
// {
// return true;
// }
return false;
}
/**
* Reads a incomming Message. It's internally synchronized !!! (is it really ?)
* @return The read Message
* @exception EmptyException When there is nothing to read
*/
public Message
recv()
throws EmptyException
{
Message mm;
if (iq == null) dbg.p(dbg.INFO,"C: Internal Error in reading ... null pointer");
//while (iq.isEmpty()) ; /* blocking if empty ..... is it alright ? */
mm = (Message)iq.fetchNext();
//dbg.p(dbg.INFO,"C: Read message [" + mm.getString() + "]");
if (filter_ctrl)
{
while (messagefilter(mm))
{
//dbg.p(dbg.INFO,"C: Read message [" + mm.getString() + "]");
mm = (Message)iq.fetchNext(); /* reads until Message is not control message */
}
}
else messagefilter(mm);
return mm;
}
/**
* Gets the number of available Messages for reading.
* @return The number of Messages available for reading
*/
public int
available()
{
return iq.Size();
}
/**
* Disconnects from the RoundRobinServerThread and stops everything.
* Should be used when stopping.
// * @param delay Delay to use in order to allow the writethread to write remained messages
*/
public void
disconnect()
{
//int delay=10;
dbg.p(dbg.INFO,"C: Disconnecting ...");
if (player != null)
{
//player.ReadThread.mysleep(delay);
//player.WriteThread.mysleep(delay);
player.down();
player = null;
}
iq = null;
oq = null;
}
}
/*eoc*/