Update latency detection method

Plus some server code refactoring
This commit is contained in:
Sardelka
2022-07-29 20:35:39 +08:00
parent 9b1587f334
commit c8bbdc69d0
9 changed files with 249 additions and 266 deletions

View File

@ -1,6 +1,7 @@
using Lidgren.Network;
using RageCoop.Core;
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
@ -48,7 +49,6 @@ namespace RageCoop.Client
AutoFlushSendQueue = false
};
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
@ -66,6 +66,7 @@ namespace RageCoop.Client
throw new Exception("Malformed URL");
}
PlayerList.Cleanup();
EntityPool.AddPlayer();
Task.Run(() =>
{
@ -112,7 +113,6 @@ namespace RageCoop.Client
get { return Client?.ConnectionStatus == NetConnectionStatus.Connected; }
}
#region -- GET --
#region -- PLAYER --
private static void PlayerConnect(Packets.PlayerConnect packet)
{
@ -137,6 +137,7 @@ namespace RageCoop.Client
}
#endregion // -- PLAYER --
#region -- GET --
private static bool GetServerPublicKey(string address, int timeout = 10000)
{
@ -146,8 +147,8 @@ namespace RageCoop.Client
Client.SendUnconnectedMessage(msg, adds[0], int.Parse(adds[1]));
return _publicKeyReceived.WaitOne(timeout);
}
#endregion
internal static void GetResponse<T>(Packet request, Action<T> callback, ConnectionChannel channel = ConnectionChannel.RequestResponse) where T : Packet, new()
public static void GetResponse<T>(Packet request, Action<T> callback, ConnectionChannel channel = ConnectionChannel.RequestResponse) where T : Packet, new()
{
var received = new AutoResetEvent(false);
var id = NewRequestID();
@ -163,6 +164,8 @@ namespace RageCoop.Client
request.Pack(msg);
Client.SendMessage(msg, NetDeliveryMethod.ReliableOrdered, (int)channel);
}
#endregion
private static int NewRequestID()
{
int ID = 0;

View File

@ -54,7 +54,6 @@ namespace RageCoop.Client
{
CoopMenu.ConnectedMenuSetting();
Main.MainChat.Init();
PlayerList.Cleanup();
GTA.UI.Notification.Show("~g~Connected!");
});
@ -64,9 +63,6 @@ namespace RageCoop.Client
Memory.RestorePatches();
DownloadManager.Cleanup();
// Reset all values
Latency = 0;
Main.QueueAction(() => Main.CleanUpWorld());
if (Main.MainChat.Focused)
@ -148,9 +144,6 @@ namespace RageCoop.Client
}
break;
}
case NetIncomingMessageType.ConnectionLatencyUpdated:
Latency = message.ReadFloat();
break;
case NetIncomingMessageType.UnconnectedData:
{
var packetType = (PacketType)message.ReadByte();

View File

@ -45,21 +45,21 @@ namespace RageCoop.Client
_lastUpdate = Util.GetTickCount64();
_mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
_mainScaleform.CallFunction("SET_DATA_SLOT", 0, $"{Networking.Latency * 1000:N0}ms", localUsername, 116, 0, 0, "", "", 2, "", "", ' ');
int i = 1;
int i=0;
foreach (var player in Players)
{
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Value.Latency * 1000:N0}ms", player.Value.Username, 116, 0, i - 1, "", "", 2, "", "", ' ');
}
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count+1} players");
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players");
_mainScaleform.CallFunction("DISPLAY_VIEW");
}
public static void SetPlayer(int id, string username, float latency = 0)
{
if(id == Main.LocalPlayerID) { Networking.Latency=latency; }
Main.Logger.Debug($"{id},{username},{latency}");
PlayerData p;
if (Players.TryGetValue(id, out p))
{

View File

@ -350,7 +350,7 @@ namespace RageCoop.Client
void DisplayVehicle()
{
var current = MainVehicle.ReadPosition();
var predicted = Position+Velocity*(SyncParameters.PositioinPredictionDefault+0.001f*LastSyncedStopWatch.ElapsedMilliseconds);
var predicted = Position+Velocity*(Networking.Latency+0.001f*LastSyncedStopWatch.ElapsedMilliseconds);
if (current.DistanceTo(Position)<5)
{
MainVehicle.Velocity = Velocity+5*(predicted - current);

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class ChatMessage : Packet
{
private Func<string, byte[]> crypt;
private Func<byte[], byte[]> decrypt;
public ChatMessage(Func<string, byte[]> crypter)
{
crypt = crypter;
}
public ChatMessage(Func<byte[], byte[]> decrypter)
{
decrypt = decrypter;
}
public string Username { get; set; }
public string Message { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.ChatMessage);
List<byte> byteArray = new List<byte>();
// Write Username
byteArray.AddString(Username);
// Write Message
byteArray.AddArray(crypt(Message));
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read username
int usernameLength = reader.ReadInt();
Username = reader.ReadString(usernameLength);
Message = decrypt(reader.ReadByteArray()).GetString();
#endregion
}
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class PingPong : Packet
{
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.ChatMessage);
message.Write(0);
#endregion
}
public override void Unpack(byte[] array)
{
}
}
}
}

