View Javadoc

    1   // ========================================================================
    2   // Copyright 2006 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.cometd.ext;
    16
    17  import java.util.Map;
    18
    19  import org.cometd.Client;
    20  import org.cometd.Extension;
    21  import org.cometd.Message;
    22  import org.mortbay.cometd.ClientImpl;
    23  import org.mortbay.util.ajax.JSON;
    24
    25  /* ------------------------------------------------------------ */
    26  /**
    27   * Timesync extension (server side). With each handshake or connect, the
    28   * extension sends timestamps within the ext field like:
    29   * <code>{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}</code> where:
    30   * <ul>
    31   * <li>tc is the client timestamp in ms since 1970 of when the message was sent.
    32   * <li>l is the network lag that the client has calculated.
    33   * <li>o is the clock offset that the client has calculated.
    34   * </ul>
    35   *
    36   * <p>
    37   * A cometd server that supports timesync, can respond with an ext field like:
    38   * <code>{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}</code>
    39   * where:
    40   * <ul>
    41   * <li>tc is the client timestamp of when the message was sent,
    42   * <li>ts is the server timestamp of when the message was received
    43   * <li>p is the poll duration in ms - ie the time the server took before sending
    44   * the response.
    45   * <li>a is the measured accuracy of the calculated offset and lag sent by the
    46   * client
    47   * </ul>
    48   * <p>
    49   * The relationship between tc, ts, o & l on the server is given by
    50   * <code>ts=tc+o+l</code> (the time the server received the message is the
    51   * client time plus the offset plus the network lag). Thus the accuracy of the o
    52   * and l settings can be determined with <code>a=tc+o+l-ts</code>.
    53   * </p>
    54   * <p>
    55   * When the client has received the response, it can make a more accurate
    56   * estimate of the lag as <code>l2=(now-tc-p)/2</code> (assuming symmetric lag).
    57   * A new offset can then be calculated with the relationship on the client that
    58   * <code>ts=tc+o2+l2</code>, thus <code>o2=ts-tc-l2</code>.
    59   * </p>
    60   * <p>
    61   * Since the client also receives the a value calculated on the server, it
    62   * should be possible to analyse this and compensate for some asymmetry in the
    63   * lag. But the current client does not do this.
    64   * </p>
    65   */
    66  public class TimesyncExtension implements Extension
    67  {
    68      private int _accuracyTarget=25;
    69
    70      public TimesyncExtension()
    71      {
    72      }
    73
    74      /* ------------------------------------------------------------ */
    75      /**
    76       * timesync responses are not set if the measured accuracy is less than the
    77       * accuracyTarget.
    78       *
    79       * @return accuracy target in ms (default 25ms)
    80       */
    81      public int getAccuracyTarget()
    82      {
    83          return _accuracyTarget;
    84      }
    85
    86      /* ------------------------------------------------------------ */
    87      /**
    88       * timesync responses are not set if the measured accuracy is less than the
    89       * accuracyTarget.
    90       *
    91       * @param target
    92       *            accuracy target in ms
    93       */
    94      public void setAccuracyTarget(int target)
    95      {
    96          _accuracyTarget=target;
    97      }
    98
    99      public Message rcv(Client from, Message message)
    100     {
    101         return message;
    102     }
    103
    104     public Message rcvMeta(Client from, Message message)
    105     {
    106         Map<String,Object> ext=message.getExt(false);
    107         if (ext != null)
    108         {
    109             Map<String,Object> sync=(Map<String,Object>)ext.get("timesync");
    110             if (sync != null)
    111             {
    112                 sync.put("ts",new Long(System.currentTimeMillis()));
    113                 Number lag=(Number)sync.get("l");
    114                 if (lag != null && from != null)
    115                     ((ClientImpl)from).setLag(lag.intValue());
    116             }
    117         }
    118         return message;
    119     }
    120
    121     public Message send(Client from, Message message)
    122     {
    123         return message;
    124     }
    125
    126     public Message sendMeta(Client from, Message message)
    127     {
    128         Message associated=message.getAssociated();
    129         if (associated != null)
    130         {
    131             Map<String,Object> extIn=associated.getExt(false);
    132
    133             if (extIn != null)
    134             {
    135                 Map<String,Object> sync=(Map<String,Object>)extIn.get("timesync");
    136                 if (sync != null)
    137                 {
    138                     final long tc=((Number)sync.get("tc")).longValue();
    139                     final long ts=((Number)sync.get("ts")).longValue();
    140
    141                     Number lag=(Number)sync.get("l");
    142                     if (lag == null)
    143                     {
    144                         // old style timesync
    145                         Map<String,Object> extOut=(Map<String,Object>)message.getExt(true);
    146                         JSON.Literal timesync=new JSON.Literal("{\"tc\":" + tc + ",\"ts\":" + ts + ",\"p\":" + (System.currentTimeMillis() - ts) + "}");
    147                         extOut.put("timesync",timesync);
    148                     }
    149                     else
    150                     {
    151                         final int l=lag.intValue();
    152                         final Number offset = (Number)sync.get("o"); // May return null if is NaN
    153                         final int o=offset == null ? 0 : offset.intValue();
    154                         final int a=(int)((tc + o + l) - ts);
    155
    156                         // is a OK ?
    157                         if (l == 0 || a >= _accuracyTarget || a <= -_accuracyTarget)
    158                         {
    159                             Map<String,Object> extOut=(Map<String,Object>)message.getExt(true);
    160                             JSON.Literal timesync=new JSON.Literal("{\"tc\":" + tc + ",\"ts\":" + ts + ",\"p\":" + (System.currentTimeMillis() - ts)
    161                                     + ",\"a\":" + a + "}");
    162
    163                             extOut.put("timesync",timesync);
    164                         }
    165                     }
    166                 }
    167             }
    168         }
    169         return message;
    170     }
    171 }