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:
Michael Bayne
2002-05-28 21:56:38 +00:00
parent f9980f9353
commit 2f1a67077a
7 changed files with 390 additions and 28 deletions
@@ -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 + "].");
}