From 9b877abb95f66c29570b7a26f9e6b6f826ed4612 Mon Sep 17 00:00:00 2001 From: SmallChi <564952747@qq.com> Date: Wed, 2 Jan 2019 20:06:56 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=E8=AE=A1=E6=95=B0=E5=99=A8?= =?UTF-8?q?=E6=AF=8F=E6=97=A5=E6=B8=85=E9=9B=B6job=202.=E5=8E=BB=E6=8E=89?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E9=85=8D=E7=BD=AE=E5=B1=9E=E6=80=A7=E9=A1=B9?= =?UTF-8?q?=203.=E6=9B=B4=E6=96=B0JT808=E6=A8=A1=E5=9D=97=204.=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=96=87=E6=A1=A3=E5=8F=8Aapi=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 58 +++++- api/README.md | 180 +++++++++++------- doc/img/demo1.png | Bin 0 -> 44575 bytes .../Configurations/JT808Configuration.cs | 5 - .../JT808.DotNetty.Core.csproj | 6 + .../JT808BackgroundService.cs | 78 ++++++++ .../Jobs/JT808TcpAtomicCouterResetDailyJob.cs | 35 ++++ .../Jobs/JT808UdpAtomicCouterResetDailyJob.cs | 39 ++++ .../Metadata/JT808AtomicCounter.cs | 5 + .../Services/JT808TcpAtomicCounterService.cs | 6 + .../Services/JT808UdpAtomicCounterService.cs | 6 + .../Handlers/JT808MsgIdTcpCustomHandler.cs | 28 +++ .../Handlers/JT808MsgIdUdpCustomHandler.cs | 28 +++ src/JT808.DotNetty.Hosting/Program.cs | 8 +- .../JT808TcpDotnettyExtensions.cs | 2 + .../JT808UdpDotnettyExtensions.cs | 2 + src/JT808.Protocol | 2 +- 17 files changed, 404 insertions(+), 84 deletions(-) create mode 100644 doc/img/demo1.png create mode 100644 src/JT808.DotNetty.Core/JT808BackgroundService.cs create mode 100644 src/JT808.DotNetty.Core/Jobs/JT808TcpAtomicCouterResetDailyJob.cs create mode 100644 src/JT808.DotNetty.Core/Jobs/JT808UdpAtomicCouterResetDailyJob.cs create mode 100644 src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdTcpCustomHandler.cs create mode 100644 src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdUdpCustomHandler.cs diff --git a/README.md b/README.md index 5a01f96..78c4fa5 100644 --- a/README.md +++ b/README.md @@ -21,18 +21,62 @@ ![design_model](https://github.com/SmallChi/JT808DotNetty/blob/master/doc/img/design_model.png) -## 集成功能实现 +## 基于Tcp的消息业务处理程序(JT808.DotNetty.Tcp) -### 1.集成原包分发器 +通过继承JT808.DotNetty.Core.Handlers.JT808MsgIdTcpHandlerBase去实现自定义的消息业务处理程序。 -### 2.集成WebApi服务器 +## 基于Udp的消息业务处理程序(JT808.DotNetty.Udp) -[WebApi接口服务](https://github.com/SmallChi/JT808DotNetty/blob/master/api/README.md) +通过继承JT808.DotNetty.Core.Handlers.JT808MsgIdUdpHandlerBase去实现自定义的消息业务处理程序。 -### 3.集成会话通知(在线/离线) +## 基于WebApi的消息业务处理程序(JT808.DotNetty.WebApi) -使用场景:有些超长待机的设备,不会实时保持连接,那么通过平台下发的命令是无法到达的,这时候就需要设备一上线,就即时通知服务去处理,然后在即时的下发消息到设备。 +通过继承JT808.DotNetty.Core.Handlers.JT808MsgIdHttpHandlerBase去实现自定义的WebApi接口服务。 + +[WebApi公共接口服务](https://github.com/SmallChi/JT808DotNetty/blob/master/api/README.md) + +## 集成接口功能(JT808.DotNetty.Abstractions) + +|接口名称|接口说明|使用场景| +|:------:|:------|:------| +| IJT808SessionPublishing| 会话通知(在线/离线)| 有些超长待机的设备,不会实时保持连接,那么通过平台下发的命令是无法到达的,这时候就需要设备一上线,就即时通知服务去处理,然后在即时的下发消息到设备。| +| IJT808SourcePackageDispatcher| 原包分发器| 需要将源数据转给其他平台| > 只要实现IJT808SessionPublishing接口的任意一款MQ都能实现该功能。 -### 4.集成业务消息处理程序 \ No newline at end of file +## 参考1 + +``` demo1 +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); + }) + .ConfigureLogging((context, logging) => + { + logging.AddConsole(); + logging.SetMinimumLevel(LogLevel.Trace); + }) + .ConfigureServices((hostContext, services) => + { + services.AddSingleton(); + services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)) + services.AddJT808Core(hostContext.Configuration) + .AddJT808TcpHost() + .AddJT808UdpHost() + .AddJT808WebApiHost(); + // 自定义Tcp消息处理业务 + services.Replace(new ServiceDescriptor(typeof(JT808MsgIdTcpHandlerBase), typeof(JT808MsgIdTcpCustomHandler), ServiceLifetime.Singleton)); + // 自定义Udp消息处理业务 + services.Replace(new ServiceDescriptor(typeof(JT808MsgIdUdpHandlerBase), typeof(JT808MsgIdUdpCustomHandler), ServiceLifetime.Singleton)); + }); + + await serverHostBuilder.RunConsoleAsync(); +} +``` + +如图所示: +![demo1](https://github.com/SmallChi/JT808DotNetty/blob/master/doc/img/demo1.png) \ No newline at end of file diff --git a/api/README.md b/api/README.md index f552e1c..90b9747 100644 --- a/api/README.md +++ b/api/README.md @@ -6,15 +6,27 @@ 默认端口:828 -## [统一下发设备消息服务](#send) +## 1.统一下发设备消息服务 -## [管理会话服务](#session) +[基于Tcp统一下发设备消息服务](#tcp_send) -## [原包分发器通道服务](#sourcepackage) +[基于Udp统一下发设备消息服务](#udp_send) -## [转发地址过滤服务](#transmit) +## 2.管理会话服务 -## [消息包计数服务](#counter) +[基于Tcp管理会话服务](#tcp_session) + +[基于Udp管理会话服务](#udp_session) + +## 3.转发地址过滤服务 + +[基于Tcp转发地址过滤服务](#tcp_transmit) + +## 4.消息包计数服务 + +[基于Tcp消息包计数服务](#tcp_counter) + +[基于Udp消息包计数服务](#udp_counter) ### 统一对象返回 JT808ResultDto\ @@ -33,9 +45,38 @@ | 404 | 没有该服务 | | 500 | 服务内部错误 | -### 统一下发设备消息接口 +### 基于Tcp统一下发设备消息服务 + +请求地址:UnificationTcpSend + +请求方式:POST + +请求参数: + +|属性|数据类型|参数说明| +|------|:------:|:------| +| TerminalPhoneNo| string| 设备终端号| +| Data| byte[]| JT808 byte[]数组| + +返回数据: + +|属性|数据类型|参数说明| +|:------:|:------:|:------| +| Data| bool| 是否成功| + +返回结果: + +``` result1 +{ + "Message":"", + "Code":200, + "Data":true +} +``` + +### 基于Udp统一下发设备消息服务 -请求地址:UnificationSend +请求地址:UnificationUdpSend 请求方式:POST @@ -62,13 +103,12 @@ } ``` -### 会话服务接口 +### 基于Tcp管理会话服务 -#### 统一会话信息对象返回 JT808SessionInfoDto +#### 统一会话信息对象返回 JT808TcpSessionInfoDto |属性|数据类型|参数说明| |------|------|------| -| ChannelId| string| 通道Id| | LastActiveTime| DateTime| 最后上线时间| | StartTime| DateTime| 上线时间| | TerminalPhoneNo|string| 终端手机号| @@ -76,7 +116,7 @@ #### 1.获取会话集合 -请求地址:Session/GetAll +请求地址:Session/Tcp/GetAll 请求方式:GET @@ -84,7 +124,7 @@ |属性|数据类型|参数说明| |:------:|:------:|:------| -| Data| List\ | 实际会话信息集合 | +| Data| List\ | 实际会话信息集合 | 返回结果: @@ -94,13 +134,11 @@ "Code":200, "Data":[ { - "ChannelId":"eadad23", "LastActiveTime":"2018-11-27 20:00:00", "StartTime":"2018-11-25 20:00:00", "TerminalPhoneNo":"123456789012", "RemoteAddressIP":"127.0.0.1:11808" },{ - "ChannelId":"eadad23", "LastActiveTime":"2018-11-27 20:00:00", "StartTime":"2018-11-25 20:00:00", "TerminalPhoneNo":"123456789013", @@ -112,7 +150,7 @@ #### 2.通过设备终端号移除对应会话 -请求地址:Session/RemoveByTerminalPhoneNo +请求地址:Session/Tcp/RemoveByTerminalPhoneNo 请求方式:POST @@ -138,11 +176,11 @@ } ``` -### 原包分发器通道服务 +### 基于Tcp转发地址过滤服务 -#### 1.添加原包转发地址 +#### 1.添加转发过滤地址 -请求地址:SourcePackage/Add +请求地址:Transmit/Add 请求方式:POST @@ -161,7 +199,7 @@ 返回结果: -``` sp1 +``` tr1 { "Message":"", "Code":200, @@ -169,77 +207,54 @@ } ``` -#### 2.删除原包转发地址(不能删除在网关服务器配置文件配的地址) +### 基于Udp管理会话服务 -请求地址:SourcePackage/Remove - -请求方式:POST - -请求参数: +#### 统一会话信息对象返回 JT808UdpSessionInfoDto |属性|数据类型|参数说明| -|:------:|:------:|:------| -| Host| string| ip地址| -| Port| int| 端口号| - -返回数据: - -|属性|数据类型|参数说明| -|:------:|:------:|:------| -| Data| bool | 是否成功| - -返回结果: - -``` sp2 -{ - "Message":"", - "Code":200, - "Data":true -} -``` +|------|------|------| +| LastActiveTime| DateTime| 最后上线时间| +| StartTime| DateTime| 上线时间| +| TerminalPhoneNo|string| 终端手机号| +| RemoteAddressIP| string| 远程ip地址| -#### 3.获取原包信息集合 +#### 1.获取会话集合 -请求地址:SourcePackage/GetAll +请求地址:Session/Udp/GetAll 请求方式:GET 返回数据: |属性|数据类型|参数说明| -|------|:------:|:------| -| RemoteAddress| string | 远程ip地址| -| Registered| bool | 通道是否注册| -| Active| bool | 通道是否激活| -| Open| bool | 通道是否打开| +|:------:|:------:|:------| +| Data| List\ | 实际会话信息集合 | 返回结果: -``` sp3 +``` session1 { "Message":"", "Code":200, "Data":[ - { - "RemoteAddress":"127.0.0.1:6665", - "Registered":true, - "Active":true, - "Open":true + { + "LastActiveTime":"2018-11-27 20:00:00", + "StartTime":"2018-11-25 20:00:00", + "TerminalPhoneNo":"123456789012", + "RemoteAddressIP":"127.0.0.1:11808" },{ - "RemoteAddress":"127.0.0.1:6667", - "Registered":true, - "Active":true, - "Open":true + "LastActiveTime":"2018-11-27 20:00:00", + "StartTime":"2018-11-25 20:00:00", + "TerminalPhoneNo":"123456789013", + "RemoteAddressIP":"127.0.0.1:11808" } ] } ``` -### 转发地址过滤服务 - -#### 1.添加转发过滤地址 +#### 2.通过设备终端号移除对应会话 -请求地址:Transmit/Add +请求地址:Session/Udp/RemoveByTerminalPhoneNo 请求方式:POST @@ -247,8 +262,7 @@ |属性|数据类型|参数说明| |:------:|:------:|:------| -| Host| string| ip地址| -| Port| int| 端口号| +| terminalPhoneNo| string| 设备终端号| 返回数据: @@ -258,7 +272,7 @@ 返回结果: -``` tr1 +``` session3 { "Message":"", "Code":200, @@ -320,9 +334,9 @@ } ``` -### 计数服务接口 +### 基于Tcp消息包计数服务 -请求地址:GetAtomicCounter +请求地址:GetTcpAtomicCounter 请求方式:GET @@ -344,4 +358,30 @@ "MsgFailCount":0 } } +``` + +### 基于Udp消息包计数服务 + +请求地址:GetUdpAtomicCounter + +请求方式:GET + +返回数据: + +|属性|数据类型|参数说明| +|------|:------:|:------| +| MsgSuccessCount| long| 消息包成功数| +| MsgFailCount| long| 消息包失败数| + +返回结果: + +``` counter +{ + "Message":"", + "Code":200, + "Data":{ + "MsgSuccessCount":1000, + "MsgFailCount":0 + } +} ``` \ No newline at end of file diff --git a/doc/img/demo1.png b/doc/img/demo1.png new file mode 100644 index 0000000000000000000000000000000000000000..da66b6583c4ba7d8a972838cfb0ada6d4e04e5ab GIT binary patch literal 44575 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 public int WebApiPort { get; set; } = 828; - /// - /// 源包分发器配置 - /// - public List SourcePackageDispatcherClientConfigurations { get; set; } - /// /// 转发远程地址 (可选项)知道转发的地址有利于提升性能 /// 按照808的消息,有些请求必须要应答,但是转发可以不需要有应答可以节省部分资源包括: diff --git a/src/JT808.DotNetty.Core/JT808.DotNetty.Core.csproj b/src/JT808.DotNetty.Core/JT808.DotNetty.Core.csproj index 01d9fc8..7c52f0b 100644 --- a/src/JT808.DotNetty.Core/JT808.DotNetty.Core.csproj +++ b/src/JT808.DotNetty.Core/JT808.DotNetty.Core.csproj @@ -31,4 +31,10 @@ + + + C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.hosting.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Hosting.Abstractions.dll + + + diff --git a/src/JT808.DotNetty.Core/JT808BackgroundService.cs b/src/JT808.DotNetty.Core/JT808BackgroundService.cs new file mode 100644 index 0000000..9c9e512 --- /dev/null +++ b/src/JT808.DotNetty.Core/JT808BackgroundService.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace JT808.DotNetty.Core +{ + /// + /// + /// + /// + public abstract class JT808BackgroundService : IHostedService, IDisposable + { + /// + /// 默认次日过期 + /// + public virtual TimeSpan DelayTimeSpan + { + get + { + DateTime current = DateTime.Now; +#if DEBUG + DateTime tmp = current.AddSeconds(10); +#else + DateTime tmp = current.Date.AddDays(1).AddMilliseconds(-1); +#endif + return tmp.Subtract(current); + } + set { } + } + + private Task _executingTask; + + private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); + + protected abstract Task ExecuteAsync(CancellationToken stoppingToken); + + public void Dispose() + { + _stoppingCts.Cancel(); + } + + public virtual Task StartAsync(CancellationToken cancellationToken) + { + // Store the task we're executing + _executingTask = ExecuteAsync(_stoppingCts.Token); + // If the task is completed then return it, + // this will bubble cancellation and failure to the caller + if (_executingTask.IsCompleted) + { + return _executingTask; + } + // Otherwise it's running + return Task.CompletedTask; + } + + public virtual async Task StopAsync(CancellationToken cancellationToken) + { + // Stop called without start + if (_executingTask == null) + { + return; + } + try + { + // Signal cancellation to the executing method + _stoppingCts.Cancel(); + } + finally + { + // Wait until the task completes or the stop token triggers + await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,cancellationToken)); + } + } + } +} diff --git a/src/JT808.DotNetty.Core/Jobs/JT808TcpAtomicCouterResetDailyJob.cs b/src/JT808.DotNetty.Core/Jobs/JT808TcpAtomicCouterResetDailyJob.cs new file mode 100644 index 0000000..8894a01 --- /dev/null +++ b/src/JT808.DotNetty.Core/Jobs/JT808TcpAtomicCouterResetDailyJob.cs @@ -0,0 +1,35 @@ +using JT808.DotNetty.Core.Services; +using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; + +namespace JT808.DotNetty.Core.Jobs +{ + public class JT808TcpAtomicCouterResetDailyJob : JT808BackgroundService + { + private readonly ILogger _logger; + + private readonly JT808TcpAtomicCounterService _jT808TcpAtomicCounterService; + + public JT808TcpAtomicCouterResetDailyJob( + JT808TcpAtomicCounterService jT808TcpAtomicCounterService, + ILoggerFactory loggerFactory) + { + _jT808TcpAtomicCounterService = jT808TcpAtomicCounterService; + _logger =loggerFactory.CreateLogger(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation($"{nameof(JT808TcpAtomicCouterResetDailyJob)} is starting."); + stoppingToken.Register(() => _logger.LogInformation($"{nameof(JT808TcpAtomicCouterResetDailyJob)} background task is stopping.")); + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation($"{nameof(JT808TcpAtomicCouterResetDailyJob)} task doing background work."); + _jT808TcpAtomicCounterService.Reset(); + await Task.Delay(DelayTimeSpan, stoppingToken); + } + _logger.LogInformation($"{nameof(JT808TcpAtomicCouterResetDailyJob)} background task is stopping."); + } + } +} diff --git a/src/JT808.DotNetty.Core/Jobs/JT808UdpAtomicCouterResetDailyJob.cs b/src/JT808.DotNetty.Core/Jobs/JT808UdpAtomicCouterResetDailyJob.cs new file mode 100644 index 0000000..973143d --- /dev/null +++ b/src/JT808.DotNetty.Core/Jobs/JT808UdpAtomicCouterResetDailyJob.cs @@ -0,0 +1,39 @@ +using JT808.DotNetty.Core.Services; +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.DotNetty.Core.Jobs +{ + public class JT808UdpAtomicCouterResetDailyJob : JT808BackgroundService + { + private readonly ILogger _logger; + + private readonly JT808UdpAtomicCounterService _jT808UdpAtomicCounterService; + + public JT808UdpAtomicCouterResetDailyJob( + JT808UdpAtomicCounterService jT808UdpAtomicCounterService, + ILoggerFactory loggerFactory) + { + _jT808UdpAtomicCounterService = jT808UdpAtomicCounterService; + _logger =loggerFactory.CreateLogger(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation($"{nameof(JT808UdpAtomicCouterResetDailyJob)} is starting."); + stoppingToken.Register(() => _logger.LogInformation($"{nameof(JT808UdpAtomicCouterResetDailyJob)} background task is stopping.")); + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation($"{nameof(JT808UdpAtomicCouterResetDailyJob)} task doing background work."); + _jT808UdpAtomicCounterService.Reset(); + await Task.Delay(DelayTimeSpan, stoppingToken); + } + _logger.LogInformation($"{nameof(JT808UdpAtomicCouterResetDailyJob)} background task is stopping."); + } + } +} diff --git a/src/JT808.DotNetty.Core/Metadata/JT808AtomicCounter.cs b/src/JT808.DotNetty.Core/Metadata/JT808AtomicCounter.cs index c0d68c5..72bc92a 100644 --- a/src/JT808.DotNetty.Core/Metadata/JT808AtomicCounter.cs +++ b/src/JT808.DotNetty.Core/Metadata/JT808AtomicCounter.cs @@ -18,6 +18,11 @@ namespace JT808.DotNetty.Core.Metadata this.counter = initialCount; } + public void Reset() + { + Interlocked.Exchange(ref counter, 0); + } + public long Increment() { return Interlocked.Increment(ref counter); diff --git a/src/JT808.DotNetty.Core/Services/JT808TcpAtomicCounterService.cs b/src/JT808.DotNetty.Core/Services/JT808TcpAtomicCounterService.cs index 069fb51..5c3fa0e 100644 --- a/src/JT808.DotNetty.Core/Services/JT808TcpAtomicCounterService.cs +++ b/src/JT808.DotNetty.Core/Services/JT808TcpAtomicCounterService.cs @@ -16,6 +16,12 @@ namespace JT808.DotNetty.Core.Services } + public void Reset() + { + MsgSuccessCounter.Reset(); + MsgFailCounter.Reset(); + } + public long MsgSuccessIncrement() { return MsgSuccessCounter.Increment(); diff --git a/src/JT808.DotNetty.Core/Services/JT808UdpAtomicCounterService.cs b/src/JT808.DotNetty.Core/Services/JT808UdpAtomicCounterService.cs index a5d0697..7c17fbf 100644 --- a/src/JT808.DotNetty.Core/Services/JT808UdpAtomicCounterService.cs +++ b/src/JT808.DotNetty.Core/Services/JT808UdpAtomicCounterService.cs @@ -16,6 +16,12 @@ namespace JT808.DotNetty.Core.Services } + public void Reset() + { + MsgSuccessCounter.Reset(); + MsgFailCounter.Reset(); + } + public long MsgSuccessIncrement() { return MsgSuccessCounter.Increment(); diff --git a/src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdTcpCustomHandler.cs b/src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdTcpCustomHandler.cs new file mode 100644 index 0000000..ba23262 --- /dev/null +++ b/src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdTcpCustomHandler.cs @@ -0,0 +1,28 @@ +using JT808.DotNetty.Core; +using JT808.DotNetty.Core.Handlers; +using JT808.DotNetty.Core.Metadata; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JT808.DotNetty.Hosting.Handlers +{ + public class JT808MsgIdTcpCustomHandler : JT808MsgIdTcpHandlerBase + { + public JT808MsgIdTcpCustomHandler( + ILoggerFactory loggerFactory, + JT808TcpSessionManager sessionManager) : base(sessionManager) + { + logger = loggerFactory.CreateLogger(); + } + + private readonly ILogger logger; + + public override JT808Response Msg0x0200(JT808Request request) + { + logger.LogDebug("Tcp_Msg0x0200"); + return base.Msg0x0200(request); + } + } +} diff --git a/src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdUdpCustomHandler.cs b/src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdUdpCustomHandler.cs new file mode 100644 index 0000000..d35eafc --- /dev/null +++ b/src/JT808.DotNetty.Hosting/Handlers/JT808MsgIdUdpCustomHandler.cs @@ -0,0 +1,28 @@ +using JT808.DotNetty.Core; +using JT808.DotNetty.Core.Handlers; +using JT808.DotNetty.Core.Metadata; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JT808.DotNetty.Hosting.Handlers +{ + public class JT808MsgIdUdpCustomHandler : JT808MsgIdUdpHandlerBase + { + public JT808MsgIdUdpCustomHandler( + ILoggerFactory loggerFactory, + JT808UdpSessionManager sessionManager) : base(sessionManager) + { + logger = loggerFactory.CreateLogger(); + } + + private readonly ILogger logger; + + public override JT808Response Msg0x0200(JT808Request request) + { + logger.LogDebug("Udp_Msg0x0200"); + return base.Msg0x0200(request); + } + } +} diff --git a/src/JT808.DotNetty.Hosting/Program.cs b/src/JT808.DotNetty.Hosting/Program.cs index 57c4630..d92f966 100644 --- a/src/JT808.DotNetty.Hosting/Program.cs +++ b/src/JT808.DotNetty.Hosting/Program.cs @@ -1,4 +1,6 @@ using JT808.DotNetty.Core; +using JT808.DotNetty.Core.Handlers; +using JT808.DotNetty.Hosting.Handlers; using JT808.DotNetty.Tcp; using JT808.DotNetty.Udp; using JT808.DotNetty.WebApi; @@ -33,11 +35,15 @@ namespace JT808.DotNetty.Hosting .ConfigureServices((hostContext, services) => { services.AddSingleton(); - services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); + services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); services.AddJT808Core(hostContext.Configuration) .AddJT808TcpHost() .AddJT808UdpHost() .AddJT808WebApiHost(); + // 自定义Tcp消息处理业务 + services.Replace(new ServiceDescriptor(typeof(JT808MsgIdTcpHandlerBase), typeof(JT808MsgIdTcpCustomHandler), ServiceLifetime.Singleton)); + // 自定义Udp消息处理业务 + services.Replace(new ServiceDescriptor(typeof(JT808MsgIdUdpHandlerBase), typeof(JT808MsgIdUdpCustomHandler), ServiceLifetime.Singleton)); }); await serverHostBuilder.RunConsoleAsync(); diff --git a/src/JT808.DotNetty.Tcp/JT808TcpDotnettyExtensions.cs b/src/JT808.DotNetty.Tcp/JT808TcpDotnettyExtensions.cs index db194ce..1db92cc 100644 --- a/src/JT808.DotNetty.Tcp/JT808TcpDotnettyExtensions.cs +++ b/src/JT808.DotNetty.Tcp/JT808TcpDotnettyExtensions.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using System; using System.Reflection; using System.Runtime.CompilerServices; +using JT808.DotNetty.Core.Jobs; [assembly: InternalsVisibleTo("JT808.DotNetty.Tcp.Test")] @@ -27,6 +28,7 @@ namespace JT808.DotNetty.Tcp serviceDescriptors.TryAddScoped(); serviceDescriptors.TryAddScoped(); serviceDescriptors.TryAddScoped(); + serviceDescriptors.AddHostedService(); serviceDescriptors.AddHostedService(); return serviceDescriptors; } diff --git a/src/JT808.DotNetty.Udp/JT808UdpDotnettyExtensions.cs b/src/JT808.DotNetty.Udp/JT808UdpDotnettyExtensions.cs index dbc16f4..7662744 100644 --- a/src/JT808.DotNetty.Udp/JT808UdpDotnettyExtensions.cs +++ b/src/JT808.DotNetty.Udp/JT808UdpDotnettyExtensions.cs @@ -1,6 +1,7 @@ using JT808.DotNetty.Core; using JT808.DotNetty.Core.Codecs; using JT808.DotNetty.Core.Handlers; +using JT808.DotNetty.Core.Jobs; using JT808.DotNetty.Core.Services; using JT808.DotNetty.Udp.Handlers; using Microsoft.Extensions.DependencyInjection; @@ -20,6 +21,7 @@ namespace JT808.DotNetty.Udp serviceDescriptors.TryAddSingleton(); serviceDescriptors.TryAddScoped(); serviceDescriptors.TryAddScoped(); + serviceDescriptors.AddHostedService(); serviceDescriptors.AddHostedService(); return serviceDescriptors; } diff --git a/src/JT808.Protocol b/src/JT808.Protocol index 5907058..641907a 160000 --- a/src/JT808.Protocol +++ b/src/JT808.Protocol @@ -1 +1 @@ -Subproject commit 59070584b9f74902431463ec770b9069429ac633 +Subproject commit 641907ad8373cf62d8973dedf76b84aaa894e52f