Files
narya/src/as/com/threerings/presents/client/Communicator.as
T
Ray Greenwell 546c44c600 Stop trying to recover from this error (it doesn't work), but try logging
the event target.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4644 542714f4-19e9-0310-aa3c-eee0fc999fb1
2007-04-04 21:40:03 +00:00

319 lines
9.8 KiB
ActionScript

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 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 {
import flash.errors.IOError;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.Socket;
import flash.utils.ByteArray;
import flash.utils.Endian;
import com.threerings.util.StringUtil;
import com.threerings.io.FrameAvailableEvent;
import com.threerings.io.FrameReader;
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;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.LogoffRequest;
import com.threerings.presents.net.UpstreamMessage;
public class Communicator
{
public function Communicator (client :Client)
{
_client = client;
}
public function logon () :void
{
// create our input/output business
_outBuffer = new ByteArray();
_outBuffer.endian = Endian.BIG_ENDIAN;
_outStream = new ObjectOutputStream(_outBuffer);
_inStream = new ObjectInputStream();
_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";
var pport :int =
(PresentsPrefs.config.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
PresentsPrefs.config.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
{
if (_socket == null) {
return;
}
sendMessage(new LogoffRequest());
shutdown(null);
}
public function postMessage (msg :UpstreamMessage) :void
{
sendMessage(msg); // send it now: we have no out queue
}
protected function shutdown (logonError :Error) :void
{
_client.notifyObservers(ClientEvent.CLIENT_DID_LOGOFF, null);
if (_socket != null) {
try {
_socket.close();
} catch (err :Error) {
Log.getLog(this).warning(
"Error closing failed socket [error=" + err + "].");
}
removeListeners();
_socket = null;
_outStream = null;
_inStream = null;
_frameReader = null;
_outBuffer = null;
}
_client.cleanup(logonError);
}
protected function sendMessage (msg :UpstreamMessage) :void
{
if (_outStream == null) {
Log.getLog(this).warning(
"No socket, dropping msg [msg=" + msg + "].");
return;
}
// write the message (ends up in _outBuffer)
_outStream.writeObject(msg);
// Log.debug("outBuffer: " + StringUtil.unhexlate(_outBuffer));
// Frame it by writing the length, then the bytes.
// We add 4 to the length, because the length is of the entire frame
// including the 4 bytes used to encode the length!
_socket.writeInt(_outBuffer.length + 4);
_socket.writeBytes(_outBuffer);
_socket.flush();
// clean up the output buffer
_outBuffer.length = 0;
_outBuffer.position = 0;
// make a note of our most recent write time
updateWriteStamp();
}
/**
* Returns the time at which we last sent a packet to the server.
*/
internal function getLastWrite () :uint
{
return _lastWrite;
}
/**
* Makes a note of the time at which we last communicated with the server.
*/
internal function updateWriteStamp () :void
{
_lastWrite = flash.utils.getTimer();
}
/**
* Called when a frame of data from the server is ready to be
* decoded into a DownstreamMessage.
*/
protected function inputFrameReceived (event :FrameAvailableEvent) :void
{
// convert the frame data into a message from the server
var frameData :ByteArray = event.getFrameData();
//Log.debug("length of in frame: " + frameData.length);
//Log.debug("inBuffer: " + StringUtil.unhexlate(frameData));
_inStream.setSource(frameData);
var msg :DownstreamMessage;
try {
msg = (_inStream.readObject() as DownstreamMessage);
} catch (e :Error) {
var log :Log = Log.getLog(this);
log.warning("Error processing downstream message: " + e);
log.logStackTrace(e);
return;
}
if (frameData.bytesAvailable > 0) {
Log.getLog(this).warning(
"Beans! We didn't fully read a frame, surely there's " +
"a bug in some streaming code. " +
"[bytesLeftOver=" + frameData.bytesAvailable + "].");
}
if (_omgr != null) {
// if we're logged on, then just do the normal thing
_omgr.processMessage(msg);
return;
}
// Otherwise, this would be the AuthResponse to our logon attempt.
var rsp :AuthResponse = (msg as AuthResponse);
var data :AuthResponseData = rsp.getData();
if (data.code !== AuthResponseData.SUCCESS) {
shutdown(new Error(data.code));
return;
}
// logon success
_omgr = new ClientDObjectMgr(this, _client);
_client.setAuthResponseData(data);
}
/**
* Called when the connection to the server was successfully opened.
*/
protected function socketOpened (event :Event) :void
{
logonToPort(true);
// well that's great! let's logon
var req :AuthRequest = new AuthRequest(
_client.getCredentials(), _client.getVersion(), _client.getBootGroups());
sendMessage(req);
}
/**
* Called when there is an io error with the socket.
*/
protected function socketError (event :IOErrorEvent) :void
{
// 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 + ", target=" + event.target);
Log.dumpStack();
shutdown(new Error("socket closed unexpectedly."));
}
/**
* Called when the connection to the server was closed.
*/
protected function socketClosed (event :Event) :void
{
Log.getLog(this).warning("socket was closed: " + event);
_client.notifyObservers(ClientEvent.CLIENT_CONNECTION_FAILED);
logoff();
}
protected var _client :Client;
protected var _omgr :ClientDObjectMgr;
protected var _outBuffer :ByteArray;
protected var _outStream :ObjectOutputStream;
protected var _inStream :ObjectInputStream;
protected var _frameReader :FrameReader;
protected var _socket :Socket;
protected var _lastWrite :uint;
/** The current port we'll try to connect to. */
protected var _portIdx :int = -1;
}
}