forked from powerfun/udpservice
560 lines
19 KiB
C#
560 lines
19 KiB
C#
using Prism.Mvvm;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.Text;
|
||
using OnlineUserPool.Hander;
|
||
using OnlineUserPool.Model;
|
||
using OnlineUserPool.Unility;
|
||
using Serilog;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Windows.Threading;
|
||
using System.Diagnostics;
|
||
using OnlineUserPool.Services;
|
||
using Newtonsoft.Json;
|
||
using System.Collections.Concurrent;
|
||
using System.Windows;
|
||
using OnlineUserPool.Api;
|
||
|
||
namespace OnlineUserPool.ViewModels
|
||
{
|
||
public class MainWindowViewModel : BindableBase
|
||
{
|
||
public ObservableCollection<HostModel> Clients { get; private set; } = new ObservableCollection<HostModel>();
|
||
private static ConcurrentBag<MsgModel> receiveMes = new ConcurrentBag<MsgModel>();
|
||
private static CustomList<RoomModel> RoomList = new CustomList<RoomModel>();
|
||
private static int RoomMaxId = 0;
|
||
private static int Ticks = 0;
|
||
private static object locker = new object();
|
||
public static System.Timers.Timer timer;
|
||
private static IHandle mapRecordRankingHander;
|
||
private Dispatcher dispatcher;
|
||
public ObservableCollection<MsgModel> Customers { get; private set; } = new ObservableCollection<MsgModel>();
|
||
private string _Title = "";
|
||
public string Title
|
||
{
|
||
get { return _Title; }
|
||
set
|
||
{
|
||
SetProperty(ref _Title, value);
|
||
}
|
||
}
|
||
|
||
private string _SendDataSize = "";
|
||
public string SendDataSize
|
||
{
|
||
get { return _SendDataSize; }
|
||
set
|
||
{
|
||
SetProperty(ref _SendDataSize, value);
|
||
}
|
||
}
|
||
private string _VirtualData = "";
|
||
public string VirtualData
|
||
{
|
||
get { return _VirtualData; }
|
||
set
|
||
{
|
||
SetProperty(ref _VirtualData, value);
|
||
}
|
||
}
|
||
|
||
private bool _closeVirtualUser = false;
|
||
public bool CloseVirtualUser
|
||
{
|
||
get { return _closeVirtualUser; }
|
||
set
|
||
{
|
||
SetProperty(ref _closeVirtualUser, value);
|
||
}
|
||
}
|
||
|
||
private const int ROOM_TIME_OUT = 60;
|
||
private const int TOTAL_PROCESS = 100;
|
||
|
||
public MainWindowViewModel()
|
||
{
|
||
//初测程序关闭事件
|
||
Application.Current.MainWindow.Closing += MainWindow_Closing;
|
||
Title = $"{ IPAddress.Any }:{ ConfigHelp.UdpPort }";
|
||
dispatcher = Dispatcher.CurrentDispatcher;
|
||
WriteLine(DateTime.Now.ToShortDateString());
|
||
LogHelper.Init();
|
||
mapRecordRankingHander = new MapRecordRankingHander();//
|
||
//mapRecordRankingHander = new MultiUserHandle();
|
||
Log.Information($"初始化连接,当前地址:{ IPAddress.Any }:{ConfigHelp.UdpPort}");
|
||
new TcpService().RunServer(ReceivedData, ClientDisconnected);
|
||
var _udpService = new UdpService();
|
||
_udpService.RunServer(ReceivedData);
|
||
|
||
Log.Information("服务启动成功");
|
||
timer = new System.Timers.Timer(500);
|
||
timer.Elapsed += Timer_Elapsed;
|
||
timer.AutoReset = true;
|
||
timer.Start();
|
||
Log.Information("等待连接");
|
||
ReleaseMemory();
|
||
}
|
||
|
||
private void ReleaseMemory()
|
||
{
|
||
var timers = new System.Timers.Timer(3333);
|
||
timers.Elapsed += (s, e) =>
|
||
{
|
||
GC.Collect();
|
||
};
|
||
timers.AutoReset = true;
|
||
timers.Start();
|
||
}
|
||
|
||
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||
{
|
||
|
||
}
|
||
|
||
private void ReceivedData(IPEndPoint remoteIpEndPoint, ReceiveModel msg, IService service)
|
||
{
|
||
dispatcher.Invoke(() =>
|
||
{
|
||
if (!Clients.Any(c => c.Equals(remoteIpEndPoint)))
|
||
{
|
||
if (msg is SetClientCommand client)
|
||
{
|
||
Clients.Add(new HostModel
|
||
{
|
||
IPEndPoint = remoteIpEndPoint,
|
||
LastActiveTime = DateTime.Now,
|
||
Service = service,
|
||
MemberId = client.MemberId,
|
||
Encoding = client.Encoding,
|
||
V = client.V
|
||
});
|
||
}
|
||
}
|
||
|
||
switch (msg.CommandType)
|
||
{
|
||
//ping命令
|
||
case 0:
|
||
{
|
||
var client = Clients.FirstOrDefault(n => n.Equals(remoteIpEndPoint));
|
||
if(client == null) return;
|
||
client.LastActiveTime = DateTime.Now;
|
||
if (msg.V > 0)
|
||
{
|
||
client.V = msg.V;
|
||
}
|
||
}
|
||
break;
|
||
//消息
|
||
case 1:
|
||
{
|
||
var client = Clients.FirstOrDefault(n => n.Equals(remoteIpEndPoint));
|
||
if(client == null) return;
|
||
var msg1 = (msg as MsgModel);
|
||
client.Competitionid = msg1.CompetitionId;
|
||
client.Model = msg1.Model;
|
||
|
||
if (client.Model == "")
|
||
{
|
||
client.RoomId = msg1.RoomId;
|
||
}
|
||
|
||
if (msg1.V > 0)
|
||
{
|
||
client.V = msg1.V;
|
||
}
|
||
|
||
receiveMes.Add(msg1);
|
||
HandleGameRoomSaved(msg1);
|
||
}
|
||
break;
|
||
//设置客户端
|
||
case 2:
|
||
{
|
||
var client = Clients.FirstOrDefault(n => n.Equals(remoteIpEndPoint));
|
||
var msg1 = (msg as SetClientCommand);
|
||
client.Encoding = msg1.Encoding;
|
||
client.Client = msg1.Client;
|
||
client.V = msg1.V;
|
||
client.MemberId = msg1.MemberId;
|
||
client.Competitionid = msg1.Competitionid;
|
||
client.IsWatch = msg1.IsWatch;
|
||
}
|
||
break;
|
||
|
||
}
|
||
});
|
||
}
|
||
|
||
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||
{
|
||
Ticks++;
|
||
NotifyClient();
|
||
}
|
||
|
||
private void HandleGameRoomSaved(MsgModel msg1)
|
||
{
|
||
//对战房间内的人保存处理
|
||
if (msg1.RoomId > 0 && msg1.Saved)
|
||
{
|
||
var current = RoomList.ToList().FirstOrDefault(c => c.RoomId == msg1.RoomId);
|
||
if (current != null)
|
||
{
|
||
//更新房间内的人的保存状态
|
||
var currentPlayer = current.List.FirstOrDefault(c => c.UserId == msg1.MemberId);
|
||
if (currentPlayer != null)
|
||
{
|
||
currentPlayer.Saved = true;
|
||
}
|
||
var notAllSaved = current.List.Any(c => !c.Saved);
|
||
if (!notAllSaved && current.Status != 2)
|
||
{
|
||
current.Status = 2;
|
||
WebService.UpdateGameRoom(current.RoomId, current.Status);
|
||
}
|
||
if (!current.Saved)
|
||
{
|
||
notAllSaved = current.List.Any(c => !c.Saved);
|
||
if (!notAllSaved && current.Status != 2)
|
||
{
|
||
current.Status = 2;
|
||
}
|
||
current.Saved = true;
|
||
WebService.AddGameRoom(current.RoomId, current.Status, current.UserId, current.Name, current.MapRouteId, current.Password, current.StartTime.Value, current.CloseTime, current.MaxMembers);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void NotifyClient()
|
||
{
|
||
try
|
||
{
|
||
if (!receiveMes.Any())
|
||
{
|
||
dispatcher.Invoke(() =>
|
||
{
|
||
//移除5钟内连接不上的客户端
|
||
Clients.ToList().ForEach(item =>
|
||
{
|
||
if (item.Expire)
|
||
{
|
||
RemoveClient(item);
|
||
}
|
||
});
|
||
});
|
||
return;
|
||
}
|
||
|
||
lock (locker)
|
||
{
|
||
dispatcher.Invoke(() =>
|
||
{
|
||
var msgs = receiveMes.ToList();
|
||
//加入虚拟人物消息
|
||
if (!CloseVirtualUser)
|
||
{
|
||
var virtualData = mapRecordRankingHander.GetVirtualUserData();
|
||
VirtualData = $"{virtualData.Count}";
|
||
msgs.AddRange(virtualData);
|
||
}
|
||
//屏蔽房间模式的用户
|
||
var c = Clients.Where(c => string.IsNullOrEmpty(c.Model)).ToList();
|
||
SendMessage(c, msgs);
|
||
RemoveClientAdvanced(msgs);
|
||
AddCustomers(msgs);
|
||
receiveMes.Clear();
|
||
msgs.Clear();
|
||
msgs = null;
|
||
});
|
||
|
||
//更新虚拟人物信息
|
||
mapRecordRankingHander.RemoveEndAndAddNewVirtualUser(Clients.Count);
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Log.Error($"NotifyClient:{ e.Message }\r\n{ e.StackTrace }");
|
||
}
|
||
}
|
||
|
||
private void RemoveClientAdvanced(List<MsgModel> msgs)
|
||
{
|
||
//移除下线的客户端
|
||
for (int i = 0; i < msgs.Count; i++)
|
||
{
|
||
if (msgs[i].Exit && msgs[i].MemberId > 0)//客户端退出,并且不是虚拟的人物
|
||
{
|
||
//这个地方有严重的逻辑错误(虚拟的人物不能和真实的人用同一个名字)
|
||
var info = Clients.FirstOrDefault(n => n.MemberId == msgs[i].MemberId);
|
||
if (info != null)
|
||
{
|
||
RemoveClient(info);
|
||
}
|
||
}
|
||
}
|
||
|
||
//移除5钟内连接不上的客户端
|
||
Clients.ToList().ForEach(item =>
|
||
{
|
||
if (item.Expire)
|
||
{
|
||
RemoveClient(item);
|
||
}
|
||
});
|
||
}
|
||
|
||
private void AddCustomers(List<MsgModel> msgs)
|
||
{
|
||
foreach (var item in msgs)
|
||
{
|
||
var client = Clients.FirstOrDefault(c => c.MemberId == item.MemberId);
|
||
if (client == null)
|
||
{
|
||
Customers.Remove(item);
|
||
continue;
|
||
}
|
||
|
||
var customer = Customers.FirstOrDefault(c => c.MemberId == item.MemberId);
|
||
if (customer != null)
|
||
{
|
||
customer.Update(item);
|
||
}
|
||
else
|
||
{
|
||
Customers.Add(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
private StringBuilder sb = new StringBuilder();
|
||
private void SendMessage(IList<HostModel> clients, List<MsgModel> list)
|
||
{
|
||
if (!clients.Any() || !list.Any())
|
||
{
|
||
return;
|
||
}
|
||
|
||
var clients1 = clients.ToList();
|
||
foreach (var item in list)
|
||
{
|
||
item.PreDistance = Math.Round(item.PreDistance, 5);
|
||
item.EndDistance = Math.Round(item.EndDistance, 5);
|
||
item.WeightKg = Math.Round(item.WeightKg, 2);
|
||
}
|
||
|
||
#region 发送数据
|
||
foreach (var item in clients1)
|
||
{
|
||
try
|
||
{
|
||
if (item == null) continue;
|
||
if (item.V != 1 && item.V != 2)
|
||
{
|
||
var jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(list.Select(m => new {
|
||
m.RouteId,
|
||
m.MemberId,
|
||
m.Point,
|
||
m.IsCompleted,
|
||
exit = m.Exit,
|
||
m.Speed,
|
||
m.PreDistance,
|
||
m.EndDistance,
|
||
//m.IsVirtual,//后面要把这个字段过滤掉
|
||
m.WeightKg,
|
||
Competitionid = m.CompetitionId,
|
||
m.Saved,
|
||
m.Frame,
|
||
}));
|
||
var data = Encoding.ASCII.GetBytes(jsonString);
|
||
item.Service.Send(data, data.Length, item.IPEndPoint);
|
||
data = null;
|
||
continue;
|
||
}
|
||
|
||
if(item.Service is TcpService)
|
||
{
|
||
NetworkData networkData = null;
|
||
if (item.V == 2)
|
||
{
|
||
networkData = HandleGzipNetworkData(list,item);
|
||
}
|
||
|
||
bool isZip = item.Encoding == "gzip";
|
||
item.Service.Send(networkData.GetBytes(isZip), networkData.GetBytes(isZip).Length, item.IPEndPoint);
|
||
networkData = null;
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.WriteLine(e.Message);
|
||
Log.Error($"{ item.IPEndPoint.ToString() }:{ e.Message }\r\n{ e.StackTrace }");
|
||
}
|
||
}
|
||
|
||
clients1 = null;
|
||
|
||
#endregion
|
||
}
|
||
|
||
private NetworkData HandleGzipNetworkData(List<MsgModel> list, HostModel item)
|
||
{
|
||
sb.Clear();
|
||
sb.Append("l{");
|
||
var runtimeList = list.Where(m => m.CompetitionId == item.Competitionid).Select(m => m.ToString()).ToList();
|
||
foreach (var obj in runtimeList)
|
||
{
|
||
sb.Append(obj);
|
||
sb.Append("|");
|
||
}
|
||
if (runtimeList.Any())
|
||
{
|
||
sb.Remove(sb.Length - 1, 1);
|
||
}
|
||
|
||
sb.Append("};w{");
|
||
|
||
var watchList = Clients.Where(c => c.IsWatch && c.Competitionid == item.Competitionid).Select(c => c.MemberId).ToList();
|
||
foreach (var obj in watchList)
|
||
{
|
||
sb.Append(obj);
|
||
sb.Append("|");
|
||
}
|
||
if (watchList.Any())
|
||
{
|
||
sb.Remove(sb.Length - 1, 1);
|
||
}
|
||
|
||
sb.Append("};e{");
|
||
|
||
var errorList = list.Where(c => c.RoomId > 0 || c.Frame > 0).ToList();
|
||
foreach (var c in errorList)
|
||
{
|
||
sb.Append($"{c.MemberId},{c.RoomId},{c.Frame},{c.TotalTicks}");
|
||
sb.Append("|");
|
||
}
|
||
|
||
if (errorList.Any())
|
||
{
|
||
sb.Remove(sb.Length - 1, 1);
|
||
}
|
||
sb.Append("};");
|
||
|
||
var strV2 = sb.ToString();
|
||
var data2 = new NetworkData(strV2);
|
||
strV2 = null;
|
||
return data2;
|
||
}
|
||
|
||
void WriteLine(string str)
|
||
{
|
||
//Debug.WriteLine(str);
|
||
}
|
||
|
||
T CloneJson<T>(T source)
|
||
{
|
||
// Don't serialize a null object, simply return the default for that object
|
||
if (Object.ReferenceEquals(source, null))
|
||
{
|
||
return default(T);
|
||
}
|
||
|
||
// initialize inner objects individually
|
||
// for example in default constructor some list property initialized with some values,
|
||
// but in 'source' these items are cleaned -
|
||
// without ObjectCreationHandling.Replace default constructor values will be added to result
|
||
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
|
||
|
||
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
|
||
}
|
||
|
||
private void ClientDisconnected(EndPoint point)
|
||
{
|
||
dispatcher.Invoke(() =>
|
||
{
|
||
try
|
||
{
|
||
var client = Clients.FirstOrDefault(f => f.IPEndPoint.ToString() == point.ToString());
|
||
if (client != null)
|
||
{
|
||
RemoveClient(client);
|
||
}
|
||
}
|
||
catch(Exception ex)
|
||
{
|
||
Log.Information("在ClientDisconnected触发以后,删除client时报错了,"+ ex.Message +", " + ex.StackTrace);
|
||
}
|
||
});
|
||
}
|
||
|
||
private void RemoveClient(HostModel client)
|
||
{
|
||
BeforeRemoved(client);
|
||
Clients.Remove(client);
|
||
Customers.Clear();
|
||
}
|
||
|
||
private void BeforeRemoved(HostModel client)
|
||
{
|
||
if(client.RoomId == 0)return;
|
||
var room = RoomList.ToList().FirstOrDefault(c => c.RoomId == client.RoomId);
|
||
if(room == null)return;
|
||
|
||
var needRemove = room.List.FirstOrDefault(c => c.UserId == client.MemberId && c.Saved == false);
|
||
if(needRemove == null)return;
|
||
|
||
//从房间中移除,并考虑是否需要删除房间和移交房主
|
||
room.List.Remove(needRemove);
|
||
if (room.List.Count == 0)
|
||
{
|
||
RoomList.Remove(room);
|
||
}
|
||
else
|
||
{
|
||
var host = room.List.FirstOrDefault();
|
||
if (host != null)
|
||
{
|
||
host.IsOwner = true;
|
||
}
|
||
}
|
||
|
||
//更新房间状态
|
||
var isComplete = room.List.All(c => c.Saved != false);
|
||
if (room.Status > 0)
|
||
{
|
||
room.Status = isComplete ? 2 : 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
public class NetworkData
|
||
{
|
||
private string _txt = "";
|
||
public NetworkData(string txt)
|
||
{
|
||
this._txt = txt;
|
||
}
|
||
|
||
public byte[] GetBytes(bool compress = false)
|
||
{
|
||
if (compress)
|
||
{
|
||
return GetCompressBytes();
|
||
}
|
||
return Encoding.UTF8.GetBytes(this._txt);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
//this._txt = null;
|
||
}
|
||
|
||
private byte[] GetCompressBytes()
|
||
{
|
||
return Encoding.UTF8.GetBytes($"*{Convert.ToBase64String(CommonHelper.Compress(this._txt))}#");
|
||
}
|
||
}
|
||
}
|