internal void StoreMessage(double now, OutgoingNetMessage msg)
        {
            int chanBufIdx = (int)msg.m_sequenceChannel - (int)NetChannel.ReliableUnordered;

            List <OutgoingNetMessage> list = m_storedMessages[chanBufIdx];

            if (list == null)
            {
                list = new List <OutgoingNetMessage>();
                m_storedMessages[chanBufIdx] = list;
            }
            list.Add(msg);

            // schedule resend
            double nextResend = now + m_owner.Configuration.ResendFunction(msg.m_numSent, m_currentAvgRoundtrip);

            msg.m_nextResend = nextResend;

            // if (msg.m_numSent >= 10) UnityEngine.Debug.Log(now + ": Try no " + msg.m_numSent + ": " + NetUtility.BytesToHex(msg.m_data.ToArray()));

            m_owner.LogVerbose("Stored " + msg + " @ " + NetTime.ToMillis(now) + " next resend in " + NetTime.ToMillis(msg.m_nextResend - now) + " ms", this);

            // earliest?
            if (nextResend < m_earliestResend[chanBufIdx])
            {
                m_earliestResend[chanBufIdx] = nextResend;
            }
        }
예제 #2
0
        private void ReceivedPong(double rtSeconds, NetMessage pong)
        {
            double now = NetTime.Now;

            m_lastPongReceived = now;
            if (pong != null)
            {
                ushort remote = pong.m_data.ReadUInt16();
                ushort local  = NetTime.Encoded(now);
                int    diff   = local - remote - (int)(rtSeconds * 1000.0);
                if (diff < 0)
                {
                    diff += ushort.MaxValue;
                }
                m_remoteTimeOffset = diff;                 // TODO: slowly go towards? (m_remoteTimeOffset + diff) / 2;
                m_owner.LogVerbose("Got pong; roundtrip was " + (int)(rtSeconds * 1000) + " ms");
            }

            m_latencyHistory[2]   = m_latencyHistory[1];
            m_latencyHistory[1]   = m_latencyHistory[0];
            m_latencyHistory[0]   = rtSeconds;
            m_currentAvgRoundtrip = ((rtSeconds * 3) + (m_latencyHistory[1] * 2) + m_latencyHistory[2]) / 6.0;

            m_ackMaxDelayTime = (float)(
                m_owner.m_config.m_maxAckWithholdTime * rtSeconds * m_owner.m_config.m_resendTimeMultiplier
                );
        }
        /// <summary>
        /// Returns a string that represents this object
        /// </summary>
        public override string ToString()
        {
            // TODO: add custom format support

            var sb = new StringBuilder();

            sb.AppendFormat("Average roundtrip time: {0}", NetTime.ToReadable(_connection.AverageRoundtripTime)).AppendLine();
            sb.AppendFormat("Current MTU: {0}", _connection.CurrentMTU).AppendLine();

            sb.AppendFormat(
                "Sent {0} bytes in {1} messages in {2} packets",
                _sentBytes, _sentMessages, _sentPackets).AppendLine();

            sb.AppendFormat(
                "Received {0} bytes in {1} messages ({2} fragments) in {3} packets",
                _receivedBytes, _receivedMessages, _receivedFragments, _receivedPackets).AppendLine();

            sb.AppendLine();
            sb.AppendFormat("Queued: {0}", QueuedMessages).AppendLine();
            sb.AppendFormat("Stored: {0}", StoredMessages).AppendLine();
            sb.AppendFormat("Witheld: {0}", WithheldMessages).AppendLine();
            sb.AppendFormat("Resent (by delay): {0}", _resentMessagesDueToDelay).AppendLine();
            sb.AppendFormat("Resent (by hole): {0}", _resentMessagesDueToHole).AppendLine();

            return(sb.ToString());
        }
