@@ -37,5 +37,5 @@ jobs: | |||
run: dotnet restore ./src/JT808.Gateway.sln | |||
- name: dotnet JT808.Gateway build | |||
run: dotnet build ./src/JT808.Gateway.Test/JT808.Gateway.Test.csproj | |||
- name: dotnet test | |||
- name: dotnet JT808.Gateway test | |||
run: dotnet test ./src/JT808.Gateway.Test/JT808.Gateway.Test.csproj |
@@ -1,21 +0,0 @@ | |||
language: csharp | |||
solution: JT808.DotNetty.sln | |||
dotnet: 3.1.100 | |||
os: linux | |||
mono: none | |||
dist: trusty2 | |||
script: | |||
- dotnet restore src/JT808.DotNetty.sln | |||
- dotnet build src/JT808.DotNetty.Tests/JT808.DotNetty.Core.Test/JT808.DotNetty.Core.Test.csproj | |||
- dotnet test src/JT808.DotNetty.Tests/JT808.DotNetty.Core.Test/JT808.DotNetty.Core.Test.csproj | |||
- dotnet build src/JT808.DotNetty.Tests/JT808.DotNetty.Tcp.Test/JT808.DotNetty.Tcp.Test.csproj | |||
- dotnet test src/JT808.DotNetty.Tests/JT808.DotNetty.Tcp.Test/JT808.DotNetty.Tcp.Test.csproj | |||
- dotnet build src/JT808.DotNetty.Tests/JT808.DotNetty.Udp.Test/JT808.DotNetty.Udp.Test.csproj | |||
- dotnet test src/JT808.DotNetty.Tests/JT808.DotNetty.Udp.Test/JT808.DotNetty.Udp.Test.csproj | |||
- dotnet build src/JT808.DotNetty.Tests/JT808.DotNetty.Udp.Test/JT808.DotNetty.Udp.Test.csproj | |||
- dotnet test src/JT808.DotNetty.Tests/JT808.DotNetty.WebApi.Test/JT808.DotNetty.WebApi.Test.csproj | |||
after_success: | |||
- echo successful build! | |||
branches: | |||
only: | |||
- master |
@@ -14,7 +14,7 @@ | |||
[玩一玩压力测试](https://github.com/SmallChi/JT808DotNetty/blob/master/doc/README.md) | |||
[](https://github.com/SmallChi/JT808DotNetty/blob/master/LICENSE)[](https://travis-ci.org/SmallChi/JT808DotNetty)[]() | |||
[](https://github.com/SmallChi/JT808DotNetty/blob/master/LICENSE)[]() | |||
## 新网关的优势 | |||
@@ -41,15 +41,11 @@ namespace JT808.DotNetty.Client | |||
Bootstrap bootstrap = new Bootstrap(); | |||
bootstrap.Group(group); | |||
bootstrap.Channel<TcpSocketChannel>(); | |||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | |||
{ | |||
bootstrap.Option(ChannelOption.SoReuseport, true); | |||
} | |||
bootstrap | |||
.Option(ChannelOption.SoBacklog, 8192) | |||
.Option(ChannelOption.Allocator, new PooledByteBufferAllocator()) | |||
.Handler(new ActionChannelInitializer<IChannel>(channel => | |||
{ | |||
channel.Pipeline.AddLast("jt808TcpBuffer", new DelimiterBasedFrameDecoder(int.MaxValue, | |||
channel.Pipeline.AddLast("jt808TcpBuffer", new DelimiterBasedFrameDecoder(65535, | |||
Unpooled.CopiedBuffer(new byte[] { JT808.Protocol.JT808Package.BeginFlag }), | |||
Unpooled.CopiedBuffer(new byte[] { JT808.Protocol.JT808Package.EndFlag }))); | |||
channel.Pipeline.AddLast("systemIdleState", new IdleStateHandler(60, deviceConfig.Heartbeat, 3600)); | |||
@@ -0,0 +1,24 @@ | |||
using Microsoft.Extensions.Options; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT808.Gateway.CleintBenchmark.Configs | |||
{ | |||
public class ClientBenchmarkOptions : IOptions<ClientBenchmarkOptions> | |||
{ | |||
public string IP { get; set; } | |||
public int Port { get; set; } | |||
public int DeviceCount { get; set; } = 10; | |||
/// <summary> | |||
/// 5000ms毫秒 | |||
/// </summary> | |||
public int Interval { get; set; } = 5000; | |||
/// <summary> | |||
/// 需要多台机器同时访问,那么可以根据这个避开重复终端号 | |||
/// 100000-200000-300000 | |||
/// </summary> | |||
public int DeviceTemplate { get; set; } = 0; | |||
public ClientBenchmarkOptions Value =>this; | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
<?xml version="1.0" encoding="utf-8" ?> | |||
<!-- | |||
参考:http://www.cnblogs.com/fuchongjundream/p/3936431.html | |||
autoReload:自动再配置 | |||
internalLogFile:可以让NLog把内部的调试和异常信息都写入指定文件里程序没问题了,日志却出了问题。这个该怎么办,到底是哪里不正确了?假如日志本身除了bug该如何解决?这就需要日志排错。把日志的错误信息写入日志。 | |||
<nlog throwExceptions="true" /> | |||
<nlog internalLogFile="file.txt" />- 设置internalLogFile属性可以让NLog把内部的调试和异常信息都写入指定文件里。 | |||
<nlog internalLogLevel="Trace|Debug|Info|Warn|Error|Fatal" /> - 决定内部日志的级别,级别越高,输出的日志信息越简洁。 | |||
<nlog internalLogToConsole="false|true" /> - 是否把内部日志输出到标准控制台。 | |||
<nlog internalLogToConsoleError="false|true" /> - 是否把内部日志输出到标准错误控制台 (stderr)。 | |||
设置throwExceptions属性为“true”可以让NLog不再阻挡这类异常,而是把它们抛给调用者。在部署是这样做可以帮我们快速定位问题。一旦应用程序已经正确配置了,我们建议把throwExceptions的值设为“false”,这样由于日志引发的问题不至于导致应用程序的崩溃。 | |||
--> | |||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
autoReload="true" | |||
internalLogFile="/data/logs/JT808.Gateway.CleintBenchmark/internalLog.txt" | |||
internalLogLevel="Debug" > | |||
<variable name="Directory" value="/data/logs/JT808.Gateway.CleintBenchmark"/> | |||
<targets> | |||
<target name="all" xsi:type="File" | |||
fileName="${Directory}/all/CleintBenchmark.${shortdate}.log" | |||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level}:${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"/> | |||
<target name="console" xsi:type="ColoredConsole" | |||
useDefaultRowHighlightingRules="false" | |||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level} ${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"> | |||
<highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGray" /> | |||
<highlight-row condition="level == LogLevel.Info" foregroundColor="Gray" /> | |||
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" /> | |||
<highlight-row condition="level == LogLevel.Error" foregroundColor="Red" /> | |||
<highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" /> | |||
</target> | |||
</targets> | |||
<rules> | |||
<logger name="*" minlevel="Debug" maxlevel="Fatal" writeTo="all,console"/> | |||
</rules> | |||
</nlog> |
@@ -0,0 +1,35 @@ | |||
<?xml version="1.0" encoding="utf-8" ?> | |||
<!-- | |||
参考:http://www.cnblogs.com/fuchongjundream/p/3936431.html | |||
autoReload:自动再配置 | |||
internalLogFile:可以让NLog把内部的调试和异常信息都写入指定文件里程序没问题了,日志却出了问题。这个该怎么办,到底是哪里不正确了?假如日志本身除了bug该如何解决?这就需要日志排错。把日志的错误信息写入日志。 | |||
<nlog throwExceptions="true" /> | |||
<nlog internalLogFile="file.txt" />- 设置internalLogFile属性可以让NLog把内部的调试和异常信息都写入指定文件里。 | |||
<nlog internalLogLevel="Trace|Debug|Info|Warn|Error|Fatal" /> - 决定内部日志的级别,级别越高,输出的日志信息越简洁。 | |||
<nlog internalLogToConsole="false|true" /> - 是否把内部日志输出到标准控制台。 | |||
<nlog internalLogToConsoleError="false|true" /> - 是否把内部日志输出到标准错误控制台 (stderr)。 | |||
设置throwExceptions属性为“true”可以让NLog不再阻挡这类异常,而是把它们抛给调用者。在部署是这样做可以帮我们快速定位问题。一旦应用程序已经正确配置了,我们建议把throwExceptions的值设为“false”,这样由于日志引发的问题不至于导致应用程序的崩溃。 | |||
--> | |||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
autoReload="true" | |||
internalLogFile="${basedir}/wwwroot/logs/internalLog.txt" | |||
internalLogLevel="Debug" > | |||
<targets> | |||
<target name="all" xsi:type="File" | |||
fileName="${basedir}/wwwroot/logs/all/CleintBenchmark.${shortdate}.log" | |||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level}:${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"/> | |||
<target name="console" xsi:type="ColoredConsole" | |||
useDefaultRowHighlightingRules="false" | |||
layout="${date:format=yyyyMMddHHmmss} ${callsite} ${level} ${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}"> | |||
<highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGray" /> | |||
<highlight-row condition="level == LogLevel.Info" foregroundColor="Gray" /> | |||
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" /> | |||
<highlight-row condition="level == LogLevel.Error" foregroundColor="Red" /> | |||
<highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" /> | |||
</target> | |||
</targets> | |||
<rules> | |||
<logger name="*" minlevel="Debug" maxlevel="Fatal" writeTo="all,console"/> | |||
</rules> | |||
</nlog> |
@@ -0,0 +1,63 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.ComponentModel.DataAnnotations; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using JT808.Gateway.Client; | |||
using JT808.Gateway.Client.Metadata; | |||
using JT808.Gateway.Client.Services; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Cors; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.Extensions.Caching.Memory; | |||
using Microsoft.Extensions.Primitives; | |||
namespace JT808.Gateway.CleintBenchmark | |||
{ | |||
/// <summary> | |||
/// 车辆控制器 | |||
/// </summary> | |||
[Route("JT808WebApi")] | |||
[ApiController] | |||
[EnableCors("Domain")] | |||
public class ReportController : ControllerBase | |||
{ | |||
private readonly IJT808TcpClientFactory clientFactory; | |||
private readonly JT808ReceiveAtomicCounterService ReceiveAtomicCounterService; | |||
private readonly JT808SendAtomicCounterService SendAtomicCounterService; | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public ReportController( | |||
IJT808TcpClientFactory factory, | |||
JT808ReceiveAtomicCounterService jT808ReceiveAtomicCounterService, | |||
JT808SendAtomicCounterService jT808SendAtomicCounterService) | |||
{ | |||
clientFactory = factory; | |||
ReceiveAtomicCounterService = jT808ReceiveAtomicCounterService; | |||
SendAtomicCounterService = jT808SendAtomicCounterService; | |||
} | |||
[HttpPost] | |||
[HttpGet] | |||
[Route("QueryReport")] | |||
public ActionResult<JT808Report> QueryReport() | |||
{ | |||
var clients = clientFactory.GetAll(); | |||
JT808Report report = new JT808Report() | |||
{ | |||
SendTotalCount = SendAtomicCounterService.MsgSuccessCount, | |||
ReceiveTotalCount = ReceiveAtomicCounterService.MsgSuccessCount, | |||
CurrentDate = DateTime.Now, | |||
Connections = clients.Count, | |||
OnlineConnections = clients.Where(w => w.IsOpen).Count(), | |||
OfflineConnections = clients.Where(w => !w.IsOpen).Count(), | |||
}; | |||
return report; | |||
} | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||
<PropertyGroup> | |||
<OutputType>Exe</OutputType> | |||
<TargetFramework>netcoreapp3.1</TargetFramework> | |||
<LangVersion>8.0</LangVersion> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Content Remove="wwwroot\echarts.min.js" /> | |||
<Content Remove="wwwroot\index.html" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="3.1.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.1" /> | |||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.1" /> | |||
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.1" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\JT808.Gateway.Client\JT808.Gateway.Client.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="wwwroot\echarts.min.js"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Include="wwwroot\index.html"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Content Update="appsettings.json"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</Content> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="appsettings.json"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Configs\nlog.unix.config"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Configs\nlog.win.config"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Configs\NLog.xsd"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
<SubType>Designer</SubType> | |||
</None> | |||
</ItemGroup> | |||
<ProjectExtensions><VisualStudio><UserProperties appsettings_1json__JsonSchema="" /></VisualStudio></ProjectExtensions> | |||
</Project> |
@@ -0,0 +1,77 @@ | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Hosting; | |||
using Microsoft.Extensions.Logging; | |||
using NLog.Extensions.Logging; | |||
using System; | |||
using System.Threading.Tasks; | |||
using JT808.Protocol; | |||
using Microsoft.Extensions.Configuration; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Builder; | |||
using JT808.Gateway.Client; | |||
using JT808.Gateway.CleintBenchmark.Configs; | |||
using JT808.Gateway.CleintBenchmark.Services; | |||
namespace JT808.Gateway.CleintBenchmark | |||
{ | |||
class Program | |||
{ | |||
static async Task Main(string[] args) | |||
{ | |||
var serverHostBuilder = new HostBuilder() | |||
.ConfigureAppConfiguration((hostingContext, config) => | |||
{ | |||
config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); | |||
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); | |||
}) | |||
.ConfigureWebHostDefaults(webBuilder => | |||
{ | |||
webBuilder.Configure(app => | |||
{ | |||
app.UseRouting(); | |||
app.UseCors("Domain"); | |||
app.UseStaticFiles(); | |||
app.UseDefaultFiles("/index.html"); | |||
app.UseEndpoints(endpoints => | |||
{ | |||
endpoints.MapControllers(); | |||
}); | |||
}) | |||
.ConfigureServices((hostContext, services) => | |||
{ | |||
services.AddControllers(); | |||
services.AddCors(options => | |||
options.AddPolicy("Domain", builder => | |||
builder.AllowAnyOrigin() | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.AllowAnyOrigin())); | |||
}); | |||
}) | |||
.ConfigureLogging((context, logging) => | |||
{ | |||
if (Environment.OSVersion.Platform == PlatformID.Unix) | |||
{ | |||
NLog.LogManager.LoadConfiguration("Configs/nlog.unix.config"); | |||
} | |||
else | |||
{ | |||
NLog.LogManager.LoadConfiguration("Configs/nlog.win.config"); | |||
} | |||
logging.AddNLog(); | |||
logging.SetMinimumLevel(LogLevel.Trace); | |||
}) | |||
.ConfigureServices((hostContext, services) => | |||
{ | |||
services.Configure<ClientBenchmarkOptions>(hostContext.Configuration.GetSection("ClientBenchmarkOptions")); | |||
services.AddSingleton<ILoggerFactory, LoggerFactory>(); | |||
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); | |||
services.AddJT808Configure() | |||
.AddJT808Client(); | |||
services.AddHostedService<CleintBenchmarkHostedService>(); | |||
}); | |||
await serverHostBuilder.RunConsoleAsync(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,123 @@ | |||
using JT808.Gateway.CleintBenchmark.Configs; | |||
using JT808.Gateway.Client; | |||
using JT808.Protocol.Enums; | |||
using JT808.Protocol.Extensions; | |||
using JT808.Protocol.MessageBody; | |||
using Microsoft.Extensions.Hosting; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace JT808.Gateway.CleintBenchmark.Services | |||
{ | |||
public class CleintBenchmarkHostedService : IHostedService | |||
{ | |||
private readonly ClientBenchmarkOptions clientBenchmarkOptions; | |||
private readonly ILogger logger; | |||
private readonly IJT808TcpClientFactory jT808TcpClientFactory; | |||
private ConcurrentQueue<string> failDeviceNoQueue; | |||
public CleintBenchmarkHostedService( | |||
ILoggerFactory loggerFactory, | |||
IJT808TcpClientFactory jT808TcpClientFactory, | |||
IOptions<ClientBenchmarkOptions> clientBenchmarkOptionsAccessor) | |||
{ | |||
this.jT808TcpClientFactory = jT808TcpClientFactory; | |||
clientBenchmarkOptions = clientBenchmarkOptionsAccessor.Value; | |||
logger = loggerFactory.CreateLogger("CleintBenchmarkHostedService"); | |||
} | |||
public Task StartAsync(CancellationToken cancellationToken) | |||
{ | |||
logger.LogInformation("StartAsync..."); | |||
ThreadPool.GetMinThreads(out var minWorkerThreads, out var minCompletionPortThreads); | |||
ThreadPool.GetMaxThreads(out var maxWorkerThreads, out var maxCompletionPortThreads); | |||
logger.LogInformation($"GetMinThreads:{minWorkerThreads}-{minCompletionPortThreads}"); | |||
logger.LogInformation($"GetMaxThreads:{maxWorkerThreads}-{maxCompletionPortThreads}"); | |||
//先建立连接 | |||
failDeviceNoQueue = new ConcurrentQueue<string>(); | |||
for (int i=0;i< clientBenchmarkOptions.DeviceCount; i++) | |||
{ | |||
string deviceNo = (i + 1 + clientBenchmarkOptions.DeviceTemplate).ToString(); | |||
var client = jT808TcpClientFactory.Create(new JT808DeviceConfig(deviceNo, | |||
clientBenchmarkOptions.IP, | |||
clientBenchmarkOptions.Port), cancellationToken); | |||
if (client == null) | |||
{ | |||
failDeviceNoQueue.Enqueue(deviceNo); | |||
} | |||
} | |||
int successCount = clientBenchmarkOptions.DeviceCount - failDeviceNoQueue.Count; | |||
logger.LogInformation($"总连接数:{clientBenchmarkOptions.DeviceCount}"); | |||
logger.LogInformation($"已建立连接数:{successCount}"); | |||
logger.LogInformation($"失败连接数:{failDeviceNoQueue.Count}"); | |||
Task.Run(() => { | |||
while (!cancellationToken.IsCancellationRequested) | |||
{ | |||
if(failDeviceNoQueue.TryDequeue(out string deviceNo)) | |||
{ | |||
logger.LogInformation($"尝试重连{deviceNo}..."); | |||
var client = jT808TcpClientFactory.Create(new JT808DeviceConfig(deviceNo, | |||
clientBenchmarkOptions.IP, | |||
clientBenchmarkOptions.Port), cancellationToken); | |||
if (client == null) | |||
{ | |||
failDeviceNoQueue.Enqueue(deviceNo); | |||
} | |||
Thread.Sleep(1000); | |||
} | |||
else | |||
{ | |||
Thread.Sleep(3000); | |||
} | |||
} | |||
}, cancellationToken); | |||
ThreadPool.QueueUserWorkItem((state) => | |||
{ | |||
while (!cancellationToken.IsCancellationRequested) | |||
{ | |||
Parallel.ForEach(jT808TcpClientFactory.GetAll(), new ParallelOptions { MaxDegreeOfParallelism = 100 }, (item) => | |||
{ | |||
try | |||
{ | |||
int lat = new Random(1000).Next(100000, 180000); | |||
int Lng = new Random(1000).Next(100000, 180000); | |||
item.Send(JT808MsgId.位置信息汇报.Create(item.DeviceConfig.TerminalPhoneNo, new JT808_0x0200() | |||
{ | |||
Lat = lat, | |||
Lng = Lng, | |||
GPSTime = DateTime.Now, | |||
Speed = 50, | |||
Direction = 30, | |||
AlarmFlag = 5, | |||
Altitude = 50, | |||
StatusFlag = 10 | |||
})); | |||
} | |||
catch (Exception ex) | |||
{ | |||
logger.LogError(ex.Message); | |||
} | |||
}); | |||
Thread.Sleep(clientBenchmarkOptions.Interval); | |||
} | |||
}); | |||
return Task.CompletedTask; | |||
} | |||
public Task StopAsync(CancellationToken cancellationToken) | |||
{ | |||
jT808TcpClientFactory.Dispose(); | |||
logger.LogInformation("StopAsync..."); | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -0,0 +1,153 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<title>收发查看</title> | |||
<script src="https://unpkg.com/dayjs"></script> | |||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> | |||
<script src="/echarts.min.js"></script> | |||
</head> | |||
<body> | |||
<div id="tcpContainer" style="text-align:center;margin:20px auto;width:80%;height: 300px;"></div> | |||
<div id="connContainer" style="text-align:center;margin:20px auto;width:80%;height: 300px;"></div> | |||
<script type="text/javascript"> | |||
var tcpDom = document.getElementById("tcpContainer"); | |||
var connDom = document.getElementById("connContainer"); | |||
var tcpChart = echarts.init(tcpDom); | |||
var connChart = echarts.init(connDom); | |||
var sendData=[]; | |||
var receiveData=[]; | |||
var onlineData=[]; | |||
var offlineData=[]; | |||
var timeData=[]; | |||
var tcpOption = { | |||
title: { | |||
text: 'TCP收发数' | |||
}, | |||
tooltip: { | |||
trigger: 'axis', | |||
axisPointer: { | |||
animation: true | |||
} | |||
}, | |||
legend: { | |||
data:['发送总次数','接收总次数'] | |||
}, | |||
xAxis: { | |||
type: 'category', | |||
boundaryGap: false, | |||
data: timeData | |||
}, | |||
yAxis: { | |||
type: 'value', | |||
boundaryGap: [0, '100%'], | |||
splitLine: { | |||
show: true | |||
} | |||
}, | |||
series: [{ | |||
name: '发送总次数', | |||
type: 'line', | |||
color: "blue", | |||
data: sendData | |||
},{ | |||
name: '接收总次数', | |||
type: 'line', | |||
color: "red", | |||
data: receiveData | |||
}] | |||
}; | |||
var connOption = { | |||
title: { | |||
text: 'TCP连接数' | |||
}, | |||
tooltip: { | |||
trigger: 'axis', | |||
axisPointer: { | |||
animation: true | |||
} | |||
}, | |||
legend: { | |||
data:['tcp在线数','tcp离线数'] | |||
}, | |||
xAxis: { | |||
type: 'category', | |||
boundaryGap: false, | |||
data: timeData | |||
}, | |||
yAxis: { | |||
type: 'value', | |||
boundaryGap: [0, '100%'], | |||
splitLine: { | |||
show: true | |||
} | |||
}, | |||
series: [{ | |||
name: 'tcp在线数', | |||
type: 'line', | |||
color: "blue", | |||
data: onlineData | |||
},{ | |||
name: 'tcp离线数', | |||
type: 'line', | |||
color: "red", | |||
data: offlineData | |||
}] | |||
}; | |||
setInterval(function () { | |||
axios.post('http://localhost:5000/JT808WebApi/QueryReport') | |||
.then((response) => { | |||
if (response.data) { | |||
if(sendData.length>16){ | |||
sendData.shift(); | |||
receiveData.shift(); | |||
onlineData.shift(); | |||
offlineData.shift(); | |||
timeData.shift(); | |||
} | |||
//console.log(response.data); | |||
timeData.push(dayjs(response.data.currentDate).format('HH:mm:ss')); | |||
sendData.push(response.data.sendTotalCount); | |||
receiveData.push(response.data.receiveTotalCount); | |||
onlineData.push(response.data.onlineConnections); | |||
offlineData.push(response.data.offlineConnections); | |||
tcpChart.setOption({ | |||
series: [{ | |||
data: sendData | |||
},{ | |||
data: receiveData | |||
}], | |||
xAxis:[{ | |||
data: timeData | |||
}] | |||
}); | |||
connChart.setOption({ | |||
series: [{ | |||
data: onlineData | |||
},{ | |||
data: offlineData | |||
}], | |||
xAxis:[{ | |||
data: timeData | |||
}] | |||
}); | |||
} else { | |||
alert("没有数据"); | |||
} | |||
}) | |||
.catch((error) => { | |||
console.log(error); | |||
}); | |||
}, 1000); | |||
if (tcpOption && typeof tcpOption === "object") { | |||
tcpChart.setOption(tcpOption, true); | |||
} | |||
if (connOption && typeof connOption === "object") { | |||
connChart.setOption(connOption, true); | |||
} | |||
</script> | |||
</body> | |||
</html> |
@@ -0,0 +1,34 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<Import Project="..\Version.props" /> | |||
<PropertyGroup> | |||
<TargetFramework>netstandard2.1</TargetFramework> | |||
<LangVersion>8.0</LangVersion> | |||
<Copyright>Copyright 2019.</Copyright> | |||
<Authors>SmallChi(Koike)</Authors> | |||
<RepositoryUrl>https://github.com/SmallChi/JT808Gateway</RepositoryUrl> | |||
<PackageProjectUrl>https://github.com/SmallChi/JT808Gateway</PackageProjectUrl> | |||
<licenseUrl>https://github.com/SmallChi/JT808Gateway/blob/master/LICENSE</licenseUrl> | |||
<license>https://github.com/SmallChi/JT808Gateway/blob/master/LICENSE</license> | |||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> | |||
<Version>$(JT808GatewayPackageVersion)</Version> | |||
<SignAssembly>false</SignAssembly> | |||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | |||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | |||
<PackageId>JT808.Gateway.Client</PackageId> | |||
<Product>JT808.Gateway.Client</Product> | |||
<Description>基于pipeline实现的JT808客户端工具</Description> | |||
<PackageReleaseNotes>基于pipeline实现的JT808客户端工具</PackageReleaseNotes> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="JT808" Version="2.2.6" /> | |||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.1" /> | |||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | |||
<PackageReference Include="System.IO.Pipelines" Version="4.7.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.1" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,42 @@ | |||
using Microsoft.Extensions.DependencyInjection; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using JT808.Protocol; | |||
using Microsoft.Extensions.Configuration; | |||
using JT808.Gateway.Client.Services; | |||
namespace JT808.Gateway.Client | |||
{ | |||
public static class JT808ClientExtensions | |||
{ | |||
public static IJT808Builder AddJT808Client(this IJT808Builder jT808Builder) | |||
{ | |||
jT808Builder.Services.AddSingleton<JT808SendAtomicCounterService>(); | |||
jT808Builder.Services.AddSingleton<JT808ReceiveAtomicCounterService>(); | |||
jT808Builder.Services.AddSingleton<IJT808TcpClientFactory, JT808TcpClientFactory>(); | |||
jT808Builder.Services.Configure<JT808ReportOptions>((options)=> { }); | |||
jT808Builder.Services.AddHostedService<JT808ReportHostedService>(); | |||
return jT808Builder; | |||
} | |||
public static IJT808Builder AddJT808Client(this IJT808Builder jT808Builder, IConfiguration Configuration) | |||
{ | |||
jT808Builder.Services.AddSingleton<JT808SendAtomicCounterService>(); | |||
jT808Builder.Services.AddSingleton<JT808ReceiveAtomicCounterService>(); | |||
jT808Builder.Services.AddSingleton<IJT808TcpClientFactory, JT808TcpClientFactory>(); | |||
jT808Builder.Services.Configure<JT808ReportOptions>(Configuration.GetSection("JT808ReportOptions")); | |||
jT808Builder.Services.AddHostedService<JT808ReportHostedService>(); | |||
return jT808Builder; | |||
} | |||
public static IJT808Builder AddJT808Client(this IJT808Builder jT808Builder, Action<JT808ReportOptions> reportOptions) | |||
{ | |||
jT808Builder.Services.AddSingleton<JT808SendAtomicCounterService>(); | |||
jT808Builder.Services.AddSingleton<JT808ReceiveAtomicCounterService>(); | |||
jT808Builder.Services.AddSingleton<IJT808TcpClientFactory, JT808TcpClientFactory>(); | |||
jT808Builder.Services.Configure(reportOptions); | |||
jT808Builder.Services.AddHostedService<JT808ReportHostedService>(); | |||
return jT808Builder; | |||
} | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
using JT808.Protocol; | |||
using JT808.Protocol.Enums; | |||
using JT808.Protocol.Interfaces; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT808.Gateway.Client | |||
{ | |||
public class JT808DeviceConfig | |||
{ | |||
public JT808DeviceConfig(string terminalPhoneNo, string tcpHost,int tcpPort, JT808Version version= JT808Version.JTT2013) | |||
{ | |||
TerminalPhoneNo = terminalPhoneNo; | |||
TcpHost = tcpHost; | |||
TcpPort = tcpPort; | |||
Version = version; | |||
} | |||
public JT808Version Version { get; private set; } | |||
public string TerminalPhoneNo { get; private set; } | |||
public string TcpHost { get; private set; } | |||
public int TcpPort { get; private set; } | |||
/// <summary> | |||
/// 心跳时间(秒) | |||
/// </summary> | |||
public int Heartbeat { get; set; } = 30; | |||
public IJT808MsgSNDistributed MsgSNDistributed { get; } | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
using Microsoft.Extensions.Options; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Text; | |||
namespace JT808.Gateway.Client | |||
{ | |||
public class JT808ReportOptions | |||
{ | |||
public string FileName { get; set; } = $"JT808Report.{DateTime.Now.ToString("yyyyMMddHHssmm")}.txt"; | |||
public string FilePath { get; set; } = AppDomain.CurrentDomain.BaseDirectory; | |||
public string FileFullPath { get { return Path.Combine(FilePath, FileName); } } | |||
public int Interval { get; set; } = 3; | |||
public void FileExistsAndCreate() | |||
{ | |||
if(!File.Exists(FileFullPath)) | |||
{ | |||
File.Create(FileFullPath).Close(); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,269 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Threading; | |||
using System.IO.Pipelines; | |||
using System.Buffers; | |||
using JT808.Protocol; | |||
using Microsoft.Extensions.Logging; | |||
using JT808.Protocol.Exceptions; | |||
using JT808.Protocol.Extensions; | |||
using JT808.Gateway.Client.Services; | |||
using JT808.Gateway.Client.Metadata; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace JT808.Gateway.Client | |||
{ | |||
public class JT808TcpClient:IDisposable | |||
{ | |||
private bool disposed = false; | |||
private Socket clientSocket; | |||
private readonly ILogger Logger; | |||
private readonly JT808Serializer JT808Serializer; | |||
private readonly JT808SendAtomicCounterService SendAtomicCounterService; | |||
private readonly JT808ReceiveAtomicCounterService ReceiveAtomicCounterService; | |||
public JT808DeviceConfig DeviceConfig { get; } | |||
public JT808TcpClient( | |||
JT808DeviceConfig deviceConfig, | |||
IServiceProvider serviceProvider) | |||
{ | |||
DeviceConfig = deviceConfig; | |||
SendAtomicCounterService = serviceProvider.GetRequiredService<JT808SendAtomicCounterService>(); | |||
ReceiveAtomicCounterService = serviceProvider.GetRequiredService<JT808ReceiveAtomicCounterService>(); | |||
JT808Serializer = serviceProvider.GetRequiredService<IJT808Config>().GetSerializer(); | |||
Logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("JT808TcpClient"); | |||
} | |||
public async ValueTask<bool> ConnectAsync(EndPoint remoteEndPoint) | |||
{ | |||
clientSocket = new Socket(remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); | |||
try | |||
{ | |||
await clientSocket.ConnectAsync(remoteEndPoint); | |||
return true; | |||
} | |||
catch (Exception e) | |||
{ | |||
return false; | |||
} | |||
} | |||
public Task StartAsync(CancellationToken cancellationToken) | |||
{ | |||
Task.Run(async () => { | |||
await Task.Factory.StartNew(async (state) => | |||
{ | |||
var session = (Socket)state; | |||
if (Logger.IsEnabled(LogLevel.Information)) | |||
{ | |||
Logger.LogInformation($"[Connected]:{session.LocalEndPoint} to {session.RemoteEndPoint}"); | |||
} | |||
var pipe = new Pipe(); | |||
Task writing = FillPipeAsync(session, pipe.Writer); | |||
Task reading = ReadPipeAsync(session, pipe.Reader); | |||
await Task.WhenAll(reading, writing); | |||
}, clientSocket); | |||
}, cancellationToken); | |||
return Task.CompletedTask; | |||
} | |||
private async Task FillPipeAsync(Socket session, PipeWriter writer) | |||
{ | |||
while (true) | |||
{ | |||
try | |||
{ | |||
Memory<byte> memory = writer.GetMemory(10240); | |||
int bytesRead = await session.ReceiveAsync(memory, SocketFlags.None); | |||
if (bytesRead == 0) | |||
{ | |||
break; | |||
} | |||
writer.Advance(bytesRead); | |||
} | |||
catch (System.Net.Sockets.SocketException ex) | |||
{ | |||
Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message}]:{session.RemoteEndPoint}"); | |||
break; | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logger.LogError(ex, $"[Receive Error]:{session.RemoteEndPoint}"); | |||
break; | |||
} | |||
FlushResult result = await writer.FlushAsync(); | |||
if (result.IsCompleted) | |||
{ | |||
break; | |||
} | |||
} | |||
writer.Complete(); | |||
} | |||
private async Task ReadPipeAsync(Socket session, PipeReader reader) | |||
{ | |||
while (true) | |||
{ | |||
ReadResult result = await reader.ReadAsync(); | |||
if (result.IsCompleted) | |||
{ | |||
break; | |||
} | |||
ReadOnlySequence<byte> buffer = result.Buffer; | |||
SequencePosition consumed = buffer.Start; | |||
SequencePosition examined = buffer.End; | |||
try | |||
{ | |||
if (result.IsCanceled) break; | |||
if (buffer.Length > 0) | |||
{ | |||
ReaderBuffer(ref buffer, session, out consumed, out examined); | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
Close(); | |||
break; | |||
} | |||
finally | |||
{ | |||
reader.AdvanceTo(consumed, examined); | |||
} | |||
} | |||
reader.Complete(); | |||
} | |||
private void ReaderBuffer(ref ReadOnlySequence<byte> buffer, Socket session, out SequencePosition consumed, out SequencePosition examined) | |||
{ | |||
consumed = buffer.Start; | |||
examined = buffer.End; | |||
SequenceReader<byte> seqReader = new SequenceReader<byte>(buffer); | |||
if (seqReader.TryPeek(out byte beginMark)) | |||
{ | |||
if (beginMark != JT808Package.BeginFlag) throw new ArgumentException("Not JT808 Packages."); | |||
} | |||
byte mark = 0; | |||
long totalConsumed = 0; | |||
while (!seqReader.End) | |||
{ | |||
if (seqReader.IsNext(JT808Package.BeginFlag, advancePast: true)) | |||
{ | |||
if (mark == 1) | |||
{ | |||
try | |||
{ | |||
var package = JT808Serializer.HeaderDeserialize(seqReader.Sequence.Slice(totalConsumed, seqReader.Consumed - totalConsumed).FirstSpan); | |||
ReceiveAtomicCounterService.MsgSuccessIncrement(); | |||
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()}"); | |||
} | |||
catch (JT808Exception ex) | |||
{ | |||
Logger.LogError(ex, $"[HeaderDeserialize ErrorCode]:{ ex.ErrorCode}"); | |||
} | |||
totalConsumed += (seqReader.Consumed - totalConsumed); | |||
if (seqReader.End) break; | |||
seqReader.Advance(1); | |||
mark = 0; | |||
} | |||
mark++; | |||
} | |||
else | |||
{ | |||
seqReader.Advance(1); | |||
} | |||
} | |||
if (seqReader.Length == totalConsumed) | |||
{ | |||
examined = consumed = buffer.End; | |||
} | |||
else | |||
{ | |||
consumed = buffer.GetPosition(totalConsumed); | |||
} | |||
} | |||
public void Send(JT808ClientRequest message) | |||
{ | |||
if (disposed) return; | |||
if (IsOpen) | |||
{ | |||
if (message.Package != null) | |||
{ | |||
try | |||
{ | |||
var sendData = JT808Serializer.SerializeReadOnlySpan(message.Package, minBufferSize: message.MinBufferSize); | |||
clientSocket.Send(sendData); | |||
SendAtomicCounterService.MsgSuccessIncrement(); | |||
} | |||
catch (System.Net.Sockets.SocketException ex) | |||
{ | |||
Logger.LogError($"[{ex.SocketErrorCode.ToString()},{ex.Message}]"); | |||
} | |||
catch (System.Exception ex) | |||
{ | |||
Logger.LogError(ex.Message); | |||
} | |||
} | |||
else if (message.HexData != null) | |||
{ | |||
clientSocket.Send(message.HexData); | |||
SendAtomicCounterService.MsgSuccessIncrement(); | |||
} | |||
} | |||
} | |||
public void Close() | |||
{ | |||
if (disposed) return; | |||
var socket = clientSocket; | |||
if (socket == null) | |||
return; | |||
if (Interlocked.CompareExchange(ref clientSocket, null, socket) == socket) | |||
{ | |||
try | |||
{ | |||
clientSocket.Shutdown(SocketShutdown.Both); | |||
} | |||
finally | |||
{ | |||
clientSocket.Close(); | |||
} | |||
} | |||
} | |||
private void Dispose(bool disposing) | |||
{ | |||
if (disposed) return; | |||
if (disposing) | |||
{ | |||
// 清理托管资源 | |||
clientSocket.Dispose(); | |||
} | |||
disposed = true; | |||
} | |||
~JT808TcpClient() | |||
{ | |||
//必须为false | |||
//这表明,隐式清理时,只要处理非托管资源就可以了。 | |||
Dispose(false); | |||
} | |||
public void Dispose() | |||
{ | |||
//必须为true | |||
Dispose(true); | |||
//通知垃圾回收机制不再调用终结器(析构器) | |||
GC.SuppressFinalize(this); | |||
} | |||
public bool IsOpen | |||
{ | |||
get | |||
{ | |||
if (disposed) return false; | |||
if (clientSocket != null) | |||
{ | |||
return clientSocket.Connected; | |||
} | |||
return false; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
using JT808.Protocol; | |||
using JT808.Protocol.MessageBody; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using JT808.Protocol.Enums; | |||
using JT808.Protocol.Extensions; | |||
using JT808.Gateway.Client.Metadata; | |||
namespace JT808.Gateway.Client | |||
{ | |||
public static class JT808TcpClientExtensions | |||
{ | |||
public static void Send(this JT808TcpClient client, JT808Package package, int minBufferSize = 4096) | |||
{ | |||
package.Header.TerminalPhoneNo = client.DeviceConfig.TerminalPhoneNo; | |||
JT808ClientRequest request = new JT808ClientRequest(package, minBufferSize); | |||
client.Send(request); | |||
} | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace JT808.Gateway.Client | |||
{ | |||
public interface IJT808TcpClientFactory : IDisposable | |||
{ | |||
ValueTask<JT808TcpClient> Create(JT808DeviceConfig deviceConfig, CancellationToken cancellationToken); | |||
List<JT808TcpClient> GetAll(); | |||
} | |||
public class JT808TcpClientFactory: IJT808TcpClientFactory | |||
{ | |||
private readonly ConcurrentDictionary<string, JT808TcpClient> dict; | |||
private readonly IServiceProvider serviceProvider; | |||
public JT808TcpClientFactory(IServiceProvider serviceProvider) | |||
{ | |||
dict = new ConcurrentDictionary<string, JT808TcpClient>(StringComparer.OrdinalIgnoreCase); | |||
this.serviceProvider = serviceProvider; | |||
} | |||
public async ValueTask<JT808TcpClient> Create(JT808DeviceConfig deviceConfig, CancellationToken cancellationToken) | |||
{ | |||
if(dict.TryGetValue(deviceConfig.TerminalPhoneNo,out var client)) | |||
{ | |||
return client; | |||
} | |||
else | |||
{ | |||
JT808TcpClient jT808TcpClient = new JT808TcpClient(deviceConfig, serviceProvider); | |||
var successed= await jT808TcpClient.ConnectAsync(new IPEndPoint(IPAddress.Parse(deviceConfig.TcpHost), deviceConfig.TcpPort)); | |||
if (successed) | |||
{ | |||
await jT808TcpClient.StartAsync(cancellationToken); | |||
dict.TryAdd(deviceConfig.TerminalPhoneNo, jT808TcpClient); | |||
return jT808TcpClient; | |||
} | |||
return default; | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
foreach(var client in dict) | |||
{ | |||
try | |||
{ | |||
client.Value.Dispose(); | |||
} | |||
catch | |||
{ | |||
} | |||
} | |||
} | |||
public List<JT808TcpClient> GetAll() | |||
{ | |||
return dict.Values.ToList(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,49 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading; | |||
namespace JT808.Gateway.Client.Metadata | |||
{ | |||
/// <summary> | |||
/// | |||
/// <see cref="Grpc.Core.Internal"/> | |||
/// </summary> | |||
internal class JT808AtomicCounter | |||
{ | |||
long counter = 0; | |||
public JT808AtomicCounter(long initialCount = 0) | |||
{ | |||
this.counter = initialCount; | |||
} | |||
public void Reset() | |||
{ | |||
Interlocked.Exchange(ref counter, 0); | |||
} | |||
public long Increment() | |||
{ | |||
return Interlocked.Increment(ref counter); | |||
} | |||
public long Add(long len) | |||
{ | |||
return Interlocked.Add(ref counter,len); | |||
} | |||
public long Decrement() | |||
{ | |||
return Interlocked.Decrement(ref counter); | |||
} | |||
public long Count | |||
{ | |||
get | |||
{ | |||
return Interlocked.Read(ref counter); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
using JT808.Protocol; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
namespace JT808.Gateway.Client.Metadata | |||
{ | |||
public class JT808ClientRequest | |||
{ | |||
public JT808Package Package { get; } | |||
public byte[] HexData { get; } | |||
/// <summary> | |||
/// 根据实际情况适当调整包的大小 | |||
/// </summary> | |||
public int MinBufferSize { get;} | |||
public JT808ClientRequest(JT808Package package,int minBufferSize=1024) | |||
{ | |||
Package = package; | |||
MinBufferSize = minBufferSize; | |||
} | |||
public JT808ClientRequest(byte[] hexData) | |||
{ | |||
HexData = hexData; | |||
} | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT808.Gateway.Client.Metadata | |||
{ | |||
public class JT808Report | |||
{ | |||
public long SendTotalCount { get; set; } | |||
public long ReceiveTotalCount { get; set; } | |||
public DateTime CurrentDate { get; set; } | |||
public int Connections { get; set; } | |||
public int OnlineConnections { get; set; } | |||
public int OfflineConnections { get; set; } | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
using JT808.Gateway.Client.Metadata; | |||
namespace JT808.Gateway.Client.Services | |||
{ | |||
/// <summary> | |||
/// 接收计数包服务 | |||
/// </summary> | |||
public class JT808ReceiveAtomicCounterService | |||
{ | |||
private readonly JT808AtomicCounter MsgSuccessCounter; | |||
public JT808ReceiveAtomicCounterService() | |||
{ | |||
MsgSuccessCounter=new JT808AtomicCounter(); | |||
} | |||
public void Reset() | |||
{ | |||
MsgSuccessCounter.Reset(); | |||
} | |||
public long MsgSuccessIncrement() | |||
{ | |||
return MsgSuccessCounter.Increment(); | |||
} | |||
public long MsgSuccessCount | |||
{ | |||
get | |||
{ | |||
return MsgSuccessCounter.Count; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,67 @@ | |||
using JT808.Gateway.Client.Metadata; | |||
using Microsoft.Extensions.Hosting; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace JT808.Gateway.Client.Services | |||
{ | |||
public class JT808ReportHostedService : BackgroundService | |||
{ | |||
private readonly IOptionsMonitor<JT808ReportOptions> jT808ReportOptions; | |||
private readonly JT808ReceiveAtomicCounterService jT808ReceiveAtomicCounterService; | |||
private readonly JT808SendAtomicCounterService jT808SendAtomicCounterService; | |||
private readonly IJT808TcpClientFactory jT808TcpClientFactory; | |||
private readonly ILogger logger; | |||
public JT808ReportHostedService( | |||
ILoggerFactory loggerFactory, | |||
IOptionsMonitor<JT808ReportOptions> jT808ReportOptionsAccessor, | |||
JT808ReceiveAtomicCounterService jT808ReceiveAtomicCounterService, | |||
JT808SendAtomicCounterService jT808SendAtomicCounterService, | |||
IJT808TcpClientFactory jT808TcpClientFactory) | |||
{ | |||
logger = loggerFactory.CreateLogger("JT808ReportHostedService"); | |||
jT808ReportOptions = jT808ReportOptionsAccessor; | |||
jT808ReportOptions.CurrentValue.FileExistsAndCreate(); | |||
this.jT808ReceiveAtomicCounterService = jT808ReceiveAtomicCounterService; | |||
this.jT808SendAtomicCounterService = jT808SendAtomicCounterService; | |||
this.jT808TcpClientFactory = jT808TcpClientFactory; | |||
jT808ReportOptions.OnChange((options) => { options.FileExistsAndCreate(); }); | |||
} | |||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | |||
{ | |||
while (!stoppingToken.IsCancellationRequested) | |||
{ | |||
var clients = jT808TcpClientFactory.GetAll(); | |||
JT808Report report = new JT808Report() | |||
{ | |||
SendTotalCount = jT808SendAtomicCounterService.MsgSuccessCount, | |||
ReceiveTotalCount = jT808ReceiveAtomicCounterService.MsgSuccessCount, | |||
CurrentDate = DateTime.Now, | |||
Connections = clients.Count, | |||
OnlineConnections = clients.Where(w => w.IsOpen).Count(), | |||
OfflineConnections = clients.Where(w => !w.IsOpen).Count(), | |||
}; | |||
string json = JsonConvert.SerializeObject(report); | |||
if (logger.IsEnabled(LogLevel.Debug)) | |||
{ | |||
logger.LogDebug(json); | |||
} | |||
using (var sw=new StreamWriter(jT808ReportOptions.CurrentValue.FileFullPath,true)) | |||
{ | |||
sw.WriteLine(json); | |||
} | |||
await Task.Delay(TimeSpan.FromSeconds(jT808ReportOptions.CurrentValue.Interval), stoppingToken); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
using JT808.Gateway.Client.Metadata; | |||
namespace JT808.Gateway.Client.Services | |||
{ | |||
/// <summary> | |||
/// 发送计数包服务 | |||
/// </summary> | |||
public class JT808SendAtomicCounterService | |||
{ | |||
private readonly JT808AtomicCounter MsgSuccessCounter; | |||
public JT808SendAtomicCounterService() | |||
{ | |||
MsgSuccessCounter=new JT808AtomicCounter(); | |||
} | |||
public void Reset() | |||
{ | |||
MsgSuccessCounter.Reset(); | |||
} | |||
public long MsgSuccessIncrement() | |||
{ | |||
return MsgSuccessCounter.Increment(); | |||
} | |||
public long MsgSuccessCount | |||
{ | |||
get | |||
{ | |||
return MsgSuccessCounter.Count; | |||
} | |||
} | |||
} | |||
} |
@@ -86,7 +86,7 @@ namespace JT808.Gateway.Test | |||
[Fact] | |||
public void Test3() | |||
{ | |||
Assert.Throws<Exception>(() => | |||
Assert.Throws<ArgumentException>(() => | |||
{ | |||
var reader = new ReadOnlySequence<byte>(new byte[] { 0, 1, 2, 0x7E }); | |||
SequenceReader<byte> seqReader = new SequenceReader<byte>(reader); | |||
@@ -14,6 +14,7 @@ | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\JT808.Gateway.Client\JT808.Gateway.Client.csproj" /> | |||
<ProjectReference Include="..\JT808.Gateway.InMemoryMQ\JT808.Gateway.InMemoryMQ.csproj" /> | |||
<ProjectReference Include="..\JT808.Gateway.Kafka\JT808.Gateway.Kafka.csproj" /> | |||
<ProjectReference Include="..\JT808.Gateway.Services\JT808.Gateway.ReplyMessage\JT808.Gateway.ReplyMessage.csproj" /> | |||
@@ -0,0 +1,70 @@ | |||
using JT808.Gateway.Client; | |||
using JT808.Protocol.Enums; | |||
using JT808.Protocol.Extensions; | |||
using JT808.Protocol.MessageBody; | |||
using Microsoft.Extensions.Hosting; | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace JT808.Gateway.TestHosting.Jobs | |||
{ | |||
public class UpJob : IHostedService | |||
{ | |||
private readonly IJT808TcpClientFactory jT808TcpClientFactory; | |||
private readonly ILogger Logger; | |||
public UpJob( | |||
ILoggerFactory loggerFactory, | |||
IJT808TcpClientFactory jT808TcpClientFactory) | |||
{ | |||
Logger = loggerFactory.CreateLogger("UpJob"); | |||
this.jT808TcpClientFactory = jT808TcpClientFactory; | |||
} | |||
public Task StartAsync(CancellationToken cancellationToken) | |||
{ | |||
Task.Run(async () => | |||
{ | |||
await Task.Delay(2 * 1000); | |||
var client = await jT808TcpClientFactory.Create(new JT808DeviceConfig("1234567890", "127.0.0.1", 808), cancellationToken); | |||
if (client != null) | |||
{ | |||
while (!cancellationToken.IsCancellationRequested) | |||
{ | |||
try | |||
{ | |||
int lat = new Random(1000).Next(100000, 180000); | |||
int Lng = new Random(1000).Next(100000, 180000); | |||
client.Send(JT808MsgId.位置信息汇报.Create(client.DeviceConfig.TerminalPhoneNo, new JT808_0x0200() | |||
{ | |||
Lat = lat, | |||
Lng = Lng, | |||
GPSTime = DateTime.Now, | |||
Speed = 50, | |||
Direction = 30, | |||
AlarmFlag = 5, | |||
Altitude = 50, | |||
StatusFlag = 10 | |||
})); | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logger.LogError(ex.Message); | |||
} | |||
await Task.Delay(3 * 1000); | |||
} | |||
} | |||
}, cancellationToken); | |||
return Task.CompletedTask; | |||
} | |||
public Task StopAsync(CancellationToken cancellationToken) | |||
{ | |||
jT808TcpClientFactory.Dispose(); | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -10,6 +10,7 @@ using JT808.Gateway.TestHosting.Jobs; | |||
using JT808.Gateway.Kafka; | |||
using JT808.Gateway.InMemoryMQ; | |||
using JT808.Gateway.ReplyMessage; | |||
using JT808.Gateway.Client; | |||
namespace JT808.Gateway.TestHosting | |||
{ | |||
@@ -36,6 +37,7 @@ namespace JT808.Gateway.TestHosting | |||
services.AddSingleton<ILoggerFactory, LoggerFactory>(); | |||
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); | |||
services.AddJT808Configure() | |||
//.AddJT808Client() | |||
//.AddJT808Gateway(options => | |||
//{ | |||
// options.TcpPort = 808; | |||
@@ -53,7 +55,10 @@ namespace JT808.Gateway.TestHosting | |||
//.AddJT808ServerKafkaMsgReplyConsumer(hostContext.Configuration) | |||
//.AddJT808ServerKafkaSessionProducer(hostContext.Configuration) | |||
; | |||
//grpc客户端调用 | |||
//services.AddHostedService<CallGrpcClientJob>(); | |||
//客户端测试 | |||
//services.AddHostedService<UpJob>(); | |||
}); | |||
await serverHostBuilder.RunConsoleAsync(); | |||
@@ -1,6 +1,8 @@ | |||
{ | |||
{ | |||
"JT808Configuration": { | |||
"TcpPort": 808, | |||
"UdpPort": 808 | |||
"UdpPort": 808, | |||
"MiniNumBufferSize": 51200, | |||
"SoBacklog": 65535 | |||
} | |||
} |
@@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway", "JT808.Gate | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.TestHosting", "JT808.Gateway.TestHosting\JT808.Gateway.TestHosting.csproj", "{AE40AFE0-0950-442C-A74C-10CDF53E9F36}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.Test", "JT808.Gateway.Test\JT808.Gateway.Test.csproj", "{28C9BC4D-7B28-4E65-971A-7156E5EE0ADF}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.Abstractions", "JT808.Gateway.Abstractions\JT808.Gateway.Abstractions.csproj", "{3AA17DF7-A1B3-449C-93C2-45B051C32933}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.Kafka", "JT808.Gateway.Kafka\JT808.Gateway.Kafka.csproj", "{274C048E-A8E3-4422-A578-A10A97DF36F2}" | |||
@@ -29,6 +27,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.Traffic", "JT | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.InMemoryMQ", "JT808.Gateway.InMemoryMQ\JT808.Gateway.InMemoryMQ.csproj", "{F7460E8F-B23E-4407-8802-375DE37BED00}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.Client", "JT808.Gateway.Client\JT808.Gateway.Client.csproj", "{AC3070AC-A938-4213-A562-C079BB4A3F9E}" | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{7CBAACEE-19BF-499A-8C41-36A1324D45E9}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.Test", "JT808.Gateway.Test\JT808.Gateway.Test.csproj", "{1E230D1B-BDFC-4D0A-9B34-592280A723BD}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.Gateway.CleintBenchmark", "JT808.Gateway.CleintBenchmark\JT808.Gateway.CleintBenchmark.csproj", "{0FBC9E4F-8585-4820-ACF1-145A14B3A727}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug|Any CPU = Debug|Any CPU | |||
@@ -43,10 +49,6 @@ Global | |||
{AE40AFE0-0950-442C-A74C-10CDF53E9F36}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{AE40AFE0-0950-442C-A74C-10CDF53E9F36}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{AE40AFE0-0950-442C-A74C-10CDF53E9F36}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{28C9BC4D-7B28-4E65-971A-7156E5EE0ADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{28C9BC4D-7B28-4E65-971A-7156E5EE0ADF}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{28C9BC4D-7B28-4E65-971A-7156E5EE0ADF}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{28C9BC4D-7B28-4E65-971A-7156E5EE0ADF}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{3AA17DF7-A1B3-449C-93C2-45B051C32933}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{3AA17DF7-A1B3-449C-93C2-45B051C32933}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{3AA17DF7-A1B3-449C-93C2-45B051C32933}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
@@ -83,17 +85,31 @@ Global | |||
{F7460E8F-B23E-4407-8802-375DE37BED00}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{F7460E8F-B23E-4407-8802-375DE37BED00}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{F7460E8F-B23E-4407-8802-375DE37BED00}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{AC3070AC-A938-4213-A562-C079BB4A3F9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{AC3070AC-A938-4213-A562-C079BB4A3F9E}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{AC3070AC-A938-4213-A562-C079BB4A3F9E}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{AC3070AC-A938-4213-A562-C079BB4A3F9E}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{1E230D1B-BDFC-4D0A-9B34-592280A723BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{1E230D1B-BDFC-4D0A-9B34-592280A723BD}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{1E230D1B-BDFC-4D0A-9B34-592280A723BD}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{1E230D1B-BDFC-4D0A-9B34-592280A723BD}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{0FBC9E4F-8585-4820-ACF1-145A14B3A727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{0FBC9E4F-8585-4820-ACF1-145A14B3A727}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{0FBC9E4F-8585-4820-ACF1-145A14B3A727}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{0FBC9E4F-8585-4820-ACF1-145A14B3A727}.Release|Any CPU.Build.0 = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
EndGlobalSection | |||
GlobalSection(NestedProjects) = preSolution | |||
{AE40AFE0-0950-442C-A74C-10CDF53E9F36} = {7CBAACEE-19BF-499A-8C41-36A1324D45E9} | |||
{D62E3054-6924-4F1A-9BEF-E52B191F16B6} = {3EF8490D-C993-49D8-8A3D-493B7F259D70} | |||
{A242A839-4F00-4434-A7E8-7E3BEBA5B75C} = {3EF8490D-C993-49D8-8A3D-493B7F259D70} | |||
{1CB84599-5F56-4461-A451-DF16E3854AB9} = {3EF8490D-C993-49D8-8A3D-493B7F259D70} | |||
{604BB5CF-9ED1-4D78-9328-59436E2B4EB4} = {3EF8490D-C993-49D8-8A3D-493B7F259D70} | |||
{598E445A-AF2E-42F0-98F4-18EC22E473FC} = {3EF8490D-C993-49D8-8A3D-493B7F259D70} | |||
{8FCC6D65-8A49-4AE7-8B19-F255100849D6} = {3EF8490D-C993-49D8-8A3D-493B7F259D70} | |||
{1E230D1B-BDFC-4D0A-9B34-592280A723BD} = {7CBAACEE-19BF-499A-8C41-36A1324D45E9} | |||
EndGlobalSection | |||
GlobalSection(ExtensibilityGlobals) = postSolution | |||
SolutionGuid = {AA9303A7-6FB3-4572-88AA-3302E85330D1} | |||
@@ -1,6 +1,6 @@ | |||
<Project> | |||
<PropertyGroup> | |||
<JT808DotNettyPackageVersion>2.3.1</JT808DotNettyPackageVersion> | |||
<JT808GatewayPackageVersion>1.0.0-preview3</JT808GatewayPackageVersion> | |||
<JT808GatewayPackageVersion>1.0.0-preview4</JT808GatewayPackageVersion> | |||
</PropertyGroup> | |||
</Project> |