From db2601217f9e72c357c5b150887f743bfc8c3d84 Mon Sep 17 00:00:00 2001 From: SmallChi <564952747@qq.com> Date: Mon, 25 Mar 2019 22:17:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Codecs/JT808ClientTcpDecoder.cs | 20 ++++ .../Codecs/JT808ClientTcpEncoder.cs | 44 ++++++++ src/JT808.DotNetty.Client/DeviceConfig.cs | 27 +++++ .../JT808TcpClientConnectionHandler.cs | 95 ++++++++++++++++ .../Handlers/JT808TcpClientHandler.cs | 27 +++++ .../JT808.DotNetty.Client.csproj | 27 ++++- .../JT808ClientDotnettyExtensions.cs | 16 +++ .../JT808ClientMsgSNDistributedImpl.cs | 15 +++ src/JT808.DotNetty.Client/JT808TcpClient.cs | 99 ++++++++++++++++- .../JT808TcpClientExtensions.cs | 103 ++++++++++++++++++ .../JT808TcpClientFactory.cs | 53 +++++++++ .../Metadata/JT808ClientRequest.cs | 30 +++++ .../Metadata/JT808Response.cs | 6 + src/JT808.DotNetty.sln | 2 +- 14 files changed, 559 insertions(+), 5 deletions(-) create mode 100644 src/JT808.DotNetty.Client/Codecs/JT808ClientTcpDecoder.cs create mode 100644 src/JT808.DotNetty.Client/Codecs/JT808ClientTcpEncoder.cs create mode 100644 src/JT808.DotNetty.Client/DeviceConfig.cs create mode 100644 src/JT808.DotNetty.Client/Handlers/JT808TcpClientConnectionHandler.cs create mode 100644 src/JT808.DotNetty.Client/Handlers/JT808TcpClientHandler.cs create mode 100644 src/JT808.DotNetty.Client/JT808ClientDotnettyExtensions.cs create mode 100644 src/JT808.DotNetty.Client/JT808ClientMsgSNDistributedImpl.cs create mode 100644 src/JT808.DotNetty.Client/JT808TcpClientExtensions.cs create mode 100644 src/JT808.DotNetty.Client/JT808TcpClientFactory.cs create mode 100644 src/JT808.DotNetty.Client/Metadata/JT808ClientRequest.cs diff --git a/src/JT808.DotNetty.Client/Codecs/JT808ClientTcpDecoder.cs b/src/JT808.DotNetty.Client/Codecs/JT808ClientTcpDecoder.cs new file mode 100644 index 0000000..9dff52c --- /dev/null +++ b/src/JT808.DotNetty.Client/Codecs/JT808ClientTcpDecoder.cs @@ -0,0 +1,20 @@ +using DotNetty.Buffers; +using DotNetty.Codecs; +using System.Collections.Generic; +using JT808.Protocol; +using DotNetty.Transport.Channels; + +namespace JT808.DotNetty.Client.Codecs +{ + public class JT808ClientTcpDecoder : ByteToMessageDecoder + { + protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List output) + { + byte[] buffer = new byte[input.Capacity + 2]; + input.ReadBytes(buffer, 1, input.Capacity); + buffer[0] = JT808Package.BeginFlag; + buffer[input.Capacity + 1] = JT808Package.EndFlag; + output.Add(buffer); + } + } +} diff --git a/src/JT808.DotNetty.Client/Codecs/JT808ClientTcpEncoder.cs b/src/JT808.DotNetty.Client/Codecs/JT808ClientTcpEncoder.cs new file mode 100644 index 0000000..46cd336 --- /dev/null +++ b/src/JT808.DotNetty.Client/Codecs/JT808ClientTcpEncoder.cs @@ -0,0 +1,44 @@ +using DotNetty.Buffers; +using DotNetty.Codecs; +using System.Collections.Generic; +using JT808.Protocol; +using DotNetty.Transport.Channels; +using Microsoft.Extensions.Logging; +using JT808.DotNetty.Client.Metadata; + +namespace JT808.DotNetty.Client.Codecs +{ + public class JT808ClientTcpEncoder : MessageToByteEncoder + { + private readonly ILogger logger; + + public JT808ClientTcpEncoder(ILoggerFactory loggerFactory) + { + logger=loggerFactory.CreateLogger(); + } + + protected override void Encode(IChannelHandlerContext context, JT808ClientRequest message, IByteBuffer output) + { + if (message.Package != null) + { + try + { + var sendData = JT808Serializer.Serialize(message.Package, message.MinBufferSize); + output.WriteBytes(sendData); + } + catch (JT808.Protocol.Exceptions.JT808Exception ex) + { + logger.LogError(ex, context.Channel.Id.AsShortText()); + } + catch (System.Exception ex) + { + logger.LogError(ex, context.Channel.Id.AsShortText()); + } + } + else if (message.HexData != null) + { + output.WriteBytes(message.HexData); + } + } + } +} diff --git a/src/JT808.DotNetty.Client/DeviceConfig.cs b/src/JT808.DotNetty.Client/DeviceConfig.cs new file mode 100644 index 0000000..11c1fe0 --- /dev/null +++ b/src/JT808.DotNetty.Client/DeviceConfig.cs @@ -0,0 +1,27 @@ +using JT808.Protocol; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JT808.DotNetty.Client +{ + public class DeviceConfig + { + public DeviceConfig(string terminalPhoneNo, string tcpHost,int tcpPort) + { + TerminalPhoneNo = terminalPhoneNo; + TcpHost = tcpHost; + TcpPort = tcpPort; + MsgSNDistributed = new JT808ClientMsgSNDistributedImpl(); + } + public string TerminalPhoneNo { get; private set; } + public string TcpHost { get; private set; } + public int TcpPort { get; private set; } + /// + /// 心跳时间(秒) + /// + public int Heartbeat { get; set; } = 30; + + public IMsgSNDistributed MsgSNDistributed { get; } + } +} diff --git a/src/JT808.DotNetty.Client/Handlers/JT808TcpClientConnectionHandler.cs b/src/JT808.DotNetty.Client/Handlers/JT808TcpClientConnectionHandler.cs new file mode 100644 index 0000000..e495ffc --- /dev/null +++ b/src/JT808.DotNetty.Client/Handlers/JT808TcpClientConnectionHandler.cs @@ -0,0 +1,95 @@ +using DotNetty.Handlers.Timeout; +using DotNetty.Transport.Channels; +using JT808.Protocol.MessageBody; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; + +namespace JT808.DotNetty.Client.Handlers +{ + /// + /// JT808客户端连接通道处理程序 + /// + public class JT808TcpClientConnectionHandler : ChannelHandlerAdapter + { + private readonly ILogger logger; + private readonly JT808TcpClient jT808TcpClient; + + public JT808TcpClientConnectionHandler( + JT808TcpClient jT808TcpClient) + { + logger = jT808TcpClient.LoggerFactory.CreateLogger(); + this.jT808TcpClient = jT808TcpClient; + } + + /// + /// 通道激活 + /// + /// + 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); + } + + /// + /// 设备主动断开 + /// + /// + 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); + } + + /// + /// 服务器主动断开 + /// + /// + /// + 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(); + + /// + /// 超时策略 + /// + /// + /// + public override void UserEventTriggered(IChannelHandlerContext context, object evt) + { + IdleStateEvent idleStateEvent = evt as IdleStateEvent; + if (idleStateEvent != null) + { + if(idleStateEvent.State== IdleState.WriterIdle) + { + string channelId = context.Channel.Id.AsShortText(); + logger.LogInformation($"{idleStateEvent.State.ToString()}>>>{channelId}"); + jT808TcpClient.Send(new JT808_0x0002()); + } + } + 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(); + } + } +} + diff --git a/src/JT808.DotNetty.Client/Handlers/JT808TcpClientHandler.cs b/src/JT808.DotNetty.Client/Handlers/JT808TcpClientHandler.cs new file mode 100644 index 0000000..c588b41 --- /dev/null +++ b/src/JT808.DotNetty.Client/Handlers/JT808TcpClientHandler.cs @@ -0,0 +1,27 @@ +using DotNetty.Buffers; +using DotNetty.Transport.Channels; +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace JT808.DotNetty.Client.Handlers +{ + /// + /// JT808客户端处理程序 + /// + internal class JT808TcpClientHandler : SimpleChannelInboundHandler + { + private readonly ILogger logger; + + public JT808TcpClientHandler(JT808TcpClient jT808TcpClient) + { + logger = jT808TcpClient.LoggerFactory.CreateLogger(); + } + + protected override void ChannelRead0(IChannelHandlerContext ctx, byte[] msg) + { + if(logger.IsEnabled(LogLevel.Debug)) + logger.LogDebug("accept msg<<<" + ByteBufferUtil.HexDump(msg)); + } + } +} diff --git a/src/JT808.DotNetty.Client/JT808.DotNetty.Client.csproj b/src/JT808.DotNetty.Client/JT808.DotNetty.Client.csproj index 91f9557..e644ef6 100644 --- a/src/JT808.DotNetty.Client/JT808.DotNetty.Client.csproj +++ b/src/JT808.DotNetty.Client/JT808.DotNetty.Client.csproj @@ -1,11 +1,34 @@ - + netstandard2.0 + 7.1 + Copyright 2018. + SmallChi + JT808.DotNetty.Client + JT808.DotNetty.Client + 基于DotNetty实现的JT808DotNetty的客户端工具 + 基于DotNetty实现的JT808DotNetty的客户端工具 + false + https://github.com/SmallChi/JT808DotNetty + https://github.com/SmallChi/JT808DotNetty + https://github.com/SmallChi/JT808DotNetty/blob/master/LICENSE + true + 1.1.1 - + + + + + + + + + + + diff --git a/src/JT808.DotNetty.Client/JT808ClientDotnettyExtensions.cs b/src/JT808.DotNetty.Client/JT808ClientDotnettyExtensions.cs new file mode 100644 index 0000000..94dfdc5 --- /dev/null +++ b/src/JT808.DotNetty.Client/JT808ClientDotnettyExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JT808.DotNetty.Client +{ + public static class JT808ClientDotnettyExtensions + { + public static IServiceCollection AddJT808Client(this IServiceCollection serviceDescriptors) + { + serviceDescriptors.AddSingleton(); + return serviceDescriptors; + } + } +} diff --git a/src/JT808.DotNetty.Client/JT808ClientMsgSNDistributedImpl.cs b/src/JT808.DotNetty.Client/JT808ClientMsgSNDistributedImpl.cs new file mode 100644 index 0000000..e0e2fee --- /dev/null +++ b/src/JT808.DotNetty.Client/JT808ClientMsgSNDistributedImpl.cs @@ -0,0 +1,15 @@ +using JT808.Protocol; +using System.Threading; + +namespace JT808.DotNetty.Client +{ + internal class JT808ClientMsgSNDistributedImpl : IMsgSNDistributed + { + int _counter = 0; + + public ushort Increment() + { + return (ushort)Interlocked.Increment(ref _counter); + } + } +} diff --git a/src/JT808.DotNetty.Client/JT808TcpClient.cs b/src/JT808.DotNetty.Client/JT808TcpClient.cs index 7a02dc5..f3a370b 100644 --- a/src/JT808.DotNetty.Client/JT808TcpClient.cs +++ b/src/JT808.DotNetty.Client/JT808TcpClient.cs @@ -1,11 +1,106 @@ -using System; +using DotNetty.Buffers; +using DotNetty.Codecs; +using DotNetty.Handlers.Timeout; +using DotNetty.Transport.Bootstrapping; +using DotNetty.Transport.Channels; +using DotNetty.Transport.Channels.Sockets; +using DotNetty.Transport.Libuv; +using JT808.DotNetty.Client.Handlers; +using Microsoft.Extensions.Logging; +using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Threading.Tasks; +using JT808.DotNetty.Client.Metadata; +using JT808.DotNetty.Client.Codecs; namespace JT808.DotNetty.Client { - public class JT808TcpClient + public sealed class JT808TcpClient : IDisposable { + private MultithreadEventLoopGroup group; + private IChannel clientChannel; + + private bool disposed = false; + + public DeviceConfig DeviceConfig { get; private set; } + + public ILoggerFactory LoggerFactory { get; private set; } + + public JT808TcpClient(DeviceConfig deviceConfig, IServiceProvider serviceProvider) + { + DeviceConfig = deviceConfig; + LoggerFactory = serviceProvider.GetRequiredService(); + group = new MultithreadEventLoopGroup(1); + Bootstrap bootstrap = new Bootstrap(); + bootstrap.Group(group); + bootstrap.Channel(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + bootstrap.Option(ChannelOption.SoReuseport, true); + } + bootstrap + .Option(ChannelOption.SoBacklog, 8192) + .Handler(new ActionChannelInitializer(channel => + { + channel.Pipeline.AddLast("jt808TcpBuffer", new DelimiterBasedFrameDecoder(int.MaxValue, + 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)); + channel.Pipeline.AddLast("jt808TcpDecode", new JT808ClientTcpDecoder()); + channel.Pipeline.AddLast("jt808TcpEncode", new JT808ClientTcpEncoder(LoggerFactory)); + channel.Pipeline.AddLast("jt808TcpClientConnection", new JT808TcpClientConnectionHandler(this)); + channel.Pipeline.AddLast("jt808TcpService", new JT808TcpClientHandler(this)); + })); + Task.Run(async () => + { + clientChannel = await bootstrap.ConnectAsync(deviceConfig.TcpHost, deviceConfig.TcpPort); + }); + } + + public async void Send(JT808ClientRequest request) + { + if (disposed) return; + if (clientChannel == null) throw new NullReferenceException("Channel is empty."); + if (request == null) throw new ArgumentNullException("JT808ClientRequest Parameter is empty."); + if (clientChannel.Active && clientChannel.Open) + { + await clientChannel.WriteAndFlushAsync(request); + } + } + + private void Dispose(bool disposing) + { + if (disposed) + { + return; + } + if (disposing) + { + + // 清理托管资源 + group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); + } + disposed = true; + } + + ~JT808TcpClient() + { + //必须为false + //这表明,隐式清理时,只要处理非托管资源就可以了。 + Dispose(false); + } + + public void Dispose() + { + //必须为true + Dispose(true); + //通知垃圾回收机制不再调用终结器(析构器) + GC.SuppressFinalize(this); + } } } diff --git a/src/JT808.DotNetty.Client/JT808TcpClientExtensions.cs b/src/JT808.DotNetty.Client/JT808TcpClientExtensions.cs new file mode 100644 index 0000000..3f886a0 --- /dev/null +++ b/src/JT808.DotNetty.Client/JT808TcpClientExtensions.cs @@ -0,0 +1,103 @@ +using JT808.DotNetty.Client.Metadata; +using JT808.Protocol; +using JT808.Protocol.MessageBody; +using System; +using System.Collections.Generic; +using System.Text; +using JT808.Protocol.Enums; +using JT808.Protocol.Extensions; + +namespace JT808.DotNetty.Client +{ + public static class JT808TcpClientExtensions + { + public static void Send(this JT808TcpClient client, JT808Header header, JT808Bodies bodies, int minBufferSize = 1024) + { + JT808Package package = new JT808Package(); + package.Header = header; + package.Bodies = bodies; + package.Header.TerminalPhoneNo = client.DeviceConfig.TerminalPhoneNo; + package.Header.MsgNum = client.DeviceConfig.MsgSNDistributed.Increment(); + JT808ClientRequest request = new JT808ClientRequest(package, minBufferSize); + client.Send(request); + } + + /// + /// 终端通用应答 + /// + /// + /// + /// + public static void Send(this JT808TcpClient client, JT808_0x0001 bodies, int minBufferSize = 100) + { + JT808Header header = new JT808Header(); + header.MsgId = JT808MsgId.终端通用应答.ToUInt16Value(); + client.Send(header, bodies, minBufferSize); + } + + /// + /// 终端心跳 + /// + /// + /// + /// + public static void Send(this JT808TcpClient client, JT808_0x0002 bodies, int minBufferSize = 100) + { + JT808Header header = new JT808Header(); + header.MsgId = JT808MsgId.终端心跳.ToUInt16Value(); + client.Send(header, bodies, minBufferSize); + } + + /// + /// 终端注销 + /// + /// + /// + /// + public static void Send(this JT808TcpClient client, JT808_0x0003 bodies, int minBufferSize = 100) + { + JT808Header header = new JT808Header(); + header.MsgId = JT808MsgId.终端注销.ToUInt16Value(); + client.Send(header, bodies, minBufferSize); + } + + /// + /// 终端鉴权 + /// + /// + /// + /// + public static void Send(this JT808TcpClient client, JT808_0x0102 bodies, int minBufferSize = 100) + { + JT808Header header = new JT808Header(); + header.MsgId = JT808MsgId.终端鉴权.ToUInt16Value(); + client.Send(header, bodies, minBufferSize); + } + + /// + /// 终端注册 + /// + /// + /// + /// + public static void Send(this JT808TcpClient client, JT808_0x0100 bodies, int minBufferSize = 100) + { + JT808Header header = new JT808Header(); + header.MsgId = JT808MsgId.终端注册.ToUInt16Value(); + client.Send(header, bodies, minBufferSize); + } + + /// + /// 位置信息汇报 + /// + /// + /// + /// + public static void Send(this JT808TcpClient client, JT808_0x0200 bodies, int minBufferSize = 200) + { + JT808Header header = new JT808Header(); + header.MsgId = JT808MsgId.位置信息汇报.ToUInt16Value(); + client.Send(header, bodies, minBufferSize); + } + } +} diff --git a/src/JT808.DotNetty.Client/JT808TcpClientFactory.cs b/src/JT808.DotNetty.Client/JT808TcpClientFactory.cs new file mode 100644 index 0000000..e9de208 --- /dev/null +++ b/src/JT808.DotNetty.Client/JT808TcpClientFactory.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; + +namespace JT808.DotNetty.Client +{ + public interface IJT808TcpClientFactory : IDisposable + { + JT808TcpClient Create(DeviceConfig deviceConfig); + } + + public class JT808TcpClientFactory: IJT808TcpClientFactory + { + private readonly ConcurrentDictionary dict; + + private readonly IServiceProvider serviceProvider; + + public JT808TcpClientFactory(IServiceProvider serviceProvider) + { + dict = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + this.serviceProvider = serviceProvider; + } + + public JT808TcpClient Create(DeviceConfig deviceConfig) + { + if(dict.TryGetValue(deviceConfig.TerminalPhoneNo,out var client)) + { + return client; + } + else + { + JT808TcpClient jT808TcpClient = new JT808TcpClient(deviceConfig, serviceProvider); + dict.TryAdd(deviceConfig.TerminalPhoneNo, jT808TcpClient); + return jT808TcpClient; + } + } + + public void Dispose() + { + foreach(var client in dict) + { + try + { + client.Value.Dispose(); + } + catch + { + } + } + } + } +} diff --git a/src/JT808.DotNetty.Client/Metadata/JT808ClientRequest.cs b/src/JT808.DotNetty.Client/Metadata/JT808ClientRequest.cs new file mode 100644 index 0000000..00c1991 --- /dev/null +++ b/src/JT808.DotNetty.Client/Metadata/JT808ClientRequest.cs @@ -0,0 +1,30 @@ +using JT808.Protocol; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace JT808.DotNetty.Client.Metadata +{ + public class JT808ClientRequest + { + public JT808Package Package { get; } + + public byte[] HexData { get; } + + /// + /// 根据实际情况适当调整包的大小 + /// + public int MinBufferSize { get;} + + public JT808ClientRequest(JT808Package package,int minBufferSize=1024) + { + Package = package; + MinBufferSize = minBufferSize; + } + + public JT808ClientRequest(byte[] hexData) + { + HexData = hexData; + } + } +} \ No newline at end of file diff --git a/src/JT808.DotNetty.Core/Metadata/JT808Response.cs b/src/JT808.DotNetty.Core/Metadata/JT808Response.cs index 1322e74..1496720 100644 --- a/src/JT808.DotNetty.Core/Metadata/JT808Response.cs +++ b/src/JT808.DotNetty.Core/Metadata/JT808Response.cs @@ -8,6 +8,7 @@ namespace JT808.DotNetty.Core.Metadata public class JT808Response { public JT808Package Package { get; set; } + public byte[] HexData { get; set; } /// /// 根据实际情况适当调整包的大小 /// @@ -23,5 +24,10 @@ namespace JT808.DotNetty.Core.Metadata Package = package; MinBufferSize = minBufferSize; } + + public JT808Response(byte[] hexData) + { + HexData = hexData; + } } } \ No newline at end of file diff --git a/src/JT808.DotNetty.sln b/src/JT808.DotNetty.sln index b31d8b3..db6e6cb 100644 --- a/src/JT808.DotNetty.sln +++ b/src/JT808.DotNetty.sln @@ -31,7 +31,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.Hosting", "J EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.WebApiClientTool", "JT808.DotNetty.WebApiClientTool\JT808.DotNetty.WebApiClientTool.csproj", "{9D86C951-94F2-4CBD-B177-8AF31DDB05D8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JT808.DotNetty.Client", "JT808.DotNetty.Client\JT808.DotNetty.Client.csproj", "{87C08239-C57F-4FC5-9579-05D0723AA4A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.Client", "JT808.DotNetty.Client\JT808.DotNetty.Client.csproj", "{87C08239-C57F-4FC5-9579-05D0723AA4A0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution