@@ -0,0 +1,15 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace JT808.Abstractions | |||||
{ | |||||
/// <summary> | |||||
/// 源包分发器 | |||||
/// </summary> | |||||
public interface ISourcePackageDispatcher | |||||
{ | |||||
Task SendAsync(byte[] data); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
</PropertyGroup> | |||||
</Project> |
@@ -0,0 +1,25 @@ | |||||
| |||||
Microsoft Visual Studio Solution File, Format Version 12.00 | |||||
# Visual Studio 15 | |||||
VisualStudioVersion = 15.0.28010.2016 | |||||
MinimumVisualStudioVersion = 10.0.40219.1 | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty", "JT808.DotNetty\JT808.DotNetty.csproj", "{80C7F67E-6B7C-4178-8726-ADD3695622DD}" | |||||
EndProject | |||||
Global | |||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||||
Debug|Any CPU = Debug|Any CPU | |||||
Release|Any CPU = Release|Any CPU | |||||
EndGlobalSection | |||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||||
{80C7F67E-6B7C-4178-8726-ADD3695622DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{80C7F67E-6B7C-4178-8726-ADD3695622DD}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{80C7F67E-6B7C-4178-8726-ADD3695622DD}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{80C7F67E-6B7C-4178-8726-ADD3695622DD}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
EndGlobalSection | |||||
GlobalSection(SolutionProperties) = preSolution | |||||
HideSolutionNode = FALSE | |||||
EndGlobalSection | |||||
GlobalSection(ExtensibilityGlobals) = postSolution | |||||
SolutionGuid = {FC0FFCEA-E1EF-4C97-A1C5-F89418B6834B} | |||||
EndGlobalSection | |||||
EndGlobal |
@@ -0,0 +1,68 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs; | |||||
using DotNetty.Transport.Channels; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using JT808.Protocol; | |||||
using JT808.DotNetty.Internal; | |||||
namespace JT808.DotNetty.Codecs | |||||
{ | |||||
/// <summary> | |||||
/// JT808解码 | |||||
/// </summary> | |||||
internal class JT808Decoder : ByteToMessageDecoder | |||||
{ | |||||
private readonly ILogger<JT808Decoder> logger; | |||||
public JT808Decoder(ILogger<JT808Decoder> logger) | |||||
{ | |||||
this.logger = logger; | |||||
} | |||||
public JT808Decoder(ILoggerFactory loggerFactory) | |||||
{ | |||||
this.logger = loggerFactory.CreateLogger<JT808Decoder>(); | |||||
} | |||||
private static readonly AtomicCounter MsgSuccessCounter = new AtomicCounter(); | |||||
private static readonly AtomicCounter MsgFailCounter = new AtomicCounter(); | |||||
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output) | |||||
{ | |||||
byte[] buffer = new byte[input.Capacity + 2]; | |||||
try | |||||
{ | |||||
input.ReadBytes(buffer, 1, input.Capacity); | |||||
buffer[0] = JT808Package.BeginFlag; | |||||
buffer[input.Capacity + 1] = JT808Package.EndFlag; | |||||
JT808Package jT808Package = JT808Serializer.Deserialize<JT808Package>(buffer); | |||||
output.Add(jT808Package); | |||||
MsgSuccessCounter.Increment(); | |||||
if (logger.IsEnabled(LogLevel.Debug)) | |||||
logger.LogDebug("accept package success count<<<" + MsgSuccessCounter.Count.ToString()); | |||||
} | |||||
catch (JT808.Protocol.Exceptions.JT808Exception ex) | |||||
{ | |||||
MsgFailCounter.Increment(); | |||||
if (logger.IsEnabled(LogLevel.Error)) | |||||
{ | |||||
logger.LogError("accept package fail count<<<" + MsgFailCounter.Count.ToString()); | |||||
logger.LogError(ex, "accept msg<<<" + buffer); | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
MsgFailCounter.Increment(); | |||||
if (logger.IsEnabled(LogLevel.Error)) | |||||
{ | |||||
logger.LogError("accept package fail count<<<" + MsgFailCounter.Count.ToString()); | |||||
logger.LogError(ex, "accept msg<<<" + buffer); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT808.DotNetty.Configurations | |||||
{ | |||||
public class JT808Configuration | |||||
{ | |||||
public int Port { get; set; } = 808; | |||||
public int QuietPeriodSeconds { get; set; } = 1; | |||||
public TimeSpan QuietPeriodTimeSpan => TimeSpan.FromSeconds(QuietPeriodSeconds); | |||||
public int ShutdownTimeoutSeconds { get; set; } = 3; | |||||
public TimeSpan ShutdownTimeoutTimeSpan => TimeSpan.FromSeconds(ShutdownTimeoutSeconds); | |||||
public int SoBacklog { get; set; } = 8192; | |||||
public int EventLoopCount { get; set; } = Environment.ProcessorCount; | |||||
public int ReaderIdleTimeSeconds { get; set; } = 3600; | |||||
public int WriterIdleTimeSeconds { get; set; } = 3600; | |||||
public int AllIdleTimeSeconds { get; set; } = 3600; | |||||
/// <summary> | |||||
/// 会话报时 | |||||
/// 默认5分钟 | |||||
/// </summary> | |||||
public int SessionReportTime { get; set; } = 30000; | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
using JT808.DotNetty.Configurations; | |||||
using JT808.DotNetty.Handlers; | |||||
using JT808.DotNetty.Internal; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using Microsoft.Extensions.Hosting; | |||||
using System; | |||||
using System.Reflection; | |||||
namespace JT808.DotNetty | |||||
{ | |||||
public static class DotnettyExtensions | |||||
{ | |||||
public static IHostBuilder UseJT808Host(this IHostBuilder builder) | |||||
{ | |||||
return builder.ConfigureServices((hostContext, services) => | |||||
{ | |||||
services.Configure<JT808Configuration>(hostContext.Configuration.GetSection("JT808Configuration")); | |||||
services.TryAddSingleton<SessionManager>(); | |||||
services.TryAddSingleton<JT808MsgIdDefaultHandler>(); | |||||
services.TryAddScoped<JT808ConnectionHandler>(); | |||||
services.TryAddScoped<JT808ServerHandler>(); | |||||
services.AddHostedService<JT808ServerHost>(); | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,96 @@ | |||||
using DotNetty.Handlers.Timeout; | |||||
using DotNetty.Transport.Channels; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Net; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace JT808.DotNetty.Handlers | |||||
{ | |||||
internal class JT808ConnectionHandler : ChannelHandlerAdapter | |||||
{ | |||||
private readonly ILogger<JT808ConnectionHandler> logger; | |||||
public JT808ConnectionHandler( | |||||
ILoggerFactory loggerFactory) | |||||
{ | |||||
logger = loggerFactory.CreateLogger<JT808ConnectionHandler>(); | |||||
} | |||||
public JT808ConnectionHandler( | |||||
ILogger<JT808ConnectionHandler> logger) | |||||
{ | |||||
this.logger = logger; | |||||
} | |||||
/// <summary> | |||||
/// 通道激活 | |||||
/// </summary> | |||||
/// <param name="context"></param> | |||||
public override void ChannelActive(IChannelHandlerContext context) | |||||
{ | |||||
string channelId = context.Channel.Id.AsShortText(); | |||||
if (logger.IsEnabled(LogLevel.Debug)) | |||||
logger.LogDebug($"<<<{ channelId } Successful client connection to server."); | |||||
base.ChannelActive(context); | |||||
} | |||||
/// <summary> | |||||
/// 设备主动断开 | |||||
/// </summary> | |||||
/// <param name="context"></param> | |||||
public override void ChannelInactive(IChannelHandlerContext context) | |||||
{ | |||||
string channelId = context.Channel.Id.AsShortText(); | |||||
if (logger.IsEnabled(LogLevel.Debug)) | |||||
logger.LogDebug($">>>{ channelId } The client disconnects from the server."); | |||||
base.ChannelInactive(context); | |||||
} | |||||
/// <summary> | |||||
/// 服务器主动断开 | |||||
/// </summary> | |||||
/// <param name="context"></param> | |||||
/// <returns></returns> | |||||
public override Task CloseAsync(IChannelHandlerContext context) | |||||
{ | |||||
string channelId = context.Channel.Id.AsShortText(); | |||||
if (logger.IsEnabled(LogLevel.Debug)) | |||||
logger.LogDebug($"<<<{ channelId } The server disconnects from the client."); | |||||
return base.CloseAsync(context); | |||||
} | |||||
public override void ChannelReadComplete(IChannelHandlerContext context) | |||||
{ | |||||
context.Flush(); | |||||
} | |||||
/// <summary> | |||||
/// 超时策略 | |||||
/// </summary> | |||||
/// <param name="context"></param> | |||||
/// <param name="evt"></param> | |||||
public override void UserEventTriggered(IChannelHandlerContext context, object evt) | |||||
{ | |||||
IdleStateEvent idleStateEvent = evt as IdleStateEvent; | |||||
if (idleStateEvent != null) | |||||
{ | |||||
string channelId = context.Channel.Id.AsShortText(); | |||||
logger.LogInformation($"{idleStateEvent.State.ToString()}>>>{channelId}"); | |||||
// 由于808是设备发心跳,如果很久没有上报数据,那么就由服务器主动关闭连接。 | |||||
context.CloseAsync(); | |||||
} | |||||
base.UserEventTriggered(context, evt); | |||||
} | |||||
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) | |||||
{ | |||||
string channelId = context.Channel.Id.AsShortText(); | |||||
logger.LogError(exception,$"{channelId} {exception.Message}" ); | |||||
context.CloseAsync(); | |||||
} | |||||
} | |||||
} | |||||
@@ -0,0 +1,39 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Transport.Channels; | |||||
using JT808.Protocol; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT808.DotNetty.Handlers | |||||
{ | |||||
internal class JT808ServerHandler : SimpleChannelInboundHandler<JT808.Protocol.JT808Package> | |||||
{ | |||||
private readonly JT808MsgIdHandlerBase handler; | |||||
public JT808ServerHandler(JT808MsgIdHandlerBase handler) | |||||
{ | |||||
this.handler = handler; | |||||
} | |||||
protected override void ChannelRead0(IChannelHandlerContext ctx, JT808Package msg) | |||||
{ | |||||
try | |||||
{ | |||||
Func<JT808Package, IChannelHandlerContext, JT808Package> handlerFunc; | |||||
if (handler.HandlerDict.TryGetValue(msg.Header.MsgId, out handlerFunc)) | |||||
{ | |||||
JT808Package jT808Package = handlerFunc(msg, ctx); | |||||
if (jT808Package != null) | |||||
{ | |||||
ctx.WriteAndFlushAsync(Unpooled.WrappedBuffer(JT808Serializer.Serialize(jT808Package))); | |||||
} | |||||
} | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using System.Threading; | |||||
namespace JT808.DotNetty.Internal | |||||
{ | |||||
/// <summary> | |||||
/// | |||||
/// <see cref="Grpc.Core.Internal"/> | |||||
/// </summary> | |||||
internal class AtomicCounter | |||||
{ | |||||
long counter = 0; | |||||
public AtomicCounter(long initialCount = 0) | |||||
{ | |||||
this.counter = initialCount; | |||||
} | |||||
public long Increment() | |||||
{ | |||||
return Interlocked.Increment(ref counter); | |||||
} | |||||
public long Decrement() | |||||
{ | |||||
return Interlocked.Decrement(ref counter); | |||||
} | |||||
public long Count | |||||
{ | |||||
get | |||||
{ | |||||
return Interlocked.Read(ref counter); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,16 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT808.DotNetty.Internal | |||||
{ | |||||
/// <summary> | |||||
/// 默认消息处理业务实现 | |||||
/// </summary> | |||||
internal class JT808MsgIdDefaultHandler : JT808MsgIdHandlerBase | |||||
{ | |||||
public JT808MsgIdDefaultHandler(SessionManager sessionManager) : base(sessionManager) | |||||
{ | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
<LangVersion>latest</LangVersion> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="DotNetty.Codecs" Version="0.6.0" /> | |||||
<PackageReference Include="DotNetty.Handlers" Version="0.6.0" /> | |||||
<PackageReference Include="DotNetty.Transport.Libuv" Version="0.6.0" /> | |||||
<PackageReference Include="JT808" Version="1.0.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,162 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using DotNetty.Transport.Channels; | |||||
using JT808.Protocol; | |||||
using JT808.Protocol.Enums; | |||||
using JT808.Protocol.Extensions; | |||||
using JT808.Protocol.MessageBody; | |||||
namespace JT808.DotNetty | |||||
{ | |||||
/// <summary> | |||||
/// 抽象消息处理业务 | |||||
/// </summary> | |||||
public abstract class JT808MsgIdHandlerBase | |||||
{ | |||||
protected SessionManager sessionManager { get; } | |||||
/// <summary> | |||||
/// 初始化消息处理业务 | |||||
/// </summary> | |||||
protected JT808MsgIdHandlerBase(SessionManager sessionManager) | |||||
{ | |||||
this.sessionManager = sessionManager; | |||||
HandlerDict = new Dictionary<JT808MsgId, Func<JT808Package, IChannelHandlerContext, JT808Package>> | |||||
{ | |||||
{JT808MsgId.终端通用应答, Msg0x0001}, | |||||
{JT808MsgId.终端鉴权, Msg0x0102}, | |||||
{JT808MsgId.终端心跳, Msg0x0002}, | |||||
{JT808MsgId.终端注销, Msg0x0003}, | |||||
{JT808MsgId.终端注册, Msg0x0100}, | |||||
{JT808MsgId.位置信息汇报,Msg0x0200 }, | |||||
{JT808MsgId.定位数据批量上传,Msg0x0704 }, | |||||
{JT808MsgId.数据上行透传,Msg0x0900 } | |||||
}; | |||||
} | |||||
public Dictionary<JT808MsgId, Func<JT808Package, IChannelHandlerContext, JT808Package>> HandlerDict { get; } | |||||
/// <summary> | |||||
/// 终端通用应答 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0001(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
return JT808MsgId.平台通用应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8001() | |||||
{ | |||||
MsgId = reqJT808Package.Header.MsgId, | |||||
JT808PlatformResult = JT808PlatformResult.Success, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
/// <summary> | |||||
/// 终端心跳 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0002(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
sessionManager.Heartbeat(reqJT808Package.Header.TerminalPhoneNo); | |||||
return JT808MsgId.平台通用应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8001() | |||||
{ | |||||
MsgId = reqJT808Package.Header.MsgId, | |||||
JT808PlatformResult = JT808PlatformResult.Success, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
/// <summary> | |||||
/// 终端注销 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0003(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
sessionManager.RemoveSessionByTerminalPhoneNo(reqJT808Package.Header.TerminalPhoneNo); | |||||
return JT808MsgId.平台通用应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8001() | |||||
{ | |||||
MsgId = reqJT808Package.Header.MsgId, | |||||
JT808PlatformResult = JT808PlatformResult.Success, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
/// <summary> | |||||
/// 终端注册 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0100(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
return JT808MsgId.终端注册应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8100() | |||||
{ | |||||
Code = "J" + reqJT808Package.Header.TerminalPhoneNo, | |||||
JT808TerminalRegisterResult = JT808TerminalRegisterResult.成功, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
/// <summary> | |||||
/// 终端鉴权 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0102(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
sessionManager.RegisterSession(new JT808Session(ctx.Channel, reqJT808Package.Header.TerminalPhoneNo)); | |||||
return JT808MsgId.平台通用应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8001() | |||||
{ | |||||
MsgId = reqJT808Package.Header.MsgId, | |||||
JT808PlatformResult = JT808PlatformResult.Success, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
/// <summary> | |||||
/// 位置信息汇报 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0200(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
return JT808MsgId.平台通用应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8001() | |||||
{ | |||||
MsgId = reqJT808Package.Header.MsgId, | |||||
JT808PlatformResult = JT808PlatformResult.Success, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
/// <summary> | |||||
/// 定位数据批量上传 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0704(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
return JT808MsgId.平台通用应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8001() | |||||
{ | |||||
MsgId = reqJT808Package.Header.MsgId, | |||||
JT808PlatformResult = JT808PlatformResult.Success, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
/// <summary> | |||||
/// 数据上行透传 | |||||
/// </summary> | |||||
/// <param name="reqJT808Package"></param> | |||||
/// <param name="ctx"></param> | |||||
/// <returns></returns> | |||||
public virtual JT808Package Msg0x0900(JT808Package reqJT808Package, IChannelHandlerContext ctx) | |||||
{ | |||||
return JT808MsgId.平台通用应答.Create(reqJT808Package.Header.TerminalPhoneNo, new JT808_0x8001() | |||||
{ | |||||
MsgId = reqJT808Package.Header.MsgId, | |||||
JT808PlatformResult = JT808PlatformResult.Success, | |||||
MsgNum = reqJT808Package.Header.MsgNum | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,90 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs; | |||||
using DotNetty.Handlers.Timeout; | |||||
using DotNetty.Transport.Bootstrapping; | |||||
using DotNetty.Transport.Channels; | |||||
using DotNetty.Transport.Libuv; | |||||
using JT808.DotNetty.Codecs; | |||||
using JT808.DotNetty.Configurations; | |||||
using JT808.DotNetty.Handlers; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Net; | |||||
using System.Runtime.InteropServices; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace JT808.DotNetty | |||||
{ | |||||
public class JT808ServerHost : IHostedService | |||||
{ | |||||
public IServiceProvider Provider { get; } | |||||
private readonly JT808Configuration configuration; | |||||
private readonly ILogger<JT808ServerHost> logger; | |||||
private DispatcherEventLoopGroup bossGroup; | |||||
private WorkerEventLoopGroup workerGroup; | |||||
private IChannel bootstrapChannel; | |||||
public JT808ServerHost( | |||||
IServiceProvider provider, | |||||
ILoggerFactory loggerFactory, | |||||
IOptions<JT808Configuration> jT808ConfigurationAccessor) | |||||
{ | |||||
Provider = provider; | |||||
configuration = jT808ConfigurationAccessor.Value; | |||||
logger=loggerFactory.CreateLogger<JT808ServerHost>(); | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
bossGroup = new DispatcherEventLoopGroup(); | |||||
workerGroup = new WorkerEventLoopGroup(bossGroup, configuration.EventLoopCount); | |||||
ServerBootstrap bootstrap = new ServerBootstrap(); | |||||
bootstrap.Group(bossGroup, workerGroup); | |||||
bootstrap.Channel<TcpServerChannel>(); | |||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) | |||||
|| RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | |||||
{ | |||||
bootstrap | |||||
.Option(ChannelOption.SoReuseport, true) | |||||
.ChildOption(ChannelOption.SoReuseaddr, true); | |||||
} | |||||
bootstrap | |||||
.Option(ChannelOption.SoBacklog, configuration.SoBacklog) | |||||
.ChildHandler(new ActionChannelInitializer<IChannel>(channel => | |||||
{ | |||||
IChannelPipeline pipeline = channel.Pipeline; | |||||
using(var scope= Provider.CreateScope()) | |||||
{ | |||||
channel.Pipeline.AddLast("systemIdleState", new IdleStateHandler( | |||||
configuration.ReaderIdleTimeSeconds, | |||||
configuration.WriterIdleTimeSeconds, | |||||
configuration.AllIdleTimeSeconds)); | |||||
channel.Pipeline.AddLast("jt808Connection", scope.ServiceProvider.GetRequiredService<JT808ConnectionHandler>()); | |||||
channel.Pipeline.AddLast("jt808Buffer", new DelimiterBasedFrameDecoder(int.MaxValue, | |||||
Unpooled.CopiedBuffer(new byte[] { JT808.Protocol.JT808Package.BeginFlag }), | |||||
Unpooled.CopiedBuffer(new byte[] { JT808.Protocol.JT808Package.EndFlag }))); | |||||
channel.Pipeline.AddLast("jt808Decode", scope.ServiceProvider.GetRequiredService<JT808Decoder>()); | |||||
channel.Pipeline.AddLast("jt808Service", scope.ServiceProvider.GetRequiredService<JT808ServerHandler>()); | |||||
} | |||||
})); | |||||
logger.LogInformation($"Server start at {IPAddress.Any}:{configuration.Port}."); | |||||
return bootstrap.BindAsync(configuration.Port) | |||||
.ContinueWith(i => bootstrapChannel = i.Result); | |||||
} | |||||
public async Task StopAsync(CancellationToken cancellationToken) | |||||
{ | |||||
await bootstrapChannel.CloseAsync(); | |||||
var quietPeriod = configuration.QuietPeriodTimeSpan; | |||||
var shutdownTimeout = configuration.ShutdownTimeoutTimeSpan; | |||||
await workerGroup.ShutdownGracefullyAsync(quietPeriod, shutdownTimeout); | |||||
await bossGroup.ShutdownGracefullyAsync(quietPeriod, shutdownTimeout); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
using DotNetty.Transport.Channels; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT808.DotNetty | |||||
{ | |||||
public class JT808Session | |||||
{ | |||||
public JT808Session(IChannel channel, string terminalPhoneNo) | |||||
{ | |||||
Channel = channel; | |||||
TerminalPhoneNo = terminalPhoneNo; | |||||
StartTime = DateTime.Now; | |||||
LastActiveTime = DateTime.Now; | |||||
SessionID = Channel.Id.AsShortText(); | |||||
} | |||||
/// <summary> | |||||
/// 终端手机号 | |||||
/// </summary> | |||||
public string TerminalPhoneNo { get; set; } | |||||
public string SessionID { get; } | |||||
public IChannel Channel { get; } | |||||
public DateTime LastActiveTime { get; set; } | |||||
public DateTime StartTime { get; } | |||||
} | |||||
} |
@@ -0,0 +1,224 @@ | |||||
using JT808.DotNetty.Configurations; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace JT808.DotNetty | |||||
{ | |||||
public class SessionManager: IDisposable | |||||
{ | |||||
private readonly ILogger<SessionManager> logger; | |||||
private readonly JT808Configuration configuration; | |||||
private readonly CancellationTokenSource cancellationTokenSource; | |||||
public SessionManager( | |||||
IOptions<JT808Configuration> jT808ConfigurationAccessor, | |||||
ILoggerFactory loggerFactory) | |||||
{ | |||||
logger = loggerFactory.CreateLogger<SessionManager>(); | |||||
configuration = jT808ConfigurationAccessor.Value; | |||||
cancellationTokenSource = new CancellationTokenSource(); | |||||
Task.Run(() => | |||||
{ | |||||
while (!cancellationTokenSource.IsCancellationRequested) | |||||
{ | |||||
logger.LogInformation($"Online Count>>>{SessionCount}"); | |||||
if (SessionCount > 0) | |||||
{ | |||||
logger.LogInformation($"SessionIds>>>{string.Join(",", SessionIdDict.Select(s => s.Key))}"); | |||||
logger.LogInformation($"TerminalPhoneNos>>>{string.Join(",", TerminalPhoneNo_SessionId_Dict.Select(s => $"{s.Key}-{s.Value}"))}"); | |||||
} | |||||
Thread.Sleep(configuration.SessionReportTime); | |||||
} | |||||
}, cancellationTokenSource.Token); | |||||
} | |||||
/// <summary> | |||||
/// Netty生成的sessionID和Session的对应关系 | |||||
/// key = seession id | |||||
/// value = Session | |||||
/// </summary> | |||||
private ConcurrentDictionary<string, JT808Session> SessionIdDict = new ConcurrentDictionary<string, JT808Session>(StringComparer.OrdinalIgnoreCase); | |||||
/// <summary> | |||||
/// 终端手机号和netty生成的sessionID的对应关系 | |||||
/// key = 终端手机号 | |||||
/// value = seession id | |||||
/// </summary> | |||||
private ConcurrentDictionary<string, string> TerminalPhoneNo_SessionId_Dict = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase); | |||||
public int SessionCount | |||||
{ | |||||
get | |||||
{ | |||||
return SessionIdDict.Count; | |||||
} | |||||
} | |||||
public void RegisterSession(JT808Session appSession) | |||||
{ | |||||
if (TerminalPhoneNo_SessionId_Dict.ContainsKey(appSession.TerminalPhoneNo)) | |||||
{ | |||||
return; | |||||
} | |||||
if (SessionIdDict.TryAdd(appSession.SessionID, appSession) && | |||||
TerminalPhoneNo_SessionId_Dict.TryAdd(appSession.TerminalPhoneNo, appSession.SessionID)) | |||||
{ | |||||
return; | |||||
} | |||||
} | |||||
public JT808Session GetSessionByID(string sessionID) | |||||
{ | |||||
if (string.IsNullOrEmpty(sessionID)) | |||||
return default; | |||||
JT808Session targetSession; | |||||
SessionIdDict.TryGetValue(sessionID, out targetSession); | |||||
return targetSession; | |||||
} | |||||
public JT808Session GetSessionByTerminalPhoneNo(string terminalPhoneNo) | |||||
{ | |||||
try | |||||
{ | |||||
if (string.IsNullOrEmpty(terminalPhoneNo)) | |||||
return default; | |||||
if (TerminalPhoneNo_SessionId_Dict.TryGetValue(terminalPhoneNo, out string sessionId)) | |||||
{ | |||||
if (SessionIdDict.TryGetValue(sessionId, out JT808Session targetSession)) | |||||
{ | |||||
return targetSession; | |||||
} | |||||
else | |||||
{ | |||||
return default; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
return default; | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, terminalPhoneNo); | |||||
return default; | |||||
} | |||||
} | |||||
public void Heartbeat(string terminalPhoneNo) | |||||
{ | |||||
try | |||||
{ | |||||
if (TerminalPhoneNo_SessionId_Dict.TryGetValue(terminalPhoneNo, out string sessionId)) | |||||
{ | |||||
if (SessionIdDict.TryGetValue(sessionId, out JT808Session oldjT808Session)) | |||||
{ | |||||
if (oldjT808Session.Channel.Active) | |||||
{ | |||||
oldjT808Session.LastActiveTime = DateTime.Now; | |||||
if (SessionIdDict.TryUpdate(sessionId, oldjT808Session, oldjT808Session)) | |||||
{ | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, terminalPhoneNo); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// 通过通道Id和设备终端号进行关联 | |||||
/// </summary> | |||||
/// <param name="sessionID"></param> | |||||
/// <param name="terminalPhoneNo"></param> | |||||
public void UpdateSessionByID(string sessionID, string terminalPhoneNo) | |||||
{ | |||||
try | |||||
{ | |||||
if (SessionIdDict.TryGetValue(sessionID, out JT808Session oldjT808Session)) | |||||
{ | |||||
oldjT808Session.TerminalPhoneNo = terminalPhoneNo; | |||||
if (SessionIdDict.TryUpdate(sessionID, oldjT808Session, oldjT808Session)) | |||||
{ | |||||
TerminalPhoneNo_SessionId_Dict.AddOrUpdate(terminalPhoneNo, sessionID, (tpn, sid) => | |||||
{ | |||||
return sessionID; | |||||
}); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, $"{sessionID},{terminalPhoneNo}"); | |||||
} | |||||
} | |||||
public void RemoveSessionByID(string sessionID) | |||||
{ | |||||
if (sessionID == null) return; | |||||
try | |||||
{ | |||||
if (SessionIdDict.TryRemove(sessionID, out JT808Session session)) | |||||
{ | |||||
if (session.TerminalPhoneNo != null) | |||||
{ | |||||
if (TerminalPhoneNo_SessionId_Dict.TryRemove(session.TerminalPhoneNo, out string sessionid)) | |||||
{ | |||||
logger.LogInformation($">>>{sessionID}-{session.TerminalPhoneNo} Session Remove."); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
logger.LogInformation($">>>{sessionID} Session Remove."); | |||||
} | |||||
// call GPS.JT808NettyServer.Handlers.JT808ConnectionHandler.CloseAsync | |||||
session.Channel.CloseAsync(); | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, $">>>{sessionID} Session Remove Exception"); | |||||
} | |||||
} | |||||
public void RemoveSessionByTerminalPhoneNo(string terminalPhoneNo) | |||||
{ | |||||
if (terminalPhoneNo == null) return; | |||||
try | |||||
{ | |||||
if (TerminalPhoneNo_SessionId_Dict.TryRemove(terminalPhoneNo, out string sessionid)) | |||||
{ | |||||
if (SessionIdDict.TryRemove(sessionid, out JT808Session session)) | |||||
{ | |||||
logger.LogInformation($">>>{terminalPhoneNo}-{sessionid} TerminalPhoneNo Remove."); | |||||
} | |||||
else | |||||
{ | |||||
logger.LogInformation($">>>{terminalPhoneNo} TerminalPhoneNo Remove."); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.LogError(ex, $">>>{terminalPhoneNo} TerminalPhoneNo Remove Exception."); | |||||
} | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
cancellationTokenSource.Cancel(); | |||||
cancellationTokenSource.Dispose(); | |||||
} | |||||
} | |||||
} | |||||