Added code to establish the time delta between the client and server
clocks immediately following authentication. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1398 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Client.java,v 1.22 2002/04/10 06:08:59 mdb Exp $
|
||||
// $Id: Client.java,v 1.23 2002/05/28 21:56:38 mdb Exp $
|
||||
|
||||
package com.threerings.presents.client;
|
||||
|
||||
@@ -13,6 +13,8 @@ import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
import com.threerings.presents.net.BootstrapData;
|
||||
import com.threerings.presents.net.Credentials;
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
|
||||
/**
|
||||
* Through the client object, a connection to the system is established
|
||||
@@ -202,6 +204,28 @@ public class Client
|
||||
return _bstrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a server time stamp to a value comparable to client clock
|
||||
* readings.
|
||||
*/
|
||||
public long fromServerTime (long stamp)
|
||||
{
|
||||
// when we calcuated our time delta, we did it such that: C - S =
|
||||
// dT, thus to convert server to client time we do: C = S + dT
|
||||
return stamp + _serverDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a server tick stamp (which is the number of milliseconds
|
||||
* since the server started up) to an absolute client timestamp.
|
||||
*/
|
||||
public long fromServerTicks (int ticks)
|
||||
{
|
||||
// we already have our adjusted server start stamp, so we just add
|
||||
// the milliseconds since that time to get absolute time
|
||||
return _serverStartStamp + ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we are logged on, false if we're not.
|
||||
*/
|
||||
@@ -263,6 +287,50 @@ public class Client
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization to initiate a sequence of ping/pong
|
||||
* messages which will be used to determine (with "good enough"
|
||||
* accuracy) the difference between the client clock and the server
|
||||
* clock so that we can later interpret server timestamps.
|
||||
*/
|
||||
protected void establishClockDelta ()
|
||||
{
|
||||
// create a new delta calculator and start the process
|
||||
_dcalc = new DeltaCalculator();
|
||||
PingRequest req = new PingRequest();
|
||||
_comm.postMessage(req);
|
||||
_dcalc.sentPing(req);
|
||||
Log.info("Sent ping.");
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when we've completed the process of pinging the
|
||||
* server a few times to establish our clock delta.
|
||||
*/
|
||||
protected void clockDeltaEstablished ()
|
||||
{
|
||||
// initialize our invocation director
|
||||
ResultListener rl = new ResultListener() {
|
||||
public void requestCompleted (Object result) {
|
||||
// keep this around
|
||||
_clobj = (ClientObject)result;
|
||||
// let the client know that logon has now fully succeeded
|
||||
notifyObservers(Client.CLIENT_DID_LOGON, null);
|
||||
}
|
||||
|
||||
public void requestFailed (Exception cause) {
|
||||
// pass the buck onto the listeners
|
||||
notifyObservers(Client.CLIENT_FAILED_TO_LOGON, cause);
|
||||
}
|
||||
};
|
||||
_invdir.init(_comm.getDObjectManager(), _cloid, _bstrap.invOid, rl);
|
||||
|
||||
// we can't quite call initialization completed at this point
|
||||
// because we need for the invocation director to fully initialize
|
||||
// (which requires a round trip to the server) before turning the
|
||||
// client loose to do things like request invocation services
|
||||
}
|
||||
|
||||
boolean notifyObservers (int code, Exception cause)
|
||||
{
|
||||
boolean rejected = false;
|
||||
@@ -320,33 +388,65 @@ public class Client
|
||||
// extract bootstrap information
|
||||
_cloid = data.clientOid;
|
||||
|
||||
// initialize our invocation director
|
||||
ResultListener rl = new ResultListener() {
|
||||
public void requestCompleted (Object result) {
|
||||
// keep this around
|
||||
_clobj = (ClientObject)result;
|
||||
// let the client know that logon has now fully succeeded
|
||||
notifyObservers(Client.CLIENT_DID_LOGON, null);
|
||||
}
|
||||
// keep track of our server start time
|
||||
_serverStartStamp = data.serverStartStamp;
|
||||
|
||||
public void requestFailed (Exception cause) {
|
||||
// pass the buck onto the listeners
|
||||
notifyObservers(Client.CLIENT_FAILED_TO_LOGON, cause);
|
||||
}
|
||||
};
|
||||
_invdir.init(_comm.getDObjectManager(), _cloid, data.invOid, rl);
|
||||
|
||||
// we can't quite call initialization completed at this point
|
||||
// because we need for the invocation director to fully initialize
|
||||
// (which requires a round trip to the server) before turning the
|
||||
// client loose to do things like request invocation services
|
||||
// send a few pings to the server to establish the clock offset
|
||||
// between this client and server standard time
|
||||
establishClockDelta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we receive a pong packet. We may be in the process of
|
||||
* calculating the client/server time differential, or we may have
|
||||
* already done that at which point we ignore pongs.
|
||||
*/
|
||||
void gotPong (PongResponse pong)
|
||||
{
|
||||
Log.info("Got pong.");
|
||||
|
||||
// if we're not calculating our client/server time delta, then we
|
||||
// don't need to do anything with the pong
|
||||
if (_dcalc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are calculating, we'll either be sending another ping...
|
||||
if (_dcalc.gotPong(pong)) {
|
||||
PingRequest req = new PingRequest();
|
||||
_comm.postMessage(req);
|
||||
_dcalc.sentPing(req);
|
||||
|
||||
} else {
|
||||
// ...or we're done so we can grab the time delta and finish
|
||||
// our business
|
||||
_serverDelta = _dcalc.getTimeDelta();
|
||||
// adjust our server start stamp into client time
|
||||
_serverStartStamp += _serverDelta;
|
||||
// free up our delta calculator
|
||||
_dcalc = null;
|
||||
// let the client continue with its initialization
|
||||
clockDeltaEstablished();
|
||||
}
|
||||
}
|
||||
|
||||
/** The credentials we used to authenticate with the server. */
|
||||
protected Credentials _creds;
|
||||
|
||||
/** An entity that gives us the ability to process events on the main
|
||||
* client thread (which is also the AWT thread). */
|
||||
protected Invoker _invoker;
|
||||
|
||||
/** Our client distribted object id. */
|
||||
protected int _cloid;
|
||||
|
||||
/** Our client distributed object. */
|
||||
protected ClientObject _clobj;
|
||||
|
||||
/** The game server host. */
|
||||
protected String _hostname;
|
||||
|
||||
/** The port on which we connect to the game server. */
|
||||
protected int _port;
|
||||
|
||||
/** Our list of client observers. */
|
||||
@@ -355,10 +455,24 @@ public class Client
|
||||
/** The entity that manages our network communications. */
|
||||
protected Communicator _comm;
|
||||
|
||||
/** General startup information provided by the server. */
|
||||
protected BootstrapData _bstrap;
|
||||
protected int _cloid;
|
||||
|
||||
/** Manages invocation services. */
|
||||
protected InvocationDirector _invdir = new InvocationDirector();
|
||||
|
||||
/** The difference between the server clock and the client clock
|
||||
* (estimated immediately after logging on). */
|
||||
protected long _serverDelta;
|
||||
|
||||
/** The time from which server ticks are computed (converted into
|
||||
* client time. */
|
||||
protected long _serverStartStamp;
|
||||
|
||||
/** Used when establishing our clock delta between the client and
|
||||
* server. */
|
||||
protected DeltaCalculator _dcalc;
|
||||
|
||||
// client observer codes
|
||||
static final int CLIENT_DID_LOGON = 0;
|
||||
static final int CLIENT_FAILED_TO_LOGON = 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: ClientDObjectMgr.java,v 1.13 2002/02/10 04:19:34 mdb Exp $
|
||||
// $Id: ClientDObjectMgr.java,v 1.14 2002/05/28 21:56:38 mdb Exp $
|
||||
|
||||
package com.threerings.presents.client;
|
||||
|
||||
@@ -134,7 +134,7 @@ public class ClientDObjectMgr
|
||||
notifyFailure(oid);
|
||||
|
||||
} else if (obj instanceof PongResponse) {
|
||||
Log.info("Got pong.");
|
||||
_client.gotPong((PongResponse)obj);
|
||||
|
||||
} else if (obj instanceof ObjectAction) {
|
||||
ObjectAction act = (ObjectAction)obj;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// $Id: DeltaCalculator.java,v 1.1 2002/05/28 21:56:38 mdb Exp $
|
||||
|
||||
package com.threerings.presents.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.Log;
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
|
||||
/**
|
||||
* Used to compute the client/server time delta, attempting to account for
|
||||
* the network delay experienced when the server sends its current time to
|
||||
* the client.
|
||||
*/
|
||||
public class DeltaCalculator
|
||||
{
|
||||
/**
|
||||
* Constructs a delta calculator which is used to calculate the time
|
||||
* delta between the client and server, accounding reasonably well for
|
||||
* the delay introduced by sending a timestamp over the network from
|
||||
* the server to the client.
|
||||
*/
|
||||
public DeltaCalculator ()
|
||||
{
|
||||
_deltas = new long[CLOCK_SYNC_PING_COUNT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called when a ping message is sent to the server.
|
||||
*/
|
||||
public void sentPing (PingRequest ping)
|
||||
{
|
||||
_ping = ping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called when the pong response arrives back from the server.
|
||||
*
|
||||
* @return true if we've iterated sufficiently many times to establish
|
||||
* a stable time delta estimate.
|
||||
*/
|
||||
public boolean gotPong (PongResponse pong)
|
||||
{
|
||||
// make a note of when the ping message was sent and when the pong
|
||||
// response was received (both in client time)
|
||||
long send = _ping.getPackStamp(), recv = pong.getUnpackStamp();
|
||||
|
||||
// make a note of when the pong response was sent (in server time)
|
||||
// and the processing delay incurred on the server
|
||||
long server = pong.getPackStamp(), delay = pong.getProcessDelay();
|
||||
|
||||
// compute the network delay (round-trip time divided by two)
|
||||
long nettime = (recv - send - delay)/2;
|
||||
|
||||
// the time delta is the client time when the pong was received
|
||||
// minus the server's send time (plus network delay): dT = C - S
|
||||
_deltas[_iter] = recv - (server + nettime);
|
||||
|
||||
return (++_iter >= CLOCK_SYNC_PING_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best estimate client/server time-delta.
|
||||
*/
|
||||
public long getTimeDelta ()
|
||||
{
|
||||
// sort the estimates and return one from the middle
|
||||
Arrays.sort(_deltas);
|
||||
Log.info("Returning time delta " +
|
||||
"[deltas=" + StringUtil.toString(_deltas) + "].");
|
||||
return _deltas[_deltas.length/2];
|
||||
}
|
||||
|
||||
/** The number of ping/pong iterations we've made. */
|
||||
protected int _iter;
|
||||
|
||||
/** Client/server time delta estimates. */
|
||||
protected long[] _deltas;
|
||||
|
||||
/** A reference to the most recently sent ping which we use to obtain
|
||||
* the appropriate send stamp when we get the corresponding receive
|
||||
* stamp. */
|
||||
protected PingRequest _ping;
|
||||
|
||||
/** The number of times we PING during clock sync to try to smooth out
|
||||
* network jiggling. */
|
||||
protected static final int CLOCK_SYNC_PING_COUNT = 3;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: BootstrapData.java,v 1.4 2002/02/04 01:47:20 mdb Exp $
|
||||
// $Id: BootstrapData.java,v 1.5 2002/05/28 21:56:38 mdb Exp $
|
||||
|
||||
package com.threerings.presents.net;
|
||||
|
||||
@@ -18,4 +18,7 @@ public class BootstrapData extends DObject
|
||||
|
||||
/** The oid to which to send invocation requests. */
|
||||
public int invOid;
|
||||
|
||||
/** The time from which server ticks are incrementing. */
|
||||
public long serverStartStamp;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
//
|
||||
// $Id: PingRequest.java,v 1.5 2001/10/11 04:07:53 mdb Exp $
|
||||
// $Id: PingRequest.java,v 1.6 2002/05/28 21:56:38 mdb Exp $
|
||||
|
||||
package com.threerings.presents.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
||||
public class PingRequest extends UpstreamMessage
|
||||
{
|
||||
/** The code for a ping request. */
|
||||
@@ -16,6 +20,46 @@ public class PingRequest extends UpstreamMessage
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp that was obtained when this packet was encoded
|
||||
* by the low-level networking code.
|
||||
*/
|
||||
public long getPackStamp ()
|
||||
{
|
||||
return _packStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp that was obtained when this packet was decoded
|
||||
* by the low-level networking code.
|
||||
*/
|
||||
public long getUnpackStamp ()
|
||||
{
|
||||
return _unpackStamp;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void writeTo (DataOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
super.writeTo(out);
|
||||
|
||||
// grab a timestamp noting when we were encoded into a raw buffer
|
||||
// for delivery over the network
|
||||
_packStamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void readFrom (DataInputStream in)
|
||||
throws IOException
|
||||
{
|
||||
super.readFrom(in);
|
||||
|
||||
// grab a timestamp noting when we were decoded from a raw buffer
|
||||
// after being received over the network
|
||||
_unpackStamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public short getType ()
|
||||
{
|
||||
return TYPE;
|
||||
@@ -25,4 +69,12 @@ public class PingRequest extends UpstreamMessage
|
||||
{
|
||||
return "[type=PING, msgid=" + messageId + "]";
|
||||
}
|
||||
|
||||
/** A time stamp obtained when we serialize this object. */
|
||||
protected long _packStamp;
|
||||
|
||||
/** A time stamp obtained when we unserialize this object (the intent
|
||||
* is to get a timestamp as close as possible to when the packet was
|
||||
* received on the network). */
|
||||
protected long _unpackStamp;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
//
|
||||
// $Id: PongResponse.java,v 1.6 2001/10/11 04:07:53 mdb Exp $
|
||||
// $Id: PongResponse.java,v 1.7 2002/05/28 21:56:38 mdb Exp $
|
||||
|
||||
package com.threerings.presents.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
||||
import com.threerings.presents.Log;
|
||||
|
||||
public class PongResponse extends DownstreamMessage
|
||||
{
|
||||
/** The code for a pong response. */
|
||||
@@ -16,6 +22,82 @@ public class PongResponse extends DownstreamMessage
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a pong response which will use the supplied ping time to
|
||||
* establish the end-to-end processing delay introduced by the server.
|
||||
*/
|
||||
public PongResponse (long pingStamp)
|
||||
{
|
||||
// save this for when we are serialized in preparation for
|
||||
// delivery over the network
|
||||
_pingStamp = pingStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time at which this packet was packed for delivery in
|
||||
* the time frame of the server that sent the packet.
|
||||
*/
|
||||
public long getPackStamp ()
|
||||
{
|
||||
return _packStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of milliseconds that elapsed between the time
|
||||
* that the ping which instigated this pong was read from the network
|
||||
* and the time that this pong was written to the network.
|
||||
*/
|
||||
public int getProcessDelay ()
|
||||
{
|
||||
return _processDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp that was obtained when this packet was decoded
|
||||
* by the low-level networking code.
|
||||
*/
|
||||
public long getUnpackStamp ()
|
||||
{
|
||||
return _unpackStamp;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void writeTo (DataOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
super.writeTo(out);
|
||||
|
||||
// make a note of the time at which we were packed
|
||||
_packStamp = System.currentTimeMillis();
|
||||
out.writeLong(_packStamp);
|
||||
|
||||
// the time spent between unpacking the ping and packing the pong
|
||||
// is the processing delay
|
||||
if (_pingStamp == 0L) {
|
||||
Log.warning("Pong response written that was not constructed " +
|
||||
"with a valid ping stamp [rsp=" + this + "].");
|
||||
_processDelay = 0;
|
||||
} else {
|
||||
_processDelay = (int)(_packStamp - _pingStamp);
|
||||
}
|
||||
out.writeInt(_processDelay);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void readFrom (DataInputStream in)
|
||||
throws IOException
|
||||
{
|
||||
super.readFrom(in);
|
||||
|
||||
// grab a timestamp noting when we were decoded from a raw buffer
|
||||
// after being received over the network
|
||||
_unpackStamp = System.currentTimeMillis();
|
||||
|
||||
// read in our time stamps
|
||||
_packStamp = in.readLong();
|
||||
_processDelay = in.readInt();
|
||||
}
|
||||
|
||||
public short getType ()
|
||||
{
|
||||
return TYPE;
|
||||
@@ -25,4 +107,23 @@ public class PongResponse extends DownstreamMessage
|
||||
{
|
||||
return "[type=PONG, msgid=" + messageId + "]";
|
||||
}
|
||||
|
||||
/** The ping unpack stamp provided at construct time to this pong
|
||||
* response; only valid on the sending process, not the receiving
|
||||
* process. */
|
||||
protected long _pingStamp;
|
||||
|
||||
/** The timestamp obtained immediately before this packet was sent out
|
||||
* over the network. */
|
||||
protected long _packStamp;
|
||||
|
||||
/** The delay in milliseconds between the time that the ping request
|
||||
* was read from the network and the time the pong response was
|
||||
* written to the network. */
|
||||
protected int _processDelay;
|
||||
|
||||
/** A time stamp obtained when we unserialize this object (the intent
|
||||
* is to get a timestamp as close as possible to when the packet was
|
||||
* received on the network). */
|
||||
protected long _unpackStamp;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: PresentsClient.java,v 1.30 2002/04/26 02:32:27 mdb Exp $
|
||||
// $Id: PresentsClient.java,v 1.31 2002/05/28 21:56:38 mdb Exp $
|
||||
|
||||
package com.threerings.presents.server;
|
||||
|
||||
@@ -561,7 +561,8 @@ public class PresentsClient
|
||||
// send a pong response
|
||||
Connection conn = client.getConnection();
|
||||
if (conn != null) {
|
||||
conn.postMessage(new PongResponse());
|
||||
PingRequest req = (PingRequest)msg;
|
||||
conn.postMessage(new PongResponse(req.getUnpackStamp()));
|
||||
} else {
|
||||
Log.info("Dropped pong response [client=" + client + "].");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user