예제 #4
0
        internal void StoreMessage(double now, OutgoingNetMessage msg)
        {
            int chanBufIdx = (int)msg.m_sequenceChannel - (int)NetChannel.ReliableUnordered;

            List <OutgoingNetMessage> list = m_storedMessages[chanBufIdx];

            if (list == null)
            {
                list = new List <OutgoingNetMessage>();
                m_storedMessages[chanBufIdx] = list;
            }
            list.Add(msg);

            // schedule resend
            float  multiplier = (1 + (msg.m_numSent * msg.m_numSent)) * m_owner.m_config.m_resendTimeMultiplier;
            double nextResend = now + (0.025f + (float)m_currentAvgRoundtrip * 1.1f * multiplier);

            msg.m_nextResend = nextResend;

            m_owner.LogVerbose("Stored " + msg + " @ " + NetTime.ToMillis(now) + " next resend in " + NetTime.ToMillis(msg.m_nextResend - now) + " ms", this);

            // earliest?
            if (nextResend < m_earliestResend[chanBufIdx])
            {
                m_earliestResend[chanBufIdx] = nextResend;
            }
        }
        internal void ReceivedPong(float now, int pongNumber, float remoteSendTime)
        {
            if ((byte)pongNumber != (byte)m_sentPingNumber)
            {
                m_peer.LogVerbose("Ping/Pong mismatch; dropped message?");
                return;
            }

            m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout;

            float rtt = now - m_sentPingTime;

            NetException.Assert(rtt >= 0);

            double diff = (remoteSendTime + (rtt / 2.0)) - now;

            if (m_averageRoundtripTime < 0)
            {
                m_remoteTimeOffset     = diff;
                m_averageRoundtripTime = rtt;
                m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + " Remote time is: " + (now + diff));
            }
            else
            {
                m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f);

                m_remoteTimeOffset = ((m_remoteTimeOffset * (double)(m_sentPingNumber - 1)) + diff) / (double)m_sentPingNumber;
                m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + ", remote time to " + (now + m_remoteTimeOffset) + " (ie. diff " + m_remoteTimeOffset + ")");
            }

            // update resend delay for all channels
            float resendDelay = GetResendDelay();
            int   ct          = m_sendChannels.Length;

            for (int i = 0; i < ct; ++i)
            {
                var chan  = m_sendChannels[i];
                var rchan = chan as NetReliableSenderChannel;
                if (rchan != null)
                {
                    rchan.m_resendDelay = resendDelay;
                }
            }

            // m_peer.LogVerbose("Timeout deadline pushed to  " + m_timeoutDeadline);

            // notify the application that average rtt changed
            if (m_peer.m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionLatencyUpdated))
            {
                NetIncomingMessage update = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionLatencyUpdated, 4);
                update.m_senderConnection = this;
                update.m_senderEndPoint   = this.m_remoteEndPoint;
                update.Write(rtt);
                m_peer.ReleaseMessage(update);
            }
        }
예제 #6
0
        public static double FromEncoded(
            double now,
            int remoteMillisOffset,
            ushort encodedRemoteTimestamp,
            out int adjustRemoteMillis)
        {
            // my encoded time
            ushort localNow      = (ushort)(now * 1000 % ushort.MaxValue);
            ushort localStamp    = NetTime.NormalizeEncoded(encodedRemoteTimestamp + remoteMillisOffset);
            int    elapsedMillis = NetTime.GetElapsedMillis(localStamp, localNow);

            adjustRemoteMillis = 0;
            return(now - ((float)elapsedMillis / 1000.0f));
        }
예제 #7
0
        internal static void QueuePing(NetBase netBase, IPEndPoint toEndPoint, double now)
        {
            ushort    nowEnc = NetTime.Encoded(now);
            NetBuffer buffer = netBase.m_scratchBuffer;

            buffer.Reset();
            buffer.Write(nowEnc);
            netBase.QueueSingleUnreliableSystemMessage(
                NetSystemType.Ping,
                buffer,
                toEndPoint,
                false
                );
        }
        internal void ResendMessages(double now)
        {
            for (int i = 0; i < m_storedMessages.Length; i++)
            {
                List <OutgoingNetMessage> list = m_storedMessages[i];
                if (list == null || list.Count < 1)
                {
                    continue;
                }

                if (now > m_earliestResend[i])
                {
                    //TODO: try to find a way to avoid this, this is a quick fix to avoid quadratic time complexity
                    var newlist = new List <OutgoingNetMessage>(list.Count);

                    double newEarliest = double.MaxValue;
                    foreach (OutgoingNetMessage msg in list)
                    {
                        double resend = msg.m_nextResend;
                        if (now > resend)
                        {
                            // Re-enqueue message in unsent list
                            m_owner.LogVerbose("Resending " + msg +
                                               " now: " + NetTime.ToMillis(now) +
                                               " nextResend: " + NetTime.ToMillis(msg.m_nextResend), this);
                            m_statistics.CountMessageResent(msg.m_type);
                            m_unsentMessages.Enqueue(msg);
                        }
                        else
                        {
                            newlist.Add(msg);
                        }
                        if (resend < newEarliest)
                        {
                            newEarliest = resend;
                        }
                    }

                    m_storedMessages[i] = newlist;
                    m_earliestResend[i] = newEarliest;
                }
            }
        }
