using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Tracing; using System.Timers; using Cysharp.Threading.Tasks; using kcp2k; using Timer = System.Timers.Timer; using System.Threading.Tasks; using System.IO; using System.Linq; using System.Net; using System.Numerics; using System.Reflection; using System.Text; using System.Threading; using BITKit.Net.Examples; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Unity.Mathematics; namespace BITKit.Net { public class KcpNetClient:INetClient,INetProvider { private readonly NetProviderCommon _common = new(); private readonly ILogger _logger; public KcpNetClient(ILogger logger) { _logger = logger; _client = new KcpClient( OnConnectedInternal, OnData, OnDisconnectInternal, OnError, KCPNet.Config ); _timer.Elapsed += Tick; _logger.LogInformation("已创建KCP客户端"); _isConnected.AddListener(ConnectionCallback); } public event Action OnStartConnect; public event Action OnConnected; public event Action OnDisconnected; public event Action OnConnectedFailed; public uint TickRate { get; set; } = 8; public bool IsConnected => _isConnected; public bool IsConnecting { get; private set; } public bool AutoReconnect { get; set; } = true; public float2 Traffic { get; private set; } public bool ManualTick { get; set; } private readonly IntervalUpdate _reconnectInterval = new(1); public int Ping { get; private set; } public int Id { get; private set; } = -1; private readonly KcpClient _client; private readonly ConcurrentQueue _commandQueue = new(); private DateTime _lastPingTime = DateTime.Now; private readonly Timer _timer = new(100) { AutoReset = true }; private readonly ValidHandle _isConnected = new(); private bool _userConnected; private int _index = int.MinValue; private DateTime _now = DateTime.Now; private TimeSpan _interval = TimeSpan.FromMilliseconds(100); private string _connectedAddress = "127.0.0.1"; private ushort _connectedPort = 27014; private async void ConnectionCallback(bool x) { await BITApp.SwitchToMainThread(); if (x) { OnConnected?.Invoke(); _logger.LogInformation("连接成功"); } else { OnDisconnected?.Invoke(); _logger.LogInformation("连接已断开"); } } private void Tick(object sender, ElapsedEventArgs e) { if (!ManualTick) Tick(); } public async void Disconnect() { _userConnected = false; DisconnectInternal(); } private async void DisconnectInternal() { IsConnecting = false; _client.Disconnect(); _isConnected.RemoveElement(this); _timer.Stop(); try { await BITApp.SwitchToMainThread(); OnDisconnected?.Invoke(); } catch (OperationCanceledException){} } private string _lastHostName; public async UniTask Connect(string address = "127.0.0.1", ushort port = 27014) { if (IsConnecting) { _logger.LogWarning("正在连接中"); return false; } _userConnected = true; //如果address是域名,解析为Ip if (address.Contains(".")) { var ip = await Dns.GetHostAddressesAsync(address); if (ip.Length > 0) { address = ip[0].ToString(); if (_lastHostName != address) { _logger.LogInformation($"解析域名:{address},IP:{ip}"); _lastHostName = address; } } } if (address is not "127.0.0.1") _connectedAddress = address; if (port is not 27014) _connectedPort = port; IsConnecting = true; if (_client.connected) return false; await BITApp.SwitchToMainThread(); OnStartConnect?.Invoke(); await UniTask.SwitchToThreadPool(); try { _common.LastHeartbeat.GetOrCreate(0); _common.LastHeartbeat[0] = DateTime.Now; _client.Connect(address, port); _timer.Start(); _interval = TimeSpan.FromMilliseconds(_timer.Interval); if (_client.connected) { HandShake(); _client.Send(new[] { (byte)NetCommandType.Heartbeat }, KcpChannel.Reliable); Traffic += new float2(1, 0); } _client.Tick(); await Task.Delay(100); if (_client.connected) { SendServerMessage(Environment.MachineName); IsConnecting = false; _connectedAddress = address; _connectedPort = port; return _client.connected; } OnConnectedFailed?.Invoke(); DisconnectInternal(); IsConnecting = false; return false; } catch (Exception e) { _logger.LogCritical(e,e.Message); if (BITApp.SynchronizationContext is not null) await UniTask.SwitchToSynchronizationContext(BITApp.SynchronizationContext); OnConnectedFailed?.Invoke(); _timer.Stop(); IsConnecting = false; return false; } } private void OnData(ArraySegment bytes, KcpChannel channel) { try { Traffic+=new float2(0,bytes.Count); OnDataInternal(bytes, channel); } catch (Exception e) { _logger.LogCritical(e,e.Message); } } private void OnDataInternal(ArraySegment bytes, KcpChannel channel) { try { _common.OnDataInternal(0,bytes,channel); } catch (Exception e) { _logger.LogCritical(e,e.Message); } } private async void OnConnectedInternal() { } private async void OnDisconnectInternal() { DisconnectInternal(); } private void OnError(ErrorCode errorCode, string message) { _logger.LogInformation($"{_client.remoteEndPoint}异常:{errorCode},{message}"); } public void SendServerMessage(string message) { var bytes = BinaryBuilder .Create() .Write(((byte)NetCommandType.Message)) .Write(message) .Build(); Traffic+=new float2(bytes.Length,0); _client.Send(bytes, KcpChannel.Reliable); } public void Tick() { _now = DateTime.Now; if(_userConnected is false)return; try { if (_client.connected) { if (DateTime.Now - _common.LastHeartbeat.GetOrCreate(0) > TimeSpan.FromSeconds(5)) { _logger.LogWarning("心跳超时,自动断开"); DisconnectInternal(); _commandQueue.Clear(); return; } while (_commandQueue.TryDequeue(out var bytes)) { Traffic += new float2(bytes.Length, 0); _client.Send(bytes, KcpChannel.Reliable); } Traffic+=new float2(1,0); _client.Send(_common.HeartBeat, KcpChannel.Unreliable); } else { if (AutoReconnect && _reconnectInterval.AllowUpdate) { if (IsConnecting is false) { Connect(_connectedAddress,_connectedPort).Forget(); } } if (_commandQueue.Count > 0) { _commandQueue.Clear(); //BIT4Log.Warning("连接已断开,清空指令队列"); } } _client.Tick(); } catch (Exception e) { _logger.LogCritical(e,e.Message); } } public void HandShake() { // send client to server Traffic+=new float2(2,0); _client.Send(new byte[]{0x01, 0x02}, KcpChannel.Reliable); } public T GetRemoteInterface() => _common.GetRemoteInterface(); public void Invoke(Memory bytes) { throw new NotImplementedException(); } public UniTask> InvokeAsync(Memory bytes) { throw new NotImplementedException(); } public void Invoke(IReadOnlyList bytes) { throw new NotImplementedException(); } public UniTask> InvokeAsync(IReadOnlyList bytes) { throw new NotImplementedException(); } } }