@@ -0,0 +1,27 @@ | |||||
using JT808.Protocol; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace JT808.Gateway.Client | |||||
{ | |||||
/// <summary> | |||||
/// 消息数据包 | |||||
/// </summary> | |||||
public interface IJT808MessageProducer : IDisposable | |||||
{ | |||||
ValueTask ProduceAsync(JT808Package package); | |||||
} | |||||
internal class JT808MessageProducerEmpty : IJT808MessageProducer | |||||
{ | |||||
public void Dispose() | |||||
{ | |||||
} | |||||
public ValueTask ProduceAsync(JT808Package package) | |||||
{ | |||||
return default; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT808.Gateway.Client.Internal | |||||
{ | |||||
public class JT808RetryBlockingCollection | |||||
{ | |||||
public BlockingCollection<JT808DeviceConfig> RetryBlockingCollection { get; } | |||||
public JT808RetryBlockingCollection() | |||||
{ | |||||
RetryBlockingCollection = new BlockingCollection<JT808DeviceConfig>(999999); | |||||
} | |||||
} | |||||
} |
@@ -23,7 +23,6 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="JT808" Version="2.2.14" /> | <PackageReference Include="JT808" Version="2.2.14" /> | ||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.9" /> | |||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.9" /> | <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.9" /> | ||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.3" /> | <PackageReference Include="System.IO.Pipelines" Version="4.7.3" /> | ||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.9" /> | <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.9" /> | ||||
@@ -5,6 +5,8 @@ using System.Text; | |||||
using JT808.Protocol; | using JT808.Protocol; | ||||
using Microsoft.Extensions.Configuration; | using Microsoft.Extensions.Configuration; | ||||
using JT808.Gateway.Client.Services; | using JT808.Gateway.Client.Services; | ||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using JT808.Gateway.Client.Internal; | |||||
namespace JT808.Gateway.Client | namespace JT808.Gateway.Client | ||||
{ | { | ||||
@@ -13,12 +15,27 @@ namespace JT808.Gateway.Client | |||||
public static IJT808ClientBuilder AddClient(this IJT808Builder jT808Builder) | public static IJT808ClientBuilder AddClient(this IJT808Builder jT808Builder) | ||||
{ | { | ||||
JT808ClientBuilderDefault jT808ClientBuilderDefault = new JT808ClientBuilderDefault(jT808Builder); | JT808ClientBuilderDefault jT808ClientBuilderDefault = new JT808ClientBuilderDefault(jT808Builder); | ||||
jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<JT808RetryBlockingCollection>(); | |||||
jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<JT808SendAtomicCounterService>(); | jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<JT808SendAtomicCounterService>(); | ||||
jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<JT808ReceiveAtomicCounterService>(); | jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<JT808ReceiveAtomicCounterService>(); | ||||
jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<IJT808TcpClientFactory, JT808TcpClientFactory>(); | jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<IJT808TcpClientFactory, JT808TcpClientFactory>(); | ||||
jT808ClientBuilderDefault.JT808Builder.Services.AddSingleton<IJT808MessageProducer, JT808MessageProducerEmpty>(); | |||||
return jT808ClientBuilderDefault; | return jT808ClientBuilderDefault; | ||||
} | } | ||||
public static IJT808ClientBuilder AddMessageProducer<TJT808MessageProducer>(this IJT808ClientBuilder jT808ClientBuilder) | |||||
where TJT808MessageProducer: IJT808MessageProducer | |||||
{ | |||||
jT808ClientBuilder.JT808Builder.Services.Replace(new ServiceDescriptor(typeof(IJT808MessageProducer), typeof(TJT808MessageProducer), ServiceLifetime.Singleton)); | |||||
return jT808ClientBuilder; | |||||
} | |||||
public static IJT808ClientBuilder AddClientRetry(this IJT808ClientBuilder jT808ClientBuilder) | |||||
{ | |||||
jT808ClientBuilder.JT808Builder.Services.AddHostedService<JT808RetryClientHostedService>(); | |||||
return jT808ClientBuilder; | |||||
} | |||||
public static IJT808ClientBuilder AddClientReport(this IJT808ClientBuilder jT808ClientBuilder) | public static IJT808ClientBuilder AddClientReport(this IJT808ClientBuilder jT808ClientBuilder) | ||||
{ | { | ||||
jT808ClientBuilder.JT808Builder.Services.Configure<JT808ReportOptions>((options) => { }); | jT808ClientBuilder.JT808Builder.Services.Configure<JT808ReportOptions>((options) => { }); | ||||
@@ -24,7 +24,10 @@ namespace JT808.Gateway.Client | |||||
/// 心跳时间(秒) | /// 心跳时间(秒) | ||||
/// </summary> | /// </summary> | ||||
public int Heartbeat { get; set; } = 30; | public int Heartbeat { get; set; } = 30; | ||||
/// <summary> | |||||
/// 自动重连 默认true | |||||
/// </summary> | |||||
public bool AutoReconnection { get; set; } = true; | |||||
public IJT808MsgSNDistributed MsgSNDistributed { get; } | public IJT808MsgSNDistributed MsgSNDistributed { get; } | ||||
} | } | ||||
} | } |
@@ -12,21 +12,24 @@ using JT808.Protocol.Extensions; | |||||
using JT808.Gateway.Client.Services; | using JT808.Gateway.Client.Services; | ||||
using JT808.Gateway.Client.Metadata; | using JT808.Gateway.Client.Metadata; | ||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
using JT808.Gateway.Client.Internal; | |||||
namespace JT808.Gateway.Client | namespace JT808.Gateway.Client | ||||
{ | { | ||||
public class JT808TcpClient:IDisposable | public class JT808TcpClient:IDisposable | ||||
{ | { | ||||
//todo: 客户端的断线重连 | |||||
//todo: 客户端心跳时间 | |||||
private bool disposed = false; | private bool disposed = false; | ||||
private Socket clientSocket; | private Socket clientSocket; | ||||
private readonly ILogger Logger; | private readonly ILogger Logger; | ||||
private readonly JT808Serializer JT808Serializer; | private readonly JT808Serializer JT808Serializer; | ||||
private readonly JT808SendAtomicCounterService SendAtomicCounterService; | private readonly JT808SendAtomicCounterService SendAtomicCounterService; | ||||
private readonly JT808ReceiveAtomicCounterService ReceiveAtomicCounterService; | private readonly JT808ReceiveAtomicCounterService ReceiveAtomicCounterService; | ||||
private readonly JT808RetryBlockingCollection RetryBlockingCollection; | |||||
private bool socketState = true; | private bool socketState = true; | ||||
public JT808DeviceConfig DeviceConfig { get; } | public JT808DeviceConfig DeviceConfig { get; } | ||||
private IJT808MessageProducer producer; | |||||
public JT808TcpClient( | public JT808TcpClient( | ||||
JT808DeviceConfig deviceConfig, | JT808DeviceConfig deviceConfig, | ||||
IServiceProvider serviceProvider) | IServiceProvider serviceProvider) | ||||
@@ -36,6 +39,8 @@ namespace JT808.Gateway.Client | |||||
ReceiveAtomicCounterService = serviceProvider.GetRequiredService<JT808ReceiveAtomicCounterService>(); | ReceiveAtomicCounterService = serviceProvider.GetRequiredService<JT808ReceiveAtomicCounterService>(); | ||||
JT808Serializer = serviceProvider.GetRequiredService<IJT808Config>().GetSerializer(); | JT808Serializer = serviceProvider.GetRequiredService<IJT808Config>().GetSerializer(); | ||||
Logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<JT808TcpClient>(); | Logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<JT808TcpClient>(); | ||||
producer = serviceProvider.GetRequiredService<IJT808MessageProducer>(); | |||||
RetryBlockingCollection = serviceProvider.GetRequiredService<JT808RetryBlockingCollection>(); | |||||
} | } | ||||
public async ValueTask<bool> ConnectAsync(EndPoint remoteEndPoint) | public async ValueTask<bool> ConnectAsync(EndPoint remoteEndPoint) | ||||
{ | { | ||||
@@ -47,6 +52,8 @@ namespace JT808.Gateway.Client | |||||
} | } | ||||
catch (Exception e) | catch (Exception e) | ||||
{ | { | ||||
Logger.LogError(e, "ConnectAsync Error"); | |||||
RetryBlockingCollection.RetryBlockingCollection.Add(DeviceConfig); | |||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
@@ -63,6 +70,7 @@ namespace JT808.Gateway.Client | |||||
Task writing = FillPipeAsync(session, pipe.Writer, cancellationToken); | Task writing = FillPipeAsync(session, pipe.Writer, cancellationToken); | ||||
Task reading = ReadPipeAsync(session, pipe.Reader); | Task reading = ReadPipeAsync(session, pipe.Reader); | ||||
await Task.WhenAll(reading, writing); | await Task.WhenAll(reading, writing); | ||||
RetryBlockingCollection.RetryBlockingCollection.Add(DeviceConfig); | |||||
}, clientSocket); | }, clientSocket); | ||||
} | } | ||||
private async Task FillPipeAsync(Socket session, PipeWriter writer, CancellationToken cancellationToken) | private async Task FillPipeAsync(Socket session, PipeWriter writer, CancellationToken cancellationToken) | ||||
@@ -79,6 +87,11 @@ namespace JT808.Gateway.Client | |||||
} | } | ||||
writer.Advance(bytesRead); | writer.Advance(bytesRead); | ||||
} | } | ||||
catch (OperationCanceledException ex) | |||||
{ | |||||
Logger.LogError($"[Receive Timeout]:{session.RemoteEndPoint}"); | |||||
break; | |||||
} | |||||
catch (System.Net.Sockets.SocketException ex) | catch (System.Net.Sockets.SocketException ex) | ||||
{ | { | ||||
Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message}]:{session.RemoteEndPoint}"); | Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message}]:{session.RemoteEndPoint}"); | ||||
@@ -148,10 +161,15 @@ namespace JT808.Gateway.Client | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
var package = JT808Serializer.HeaderDeserialize(seqReader.Sequence.Slice(totalConsumed, seqReader.Consumed - totalConsumed).ToArray(),minBufferSize:8096); | |||||
var data = seqReader.Sequence.Slice(totalConsumed, seqReader.Consumed - totalConsumed).ToArray(); | |||||
var package = JT808Serializer.Deserialize(data, minBufferSize: 8096); | |||||
if (producer != null) | |||||
{ | |||||
producer.ProduceAsync(package); | |||||
} | |||||
ReceiveAtomicCounterService.MsgSuccessIncrement(); | ReceiveAtomicCounterService.MsgSuccessIncrement(); | ||||
if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebug($"[Atomic Success Counter]:{ReceiveAtomicCounterService.MsgSuccessCount}"); | if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebug($"[Atomic Success Counter]:{ReceiveAtomicCounterService.MsgSuccessCount}"); | ||||
if (Logger.IsEnabled(LogLevel.Trace)) Logger.LogTrace($"[Accept Hex {session.RemoteEndPoint}]:{package.OriginalData.ToArray().ToHexString()}"); | |||||
if (Logger.IsEnabled(LogLevel.Trace)) Logger.LogTrace($"[Accept Hex {session.RemoteEndPoint}]:{data.ToHexString()}"); | |||||
} | } | ||||
catch (JT808Exception ex) | catch (JT808Exception ex) | ||||
{ | { | ||||
@@ -178,9 +196,9 @@ namespace JT808.Gateway.Client | |||||
consumed = buffer.GetPosition(totalConsumed); | consumed = buffer.GetPosition(totalConsumed); | ||||
} | } | ||||
} | } | ||||
public async ValueTask SendAsync(JT808ClientRequest message) | |||||
public async ValueTask<bool> SendAsync(JT808ClientRequest message) | |||||
{ | { | ||||
if (disposed) return; | |||||
if (disposed) return false; | |||||
if (IsOpen && socketState) | if (IsOpen && socketState) | ||||
{ | { | ||||
if (message.Package != null) | if (message.Package != null) | ||||
@@ -188,38 +206,44 @@ namespace JT808.Gateway.Client | |||||
try | try | ||||
{ | { | ||||
var sendData = JT808Serializer.Serialize(message.Package, minBufferSize: message.MinBufferSize); | var sendData = JT808Serializer.Serialize(message.Package, minBufferSize: message.MinBufferSize); | ||||
//clientSocket.Send(sendData); | |||||
await clientSocket.SendAsync(sendData, SocketFlags.None); | await clientSocket.SendAsync(sendData, SocketFlags.None); | ||||
SendAtomicCounterService.MsgSuccessIncrement(); | SendAtomicCounterService.MsgSuccessIncrement(); | ||||
return true; | |||||
} | } | ||||
catch (System.Net.Sockets.SocketException ex) | catch (System.Net.Sockets.SocketException ex) | ||||
{ | { | ||||
socketState = false; | socketState = false; | ||||
Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message},{DeviceConfig.TerminalPhoneNo}]"); | Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message},{DeviceConfig.TerminalPhoneNo}]"); | ||||
return false; | |||||
} | } | ||||
catch (System.Exception ex) | catch (System.Exception ex) | ||||
{ | { | ||||
Logger.LogError(ex.Message); | Logger.LogError(ex.Message); | ||||
return false; | |||||
} | } | ||||
} | } | ||||
else if (message.HexData != null) | else if (message.HexData != null) | ||||
{ | { | ||||
try | try | ||||
{ | { | ||||
clientSocket.Send(message.HexData); | |||||
await clientSocket.SendAsync(message.HexData, SocketFlags.None); | |||||
SendAtomicCounterService.MsgSuccessIncrement(); | SendAtomicCounterService.MsgSuccessIncrement(); | ||||
return true; | |||||
} | } | ||||
catch (System.Net.Sockets.SocketException ex) | catch (System.Net.Sockets.SocketException ex) | ||||
{ | { | ||||
socketState = false; | socketState = false; | ||||
Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message},{DeviceConfig.TerminalPhoneNo}]"); | Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message},{DeviceConfig.TerminalPhoneNo}]"); | ||||
return false; | |||||
} | } | ||||
catch (System.Exception ex) | catch (System.Exception ex) | ||||
{ | { | ||||
Logger.LogError(ex.Message); | Logger.LogError(ex.Message); | ||||
return false; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return false; | |||||
} | } | ||||
public void Close() | public void Close() | ||||
@@ -0,0 +1,69 @@ | |||||
using JT808.Gateway.Client.Internal; | |||||
using JT808.Gateway.Client.Metadata; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Text.Json; | |||||
using System.Text.Json.Serialization; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace JT808.Gateway.Client.Services | |||||
{ | |||||
internal class JT808RetryClientHostedService : BackgroundService | |||||
{ | |||||
private readonly IJT808TcpClientFactory jT808TcpClientFactory; | |||||
private readonly ILogger logger; | |||||
private readonly JT808RetryBlockingCollection RetryBlockingCollection; | |||||
public JT808RetryClientHostedService( | |||||
JT808RetryBlockingCollection retryBlockingCollection, | |||||
ILoggerFactory loggerFactory, | |||||
IJT808TcpClientFactory jT808TcpClientFactory) | |||||
{ | |||||
logger = loggerFactory.CreateLogger<JT808RetryClientHostedService>(); | |||||
this.jT808TcpClientFactory = jT808TcpClientFactory; | |||||
RetryBlockingCollection = retryBlockingCollection; | |||||
} | |||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | |||||
{ | |||||
foreach (var item in RetryBlockingCollection.RetryBlockingCollection.GetConsumingEnumerable(stoppingToken)) | |||||
{ | |||||
try | |||||
{ | |||||
jT808TcpClientFactory.Remove(item); | |||||
if (item.AutoReconnection) | |||||
{ | |||||
var result = await jT808TcpClientFactory.Create(item, stoppingToken); | |||||
if (result != null) | |||||
{ | |||||
if (logger.IsEnabled(LogLevel.Information)) | |||||
{ | |||||
logger.LogInformation($"Retry Success-{JsonSerializer.Serialize(item)}"); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (logger.IsEnabled(LogLevel.Warning)) | |||||
{ | |||||
logger.LogWarning($"Retry Fail-{JsonSerializer.Serialize(item)}"); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, $"Retry Error-{JsonSerializer.Serialize(item)}"); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -62,7 +62,7 @@ namespace JT808.Gateway | |||||
server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); | server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); | ||||
server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, Configuration.MiniNumBufferSize); | server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, Configuration.MiniNumBufferSize); | ||||
server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, Configuration.MiniNumBufferSize); | server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, Configuration.MiniNumBufferSize); | ||||
server.LingerState = new LingerOption(false, 0); | |||||
server.LingerState = new LingerOption(true, 0); | |||||
server.Bind(IPEndPoint); | server.Bind(IPEndPoint); | ||||
server.Listen(Configuration.SoBacklog); | server.Listen(Configuration.SoBacklog); | ||||
} | } | ||||
@@ -201,11 +201,11 @@ namespace JT808.Gateway | |||||
} | } | ||||
catch (NotImplementedException ex) | catch (NotImplementedException ex) | ||||
{ | { | ||||
Logger.LogError(ex.Message); | |||||
Logger.LogError(ex.Message,$"{session.Client.RemoteEndPoint}"); | |||||
} | } | ||||
catch (JT808Exception ex) | catch (JT808Exception ex) | ||||
{ | { | ||||
Logger.LogError($"[HeaderDeserialize ErrorCode]:{ ex.ErrorCode},[ReaderBuffer]:{contentSpan.ToArray().ToHexString()}"); | |||||
Logger.LogError($"[HeaderDeserialize ErrorCode]:{ ex.ErrorCode},[ReaderBuffer]:{contentSpan.ToArray().ToHexString()},{session.Client.RemoteEndPoint}"); | |||||
} | } | ||||
totalConsumed += (seqReader.Consumed - totalConsumed); | totalConsumed += (seqReader.Consumed - totalConsumed); | ||||
if (seqReader.End) break; | if (seqReader.End) break; | ||||