You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel/Services/Network/SocketClient.cs

495 lines
17 KiB

6 years ago
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
5 years ago
using ZeroLevel.Services;
5 years ago
using ZeroLevel.Services.Pools;
6 years ago
using ZeroLevel.Services.Serialization;
namespace ZeroLevel.Network
6 years ago
{
5 years ago
public class SocketClient
: BaseSocket, ISocketClient
6 years ago
{
#region Private
5 years ago
private class IncomingFrame
{
public FrameType type;
public int identity;
public byte[] data;
}
private class SendFrame
{
public bool isRequest;
public int identity;
public byte[] data;
}
6 years ago
private Socket _clientSocket;
private NetworkStream _stream;
5 years ago
6 years ago
private FrameParser _parser = new FrameParser();
5 years ago
private readonly RequestBuffer _requests = new RequestBuffer();
private bool _socket_freezed = false; // используется для связи сервер-клиент, запрещает пересоздание сокета
private int _current_heartbeat_period_in_ms = 0;
5 years ago
private long _heartbeat_key = -1;
6 years ago
private long _last_rw_time = DateTime.UtcNow.Ticks;
private readonly byte[] _buffer = new byte[DEFAULT_RECEIVE_BUFFER_SIZE];
private readonly object _reconnection_lock = new object();
5 years ago
private Thread _sendThread;
private Thread _receiveThread;
private BlockingCollection<IncomingFrame> _incoming_queue = new BlockingCollection<IncomingFrame>();
private BlockingCollection<SendFrame> _send_queue = new BlockingCollection<SendFrame>(BaseSocket.MAX_SEND_QUEUE_SIZE);
private static ObjectPool<IncomingFrame> _incoming_pool = new ObjectPool<IncomingFrame>(() => new IncomingFrame());
private static ObjectPool<SendFrame> _sendinfo_pool = new ObjectPool<SendFrame>(() => new SendFrame());
#endregion Private
6 years ago
5 years ago
public IRouter Router { get; }
6 years ago
public bool IsEmptySendQueue { get { return _send_queue.Count == 0; } }
public SocketClient(IPEndPoint ep, IRouter router)
6 years ago
{
5 years ago
Router = router;
6 years ago
Endpoint = ep;
5 years ago
_parser.OnIncoming += _parser_OnIncoming;
5 years ago
StartInternalThreads();
EnsureConnection();
5 years ago
}
6 years ago
public SocketClient(Socket socket, IRouter router)
5 years ago
{
5 years ago
Router = router;
5 years ago
_socket_freezed = true;
_clientSocket = socket;
_stream = new NetworkStream(_clientSocket, true);
5 years ago
Endpoint = (IPEndPoint)_clientSocket.RemoteEndPoint;
_parser.OnIncoming += _parser_OnIncoming;
5 years ago
StartInternalThreads();
Working();
_stream.BeginRead(_buffer, 0, DEFAULT_RECEIVE_BUFFER_SIZE, ReceiveAsyncCallback, null);
}
private void StartInternalThreads()
{
6 years ago
_sendThread = new Thread(SendFramesJob);
_sendThread.IsBackground = true;
5 years ago
_sendThread.Start();
5 years ago
_receiveThread = new Thread(IncomingFramesJob);
_receiveThread.IsBackground = true;
_receiveThread.Start();
6 years ago
}
5 years ago
#region API
public event Action<ISocketClient> OnConnect = (_) => { };
public event Action<ISocketClient> OnDisconnect = (_) => { };
5 years ago
public IPEndPoint Endpoint { get; }
public void Request(Frame frame, Action<byte[]> callback, Action<string> fail = null)
6 years ago
{
5 years ago
if (frame == null) throw new ArgumentNullException(nameof(frame));
5 years ago
var data = NetworkPacketFactory.Reqeust(MessageSerializer.Serialize(frame), out int id);
Dbg.Timestamp((int)DbgNetworkEvents.ClientStartPushRequest, id.ToString());
frame.Release();
if (!_send_queue.IsAddingCompleted)
6 years ago
{
5 years ago
while (_send_queue.Count >= MAX_SEND_QUEUE_SIZE)
{
5 years ago
Thread.Sleep(1);
5 years ago
}
5 years ago
var sendInfo = _sendinfo_pool.Allocate();
sendInfo.isRequest = true;
5 years ago
sendInfo.data = data;
5 years ago
sendInfo.identity = id;
_requests.RegisterForFrame(id, callback, fail);
_send_queue.Add(sendInfo);
5 years ago
6 years ago
}
5 years ago
Dbg.Timestamp((int)DbgNetworkEvents.ClientCompletePushRequest, id.ToString());
6 years ago
}
5 years ago
public void ForceConnect()
{
EnsureConnection();
}
public void Send(Frame frame)
6 years ago
{
5 years ago
if (frame == null) throw new ArgumentNullException(nameof(frame));
5 years ago
var data = NetworkPacketFactory.Message(MessageSerializer.Serialize(frame));
frame.Release();
if (!_send_queue.IsAddingCompleted)
6 years ago
{
5 years ago
while (_send_queue.Count >= MAX_SEND_QUEUE_SIZE)
6 years ago
{
5 years ago
Thread.Sleep(1);
6 years ago
}
5 years ago
var info = _sendinfo_pool.Allocate();
info.isRequest = false;
info.identity = 0;
5 years ago
info.data = data;
5 years ago
_send_queue.Add(info);
6 years ago
}
5 years ago
}
public void Response(byte[] data, int identity)
{
if (data == null) throw new ArgumentNullException(nameof(data));
5 years ago
Dbg.Timestamp((int)DbgNetworkEvents.ClientStartSendResponse, identity.ToString());
if (!_send_queue.IsAddingCompleted)
6 years ago
{
5 years ago
while (_send_queue.Count >= MAX_SEND_QUEUE_SIZE)
6 years ago
{
5 years ago
Thread.Sleep(1);
6 years ago
}
5 years ago
var info = _sendinfo_pool.Allocate();
info.isRequest = false;
info.identity = 0;
info.data = NetworkPacketFactory.Response(data, identity);
_send_queue.Add(info);
6 years ago
}
5 years ago
Dbg.Timestamp((int)DbgNetworkEvents.ClientCompleteSendResponse, identity.ToString());
6 years ago
}
5 years ago
public void UseKeepAlive(TimeSpan period)
6 years ago
{
5 years ago
if (_heartbeat_key != -1)
6 years ago
{
5 years ago
Sheduller.Remove(_heartbeat_key);
6 years ago
}
5 years ago
if (period != TimeSpan.Zero && period.TotalMilliseconds > MINIMUM_HEARTBEAT_UPDATE_PERIOD_MS)
6 years ago
{
5 years ago
_current_heartbeat_period_in_ms = (int)period.TotalMilliseconds;
_heartbeat_key = Sheduller.RemindEvery(period, Heartbeat);
6 years ago
}
5 years ago
else
6 years ago
{
5 years ago
_current_heartbeat_period_in_ms = 0;
6 years ago
}
}
5 years ago
#endregion
6 years ago
5 years ago
#region Private methods
5 years ago
private void IncomingFramesJob()
6 years ago
{
5 years ago
IncomingFrame frame = default(IncomingFrame);
while (Status != SocketClientStatus.Disposed)
6 years ago
{
5 years ago
if (_send_queue.IsCompleted)
6 years ago
{
5 years ago
return;
}
try
{
frame = _incoming_queue.Take();
}
catch (Exception ex)
{
Log.SystemError(ex, "[SocketClient.IncomingFramesJob] _incoming_queue.Take");
_incoming_queue.Dispose();
_incoming_queue = new BlockingCollection<IncomingFrame>();
continue;
}
try
{
switch (frame.type)
{
case FrameType.Message:
Router?.HandleMessage(MessageSerializer.Deserialize<Frame>(frame.data), this);
break;
case FrameType.Request:
{
5 years ago
Dbg.Timestamp((int)DbgNetworkEvents.ClientStartHandleRequest, frame.identity.ToString());
Router?.HandleRequest(MessageSerializer.Deserialize<Frame>(frame.data), this, response =>
{
if (response != null)
{
this.Response(response, frame.identity);
}
Dbg.Timestamp((int)DbgNetworkEvents.ClientCompleteHandleRequest, frame.identity.ToString());
});
5 years ago
}
break;
case FrameType.Response:
5 years ago
{
Dbg.Timestamp((int)DbgNetworkEvents.ClientGotResponse, frame.identity.ToString());
_requests.Success(frame.identity, frame.data);
}
5 years ago
break;
}
}
catch (Exception ex)
{
Log.SystemError(ex, "[SocketClient.IncomingFramesJob] Handle frame");
6 years ago
}
5 years ago
finally
{
_incoming_pool.Free(frame);
}
}
}
private void _parser_OnIncoming(FrameType type, int identity, byte[] data)
{
try
{
if (type == FrameType.KeepAlive) return;
var incoming = _incoming_pool.Allocate();
incoming.data = data;
incoming.type = type;
incoming.identity = identity;
_incoming_queue.Add(incoming);
6 years ago
}
5 years ago
catch (Exception ex)
{
Log.Error(ex, $"[SocketClient._parser_OnIncoming]");
}
6 years ago
}
6 years ago
private bool TryConnect()
{
5 years ago
if (Status == SocketClientStatus.Working)
6 years ago
{
return true;
}
5 years ago
if (Status == SocketClientStatus.Disposed)
6 years ago
{
return false;
}
6 years ago
if (_clientSocket != null)
{
5 years ago
Dbg.Timestamp((int)DbgNetworkEvents.ClientLostConnection, $"{(_clientSocket.RemoteEndPoint as IPEndPoint).Address}:{(_clientSocket.RemoteEndPoint as IPEndPoint).Port}");
6 years ago
try
{
_stream?.Close();
_stream?.Dispose();
_clientSocket.Dispose();
}
catch
{
/* ignore */
}
_clientSocket = null;
_stream = null;
}
try
{
_clientSocket = MakeClientSocket();
6 years ago
_clientSocket.Connect(Endpoint);
6 years ago
_stream = new NetworkStream(_clientSocket, true);
_stream.BeginRead(_buffer, 0, DEFAULT_RECEIVE_BUFFER_SIZE, ReceiveAsyncCallback, null);
}
catch (Exception ex)
{
Log.SystemError(ex, "[SocketClient.TryConnect] Connection fault");
6 years ago
Broken();
6 years ago
return false;
}
6 years ago
Working();
5 years ago
OnConnect(this);
6 years ago
return true;
}
public void EnsureConnection()
{
5 years ago
if (_socket_freezed)
{
return;
}
6 years ago
lock (_reconnection_lock)
{
5 years ago
if (Status == SocketClientStatus.Disposed)
6 years ago
{
throw new ObjectDisposedException("connection");
}
5 years ago
if (Status != SocketClientStatus.Working)
6 years ago
{
if (false == TryConnect())
{
throw new Exception("No connection");
6 years ago
}
}
}
}
5 years ago
private void Heartbeat()
6 years ago
{
5 years ago
try
{
EnsureConnection();
}
catch (Exception ex)
{
Log.SystemError(ex, "[SocketClient.Heartbeat.EnsureConnection]");
Broken();
OnDisconnect(this);
5 years ago
return;
}
_requests.TestForTimeouts();
try
6 years ago
{
5 years ago
var info = _sendinfo_pool.Allocate();
info.isRequest = false;
info.identity = 0;
info.data = NetworkPacketFactory.KeepAliveMessage();
_send_queue.Add(info);
5 years ago
}
catch (Exception ex)
{
Log.SystemError(ex, "[SocketClient.Heartbeat.Request]");
}
var diff_request_ms = ((DateTime.UtcNow.Ticks - _last_rw_time) / TimeSpan.TicksPerMillisecond);
if (diff_request_ms > (_current_heartbeat_period_in_ms * 2))
{
var port = (_clientSocket.LocalEndPoint as IPEndPoint)?.Port;
Log.Debug($"[SocketClient.Heartbeat] server disconnected, because last data was more thas {diff_request_ms} ms ago. Client port {port}");
Broken();
6 years ago
}
}
5 years ago
private void ReceiveAsyncCallback(IAsyncResult ar)
6 years ago
{
try
5 years ago
{
5 years ago
var count = _stream.EndRead(ar);
if (count > 0)
{
_parser.Push(_buffer, count);
_last_rw_time = DateTime.UtcNow.Ticks;
}
else
{
5 years ago
// TODO or not TODO
Thread.Sleep(1);
}
EnsureConnection();
5 years ago
_stream.BeginRead(_buffer, 0, DEFAULT_RECEIVE_BUFFER_SIZE, ReceiveAsyncCallback, null);
6 years ago
}
5 years ago
catch (ObjectDisposedException)
6 years ago
{
5 years ago
/// Nothing
6 years ago
}
catch (Exception ex)
{
5 years ago
Log.SystemError(ex, $"[SocketClient.ReceiveAsyncCallback] Error read data");
6 years ago
Broken();
5 years ago
OnDisconnect(this);
}
}
5 years ago
5 years ago
private void SendFramesJob()
{
5 years ago
SendFrame frame;
5 years ago
int unsuccess = 0;
while (Status != SocketClientStatus.Disposed)
{
if (_send_queue.IsCompleted)
{
return;
}
try
{
frame = _send_queue.Take();
}
catch (Exception ex)
{
Log.SystemError(ex, "[SocketClient.SendFramesJob] send_queue.Take");
_send_queue.Dispose();
5 years ago
_send_queue = new BlockingCollection<SendFrame>();
5 years ago
continue;
}
5 years ago
while (_stream?.CanWrite == false || Status != SocketClientStatus.Working)
5 years ago
{
try
{
EnsureConnection();
}
catch (Exception ex)
{
5 years ago
Log.SystemError(ex, "[SocketClient.SendFramesJob] Connection broken");
5 years ago
}
if (Status == SocketClientStatus.Disposed)
{
return;
}
if (Status == SocketClientStatus.Broken)
{
unsuccess++;
if (unsuccess > 30) unsuccess = 30;
}
if (Status == SocketClientStatus.Working)
{
unsuccess = 0;
5 years ago
break;
5 years ago
}
Thread.Sleep(unsuccess * 128);
}
5 years ago
if (frame != null)
5 years ago
{
5 years ago
try
5 years ago
{
5 years ago
if (frame.isRequest)
{
5 years ago
Dbg.Timestamp((int)DbgNetworkEvents.ClientStartSendRequest, frame.identity.ToString());
5 years ago
_requests.StartSend(frame.identity);
}
_stream.Write(frame.data, 0, frame.data.Length);
_last_rw_time = DateTime.UtcNow.Ticks;
5 years ago
if (frame.isRequest)
{
Dbg.Timestamp((int)DbgNetworkEvents.ClientCompleteSendRequest, frame.identity.ToString());
}
5 years ago
}
catch (Exception ex)
{
Log.SystemError(ex, $"[SocketClient.SendFramesJob] _stream.Write");
Broken();
OnDisconnect(this);
}
finally
{
_sendinfo_pool.Free(frame);
5 years ago
}
}
}
}
5 years ago
#endregion
6 years ago
#region Helper
6 years ago
private static Socket MakeClientSocket()
{
var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.SetIPProtectionLevel(IPProtectionLevel.Unrestricted);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
return s;
}
#endregion Helper
6 years ago
public override void Dispose()
{
5 years ago
if (Status == SocketClientStatus.Working)
6 years ago
{
5 years ago
OnDisconnect(this);
6 years ago
}
6 years ago
Disposed();
6 years ago
Sheduller.Remove(_heartbeat_key);
_stream?.Close();
_stream?.Dispose();
}
}
5 years ago
}

Powered by TurnKey Linux.