View File

@ -73,7 +73,8 @@ namespace RageCoop.Core
File = 8,
Event = 9,
RequestResponse=10,
VehicleSync=20,
PingPong = 11,
VehicleSync =20,
PedSync=21,
ProjectileSync = 22,
SyncEvents =30,
@ -150,62 +151,6 @@ namespace RageCoop.Core
public abstract void Unpack(byte[] array);
}
internal partial class Packets
{
internal class ChatMessage : Packet
{
private Func<string, byte[]> crypt;
private Func<byte[], byte[]> decrypt;
public ChatMessage(Func<string,byte[]> crypter)
{
crypt = crypter;
}
public ChatMessage(Func<byte[], byte[]> decrypter)
{
decrypt = decrypter;
}
public string Username { get; set; }
public string Message { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.ChatMessage);
List<byte> byteArray = new List<byte>();
// Write Username
byteArray.AddString(Username);
// Write Message
byteArray.AddArray(crypt(Message));
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read username
int usernameLength = reader.ReadInt();
Username = reader.ReadString(usernameLength);
Message = decrypt(reader.ReadByteArray()).GetString();
#endregion
}
}
}
internal static class CoopSerializer
{

View File

@ -2,8 +2,7 @@
using System.Collections.Generic;
using RageCoop.Core;
using Lidgren.Network;
using System.Linq;
using GTA;
using System.Diagnostics;
using RageCoop.Core.Scripting;
using System.Security.Cryptography;
using RageCoop.Server.Scripting;
@ -60,6 +59,7 @@ namespace RageCoop.Server
}
private bool _displayNameTag=true;
private Stopwatch _latencyWatch = new Stopwatch();
/// <summary>
/// Gets or sets whether to enable automatic respawn for this client's main ped.
@ -73,40 +73,21 @@ namespace RageCoop.Server
_displayNameTag=value;
}
}
#region CUSTOMDATA FUNCTIONS
/*
public void SetData<T>(string name, T data)
internal void UpdateLatency()
{
if (HasData(name))
_latencyWatch.Restart();
Server.GetResponse<Packets.PingPong>(this, new Packets.PingPong(), ConnectionChannel.PingPong);
_latencyWatch.Stop();
Latency = (float)_latencyWatch.ElapsedMilliseconds/2000;
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
new Packets.PlayerInfoUpdate()
{
_customData[name] = data;
}
else
{
_customData.Add(name, data);
}
PedID=Player.ID,
Username=Username,
Latency=Latency,
}.Pack(outgoingMessage);
Server.MainNetServer.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableSequenced, (byte)ConnectionChannel.Default);
}
public bool HasData(string name)
{
return _customData.ContainsKey(name);
}
public T GetData<T>(string name)
{
return HasData(name) ? (T)_customData[name] : default;
}
public void RemoveData(string name)
{
if (HasData(name))
{
_customData.Remove(name);
}
}
*/
#endregion
#region FUNCTIONS
/// <summary>
/// Kick this client

