private void WriteOptionalHeaders(NetworkBundle bundle, ServerPacket packet) { PacketHeader packetHeader = packet.Header; if (bundle.SendAck) // 0x4000 { packetHeader.Flags |= PacketHeaderFlags.AckSequence; log.DebugFormat("[{0}] Outgoing AckSeq: {1}", session.LoggingIdentifier, lastReceivedPacketSequence); packet.BodyWriter.Write(lastReceivedPacketSequence); } if (bundle.TimeSync) // 0x1000000 { packetHeader.Flags |= PacketHeaderFlags.TimeSynch; log.DebugFormat("[{0}] Outgoing TimeSync TS: {1}", session.LoggingIdentifier, ConnectionData.ServerTime); packet.BodyWriter.Write(ConnectionData.ServerTime); } if (bundle.ClientTime != -1f) // 0x4000000 { packetHeader.Flags |= PacketHeaderFlags.EchoResponse; log.DebugFormat("[{0}] Outgoing EchoResponse: {1}", session.LoggingIdentifier, bundle.ClientTime); packet.BodyWriter.Write(bundle.ClientTime); packet.BodyWriter.Write((float)ConnectionData.ServerTime - bundle.ClientTime); } }
/// <summary> /// It is assumed that this will only be called from a single thread. WorldManager.UpdateWorld()->Session.Update(lastTick)->This /// </summary> public void Update(double lastTick) { ConnectionData.ServerTime += lastTick; lock (currentBundleLock) { if (sendResync && !currentBundle.TimeSync && DateTime.UtcNow > nextResync) { currentBundle.TimeSync = true; currentBundle.EncryptedChecksum = true; nextResync = DateTime.UtcNow.AddMilliseconds(timeBetweenTimeSync); } if (sendAck && !currentBundle.SendAck && DateTime.UtcNow > nextAck) { currentBundle.SendAck = true; nextAck = DateTime.UtcNow.AddMilliseconds(timeBetweenAck); } if (currentBundle.NeedsSending && DateTime.UtcNow > nextSend) { // Flush the bundle here var bundleToSend = currentBundle; currentBundle = new NetworkBundle(); SendBundle(bundleToSend); nextSend = DateTime.UtcNow.AddMilliseconds(minimumTimeBetweenBundles); } } FlushPackets(); }
public NetworkSession(Session session) { this.session = session; ConnectionData = new SessionConnectionData(); currentBundle = new NetworkBundle(); nextSend = DateTime.UtcNow; nextResync = DateTime.UtcNow; nextAck = DateTime.UtcNow; }
private void FlushBundle() { if (currentBundle.NeedsSending) { var bundleToSend = currentBundle; currentBundle = new NetworkBundle(); SendBundle(bundleToSend); nextSend = DateTime.UtcNow.AddMilliseconds(minimumTimeBetweenBundles); } }
/// <summary> /// Force flushing of packet buffers. Only to be used in special circumstances, such as during login sequence. /// </summary> public void Flush() { if (currentBundle.NeedsSending) { var bundleToSend = currentBundle; currentBundle = new NetworkBundle(); SendBundle(bundleToSend); } FlushPackets(); }
// It is assumed that this will only be called from a single thread.WorldManager.UpdateWorld()->Session.Update(lastTick)->This /// <summary> /// Checks if we should send the current bundle and then flushes all pending packets. /// </summary> /// <param name="lastTick">Amount of time that has passed for the last cycle.</param> public void Update(double lastTick) { ConnectionData.ServerTime += lastTick; NetworkBundle bundleToSend = null; lock (currentBundleLock) { if (sendResync && !currentBundle.TimeSync && DateTime.UtcNow > nextResync) { log.DebugFormat("[{0}] Setting to send TimeSync packet", session.LoggingIdentifier); currentBundle.TimeSync = true; currentBundle.EncryptedChecksum = true; nextResync = DateTime.UtcNow.AddMilliseconds(timeBetweenTimeSync); } if (sendAck && !currentBundle.SendAck && DateTime.UtcNow > nextAck) { log.DebugFormat("[{0}] Setting to send ACK packet", session.LoggingIdentifier); currentBundle.SendAck = true; nextAck = DateTime.UtcNow.AddMilliseconds(timeBetweenAck); } if (currentBundle.NeedsSending && DateTime.UtcNow > nextSend) { log.DebugFormat("[{0}] Swaping bundle", session.LoggingIdentifier); // Swap out bundle so we can process it bundleToSend = currentBundle; currentBundle = new NetworkBundle(); } } // Send our bundle if we have one // We should be able to execute this outside the lock as Sending is single threaded // and all future writes from other threads will go to the new bundle if (bundleToSend != null) { SendBundle(bundleToSend); nextSend = DateTime.UtcNow.AddMilliseconds(minimumTimeBetweenBundles); } FlushPackets(); }
/// <summary> /// This function handles turning a bundle of messages (representing all messages accrued in a timeslice), /// into 1 or more packets, combining multiple messages into one packet or spliting large message across /// several packets as needed. /// /// 1 Create Packet /// 2 If first packet for this bundle, fill any optional headers /// 3 Append messages that will fit /// 4 If we have more messages, create additional packets. /// 5 If any packet is greater than the max packet size, split it across two fragments /// </summary> /// <param name="bundle"></param> private void SendBundle(NetworkBundle bundle) { bool firstPacket = true; MessageFragment carryOverMessage = null; while (firstPacket || carryOverMessage != null || bundle.Messages.Count > 0) { ServerPacket packet = new ServerPacket(); PacketHeader packetHeader = packet.Header; if (bundle.EncryptedChecksum) { packetHeader.Flags |= PacketHeaderFlags.EncryptedChecksum; } uint availableSpace = Packet.MaxPacketDataSize; if (firstPacket) { firstPacket = false; if (bundle.SendAck) //0x4000 { packetHeader.Flags |= PacketHeaderFlags.AckSequence; packet.BodyWriter.Write(lastReceivedSequence); } if (bundle.TimeSync) //0x1000000 { packetHeader.Flags |= PacketHeaderFlags.TimeSynch; packet.BodyWriter.Write(ConnectionData.ServerTime); } if (bundle.ClientTime != -1f) //0x4000000 { packetHeader.Flags |= PacketHeaderFlags.EchoResponse; packet.BodyWriter.Write(bundle.ClientTime); packet.BodyWriter.Write((float)ConnectionData.ServerTime - bundle.ClientTime); } availableSpace -= (uint)packet.Data.Length; } if (carryOverMessage != null || bundle.Messages.Count > 0) { packetHeader.Flags |= PacketHeaderFlags.BlobFragments; int fragmentCount = 0; while (carryOverMessage != null || bundle.Messages.Count > 0) { MessageFragment currentMessageFragment; if (carryOverMessage != null) // If we have a carryOverMessage, use that { currentMessageFragment = carryOverMessage; carryOverMessage = null; } else // If we don't have a carryOverMessage, go ahead and dequeue next message from the bundle { currentMessageFragment = new MessageFragment(bundle.Messages.Dequeue()); } var currentGameMessage = currentMessageFragment.message; availableSpace -= PacketFragmentHeader.HeaderSize; // Account for fragment header // Compute amount of data to send based on the total length and current position uint dataToSend = (uint)currentGameMessage.Data.Length - currentMessageFragment.position; if (dataToSend > availableSpace) // Message is too large to fit in packet { carryOverMessage = currentMessageFragment; if (fragmentCount == 0) // If this is first message in packet, this is just a really large message, so proceed with splitting it { dataToSend = availableSpace; } else // Otherwise there are other messages already, so we'll break and come back and see if the message will fit { break; } } if (currentMessageFragment.count == 0) // Compute number of fragments if we have not already { uint remainingData = (uint)currentGameMessage.Data.Length - dataToSend; currentMessageFragment.count = (ushort)(Math.Ceiling((double)remainingData / PacketFragment.MaxFragmentDataSize) + 1); } // Set sequence, if new, pull next sequence from ConnectionData, if it is a carryOver, reuse that sequence currentMessageFragment.sequence = currentMessageFragment.sequence == 0 ? ConnectionData.FragmentSequence++ : currentMessageFragment.sequence; // Build ServerPacketFragment structure ServerPacketFragment fragment = new ServerPacketFragment(); PacketFragmentHeader fragmentHeader = fragment.Header; fragmentHeader.Sequence = currentMessageFragment.sequence; fragmentHeader.Id = 0x80000000; fragmentHeader.Count = currentMessageFragment.count; fragmentHeader.Index = currentMessageFragment.index; fragmentHeader.Group = (ushort)currentMessageFragment.message.Group; // Read data starting at current position reading dataToSend bytes currentGameMessage.Data.Seek(currentMessageFragment.position, SeekOrigin.Begin); fragment.Content = new byte[dataToSend]; currentGameMessage.Data.Read(fragment.Content, 0, (int)dataToSend); // Increment position and index currentMessageFragment.position = currentMessageFragment.position + dataToSend; currentMessageFragment.index++; // Add fragment to packet packet.AddFragment(fragment); fragmentCount++; // Deduct consumed space availableSpace -= dataToSend; // Smallest message I am aware of requires HeaderSize + 4 bytes, so if we have less space then that, go ahead and break if (availableSpace <= PacketFragmentHeader.HeaderSize + 4) { break; } } } PacketQueue.Enqueue(packet); } }
/// <summary> /// This function handles turning a bundle of messages (representing all messages accrued in a timeslice), /// into 1 or more packets, combining multiple messages into one packet or spliting large message across /// several packets as needed. /// /// 1 Create Packet /// 2 If first packet for this bundle, fill any optional headers /// 3 Append fragments that will fit /// 3.1 Check each fragment to see if it is a partial fit /// 4 If we have a partial message remaining or more messages, create additional packets. /// </summary> /// <param name="bundle"></param> private void SendBundle(NetworkBundle bundle) { bool firstPacket = true; MessageFragment carryOverMessage = null; while (firstPacket || carryOverMessage != null || bundle.Messages.Count > 0) { ServerPacket packet = new ServerPacket(); PacketHeader packetHeader = packet.Header; if (bundle.encryptedChecksum) { packetHeader.Flags |= PacketHeaderFlags.EncryptedChecksum; } uint availableSpace = Packet.MaxPacketDataSize; if (firstPacket) { firstPacket = false; if (bundle.SendAck) //0x4000 { packetHeader.Flags |= PacketHeaderFlags.AckSequence; packet.BodyWriter.Write(lastReceivedSequence); } if (bundle.TimeSync) //0x1000000 { packetHeader.Flags |= PacketHeaderFlags.TimeSynch; packet.BodyWriter.Write(ConnectionData.ServerTime); } if (bundle.ClientTime != -1f) //0x4000000 { packetHeader.Flags |= PacketHeaderFlags.EchoResponse; packet.BodyWriter.Write(bundle.ClientTime); packet.BodyWriter.Write((float)ConnectionData.ServerTime - bundle.ClientTime); } availableSpace -= (uint)packet.Data.Length; } if (carryOverMessage != null || bundle.Messages.Count > 0) { packetHeader.Flags |= PacketHeaderFlags.BlobFragments; while (carryOverMessage != null || bundle.Messages.Count > 0) { if (availableSpace <= PacketFragmentHeader.HeaderSize + 4) { break; } MessageFragment currentMessageFragment; if (carryOverMessage != null) { currentMessageFragment = carryOverMessage; carryOverMessage = null; currentMessageFragment.index++; } else { currentMessageFragment = new MessageFragment(bundle.Messages.Dequeue()); } var currentGameMessage = currentMessageFragment.message; ServerPacketFragment fragment = new ServerPacketFragment(); PacketFragmentHeader fragmentHeader = fragment.Header; availableSpace -= PacketFragmentHeader.HeaderSize; currentMessageFragment.sequence = currentMessageFragment.sequence == 0 ? ConnectionData.FragmentSequence++ : currentMessageFragment.sequence; uint dataToSend = (uint)currentGameMessage.Data.Length - currentMessageFragment.position; if (dataToSend > availableSpace) { dataToSend = availableSpace; carryOverMessage = currentMessageFragment; } if (currentMessageFragment.count == 0) { uint remainingData = (uint)currentGameMessage.Data.Length - dataToSend; currentMessageFragment.count = (ushort)(Math.Ceiling((double)remainingData / PacketFragment.MaxFragmentDataSize) + 1); } fragmentHeader.Sequence = currentMessageFragment.sequence; fragmentHeader.Id = 0x80000000; fragmentHeader.Count = currentMessageFragment.count; fragmentHeader.Index = currentMessageFragment.index; fragmentHeader.Group = (ushort)currentMessageFragment.message.Group; currentGameMessage.Data.Seek(currentMessageFragment.position, SeekOrigin.Begin); fragment.Content = new byte[dataToSend]; currentGameMessage.Data.Read(fragment.Content, 0, (int)dataToSend); currentMessageFragment.position = currentMessageFragment.position + dataToSend; availableSpace -= dataToSend; packet.AddFragment(fragment); //break; } } PacketQueue.Enqueue(packet); } }
/// <summary> /// This function handles turning a bundle of messages (representing all messages accrued in a timeslice), /// into 1 or more packets, combining multiple messages into one packet or spliting large message across /// several packets as needed. /// </summary> /// <param name="bundle"></param> private void SendBundle(NetworkBundle bundle) { log.DebugFormat("[{0}] Sending Bundle", session.LoggingIdentifier); bool writeOptionalHeaders = true; List <MessageFragment> fragments = new List <MessageFragment>(); // Pull all messages out and create MessageFragment objects while (bundle.HasMoreMessages) { var message = bundle.Dequeue(); var fragment = new MessageFragment(message, ConnectionData.FragmentSequence++); fragments.Add(fragment); } log.DebugFormat("[{0}] Bundle Fragment Count: {1}", session.LoggingIdentifier, fragments.Count); // Loop through while we have fragements while (fragments.Count > 0 || writeOptionalHeaders) { ServerPacket packet = new ServerPacket(); PacketHeader packetHeader = packet.Header; if (fragments.Count > 0) { packetHeader.Flags |= PacketHeaderFlags.BlobFragments; } if (bundle.EncryptedChecksum) { packetHeader.Flags |= PacketHeaderFlags.EncryptedChecksum; } uint availableSpace = Packet.MaxPacketDataSize; // Pull first message and see if it is a large one var firstMessage = fragments.FirstOrDefault(); if (firstMessage != null) { // If a large message send only this one, filling the whole packet if (firstMessage.DataRemaining >= availableSpace) { log.DebugFormat("[{0}] Sending large fragment", session.LoggingIdentifier); ServerPacketFragment spf = firstMessage.GetNextFragment(); packet.Fragments.Add(spf); availableSpace -= (uint)spf.Length; if (firstMessage.DataRemaining <= 0) { fragments.Remove(firstMessage); } } // Otherwise we'll write any optional headers and process any small messages that will fit else { if (writeOptionalHeaders) { writeOptionalHeaders = false; WriteOptionalHeaders(bundle, packet); availableSpace -= (uint)packet.Data.Length; } // Create a list to remove completed messages after iterator List <MessageFragment> removeList = new List <MessageFragment>(); foreach (MessageFragment fragment in fragments) { // Is this a large fragment and does it have a tail that needs sending? if (!fragment.TailSent && availableSpace >= fragment.TailSize) { log.DebugFormat("[{0}] Sending tail fragment", session.LoggingIdentifier); ServerPacketFragment spf = fragment.GetTailFragment(); packet.Fragments.Add(spf); availableSpace -= (uint)spf.Length; } // Otherwise will this message fit in the remaining space? else if (availableSpace >= fragment.NextSize) { log.DebugFormat("[{0}] Sending small message", session.LoggingIdentifier); ServerPacketFragment spf = fragment.GetNextFragment(); packet.Fragments.Add(spf); availableSpace -= (uint)spf.Length; } // If message is out of data, set to remove it if (fragment.DataRemaining <= 0) { removeList.Add(fragment); } } // Remove all completed messages fragments.RemoveAll(x => removeList.Contains(x)); } } // If no messages, write optional headers else { log.DebugFormat("[{0}] No messages, just sending optional headers", session.LoggingIdentifier); if (writeOptionalHeaders) { writeOptionalHeaders = false; WriteOptionalHeaders(bundle, packet); availableSpace -= (uint)packet.Data.Length; } } EnqueueSend(packet); } }