/* * 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*/