View File

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Net;
using System.Linq;
@ -51,10 +52,10 @@ namespace RageCoop.Server
internal Resources Resources;
internal Logger Logger;
private Security Security;
private System.Timers.Timer _sendInfoTimer = new System.Timers.Timer(5000);
private bool _stopping = false;
private Thread _listenerThread;
private Thread _announceThread;
private Thread _latencyThread;
private Worker _worker;
private Dictionary<int,Action<PacketType,byte[]>> PendingResponses=new();
internal Dictionary<PacketType, Func<byte[],Client,Packet>> RequestHandlers=new();
@ -76,7 +77,125 @@ namespace RageCoop.Server
Security=new Security(Logger);
Entities=new ServerEntities(this);
BaseScript=new BaseScript(this);
_worker=new Worker("ServerWorker", Logger);
_listenerThread=new Thread(() => Listen());
_latencyThread=new Thread(() =>
{
while (!_stopping)
{
foreach(var c in Clients.Values)
{
c.UpdateLatency();
}
Thread.Sleep(3000);
}
});
_announceThread=new Thread(async () =>
{
try
{
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
HttpClient httpClient = new();
IpInfo info;
try
{
HttpResponseMessage response = await httpClient.GetAsync("https://ipinfo.io/json");
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]");
}
string content = await response.Content.ReadAsStringAsync();
info = JsonConvert.DeserializeObject<IpInfo>(content);
Logger?.Info($"Your public IP is {info.Address}, announcing to master server...");
}
catch (Exception ex)
{
Logger?.Error(ex.InnerException?.Message ?? ex.Message);
return;
}
while (!_stopping)
{
string msg =
"{ " +
"\"address\": \"" + info.Address + "\", " +
"\"port\": \"" + Settings.Port + "\", " +
"\"country\": \"" + info.Country + "\", " +
"\"name\": \"" + Settings.Name + "\", " +
"\"version\": \"" + _compatibleVersion.Replace("_", ".") + "\", " +
"\"players\": \"" + MainNetServer.ConnectionsCount + "\", " +
"\"maxPlayers\": \"" + Settings.MaxPlayers + "\", " +
"\"description\": \"" + Settings.Description + "\", " +
"\"website\": \"" + Settings.Website + "\", " +
"\"gameMode\": \"" + Settings.GameMode + "\", " +
"\"language\": \"" + Settings.Language + "\"" +
" }";
HttpResponseMessage response = null;
try
{
var realUrl = Util.GetFinalRedirect(Settings.MasterServer);
response = await httpClient.PostAsync(realUrl, new StringContent(msg, Encoding.UTF8, "application/json"));
}
catch (Exception ex)
{
Logger?.Error($"MasterServer: {ex.Message}");
// Sleep for 5s
Thread.Sleep(5000);
continue;
}
if (response == null)
{
Logger?.Error("MasterServer: Something went wrong!");
}
else if (response.StatusCode != HttpStatusCode.OK)
{
if (response.StatusCode == HttpStatusCode.BadRequest)
{
string requestContent = await response.Content.ReadAsStringAsync();
Logger?.Error($"MasterServer: [{(int)response.StatusCode}], {requestContent}");
}
else
{
Logger?.Error($"MasterServer: [{(int)response.StatusCode}]");
Logger?.Error($"MasterServer: [{await response.Content.ReadAsStringAsync()}]");
}
}
// Sleep for 10s
for (int i = 0; i<10; i++)
{
if (_stopping)
{
break;
}
else
{
Thread.Sleep(1000);
}
}
}
}
catch (HttpRequestException ex)
{
Logger?.Error($"MasterServer: {ex.InnerException.Message}");
}
catch (Exception ex)
{
Logger?.Error($"MasterServer: {ex.Message}");
}
});
}
/// <summary>
/// Spawn threads and start the server
/// </summary>
@ -98,129 +217,22 @@ namespace RageCoop.Server
};
config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
MainNetServer = new NetServer(config);
MainNetServer.Start();
_worker=new Worker("ServerWorker",Logger);
_sendInfoTimer.Elapsed+=(s, e) => { SendPlayerInfos(); };
_sendInfoTimer.AutoReset=true;
_sendInfoTimer.Enabled=true;
Logger?.Info(string.Format("Server listening on {0}:{1}", config.LocalAddress.ToString(), config.Port));
if (Settings.AnnounceSelf)
{
#region -- MASTERSERVER --
_announceThread=new Thread(async () =>
{
try
{
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 ;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
HttpClient httpClient = new();
IpInfo info;
try
{
HttpResponseMessage response = await httpClient.GetAsync("https://ipinfo.io/json");
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]");
}
string content = await response.Content.ReadAsStringAsync();
info = JsonConvert.DeserializeObject<IpInfo>(content);
Logger?.Info($"Your public IP is {info.Address}, announcing to master server...");
}
catch (Exception ex)
{
Logger?.Error(ex.InnerException?.Message ?? ex.Message);
return;
}
while (!_stopping)
{
string msg =
"{ " +
"\"address\": \"" + info.Address + "\", " +
"\"port\": \"" + Settings.Port + "\", " +
"\"country\": \"" + info.Country + "\", " +
"\"name\": \"" + Settings.Name + "\", " +
"\"version\": \"" + _compatibleVersion.Replace("_", ".") + "\", " +
"\"players\": \"" + MainNetServer.ConnectionsCount + "\", " +
"\"maxPlayers\": \"" + Settings.MaxPlayers + "\", " +
"\"description\": \"" + Settings.Description + "\", " +
"\"website\": \"" + Settings.Website + "\", " +
"\"gameMode\": \"" + Settings.GameMode + "\", " +
"\"language\": \"" + Settings.Language + "\"" +
" }";
HttpResponseMessage response = null;
try
{
var realUrl = Util.GetFinalRedirect(Settings.MasterServer);
response = await httpClient.PostAsync(realUrl, new StringContent(msg, Encoding.UTF8, "application/json"));
}
catch (Exception ex)
{
Logger?.Error($"MasterServer: {ex.Message}");
// Sleep for 5s
Thread.Sleep(5000);
continue;
}
if (response == null)
{
Logger?.Error("MasterServer: Something went wrong!");
}
else if (response.StatusCode != HttpStatusCode.OK)
{
if (response.StatusCode == HttpStatusCode.BadRequest)
{
string requestContent = await response.Content.ReadAsStringAsync();
Logger?.Error($"MasterServer: [{(int)response.StatusCode}], {requestContent}");
}
else
{
Logger?.Error($"MasterServer: [{(int)response.StatusCode}]");
Logger?.Error($"MasterServer: [{await response.Content.ReadAsStringAsync()}]");
}
}
// Sleep for 10s
for(int i = 0; i<10; i++)
{
if (_stopping)
{
break;
}
else
{
Thread.Sleep(1000);
}
}
}
}
catch (HttpRequestException ex)
{
Logger?.Error($"MasterServer: {ex.InnerException.Message}");
}
catch (Exception ex)
{
Logger?.Error($"MasterServer: {ex.Message}");
}
});
_announceThread.Start();
#endregion
}
BaseScript.API=API;
BaseScript.OnStart();
Resources.LoadAll();
_listenerThread=new Thread(() => Listen());
_listenerThread.Start();
_latencyThread.Start();
if (Settings.AnnounceSelf)
{
_announceThread.Start();
}
Logger?.Info("Listening for clients");
}
/// <summary>
@ -229,12 +241,10 @@ namespace RageCoop.Server
public void Stop()
{
_stopping = true;
_sendInfoTimer.Stop();
_sendInfoTimer.Enabled=false;
_sendInfoTimer.Dispose();
Logger?.Flush();
_listenerThread?.Join();
_announceThread?.Join();
_listenerThread.Join();
_announceThread.Join();
_latencyThread.Join();
_worker.Dispose();
}
private void Listen()
@ -386,19 +396,6 @@ namespace RageCoop.Server
}
break;
}
case NetIncomingMessageType.ConnectionLatencyUpdated:
{
// Get sender client
if (!Clients.TryGetValue(message.SenderConnection.RemoteUniqueIdentifier, out sender))
{
break;
}
if (sender != null)
{
sender.Latency = message.ReadFloat();
}
}
break;
case NetIncomingMessageType.ErrorMessage:
Logger?.Error(message.ReadString());
break;
@ -441,7 +438,6 @@ namespace RageCoop.Server
switch (type)
{
#region INTERVAL-SYNC
case PacketType.PedSync:
{
@ -471,9 +467,6 @@ namespace RageCoop.Server
}
break;
#endregion
case PacketType.ChatMessage:
{
@ -504,28 +497,6 @@ namespace RageCoop.Server
DisconnectAndLog(sender.Connection, type, e);
}
}
object _sendPlayersLock=new object();
internal void SendPlayerInfos()
{
lock (_sendPlayersLock)
{
foreach (Client c in Clients.Values)
{
MainNetServer.Connections.FindAll(x => x.RemoteUniqueIdentifier != c.NetID).ForEach(x =>
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.PlayerInfoUpdate()
{
PedID=c.Player.ID,
Username=c.Username,
Latency=c.Latency,
}.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.ReliableSequenced, (byte)ConnectionChannel.Default);
});
}
}
}
private void DisconnectAndLog(NetConnection senderConnection,PacketType type, Exception e)
{