Ported over the automatic port-retrying code, but had to modify some things
due to differences in the way flash throws errors (and apparently can't re-use a socket object). git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4183 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -22,7 +22,7 @@ import com.threerings.presents.net.PongResponse;
|
||||
public class Client extends EventDispatcher
|
||||
{
|
||||
/** The default port on which the server listens for client connections. */
|
||||
public static const DEFAULT_SERVER_PORT :int = 47624;
|
||||
public static const DEFAULT_SERVER_PORTS :Array = [ 47624 ];
|
||||
|
||||
private static const log :Log = Log.getLog(Client);
|
||||
|
||||
@@ -84,10 +84,10 @@ public class Client extends EventDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
public function setServer (hostname :String, port :int) :void
|
||||
public function setServer (hostname :String, ports :Array) :void
|
||||
{
|
||||
_hostname = hostname;
|
||||
_port = port;
|
||||
_ports = ports;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,9 +103,12 @@ public class Client extends EventDispatcher
|
||||
return _hostname;
|
||||
}
|
||||
|
||||
public function getPort () :int
|
||||
/**
|
||||
* Returns the ports on which this client is configured to connect.
|
||||
*/
|
||||
public function getPorts () :Array
|
||||
{
|
||||
return _port;
|
||||
return _ports;
|
||||
}
|
||||
|
||||
public function getCredentials () :Credentials
|
||||
@@ -276,6 +279,15 @@ public class Client extends EventDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link Communicator} if it is experiencing trouble logging
|
||||
* on but is still trying fallback strategies.
|
||||
*/
|
||||
internal function reportLogonTribulations (cause :LogonError) :void
|
||||
{
|
||||
notifyObservers(ClientEvent.CLIENT_FAILED_TO_LOGON, cause);
|
||||
}
|
||||
|
||||
// TODO: this should be 'internal' except for a fucking bug with AS3
|
||||
public function gotClientObject (clobj :ClientObject) :void
|
||||
{
|
||||
@@ -359,7 +371,7 @@ public class Client extends EventDispatcher
|
||||
protected var _hostname :String;
|
||||
|
||||
/** The port on which we connect to the game server. */
|
||||
protected var _port :int;
|
||||
protected var _ports :Array; /* of int */
|
||||
|
||||
/** The entity that manages our network communications. */
|
||||
protected var _comm :Communicator;
|
||||
|
||||
@@ -53,6 +53,14 @@ public interface ClientObserver extends SessionObserver
|
||||
* Called if anything fails during the logon attempt. This could be a
|
||||
* network failure, authentication failure or otherwise. The exception
|
||||
* provided will indicate the cause of the failure.
|
||||
*
|
||||
* @param cause an exception indicating the cause of the logon failure.
|
||||
* <em>Note:</em> this may be a {@link LogonError} and if so, the
|
||||
* caller <em>must</em> check {@link LogonException#isStillInProgress} to
|
||||
* find out if the logon process has totally failed or if we are simply
|
||||
* reporting intermediate status (we might be falling back to an
|
||||
* alternative port or delaying our auto-retry attempt due to server
|
||||
* overload).
|
||||
*/
|
||||
function clientFailedToLogon (event :ClientEvent) :void;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.threerings.presents.client {
|
||||
|
||||
import flash.errors.IOError;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.events.IOErrorEvent;
|
||||
|
||||
@@ -16,6 +18,7 @@ import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.Translations;
|
||||
|
||||
import com.threerings.presents.data.AuthCodes;
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
@@ -32,24 +35,77 @@ public class Communicator
|
||||
|
||||
public function logon () :void
|
||||
{
|
||||
// create the socket and set up listeners
|
||||
_socket = new Socket();
|
||||
_socket.endian = Endian.BIG_ENDIAN;
|
||||
_socket.addEventListener(Event.CONNECT, socketOpened);
|
||||
_socket.addEventListener(IOErrorEvent.IO_ERROR, socketError);
|
||||
_socket.addEventListener(Event.CLOSE, socketClosed);
|
||||
|
||||
// create our input/output business
|
||||
_outBuffer = new ByteArray();
|
||||
_outBuffer.endian = Endian.BIG_ENDIAN;
|
||||
_outStream = new ObjectOutputStream(_outBuffer);
|
||||
|
||||
_frameReader = new FrameReader(_socket);
|
||||
_frameReader.addEventListener(FrameAvailableEvent.FRAME_AVAILABLE,
|
||||
inputFrameReceived);
|
||||
_inStream = new ObjectInputStream();
|
||||
|
||||
_socket.connect(_client.getHostname(), _client.getPort());
|
||||
_portIdx = 0;
|
||||
logonToPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is strangely named, and it does two things which is
|
||||
* bad style. Either log on to the next port, or save that the port
|
||||
* we just logged on to was a good one.
|
||||
*/
|
||||
protected function logonToPort (logonWasSuccessful :Boolean = false) :Boolean
|
||||
{
|
||||
var ports :Array = _client.getPorts();
|
||||
|
||||
if (!logonWasSuccessful) {
|
||||
if (_portIdx >= ports.length) {
|
||||
return false;
|
||||
}
|
||||
if (_portIdx != 0) {
|
||||
_client.reportLogonTribulations(
|
||||
new LogonError(AuthCodes.TRYING_NEXT_PORT, true));
|
||||
|
||||
removeListeners();
|
||||
}
|
||||
|
||||
// create the socket and set up listeners
|
||||
_socket = new Socket();
|
||||
_socket.endian = Endian.BIG_ENDIAN;
|
||||
_socket.addEventListener(Event.CONNECT, socketOpened);
|
||||
_socket.addEventListener(IOErrorEvent.IO_ERROR, socketError);
|
||||
_socket.addEventListener(Event.CLOSE, socketClosed);
|
||||
|
||||
_frameReader = new FrameReader(_socket);
|
||||
_frameReader.addEventListener(FrameAvailableEvent.FRAME_AVAILABLE,
|
||||
inputFrameReceived);
|
||||
}
|
||||
|
||||
var host :String = _client.getHostname();
|
||||
var pportKey :String = host + ".preferred_port";
|
||||
// TODO: extract preferred port from saved prefs
|
||||
var pport :int = /** TODO Prefs.getValue(pportKey, **/ (ports[0] as int);
|
||||
var ppidx :int = Math.max(0, ports.indexOf(pport));
|
||||
var port :int = (ports[(_portIdx + ppidx) % ports.length] as int);
|
||||
|
||||
if (logonWasSuccessful) {
|
||||
_portIdx = -1; // indicate that we're no longer trying new ports
|
||||
// TODO: Prefs.setValue(pportKey, port);
|
||||
|
||||
} else {
|
||||
Log.getLog(this).info(
|
||||
"Connecting [host=" + host + ", port=" + port + "].");
|
||||
_socket.connect(host, port);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function removeListeners () :void
|
||||
{
|
||||
_socket.removeEventListener(Event.CONNECT, socketOpened);
|
||||
_socket.removeEventListener(IOErrorEvent.IO_ERROR, socketError);
|
||||
_socket.removeEventListener(Event.CLOSE, socketClosed);
|
||||
|
||||
_frameReader.removeEventListener(FrameAvailableEvent.FRAME_AVAILABLE,
|
||||
inputFrameReceived);
|
||||
}
|
||||
|
||||
public function logoff () :void
|
||||
@@ -77,6 +133,7 @@ public class Communicator
|
||||
Log.getLog(this).warning(
|
||||
"Error closing failed socket [error=" + err + "].");
|
||||
}
|
||||
removeListeners();
|
||||
_socket = null;
|
||||
_outStream = null;
|
||||
_inStream = null;
|
||||
@@ -169,6 +226,7 @@ public class Communicator
|
||||
*/
|
||||
protected function socketOpened (event :Event) :void
|
||||
{
|
||||
logonToPort(true);
|
||||
// well that's great! let's logon
|
||||
var req :AuthRequest = new AuthRequest(_client.getCredentials(),
|
||||
_client.getVersion());
|
||||
@@ -180,6 +238,16 @@ public class Communicator
|
||||
*/
|
||||
protected function socketError (event :IOErrorEvent) :void
|
||||
{
|
||||
_socket.close();
|
||||
// if we're trying ports, try the next one.
|
||||
if (_portIdx != -1) {
|
||||
_portIdx++;
|
||||
if (logonToPort()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// total failure
|
||||
Log.getLog(this).warning("socket error: " + event);
|
||||
shutdown(new Error("socket closed unexpectedly."));
|
||||
}
|
||||
@@ -206,5 +274,8 @@ public class Communicator
|
||||
protected var _socket :Socket;
|
||||
|
||||
protected var _lastWrite :uint;
|
||||
|
||||
/** The current port we'll try to connect to. */
|
||||
protected var _portIdx :int = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id: LogonException.java 4158 2006-05-30 22:12:15Z mdb $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.client {
|
||||
|
||||
/**
|
||||
* A logon exception is used to indicate a failure to log on to the
|
||||
* server. The reason for failure is encoded as a string and stored in the
|
||||
* message field of the exception.
|
||||
*/
|
||||
public class LogonError extends Error
|
||||
{
|
||||
/**
|
||||
* Constructs a logon exception with the supplied logon failure code.
|
||||
*/
|
||||
public function LogonError (msg :String, stillInProgress :Boolean = false)
|
||||
{
|
||||
super(msg);
|
||||
_stillInProgress = stillInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this exception is reporting an intermediate status
|
||||
* rather than total logon failure. The client may be falling back to an
|
||||
* alternative port or delaying an auto-retry attempt due to server
|
||||
* overload. If this method returns true, the client <em>should not</em>
|
||||
* allow additional calls to {@link Client#logon} as the current attempt is
|
||||
* still in progress.
|
||||
*/
|
||||
public function isStillInProgress () :Boolean
|
||||
{
|
||||
return _stillInProgress;
|
||||
}
|
||||
|
||||
protected var _stillInProgress :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id: AuthCodes.java 4158 2006-05-30 22:12:15Z mdb $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.data {
|
||||
|
||||
/**
|
||||
* Basic authentication response codes.
|
||||
*/
|
||||
public class AuthCodes
|
||||
{
|
||||
/** A code indicating that no user exists with the specified
|
||||
* username. */
|
||||
public static const NO_SUCH_USER :String = "m.no_such_user";
|
||||
|
||||
/** A code indicating that the supplied password was invalid. */
|
||||
public static const INVALID_PASSWORD :String = "m.invalid_password";
|
||||
|
||||
/** A code indicating that an internal server error occurred while
|
||||
* trying to log the user on. */
|
||||
public static const SERVER_ERROR :String = "m.server_error";
|
||||
|
||||
/** A code indicating that the server is not available at the moment. */
|
||||
public static const SERVER_UNAVAILABLE :String = "m.server_unavailable";
|
||||
|
||||
/** A code indicating that we failed to connect to the server on a port and
|
||||
* are trying the next port in the list. */
|
||||
public static const TRYING_NEXT_PORT :String = "m.trying_next_port";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user