The ping/pong orders are replaced with a dedicated (and much smaller) Ping packet that is handled directly in the client and server Connection wrappers. This allows clients to respond when the orders are processed, instead of queuing the pong order to be sent in the next frame (which added an extra 120ms of unwanted latency). The ping frequency has been raised to 1Hz, and pings are now routed through the server events queue in preparation for the future dynamic latency system. The raw ping numbers are no longer sent to clients, the server instead evaluates a single ConnectionQuality value that in the future may be based on more than just the ping times.
171 lines
4.4 KiB
C#
171 lines
4.4 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
|
|
namespace OpenRA.Network
|
|
{
|
|
public class OrderPacket
|
|
{
|
|
readonly Order[] orders;
|
|
readonly MemoryStream data;
|
|
public OrderPacket(Order[] orders)
|
|
{
|
|
this.orders = orders;
|
|
data = null;
|
|
}
|
|
|
|
public OrderPacket(MemoryStream data)
|
|
{
|
|
orders = null;
|
|
this.data = data;
|
|
}
|
|
|
|
public IEnumerable<Order> GetOrders(World world)
|
|
{
|
|
return orders ?? ParseData(world);
|
|
}
|
|
|
|
IEnumerable<Order> ParseData(World world)
|
|
{
|
|
if (data == null)
|
|
yield break;
|
|
|
|
// Order deserialization depends on the current world state,
|
|
// so must be deferred until we are ready to consume them.
|
|
var reader = new BinaryReader(data);
|
|
while (data.Position < data.Length)
|
|
{
|
|
var o = Order.Deserialize(world, reader);
|
|
if (o != null)
|
|
yield return o;
|
|
}
|
|
}
|
|
|
|
public byte[] Serialize(int frame)
|
|
{
|
|
if (data != null)
|
|
return data.ToArray();
|
|
|
|
var ms = new MemoryStream();
|
|
ms.WriteArray(BitConverter.GetBytes(frame));
|
|
foreach (var o in orders)
|
|
ms.WriteArray(o.Serialize());
|
|
return ms.ToArray();
|
|
}
|
|
}
|
|
|
|
public static class OrderIO
|
|
{
|
|
static readonly OrderPacket NoOrders = new OrderPacket(Array.Empty<Order>());
|
|
|
|
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
|
{
|
|
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
|
ms.WriteArray(BitConverter.GetBytes(data.Frame));
|
|
ms.WriteByte((byte)OrderType.SyncHash);
|
|
ms.WriteArray(BitConverter.GetBytes(data.SyncHash));
|
|
ms.WriteArray(BitConverter.GetBytes(data.DefeatState));
|
|
return ms.GetBuffer();
|
|
}
|
|
|
|
public static bool TryParseDisconnect((int FromClient, byte[] Data) packet, out (int Frame, int ClientId) disconnect)
|
|
{
|
|
// Valid Disconnect packets are only ever generated by the server
|
|
if (packet.FromClient != 0 || packet.Data.Length != Order.DisconnectOrderLength + 4 || packet.Data[4] != (byte)OrderType.Disconnect)
|
|
{
|
|
disconnect = (0, 0);
|
|
return false;
|
|
}
|
|
|
|
var frame = BitConverter.ToInt32(packet.Data, 0);
|
|
var clientId = BitConverter.ToInt32(packet.Data, 5);
|
|
disconnect = (frame, clientId);
|
|
return true;
|
|
}
|
|
|
|
public static bool TryParseSync(byte[] packet, out (int Frame, int SyncHash, ulong DefeatState) data)
|
|
{
|
|
if (packet.Length != 4 + Order.SyncHashOrderLength || packet[4] != (byte)OrderType.SyncHash)
|
|
{
|
|
data = (0, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
var frame = BitConverter.ToInt32(packet, 0);
|
|
var syncHash = BitConverter.ToInt32(packet, 5);
|
|
var defeatState = BitConverter.ToUInt64(packet, 9);
|
|
data = (frame, syncHash, defeatState);
|
|
return true;
|
|
}
|
|
|
|
public static bool TryParsePing(int fromClient, byte[] packet, out byte[] ping)
|
|
{
|
|
// Valid Ping packets are only ever generated by the server
|
|
if (fromClient != 0 || packet.Length != 13 || packet[4] != (byte)OrderType.Ping)
|
|
{
|
|
ping = null;
|
|
return false;
|
|
}
|
|
|
|
// Valid Ping packets always have frame 0
|
|
var frame = BitConverter.ToInt32(packet, 0);
|
|
if (frame != 0)
|
|
{
|
|
ping = null;
|
|
return false;
|
|
}
|
|
|
|
ping = packet;
|
|
return true;
|
|
}
|
|
|
|
public static bool TryParseAck((int FromClient, byte[] Data) packet, out int frame)
|
|
{
|
|
// Ack packets are only accepted from the server
|
|
if (packet.FromClient != 0 || packet.Data.Length != 5 || packet.Data[4] != (byte)OrderType.Ack)
|
|
{
|
|
frame = 0;
|
|
return false;
|
|
}
|
|
|
|
frame = BitConverter.ToInt32(packet.Data, 0);
|
|
return true;
|
|
}
|
|
|
|
public static bool TryParseOrderPacket(byte[] packet, out (int Frame, OrderPacket Orders) data)
|
|
{
|
|
// Not a valid packet
|
|
if (packet.Length < 4)
|
|
{
|
|
data = (0, null);
|
|
return false;
|
|
}
|
|
|
|
// Wrong packet type
|
|
if (packet.Length >= 5 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash))
|
|
{
|
|
data = (0, null);
|
|
return false;
|
|
}
|
|
|
|
var frame = BitConverter.ToInt32(packet, 0);
|
|
|
|
// PERF: Skip empty order frames, often per client each frame
|
|
var orders = packet.Length > 4 ? new OrderPacket(new MemoryStream(packet, 4, packet.Length - 4)) : NoOrders;
|
|
data = (frame, orders);
|
|
return true;
|
|
}
|
|
}
|
|
}
|