예제 #9
0
        internal void ResendMessages(double now)
        {
            for (int i = 0; i < m_storedMessages.Length; i++)
            {
                List <OutgoingNetMessage> list = m_storedMessages[i];
                if (list == null || list.Count < 1)
                {
                    continue;
                }

                if (now > m_earliestResend[i])
                {
                    double newEarliest = double.MaxValue;
                    foreach (OutgoingNetMessage msg in list)
                    {
                        double resend = msg.m_nextResend;
                        if (now > resend)
                        {
                            // Re-enqueue message in unsent list
                            m_owner.LogVerbose("Resending " + msg +
                                               " now: " + NetTime.ToMillis(now) +
                                               " nextResend: " + NetTime.ToMillis(msg.m_nextResend), this);
                            m_statistics.CountMessageResent(msg.m_type);
                            m_removeList.Add(msg);
                            m_unsentMessages.Enqueue(msg);
                        }
                        if (resend < newEarliest)
                        {
                            newEarliest = resend;
                        }
                    }

                    m_earliestResend[i] = newEarliest;
                    foreach (OutgoingNetMessage msg in m_removeList)
                    {
                        list.Remove(msg);
                    }
                    m_removeList.Clear();
                }
            }
        }
        internal void ReceivedPong(float now, int pongNumber)
        {
            if (pongNumber != m_sentPingNumber)
            {
                m_peer.LogVerbose("Ping/Pong mismatch; dropped message?");
                return;
            }

            m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout;

            float rtt = now - m_sentPingTime;

            NetException.Assert(rtt >= 0);

            if (m_averageRoundtripTime < 0)
            {
                m_averageRoundtripTime = rtt;                 // initial estimate
                m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime));
            }
            else
            {
                m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f);
                m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime));
            }

            // update resend delay for all channels
            float resendDelay = GetResendDelay();

            foreach (var chan in m_sendChannels)
            {
                var rchan = chan as NetReliableSenderChannel;
                if (rchan != null)
                {
                    rchan.m_resendDelay = resendDelay;
                }
            }

            m_peer.LogVerbose("Timeout deadline pushed to  " + m_timeoutDeadline);
        }
        internal void HandleAckMessage(double now, IncomingNetMessage ackMessage)
        {
            int len = ackMessage.m_data.LengthBytes;

            if ((len % 3) != 0)
            {
                if ((m_owner.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                {
                    m_owner.NotifyApplication(NetMessageType.BadMessageReceived, "Malformed ack message; length must be multiple of 3; it's " + len, this, ackMessage.m_senderEndPoint);
                }
                return;
            }

            for (int i = 0; i < len; i += 3)             //for each channel + seq nbr in ACK
            {
                NetChannel chan  = (NetChannel)ackMessage.m_data.ReadByte();
                int        seqNr = ackMessage.m_data.ReadUInt16();

                // LogWrite("Acknowledgement received: " + chan + "|" + seqNr);
                m_statistics.CountAcknowledgesReceived(1);

                // remove saved message
                int relChanNr = (int)chan - (int)NetChannel.ReliableUnordered;
                if (relChanNr < 0)
                {
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                    {
                        m_owner.NotifyApplication(NetMessageType.BadMessageReceived, "Malformed ack message; indicated netchannel " + chan, this, ackMessage.m_senderEndPoint);
                    }
                    continue;
                }

                List <OutgoingNetMessage> list = m_storedMessages[relChanNr];
                if (list != null)
                {
                    int cnt = list.Count;
                    if (cnt > 0)
                    {
                        for (int j = 0; j < cnt; j++)                         //for each stored message on channel
                        {
                            OutgoingNetMessage msg = list[j];
                            if (msg.m_sequenceNumber == seqNr)                             //find correct message
                            {
                                // if (msg.m_numSent >= 10) UnityEngine.Debug.Log(now + ": Ack for " + msg.m_numSent + ": " + NetUtility.BytesToHex(msg.m_data.ToArray()));

                                //LogWrite("Removed stored message: " + msg);
                                list.RemoveAt(j);

                                // reduce estimated amount of packets on wire
                                //CongestionCountAck(msg.m_packetNumber);

                                // fire receipt
                                if (msg.m_receiptData != null)
                                {
                                    m_owner.LogVerbose("Got ack, removed from storage: " + msg + " firing receipt; " + msg.m_receiptData, this);
                                    m_owner.FireReceipt(this, msg.m_receiptData);
                                }
                                else
                                {
                                    m_owner.LogVerbose("Got ack, removed from storage: " + msg, this);
                                }

                                // recycle
                                msg.m_data.m_refCount--;
                                if (msg.m_data.m_refCount <= 0)
                                {
                                    m_owner.RecycleBuffer(msg.m_data);                                     // time to recycle buffer
                                }
                                msg.m_data = null;
                                //m_owner.m_messagePool.Push(msg);

#if !NO_NAK
                                if (j > 0)
                                {
                                    int k;
                                    for (k = 0; k < j; k++)                                     //for each message stored prior to the one matching seq nbr
                                    {
                                        var m = list[k];
                                        if (m.m_sequenceNumber > seqNr)
                                        {
                                            break;
                                        }

                                        // Re-enqueue message in unsent list
                                        m_owner.LogVerbose("Implicit NAK Resending " + m +
                                                           " now: " + NetTime.NowInMillis +
                                                           " nextResend: " + NetTime.ToMillis(m.m_nextResend), this);
                                        m_statistics.CountMessageResent(m.m_type);
                                        m_unsentMessages.Enqueue(m);
                                    }

                                    list.RemoveRange(0, k);
                                }
#endif

                                break;                                 //exit stored message loop since this was the message corresponding to seq nbr
                                //now returning to next sequence number in ACK packet
                            }
                        }
                    }
                }
            }

            // recycle
            NetBuffer rb = ackMessage.m_data;
            rb.m_refCount     = 0;         // ack messages can't be used by more than one message
            ackMessage.m_data = null;

            m_owner.RecycleBuffer(rb);
            //m_owner.m_messagePool.Push(ackMessage);
        }
예제 #12
0
        public string GetStatisticsString(NetConnection connection)
        {
            double now    = NetTime.Now;
            string retval =
                "--- Application wide statistics ---" + Environment.NewLine +
                "Heartbeats: " + this.HeartbeatAverageFrequency + "/sec" + Environment.NewLine +
                "Packets sent: " + m_statistics.PacketsSent + " (" + m_statistics.GetPacketsSentPerSecond(now).ToString("N1") + "/sec)" + Environment.NewLine +
                "Bytes sent: " + m_statistics.BytesSent + " (" + m_statistics.GetBytesSentPerSecond(now).ToString("N1") + "/sec)" + Environment.NewLine +
                "Packets received: " + m_statistics.PacketsReceived + " (" + m_statistics.GetPacketsReceivedPerSecond(now).ToString("N1") + "/sec)" + Environment.NewLine +
                "Bytes received: " + m_statistics.BytesReceived + " (" + m_statistics.GetBytesReceivedPerSecond(now).ToString("N1") + "/sec)" + Environment.NewLine;

            if (m_simulatedLoss > 0.0f)
            {
                retval = retval +
                         "Simulated dropped packets: " + m_statistics.SimulatedDroppedPackets + Environment.NewLine +
                         "Simulated delayed packets: " + m_delayedPackets.Count + Environment.NewLine
                ;
            }

            if (connection != null)
            {
                NetConnectionStatistics connStats = connection.Statistics;
                retval += Environment.NewLine +
                          "--- Connection wide statistics ---" + Environment.NewLine +
                          "Status: " + connection.Status + Environment.NewLine +

                          "Received -----" + Environment.NewLine +
                          "Messages: " + connStats.GetMessagesReceived(true) + "/sec)" + Environment.NewLine +
                          "  User/type: " +
                          connStats.GetUserUnreliableReceived() + "/" +
                          connStats.GetUserSequencedReceived() + "/" +
                          connStats.GetUserReliableUnorderedReceived() + "/" +
                          connStats.GetUserOrderedReceived() + Environment.NewLine +
                          "Packets: " + connStats.PacketsReceived + Environment.NewLine +
                          "Bytes: " + connStats.GetBytesReceived(true) + Environment.NewLine +
                          "Acks: " + connStats.AcknowledgesReceived + Environment.NewLine +
                          Environment.NewLine +

                          "Sent ------" + Environment.NewLine +
                          "Messages: " + connStats.GetMessagesSent(true) +
                          " (excl. resends " + (connStats.GetMessagesSent(true) - connStats.MessagesResent) + ")" + Environment.NewLine +
                          "  User/type: " +
                          connStats.GetUserUnreliableSent() + "/" +
                          connStats.GetUserSequencedSent() + "/" +
                          connStats.GetUserReliableUnorderedSent() + "/" +
                          connStats.GetUserOrderedSent() + Environment.NewLine +

                          "Packets: " + connStats.PacketsSent + Environment.NewLine +
                          "Bytes: " + connStats.GetBytesSent(true) + Environment.NewLine +
                          "Acks: " + connStats.AcknowledgesSent + Environment.NewLine +
                          Environment.NewLine +
                          "Resent: " + connStats.MessagesResent + Environment.NewLine +
                          "Duplicates: " + connStats.DuplicateMessagesRejected + Environment.NewLine +
                          "Dropped sequenced: " + connStats.SequencedMessagesRejected + Environment.NewLine +
                          Environment.NewLine +
                          "Unsent messages: " + connection.m_unsentMessages.Count + Environment.NewLine +
                          "Stored messages: " + connStats.CurrentlyStoredMessagesCount + Environment.NewLine +
                          "Withheld messages: " + connStats.CurrentlyWithheldMessagesCount + Environment.NewLine +
                          "Average roundtrip: " + NetTime.ToMillis(connection.AverageRoundtripTime) + " ms" + Environment.NewLine
                          //"Window: " + connection.GetEstimatedPacketsOnWire() + " of " + connection.m_congestionWindowSize
                ;
            }
            return(retval);
        }
        /// <summary>
        /// Called to bind to socket and start heartbeat thread.
        /// The socket will be bound to listen on any network interface unless the <see cref="NetConfiguration.Address"/> explicitly specifies an interface.
        /// </summary>
        public void Start()
        {
            if (m_isBound)
            {
                return;
            }

            // TODO: this check should be done somewhere earlier, in uLink preferably.
            if (m_config.StartPort > m_config.EndPort)
            {
                throw new NetException("The start port (" + m_config.StartPort + ") must be less or equal to the end port (" + m_config.EndPort + ")");
            }

            //by WuNan @2016/09/28 14:26:22
            var bindIP = String.IsNullOrEmpty(m_config.AddressStr) ?
#if (UNITY_IOS || UNITY_TVOS) && !UNITY_EDITOR
                         (uLink.NetworkUtility.IsSupportIPv6() ? IPAddress.IPv6Any : IPAddress.Any) : NetUtility.Resolve(m_config.AddressStr);
#else
                         IPAddress.Any : NetUtility.Resolve(m_config.AddressStr);
#endif

            Log.Debug(LogFlags.Socket, "Creating non-blocking UDP socket");

            var sock = NetworkSocket.Create(m_config.SocketType);

            Log.Debug(LogFlags.Socket, "Successfully created Socket");

            for (int port = m_config.StartPort; port <= m_config.EndPort; port++)
            {
                try
                {
                    sock.Bind(new NetworkEndPoint(bindIP, port));

                    m_isBound = true;
                    break;
                }
                catch (SocketException ex)
                {
                    Log.Debug(LogFlags.Socket, "Failed to bind to specific port ", port, ": ", ex);

                    if (port == m_config.EndPort)
                    {
                        try
                        {
                            sock.Close(0);
                        }
                        catch
                        {
                        }

                        throw new NetException("Failed to bind to port range " + m_config.StartPort + "-" + m_config.EndPort, ex);
                    }
                }
            }

            m_socket = sock;

            LogWrite("Listening on " + m_socket.listenEndPoint);

            if (m_config.ReceiveBufferSize != 0)
            {
                try
                {
                    m_socket.receiveBufferSize = m_config.ReceiveBufferSize;
                    m_config.ReceiveBufferSize = m_socket.receiveBufferSize;                     // make sure we have the actual size
                }
                catch (Exception ex)
                {
                    Log.Warning(LogFlags.Socket, "Unable to set socket ", SocketOptionName.ReceiveBuffer, " size to ", m_config.ReceiveBufferSize, ": ", ex);
                }
            }
            else
            {
                try
                {
                    m_config.ReceiveBufferSize = m_socket.receiveBufferSize;

                    Log.Debug(LogFlags.Socket, "Socket ", SocketOptionName.ReceiveBuffer, " is set to OS-specific default ", m_config.ReceiveBufferSize);
                }
                catch (Exception ex)
                {
                    Log.Warning(LogFlags.Socket, "Unable to get socket ", SocketOptionName.ReceiveBuffer, ": ", ex);
                }
            }

            if (m_config.SendBufferSize != 0)
            {
                try
                {
                    m_socket.sendBufferSize = m_config.SendBufferSize;
                    m_config.SendBufferSize = m_socket.sendBufferSize;                     // make sure we have the actual size
                }
                catch (Exception ex)
                {
                    Log.Warning(LogFlags.Socket, "Unable to set socket ", SocketOptionName.SendBuffer, " size to ", m_config.SendBufferSize, ": ", ex);
                }
            }
            else
            {
                try
                {
                    m_config.SendBufferSize = m_socket.sendBufferSize;

                    Log.Debug(LogFlags.Socket, "Socket ", SocketOptionName.SendBuffer, " is set to OS-specific default ", m_config.SendBufferSize);
                }
                catch (Exception ex)
                {
                    Log.Warning(LogFlags.Socket, "Unable to get socket ", SocketOptionName.SendBuffer, ": ", ex);
                }
            }

            m_receiveBuffer.EnsureBufferSizeInBytes(m_config.ReceiveBufferSize);
            m_sendBuffer.EnsureBufferSizeInBytes(m_config.SendBufferSize);

            // TODO: ugly hack to determine if server
            if (this is NetServer)
            {
                m_socket.Listen(m_config.MaxConnections);
            }

            // display simulated networking conditions in debug log
            if (m_simulatedLoss > 0.0f)
            {
                LogWrite("Simulating " + (m_simulatedLoss * 100.0f) + "% loss");
            }
            if (m_simulatedMinimumLatency > 0.0f || m_simulatedLatencyVariance > 0.0f)
            {
                LogWrite("Simulating " + ((int)(m_simulatedMinimumLatency * 1000.0f)) + " - " + NetTime.ToMillis(m_simulatedMinimumLatency + m_simulatedLatencyVariance) + " ms roundtrip latency");
            }
            if (m_simulatedDuplicateChance > 0.0f)
            {
                LogWrite("Simulating " + (m_simulatedDuplicateChance * 100.0f) + "% chance of packet duplication");
            }

            if (m_config.m_throttleBytesPerSecond > 0)
            {
                LogWrite("Throttling to " + m_config.m_throttleBytesPerSecond + " bytes per second");
            }

            m_isBound          = true;
            m_shutdownComplete = false;
            m_statistics.Reset();
        }
예제 #14
0
 public override string ToString()
 {
     return("roundtrip " + NetTime.ToMillis(roundtripTime) + " ms, offset " + timeOffset + " s");
 }