// used when datagram already constructed, e.g. re-sending unACKnowledged datagram internal void EnqueueSend(Datagram datagram) { queue.Enqueue(datagram); }
// returns true if caller should continue adding any additional packets in datagram internal bool TryAddReceivedPacket( ushort seq, SendOptions opts, PacketType type, byte[] buffer, int index, int payloadSize, bool isFirstPacketInDatagram) { // If we are not the keep alive master, i.e. this remote peer is, and this packet was // sent reliably reset ellpasedMilliseondsAtLastRealiablePacket[Received]. if (IsKeepAliveMaster && ((opts & SendOptions.Reliable) == SendOptions.Reliable)) { ellapasedSecondsSinceLastRealiablePacket = 0.0f; } switch (type) { case PacketType.Application: case PacketType.KeepAlive: case PacketType.AcceptJoin: { bool wasAppPacketAdded; ReceiveChannel channel = noneReceiveChannel; switch (opts) { case SendOptions.InOrder: channel = inOrderReceiveChannel; break; case SendOptions.Reliable: channel = reliableReceiveChannel; break; case SendOptions.ReliableInOrder: channel = reliableInOrderReceiveChannel; break; } if (!channel.TryAddReceivedPacket(seq, type, buffer, index, payloadSize, isFirstPacketInDatagram, out wasAppPacketAdded)) { return(false); } if (wasAppPacketAdded) { unreadPacketCount++; } return(true); } case PacketType.ACK: { // Look for the oldest datagram with the same seq AND channel type // seq is for which we ASSUME the ACK is for. int datagramIndex; Datagram sentDatagramAwaitingAck = null; for (datagramIndex = 0; datagramIndex < sentDatagramsAwaitingACK.Count; datagramIndex++) { Datagram datagram = sentDatagramsAwaitingACK[datagramIndex]; if (datagram.Sequence == seq && datagram.SendOptions == opts) { sentDatagramAwaitingAck = datagram; break; } } if (sentDatagramAwaitingAck == null) { // Possible reasons: // 1) ACK has arrived too late and the datagram must have already been removed. // 2) ACK duplicated and has already been processed // 3) ACK was unsolicited (i.e. malicious or buggy peer) // NOTE: If ACK "piggy-backed" on a reliable datagram that was re-sent // that datagram if recieved more than once would have been dropped // when processing the first Application packet so should never // get to here. localPeer.Log(LogLevel.Warning, "Datagram for ACK not found - too late?"); } else { // recieving our first ACK from this peer means they have Accepted us if (!hasAccepted) { hasAccepted = true; } // remove datagram sentDatagramsAwaitingACK.RemoveAt(datagramIndex); // Update QoS // ---------- // // If the datagram was not re-sent update latency, otherwise we do not // know which send this ACK is for so cannot determine latency. // // Also update re-sends sample if datagram was not re-sent with: not // re-sent. If was re-sent sample would have already been updated when // re-sent. if (sentDatagramAwaitingAck.ResentCount == 0) { TimeSpan rtt = localPeer.Stopwatch.Elapsed - sentDatagramAwaitingAck.EllapsedAtSent; qualityOfService.UpdateLatency(rtt); qualityOfService.UpdateResentSample(false); localPeer.Log(LogLevel.Debug, String.Format("ACK from: {0}, channel: {1}, seq: {2}, RTT: {3}s", PeerName, opts.ToString(), seq.ToString(), rtt.TotalSeconds.ToString())); localPeer.Log(LogLevel.Debug, String.Format("Latency now: {0}s", qualityOfService.RoudTripTime.TotalSeconds.ToString())); } else { localPeer.Log(LogLevel.Info, String.Format("ACK for re-sent datagram: {0}, channel: {1}, seq: {2}", PeerName, opts.ToString(), seq.ToString())); } // return datagram sendDatagramsPool.Return(sentDatagramAwaitingAck); } return(true); } case PacketType.Ping: { return(Pong()); } case PacketType.Pong: { if (localPeer.HasPingsAwaitingPong) { PingDetail detail = localPeer.PingsAwaitingPong.Find(pd => pd.PeerIdPingSentTo == Id); if (detail != null) { localPeer.RaisePongReceived(this, TimeSpan.FromSeconds(detail.EllapsedSeconds)); localPeer.RemovePingAwaitingPongDetail(detail); } } return(true); } case PacketType.JoinRequest: { if (hasAccepted) { // If peer has accepted must be is joining again (possible when did not // say Bye and has restarted again before timed out). Drop this // instance of peer and process Accept next update so both peers on // the same page, i.e. that we are starting a new connection. // byte[] datagram = new byte[Const.FALCON_PACKET_HEADER_SIZE + payloadSize]; FalconHelper.WriteFalconHeader(datagram, 0, PacketType.JoinRequest, SendOptions.None, (ushort)seq, (ushort)payloadSize); if (payloadSize > 0) { Buffer.BlockCopy(buffer, index, datagram, Const.FALCON_PACKET_HEADER_SIZE, payloadSize); } localPeer.RemovePeerOnNextUpdate(this); localPeer.EnqueuePacketToProcessOnNextUpdate(this.EndPoint, datagram); return(false); } return(Accept()); } case PacketType.DiscoverRequest: { DiscoverReply(); return(true); } case PacketType.DiscoverReply: { // do nothing, DiscoveryReply only relevant when peer not added return(true); } case PacketType.Bye: { localPeer.Log(LogLevel.Info, String.Format("Bye received from: {0}.", PeerName)); localPeer.RemovePeerOnNextUpdate(this); return(false); } default: { localPeer.Log(LogLevel.Error, String.Format("Datagram dropped - unexpected type: {0}, received from authenticated peer: {1}.", type, PeerName)); return(false); } } }
private bool TrySendDatagram(Datagram datagram, bool hasAlreadyBeenDelayed = false) { // If we are the keep alive master (i.e. this remote peer is not) and this // packet is reliable: update ellpasedMilliseondsAtLastRealiablePacket[Sent] if (!IsKeepAliveMaster && datagram.IsReliable) { ellapasedSecondsSinceLastRealiablePacket = 0.0f; } // simulate packet loss if (localPeer.SimulatePacketLossProbability > 0.0) { if (SingleRandom.NextDouble() < localPeer.SimulatePacketLossProbability) { localPeer.Log(LogLevel.Info, String.Format("DROPPED packet to send - simulate packet loss set at: {0}", localPeer.SimulatePacketLossChance)); return(true); } } // if reliable and not already delayed update time sent used to measure RTT when ACK response received if (datagram.IsReliable && !hasAlreadyBeenDelayed) { datagram.EllapsedAtSent = localPeer.Stopwatch.Elapsed; } // simulate delay if (localPeer.SimulateLatencySeconds > 0.0f && !hasAlreadyBeenDelayed && datagram.Type != PacketType.Bye) // if Bye we don't delay as if closing will not be sent { float delay = localPeer.SimulateLatencySeconds; // jitter if (localPeer.SimulateJitterSeconds > 0.0f) { float jitter = localPeer.SimulateJitterSeconds * (float)SingleRandom.NextDouble(); if (SingleRandom.NextDouble() < 0.5) { jitter *= -1; } delay += jitter; } DelayedDatagram delayedDatagram = new DelayedDatagram(delay, datagram); localPeer.Log(LogLevel.Debug, String.Format("...DELAYED Sending datagram to: {0}, seq {1}, channel: {2}, total size: {3}; by {4}s...", endPoint.ToString(), datagram.Sequence.ToString(), datagram.SendOptions.ToString(), datagram.Count.ToString(), delayedDatagram.EllapsedSecondsRemainingToDelay.ToString())); delayedDatagrams.Add(delayedDatagram); return(true); } localPeer.Log(LogLevel.Debug, String.Format("--> Sending datagram to: {0}, seq {1}, channel: {2}, total size: {3}...", endPoint.ToString(), datagram.Sequence.ToString(), datagram.SendOptions.ToString(), datagram.Count.ToString())); // Expedite send if less than 5% of recent reliable packets have to be re-sent bool expedite = qualityOfService.ResendRatio < 0.05f; //----------------------------------------------------------------------------------------------------------------- bool success = localPeer.Transceiver.Send(datagram.BackingBuffer, datagram.Offset, datagram.Count, endPoint, expedite); //----------------------------------------------------------------------------------------------------------------- if (success) { if (localPeer.IsCollectingStatistics) { localPeer.Statistics.AddBytesSent(datagram.Count); } // return the datagram to pool for re-use if we are not waiting for an ACK if (!datagram.IsReliable) { sendDatagramsPool.Return(datagram); } } else { // something fatal has gone wrong and this peer can no longer be sent to localPeer.RemovePeerOnNextUpdate(this); } return(success); }
internal void Update(float dt) { // // Update counters // ellapasedSecondsSinceLastRealiablePacket += dt; ellapsedSecondsSinceSendQueuesLastFlushed += dt; //// //// Update enqued ACKs stopover time //// //if (enqueudAcks.Count > 0) //{ // for(int i = 0; i <enqueudAcks.Count; i++) // { // AckDetail oldAck = enqueudAcks[i]; // AckDetail newAck = new AckDetail(); // newAck.Seq = oldAck.Seq; // newAck.Channel = oldAck.Channel; // newAck.EllapsedSecondsSinceEnqueued = oldAck.EllapsedSecondsSinceEnqueued + dt; // enqueudAcks[i] = newAck; // } //} // // Datagrams awaiting ACKs // if (sentDatagramsAwaitingACK.Count > 0) { bool anyResendsAwaitingACK = false; for (int i = 0; i < sentDatagramsAwaitingACK.Count; i++) { Datagram datagramAwaitingAck = sentDatagramsAwaitingACK[i]; datagramAwaitingAck.EllapsedSecondsSincePacketSent += dt; float ackTimeout = localPeer.GetAckTimeout(datagramAwaitingAck.ResentCount); if (datagramAwaitingAck.EllapsedSecondsSincePacketSent >= ackTimeout) { datagramAwaitingAck.ResentCount++; if (datagramAwaitingAck.ResentCount > localPeer.MaxResends) { // give-up, assume the peer has disconnected (without saying bye) and drop it sentDatagramsAwaitingACK.RemoveAt(i); i--; localPeer.Log(LogLevel.Warning, String.Format("Peer failed to ACK {0} re-sends of Reliable datagram seq {1}, channel {2}. total size: {3}, in time.", localPeer.MaxResends, datagramAwaitingAck.Sequence.ToString(), datagramAwaitingAck.SendOptions.ToString(), datagramAwaitingAck.Count.ToString())); sendDatagramsPool.Return(datagramAwaitingAck); localPeer.RemovePeerOnNextUpdate(this); return; } else { // try again.. datagramAwaitingAck.EllapsedSecondsSincePacketSent = 0.0f; ReSend(datagramAwaitingAck); ellapasedSecondsSinceLastRealiablePacket = 0.0f; // NOTE: this is reset again when packet actually sent but another Update() may occur before then localPeer.Log(LogLevel.Info, String.Format("Datagram to: {0}, seq {1}, channel: {2}, total size: {3} re-sent as not ACKnowledged in {4}s.", PeerName, datagramAwaitingAck.Sequence.ToString(), datagramAwaitingAck.SendOptions.ToString(), datagramAwaitingAck.Count.ToString(), ackTimeout.ToString())); } } if (datagramAwaitingAck.IsResend) { anyResendsAwaitingACK = true; } } qualityOfService.IsNotResponding = anyResendsAwaitingACK; } // // KeepAlives and AutoFlush // if (keepAliveAndAutoFlush) { if (IsKeepAliveMaster) // i.e. this remote peer is the keep alive master, not us { if (ellapasedSecondsSinceLastRealiablePacket >= localPeer.KeepAliveProbeAfterSeconds) { // This remote peer has not sent a reliable message for too long, send a // KeepAlive to probe them and they are alive! reliableSendChannel.EnqueueSend(PacketType.KeepAlive, null); ellapasedSecondsSinceLastRealiablePacket = 0.0f; } } else if (ellapasedSecondsSinceLastRealiablePacket >= localPeer.KeepAliveIntervalSeconds) { reliableSendChannel.EnqueueSend(PacketType.KeepAlive, null); ellapasedSecondsSinceLastRealiablePacket = 0.0f; // NOTE: this is reset again when packet actually sent but another Update() may occur before then } if (localPeer.AutoFlushIntervalSeconds > 0.0f) { if (ellapsedSecondsSinceSendQueuesLastFlushed >= localPeer.AutoFlushIntervalSeconds) { localPeer.Log(LogLevel.Info, "AutoFlush"); bool success = TryFlushSendQueues(); // resets ellapsedSecondsSinceSendQueuesLastFlushed if (!success) { return; } } } } // // Simulate Delay // if (delayedDatagrams.Count > 0) { for (int i = 0; i < delayedDatagrams.Count; i++) { DelayedDatagram delayedDatagram = delayedDatagrams[i]; delayedDatagram.EllapsedSecondsRemainingToDelay -= dt; if (delayedDatagram.EllapsedSecondsRemainingToDelay <= 0.0f) { bool success = TrySendDatagram(delayedDatagram.Datagram, true); if (!success) { return; } delayedDatagrams.RemoveAt(i); i--; } } } }