using Mirle.Component.Record; using Mirle.Component.SocketDirver.FrameBuilder; using NLog; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Mirle.Component.SocketDirver { /// /// Class for Socket wrapper /// public class SocketWrapper : IDisposable { /// /// Constructure /// /// Log public SocketWrapper(Logger log) { _log = log; _ = Task.Factory.StartNew(async delegate { while (true) { await StartupSocketProcess(); await Task.Delay(TimeSpan.FromMilliseconds(1.0)); } }, TaskCreationOptions.LongRunning); _ = Task.Factory.StartNew(async delegate { while (true) { await StartupMessageProcess(); await Task.Delay(TimeSpan.FromMilliseconds(1.0)); } }, TaskCreationOptions.LongRunning); } /// /// Constructure /// /// Log /// Interface for message frame builder public SocketWrapper(Logger log, IFrameBuilder frameBuilder) { _log = log; _frameBuilder = frameBuilder; _ = Task.Factory.StartNew(async delegate { while (true) { await StartupSocketProcess(); await Task.Delay(TimeSpan.FromMilliseconds(1.0)); } }, TaskCreationOptions.LongRunning); _ = Task.Factory.StartNew(async delegate { while (true) { await StartupMessageProcess(); await Task.Delay(TimeSpan.FromMilliseconds(1.0)); } }, TaskCreationOptions.LongRunning); } #region === [Private Properties] === private readonly Logger _log; private readonly IFrameBuilder _frameBuilder; private ConnectState _connectState = ConnectState.Stop; private Socket _tcpServer; private Socket _tcpClient; private readonly List _tcpClients = new List(); private IPAddress _address; private int _port; private readonly SemaphoreSlim _sendMessageLock = new SemaphoreSlim(1); private readonly Stopwatch _socketFailedStopWatch = Stopwatch.StartNew(); private TimeSpan _socketFailedTime = TimeSpan.Zero; // For connect mode = server private readonly List> _receivedQueueList = new List>(); private readonly List> _onReceivedSubjectList = new List>(); // For connect mode = client private readonly ConcurrentQueue _receivedQueue = new ConcurrentQueue(); private readonly Subject _onReceivedSubject = new Subject(); #endregion /// /// Socket connect mode /// public ConnectMode ConnectMode { get; set; } /// /// TCP client list /// public List TCPClients => _tcpClients; /// /// TCP client count threshold /// public int ClientCount { get; set; } /// /// Socket server/client address /// public string Address { get { return _address.ToString(); } set { _address = IPAddress.Parse(value); } } /// /// Socket server/client port /// public int Port { get { return _port; } set { if (value > 1024 && value <= 65535) { _port = value; return; } throw new ArgumentOutOfRangeException($"Port is out of range: {value}"); } } /// /// Is socket server/client connected /// public bool IsConnected => _connectState == ConnectState.Connected; /// /// Time out of keep connected /// public TimeSpan KeepConnectedTimeout { get; set; } = TimeSpan.Zero; /// /// Subject event of receive /// public IObservable OnReceived => _onReceivedSubject.AsObservable(); /// /// Subject event of receive /// /// Index of client receive subject list /// public IObservable OnReceivedList(int index) => _onReceivedSubjectList[index].AsObservable(); /// /// Open socket connect /// /// Completed task successfully public Task OpenAsync() { switch (ConnectMode) { case ConnectMode.Server: { if (_tcpServer != null) { return Task.CompletedTask; } break; } case ConnectMode.Client: { if (_tcpClient != null) { return Task.CompletedTask; } break; } default: { throw new ArgumentException($"Unknown socket connect mode by {ConnectMode}"); } } if (ConnectMode == ConnectMode.Server) { for (int i = 0; i < ClientCount; i++) { ConcurrentQueue received_queue_list = new ConcurrentQueue(); _receivedQueueList.Add(received_queue_list); Subject on_received_subject = new Subject(); _onReceivedSubjectList.Add(on_received_subject); } } SetConnectState(ConnectState.Initial); return Task.CompletedTask; } /// /// Close socket connect /// /// Completed task successfully public Task CloseAsync() { SetConnectState(ConnectState.Stop); return Task.CompletedTask; } #region === [Send Message] === /// /// Send message /// /// Message payload /// Remote end point of address public void Send(byte[] payload, EndPoint endPoint = null) { try { _sendMessageLock.Wait(); InspectClientAndPayload(out Socket client, payload, endPoint); _ = _frameBuilder == null ? client.Send(new ArraySegment(payload), SocketFlags.None) : client.Send(new ArraySegment(_frameBuilder.EncodeFrame(payload)), SocketFlags.None); ResetSocketFailedTime(); } catch { SetSocketFailedTime(); throw; } finally { _sendMessageLock.Release(); } } /// /// Send message /// /// Message payload /// Client remote end point address /// Completed task successfully public async Task SendAsync(byte[] payload, EndPoint endPoint = null) { CancellationTokenSource cancellationToken_source = new CancellationTokenSource(); CancellationToken cts = cancellationToken_source.Token; try { await _sendMessageLock.WaitAsync(); InspectClientAndPayload(out Socket client, payload, endPoint); _ = _frameBuilder == null ? await client.SendAsync(new ArraySegment(payload), SocketFlags.None, cts) : await client.SendAsync(new ArraySegment(_frameBuilder.EncodeFrame(payload)), SocketFlags.None, cts); ResetSocketFailedTime(); } catch { SetSocketFailedTime(); cancellationToken_source.Cancel(); throw; } finally { cancellationToken_source.Dispose(); _sendMessageLock.Release(); } } private void InspectClientAndPayload(out Socket client, byte[] payload, EndPoint endPoint = null) { client = endPoint == null ? _tcpClient : _tcpClients.Where(x => x.RemoteEndPoint == endPoint).Select(x => x).FirstOrDefault() ?? throw new ArgumentNullException($"Can't found socket client by {endPoint} or {_tcpClient.LocalEndPoint}"); if (payload == null) { throw new ArgumentNullException($"Payload is null: {payload}"); } if (!IsConnected) { throw new ArgumentException($"Socket client is not client by {client.LocalEndPoint}"); } if (!client.Connected) { throw new SocketException(10004); } } #endregion #region === [Socket Failed Time Process] === private void ResetSocketFailedTime() { _socketFailedTime = TimeSpan.Zero; } private void SetSocketFailedTime() { TimeSpan elapsed = _socketFailedStopWatch.Elapsed; if (KeepConnectedTimeout == TimeSpan.Zero) { SetConnectState(ConnectState.Initial); } else if (_socketFailedTime == TimeSpan.Zero) { _socketFailedTime = elapsed; } else { CheckSocketFailedTime(); } } private void CheckSocketFailedTime() { if (_socketFailedTime != TimeSpan.Zero && _socketFailedStopWatch.Elapsed - _socketFailedTime > KeepConnectedTimeout) { SetConnectState(ConnectState.Initial); } } #endregion private void SetConnectState(ConnectState connectState) { _connectState = connectState; } private async Task StartupSocketProcess() { try { await ConnectionStatus(); } catch { SetConnectState(ConnectState.Initial); await Task.Delay(2000); } } private async Task ConnectionStatus() { switch (_connectState) { case ConnectState.Initial: { ProcessConnectStateByInitial(); break; } case ConnectState.WaitForConnecting: { await ProcessConnectStateByWatiForConnecting(); break; } case ConnectState.Connected: { await ProcessConnectStateByConnected(); break; } case ConnectState.Stop: { ProcessConnectStateByStop(); break; } } } #region === [Process Socket Connect State By Initial] === private void ProcessConnectStateByInitial() { try { Initial(); ResetSocketFailedTime(); SetConnectState(ConnectState.WaitForConnecting); } catch (Exception ex) { _log.LogError(MethodBase.GetCurrentMethod().Name, ex); SetConnectState(ConnectState.Initial); } } private void Initial() { switch (ConnectMode) { case ConnectMode.Client: { while (_receivedQueue.TryDequeue(out byte[] result)) { _log.Warn($"Clear buffer after TCP client disconnect: {Encoding.ASCII.GetString(result)}"); } _tcpClient?.Dispose(); _tcpClient = null; _tcpServer?.Dispose(); _tcpServer = null; break; } case ConnectMode.Server: { _tcpClients.ForEach(x => x.Dispose()); _tcpClients.Clear(); foreach (ConcurrentQueue message_queue in _receivedQueueList) { while (_receivedQueue.TryDequeue(out byte[] result)) { _log.Warn($"Clear buffer after TCP client disconnect: {Encoding.ASCII.GetString(result)}"); } } break; } default: { throw new ArgumentException($"Unknown socket connect mode by {ConnectMode}"); } } } #endregion #region === [Process Socket Connect State By Wait For Connecting] === /// /// Process socket connect state by wait for connecting /// /// Completed task successfully private async Task ProcessConnectStateByWatiForConnecting() { IPEndPoint end_point = new IPEndPoint(_address, _port); switch (ConnectMode) { case ConnectMode.Server: { _tcpServer = new Socket(end_point.AddressFamily, SocketType.Stream, ProtocolType.Tcp); _tcpServer.SetIPProtectionLevel(IPProtectionLevel.EdgeRestricted); _tcpServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.IpTimeToLive, false); _tcpServer.Bind(end_point); _tcpServer.Listen(ClientCount); while (_tcpClients.Count != ClientCount) { try { Socket client = await _tcpServer.AcceptAsync(); SetSocketOptions(client); _tcpClients.Add(client); } catch { SetConnectState(ConnectState.Initial); } } SetConnectState(ConnectState.Connected); break; } case ConnectMode.Client: { _tcpClient = new Socket(end_point.AddressFamily, SocketType.Stream, ProtocolType.Tcp); SetSocketOptions(_tcpClient); await _tcpClient.ConnectAsync(end_point); if (_tcpClient.Connected) { SetConnectState(ConnectState.Connected); } else { SetConnectState(ConnectState.Initial); } break; } } } private static void SetSocketOptions(Socket socket) { socket.ReceiveBufferSize = 4096; socket.SendBufferSize = 4096; socket.ReceiveTimeout = (int)TimeSpan.Zero.TotalMilliseconds; socket.SendTimeout = (int)TimeSpan.Zero.TotalMilliseconds; socket.NoDelay = true; socket.LingerState = new LingerOption(true, 0); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.IpTimeToLive, true); } #endregion #region === [Process Socket Connect State By Connected] === /// /// Process socket connect state by connected /// /// Completed task successfully private async Task ProcessConnectStateByConnected() { if (ConnectMode == ConnectMode.Client) { await ReadyReadFromOneClient(); } else { await ReadyReadFromMultipleClient(); } } private async Task ReadyReadFromOneClient() { try { await ReadyRead(_tcpClient, _receivedQueue); } catch (Exception ex) { _log.Error(ex.ToString()); } finally { CheckSocketFailedTime(); } } private async Task ReadyReadFromMultipleClient() { try { List task_list = new List(); for (int i = 0; i < _tcpClients.Count; i++) { Task result = Task.Run(() => ReadyRead(_tcpClients[i], _receivedQueueList[i])); await Task.Delay(100); task_list.Add(result); } Task.WaitAll(task_list.ToArray()); } catch (Exception ex) { _log.LogError(MethodBase.GetCurrentMethod().Name, ex); } finally { CheckSocketFailedTime(); } } private async Task ReadyRead(Socket client, ConcurrentQueue queue) { List buffer = new List(); int decodeFaileCount = 0; while (true) { int available = client.Available; if (available > 0) { byte[] array = new byte[available]; if (client.Receive(array) != array.Length) { _log.Warn("Receive buffer length error"); } buffer.AddRange(array); } while (buffer.Any()) { if (_frameBuilder != null) { if (_frameBuilder.TryDecodeFrame(buffer.ToArray(), out int receiveCount, out byte[] payload)) { decodeFaileCount = 0; buffer.RemoveRange(0, receiveCount); queue.Enqueue(payload); } else { await Task.Delay(100); decodeFaileCount++; _log.Warn($"Decode frame failed times: {decodeFaileCount}"); break; } } else { byte[] result = buffer.ToArray(); buffer.RemoveRange(0, result.Count()); queue.Enqueue(result); } } if (client.Poll(10, SelectMode.SelectRead) && client.Available == 0) { SetConnectState(ConnectState.Initial); } if (_connectState != ConnectState.Connected) { break; } SpinWait.SpinUntil(() => client.Available > 0, TimeSpan.FromSeconds(1.0)); } } #endregion #region === [Process Connect State By Stop] === /// /// Process socket connect state by stop /// private void ProcessConnectStateByStop() { Initial(); ResetSocketFailedTime(); Task.Delay(1000); } #endregion private async Task StartupMessageProcess() { try { switch (ConnectMode) { case ConnectMode.Client: { HandleReceivedMessages(); break; } case ConnectMode.Server: { if (_tcpClients.Count == ClientCount) { List task_list = new List(); for (int i = 0; i < ClientCount; i++) { Task result = Task.Run(() => HandleReceivedMessages(_receivedQueueList[i], _onReceivedSubjectList[i])); await Task.Delay(100); task_list.Add(result); } Task.WaitAll(task_list.ToArray()); } break; } default: { throw new ArgumentException($"Unknown socket connect mode by {ConnectMode}"); } } } catch (Exception ex) { _log.LogError(MethodBase.GetCurrentMethod().Name, ex); } if (_connectState == ConnectState.Stop) { await Task.Delay(100); } } #region === [Received to message] private void HandleReceivedMessages() { while (_receivedQueue.TryDequeue(out byte[] result)) { _onReceivedSubject.OnNext(result); } } private void HandleReceivedMessages(ConcurrentQueue messageQueue, Subject receivedSubject) { while (true) { while (messageQueue.TryDequeue(out byte[] result)) { receivedSubject.OnNext(result); } Thread.Sleep(1); if (_connectState != ConnectState.Connected) { break; } } } #endregion #region === [Dispose] === private bool disposedValue; /// protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { Initial(); } disposedValue = true; } } /// ~SocketWrapper() { // 請勿變更此程式碼。請將清除程式碼放入 'Dispose(bool disposing)' 方法 Dispose(disposing: false); } /// public void Dispose() { // 請勿變更此程式碼。請將清除程式碼放入 'Dispose(bool disposing)' 方法 Dispose(disposing: true); GC.SuppressFinalize(this); } #endregion } /// /// Connect mode /// public enum ConnectMode { /// /// Server /// Server, /// /// Client /// Client } /// /// 連線狀態 /// public enum ConnectState { /// /// Initial /// Initial, /// /// Wait for connecting /// WaitForConnecting, /// /// Connected /// Connected, /// /// Stop /// Stop } }