/// <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); } }
/// <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.HasMoreMessages) { 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(lastReceivedPacketSequence); } if (bundle.TimeSync) // 0x1000000 { packetHeader.Flags |= PacketHeaderFlags.TimeSynch; log.DebugFormat("[{0}] Outgoing TimeSync TS: {1}", session.Account, ConnectionData.ServerTime); 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.HasMoreMessages) { packetHeader.Flags |= PacketHeaderFlags.BlobFragments; int fragmentCount = 0; while (carryOverMessage != null || bundle.HasMoreMessages) { MessageFragment currentMessageFragment = null; 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.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; // Read data starting at current position reading dataToSend bytes currentGameMessage.Data.Seek(currentMessageFragment.Position, SeekOrigin.Begin); byte[] data = new byte[dataToSend]; currentGameMessage.Data.Read(data, 0, (int)dataToSend); // Build ServerPacketFragment structure ServerPacketFragment fragment = new ServerPacketFragment(data); fragment.Header.Sequence = currentMessageFragment.Sequence; fragment.Header.Id = 0x80000000; fragment.Header.Count = currentMessageFragment.Count; fragment.Header.Index = currentMessageFragment.Index; fragment.Header.Group = (ushort)currentMessageFragment.Message.Group; // 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); } }