|
|
|
|
using MemoryPools;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using ZeroLevel.Services.Serialization;
|
|
|
|
|
|
|
|
|
|
namespace ZeroLevel.Network
|
|
|
|
|
{
|
|
|
|
|
public class SocketClient
|
|
|
|
|
: BaseSocket, ISocketClient
|
|
|
|
|
{
|
|
|
|
|
#region Private
|
|
|
|
|
|
|
|
|
|
#region Queues
|
|
|
|
|
private struct IncomingFrame
|
|
|
|
|
{
|
|
|
|
|
public FrameType type;
|
|
|
|
|
public int identity;
|
|
|
|
|
public byte[] data;
|
|
|
|
|
}
|
|
|
|
|
private struct OutcomingFrame
|
|
|
|
|
{
|
|
|
|
|
public bool is_request;
|
|
|
|
|
public int identity;
|
|
|
|
|
public byte[] data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private readonly JetValPool<OutcomingFrame> _outcomingFramesPool = new JetValPool<OutcomingFrame>();
|
|
|
|
|
private ConcurrentQueue<IncomingFrame> _incoming_queue = new ConcurrentQueue<IncomingFrame>();
|
|
|
|
|
private ConcurrentQueue<OutcomingFrame> _outcoming_queue = new ConcurrentQueue<OutcomingFrame>();
|
|
|
|
|
private ManualResetEventSlim _outcomingFrameEvent = new ManualResetEventSlim(false);
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
private Socket _clientSocket;
|
|
|
|
|
private FrameParser _parser;
|
|
|
|
|
private readonly RequestBuffer _requests = new RequestBuffer();
|
|
|
|
|
private readonly byte[] _buffer = new byte[DEFAULT_RECEIVE_BUFFER_SIZE];
|
|
|
|
|
private bool _socket_freezed = false; // используется для связи сервер-клиент, запрещает пересоздание сокета
|
|
|
|
|
private readonly object _reconnection_lock = new object();
|
|
|
|
|
private long _heartbeat_key;
|
|
|
|
|
private Thread _receiveThread;
|
|
|
|
|
private Thread _sendingThread;
|
|
|
|
|
|
|
|
|
|
#endregion Private
|
|
|
|
|
|
|
|
|
|
public IRouter Router { get; }
|
|
|
|
|
|
|
|
|
|
public SocketClient(IPEndPoint ep, IRouter router)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_clientSocket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
|
|
|
_clientSocket.Connect(ep);
|
|
|
|
|
OnConnect(this);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.SystemError(ex, $"[SocketClient.ctor] connection fault. Endpoint: {ep.Address}:{ep.Port}");
|
|
|
|
|
Broken();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Router = router;
|
|
|
|
|
Endpoint = ep;
|
|
|
|
|
_parser = new FrameParser(_parser_OnIncoming);
|
|
|
|
|
|
|
|
|
|
Working();
|
|
|
|
|
|
|
|
|
|
StartInternalThreads();
|
|
|
|
|
StartReceive();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SocketClient(Socket socket, IRouter router)
|
|
|
|
|
{
|
|
|
|
|
Router = router;
|
|
|
|
|
_clientSocket = socket;
|
|
|
|
|
Endpoint = (IPEndPoint)_clientSocket.RemoteEndPoint;
|
|
|
|
|
_parser = new FrameParser(_parser_OnIncoming);
|
|
|
|
|
_socket_freezed = true;
|
|
|
|
|
|
|
|
|
|
Working();
|
|
|
|
|
|
|
|
|
|
StartInternalThreads();
|
|
|
|
|
StartReceive();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StartInternalThreads()
|
|
|
|
|
{
|
|
|
|
|
_receiveThread = new Thread(IncomingFramesJob);
|
|
|
|
|
_receiveThread.IsBackground = true;
|
|
|
|
|
_receiveThread.Start();
|
|
|
|
|
|
|
|
|
|
_sendingThread = new Thread(OutcomingFramesJob);
|
|
|
|
|
_sendingThread.IsBackground = true;
|
|
|
|
|
_sendingThread.Start();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_heartbeat_key = Sheduller.RemindEvery(TimeSpan.FromMilliseconds(MINIMUM_HEARTBEAT_UPDATE_PERIOD_MS), Heartbeat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StartReceive()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_clientSocket.BeginReceive(_buffer, 0, DEFAULT_RECEIVE_BUFFER_SIZE, SocketFlags.None, ReceiveAsyncCallback, null);
|
|
|
|
|
}
|
|
|
|
|
catch (NullReferenceException)
|
|
|
|
|
{
|
|
|
|
|
Broken();
|
|
|
|
|
Log.SystemError("[SocketClient.TryConnect] Client : Null Reference Exception - On Connect (begin receive section)");
|
|
|
|
|
_clientSocket.Disconnect(false);
|
|
|
|
|
}
|
|
|
|
|
catch (SocketException e)
|
|
|
|
|
{
|
|
|
|
|
Broken();
|
|
|
|
|
Log.SystemError(e, "[SocketClient.TryConnect] Client : Exception - On Connect (begin receive section)");
|
|
|
|
|
_clientSocket.Disconnect(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region API
|
|
|
|
|
public event Action<ISocketClient> OnConnect = (_) => { };
|
|
|
|
|
public event Action<ISocketClient> OnDisconnect = (_) => { };
|
|
|
|
|
public IPEndPoint Endpoint { get; }
|
|
|
|
|
|
|
|
|
|
public void Request(Frame frame, Action<byte[]> callback, Action<string> fail = null!)
|
|
|
|
|
{
|
|
|
|
|
if (Status != SocketClientStatus.Working) throw new Exception($"[SocketClient.Request] Socket status: {Status}");
|
|
|
|
|
var data = NetworkPacketFactory.Reqeust(MessageSerializer.Serialize(frame), out int id);
|
|
|
|
|
_requests.RegisterForFrame(id, callback, fail);
|
|
|
|
|
Send(id, true, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Send(Frame frame)
|
|
|
|
|
{
|
|
|
|
|
if (Status != SocketClientStatus.Working) throw new Exception($"[SocketClient.Send] Socket status: {Status}");
|
|
|
|
|
var data = NetworkPacketFactory.Message(MessageSerializer.Serialize(frame));
|
|
|
|
|
Send(0, false, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Response(byte[] data, int identity)
|
|
|
|
|
{
|
|
|
|
|
if (data == null!) throw new ArgumentNullException(nameof(data));
|
|
|
|
|
if (Status != SocketClientStatus.Working) throw new Exception($"[SocketClient.Response] Socket status: {Status}");
|
|
|
|
|
Send(0, false, NetworkPacketFactory.Response(data, identity));
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Private methods
|
|
|
|
|
|
|
|
|
|
private void _parser_OnIncoming(FrameType type, int identity, byte[] data)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (type == FrameType.KeepAlive) return;
|
|
|
|
|
_incoming_queue.Enqueue(new IncomingFrame
|
|
|
|
|
{
|
|
|
|
|
data = data,
|
|
|
|
|
type = type,
|
|
|
|
|
identity = identity
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.Error(ex, $"[SocketClient._parser_OnIncoming]");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ReceiveAsyncCallback(IAsyncResult ar)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var count = _clientSocket.EndReceive(ar);
|
|
|
|
|
if (count > 0)
|
|
|
|
|
{
|
|
|
|
|
_parser.Push(_buffer, count);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// TODO or not TODO
|
|
|
|
|
Thread.Sleep(1);
|
|
|
|
|
}
|
|
|
|
|
StartReceive();
|
|
|
|
|
}
|
|
|
|
|
catch (ObjectDisposedException)
|
|
|
|
|
{
|
|
|
|
|
/// Nothing
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.SystemError(ex, $"[SocketClient.ReceiveAsyncCallback] Error read data");
|
|
|
|
|
Broken();
|
|
|
|
|
OnDisconnect(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EnsureConnection()
|
|
|
|
|
{
|
|
|
|
|
if (_socket_freezed)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
lock (_reconnection_lock)
|
|
|
|
|
{
|
|
|
|
|
if (Status == SocketClientStatus.Disposed)
|
|
|
|
|
{
|
|
|
|
|
throw new ObjectDisposedException("connection");
|
|
|
|
|
}
|
|
|
|
|
if (Status != SocketClientStatus.Working)
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("No connection");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Heartbeat()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
EnsureConnection();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.SystemError(ex, "[SocketClient.Heartbeat.EnsureConnection]");
|
|
|
|
|
Broken();
|
|
|
|
|
OnDisconnect(this);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_requests.TestForTimeouts();
|
|
|
|
|
|
|
|
|
|
Send(0, false, NetworkPacketFactory.KeepAliveMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void IncomingFramesJob()
|
|
|
|
|
{
|
|
|
|
|
IncomingFrame frame = default(IncomingFrame);
|
|
|
|
|
while (Status != SocketClientStatus.Disposed)
|
|
|
|
|
{
|
|
|
|
|
if (_incoming_queue.TryDequeue(out frame))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
switch (frame.type)
|
|
|
|
|
{
|
|
|
|
|
case FrameType.Message:
|
|
|
|
|
Router?.HandleMessage(MessageSerializer.Deserialize<Frame>(frame.data), this);
|
|
|
|
|
break;
|
|
|
|
|
case FrameType.Request:
|
|
|
|
|
{
|
|
|
|
|
Router?.HandleRequest(MessageSerializer.Deserialize<Frame>(frame.data), this, frame.identity, (id, response) =>
|
|
|
|
|
{
|
|
|
|
|
if (response != null!)
|
|
|
|
|
{
|
|
|
|
|
this.Response(response, id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case FrameType.Response:
|
|
|
|
|
{
|
|
|
|
|
_requests.Success(frame.identity, frame.data);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.SystemError(ex, "[SocketClient.IncomingFramesJob] Handle frame");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OutcomingFramesJob()
|
|
|
|
|
{
|
|
|
|
|
while (Status != SocketClientStatus.Disposed)
|
|
|
|
|
{
|
|
|
|
|
if (Status == SocketClientStatus.Working)
|
|
|
|
|
{
|
|
|
|
|
if (_outcomingFrameEvent.Wait(100))
|
|
|
|
|
{
|
|
|
|
|
_outcomingFrameEvent.Reset();
|
|
|
|
|
}
|
|
|
|
|
while (_outcoming_queue.TryDequeue(out var frame))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (frame.is_request)
|
|
|
|
|
{
|
|
|
|
|
_requests.StartSend(frame.identity);
|
|
|
|
|
}
|
|
|
|
|
_clientSocket.Send(frame.data, frame.data.Length, SocketFlags.None);
|
|
|
|
|
//var sended = _clientSocket.Send(frame.data, frame.data.Length, SocketFlags.None);
|
|
|
|
|
//return sended == frame.data.Length;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.SystemError(ex, $"[SocketClient.OutcomingFramesJob] _str_clientSocketeam.Send");
|
|
|
|
|
Broken();
|
|
|
|
|
OnDisconnect(this);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_outcomingFramesPool.Return(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Thread.Sleep(400);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Send(int id, bool is_request, byte[] data)
|
|
|
|
|
{
|
|
|
|
|
var frame = _outcomingFramesPool.Get();
|
|
|
|
|
frame.data = data;
|
|
|
|
|
frame.identity = id;
|
|
|
|
|
frame.is_request = is_request;
|
|
|
|
|
_outcoming_queue.Enqueue(frame);
|
|
|
|
|
_outcomingFrameEvent.Set();
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
public override void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (Status == SocketClientStatus.Working)
|
|
|
|
|
{
|
|
|
|
|
OnDisconnect(this);
|
|
|
|
|
}
|
|
|
|
|
Disposed();
|
|
|
|
|
Sheduller.Remove(_heartbeat_key);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_clientSocket?.Close();
|
|
|
|
|
_clientSocket?.Dispose();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.Error(ex, "[SocketClient.Dispose]");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|