Browse Source

1.增加JTNE协议服务端解码器

2.增加服务端粘包/拆包测试
3.更新文档
master
SmallChi 6 years ago
parent
commit
17433e726a
26 changed files with 914 additions and 2 deletions
  1. +23
    -1
      README.md
  2. BIN
      doc/img/design_model.png
  3. BIN
      doc/img/tcp_test1.png
  4. BIN
      doc/img/tcp_test2.png
  5. +80
    -0
      doc/jtne_tcp.md
  6. +18
    -0
      src/JTNE.DotNetty.Core/Codecs/JTNETcpDecoder.cs
  7. +31
    -0
      src/JTNE.DotNetty.Core/Configurations/JTNEClientConfiguration.cs
  8. +44
    -0
      src/JTNE.DotNetty.Core/Configurations/JTNEConfiguration.cs
  9. +32
    -0
      src/JTNE.DotNetty.Core/JTNE.DotNetty.Core.csproj
  10. +42
    -0
      src/JTNE.DotNetty.Core/JTNECoreDotnettyExtensions.cs
  11. +49
    -0
      src/JTNE.DotNetty.Core/Metadata/JTNEAtomicCounter.cs
  12. +51
    -0
      src/JTNE.DotNetty.Core/Services/JTNETcpAtomicCounterService.cs
  13. +93
    -0
      src/JTNE.DotNetty.Tcp/Handlers/JTNETcpConnectionHandler.cs
  14. +61
    -0
      src/JTNE.DotNetty.Tcp/Handlers/JTNETcpServerHandler.cs
  15. +32
    -0
      src/JTNE.DotNetty.Tcp/JTNE.DotNetty.Tcp.csproj
  16. +26
    -0
      src/JTNE.DotNetty.Tcp/JTNETcpDotnettyExtensions.cs
  17. +103
    -0
      src/JTNE.DotNetty.Tcp/JTNETcpServerHost.cs
  18. +23
    -0
      src/JTNE.DotNetty.Tests/JTNE.DotNetty.Hosting/JTNE.DotNetty.Hosting.csproj
  19. +39
    -0
      src/JTNE.DotNetty.Tests/JTNE.DotNetty.Hosting/Program.cs
  20. +18
    -0
      src/JTNE.DotNetty.Tests/JTNE.DotNetty.Hosting/appsettings.json
  21. +22
    -0
      src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/JTNE.DotNetty.Tcp.Test.csproj
  22. +36
    -0
      src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/TestBase.cs
  23. +14
    -0
      src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/UnitTest1.cs
  24. +18
    -0
      src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/appsettings.json
  25. +58
    -0
      src/JTNE.DotNetty.sln
  26. +1
    -1
      src/JTNE.Protocol

+ 23
- 1
README.md View File

@@ -1,2 +1,24 @@
# JTNewEnergyDotNetty
JTNewEnergyDotNetty

基于DotNetty封装的JTNEDotNetty通用消息业务处理

[了解JT808协议进这边](https://github.com/SmallChi/JT808)

[了解JT809协议进这边](https://github.com/SmallChi/JT809)

[了解JTNE协议进这边](https://github.com/SmallChi/JTNewEnergy)

[了解JTNE协议处理tcp粘包/拆包](https://github.com/SmallChi/JTNewEnergyDotNetty/blob/master/doc/jtne_tcp.md)

[![MIT Licence](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/SmallChi/JTNewEnergyDotNetty/blob/master/LICENSE)

## 新网关的优势:

1. 跨平台
2. 借助 .NET Core模块化的思想
3. 单机同时一万辆车在线不是梦(真有一万辆车那都很吃香了<( ̄3 ̄)> <( ̄3 ̄)> <( ̄3 ̄)> )
4. 简单易上手

## 设计模型

![design_model](https://github.com/SmallChi/JTNewEnergyDotNetty/blob/master/doc/img/design_model.png)

BIN
doc/img/design_model.png View File

Before After
Width: 689  |  Height: 401  |  Size: 39 KiB

BIN
doc/img/tcp_test1.png View File

Before After
Width: 1058  |  Height: 474  |  Size: 212 KiB

BIN
doc/img/tcp_test2.png View File

Before After
Width: 1046  |  Height: 472  |  Size: 303 KiB

+ 80
- 0
doc/jtne_tcp.md View File

@@ -0,0 +1,80 @@
# 了解JTNE协议处理tcp粘包/拆包

## 使用DotNetty的LengthFieldBasedFrameDecoder定长解码器

参数说明:

maxFrameLength:解码的帧的最大长度
lengthFieldOffset:长度字段的偏差(长度属性的起始位(偏移位),包中存放有整个大数据包长度的字节,这段字节的其实位置)
lengthFieldLength:长度字段占的字节数(即存放整个大数据包长度的字节所占的长度)
lengthAdjustmen:添加到长度字段的补偿值(长度调节值,在总长被定义为包含包头长度时,修正信息长度)。
initialBytesToStrip:从解码帧中第一次去除的字节数(跳过的字节数,根据需要我们跳过lengthFieldLength个字节,以便接收端直接接受到不含“长度属性”的内容)
failFast :为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常

### JTNE协议处理

22 JTNEPackage数据体长度
2 JTNEPackage数据体长度占两个字节
1 JTNEPackage校验位

``` netty

channel.Pipeline.AddLast("jtneTcpDecoder", new LengthFieldBasedFrameDecoder(int.MaxValue, 22, 2, 1, 0));
```

## 使用SuperSocket的FixedHeaderReceiveFilter固定头部解码器

``` supersocket

public class JTNEReceiveFilter: FixedHeaderReceiveFilter<JTNERequestInfo>
{
// 24 JTNEPackage头部固定长度
public NEReceiveFilter()
: base(24)
{

}

protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
{
// +1 JTNEPackage数据长度加上一个字节的校验位
return header.toIntH2L(22 + offset, 2) + 1;
}

protected override JTNERequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length)
{
var reInfo = new JTNERequestInfo();
reInfo.OriginalPackage = new byte[header.Count + length];
Array.Copy(header.Array, 0, reInfo.OriginalPackage, 0, header.Count);
Array.Copy(bodyBuffer, offset, reInfo.OriginalPackage, header.Count, length);
return reInfo;
}
}

```

## 使用DotNetty测试粘包

1.一条实时位置上报的测试数据

``` data
2323020131323334353637383900000000000000000100D001040507003A00001A0A00640063030602007B02030202010201004100370300EC00640203020042023605085800650308AE006F0C9600030102030D1B221A0A560D086502040100CB006605010031AD030012D1CB061115007B0709000832124211320607110000159D03000003E8000003E9000003EA03000007D0000007D1000007D20300000BB800000BB900000BBA0300000FA000000FA100000FA20802010002007B0037006F03006F00DE014D03000504D2004200DE0301BC022B029A0902010004010203040200040506070867
```

2.启动JTNE服务端程序

3.使用tcp客户端工具

创建两个tcp客户端,如图所示:

![tcp_test1](https://github.com/SmallChi/JTNewEnergyDotNetty/blob/master/doc/img/tcp_test1.png)

4.客户端分别发送消息

每个客户端分别连续发送1w个包进行测试,如图所示:

服务端接收并解析到包的数量等于客户端所发送的数量,那么处理就算正确。

![tcp_test2](https://github.com/SmallChi/JTNewEnergyDotNetty/blob/master/doc/img/tcp_test2.png)

5.解决了服务端协议的解码问题对于上层业务处理来说就是搬砖的是了

+ 18
- 0
src/JTNE.DotNetty.Core/Codecs/JTNETcpDecoder.cs View File

@@ -0,0 +1,18 @@
using DotNetty.Buffers;
using DotNetty.Codecs;
using System.Collections.Generic;
using JTNE.Protocol;
using DotNetty.Transport.Channels;

namespace JTNE.DotNetty.Core.Codecs
{
public class JTNETcpDecoder : ByteToMessageDecoder
{
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
byte[] buffer = new byte[input.Capacity];
input.ReadBytes(buffer, 0, input.Capacity);
output.Add(buffer);
}
}
}

+ 31
- 0
src/JTNE.DotNetty.Core/Configurations/JTNEClientConfiguration.cs View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;

namespace JTNE.DotNetty.Core.Configurations
{
public class JTNEClientConfiguration
{
public string Host { get; set; }

public int Port { get; set; }

private EndPoint endPoint;

public EndPoint EndPoint
{
get
{
if (endPoint == null)
{
if (IPAddress.TryParse(Host, out IPAddress ip))
{
endPoint = new IPEndPoint(ip, Port);
}
}
return endPoint;
}
}
}
}

+ 44
- 0
src/JTNE.DotNetty.Core/Configurations/JTNEConfiguration.cs View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace JTNE.DotNetty.Core.Configurations
{
public class JTNEConfiguration
{
public int TcpPort { get; set; } = 909;

public int UdpPort { get; set; } = 919;

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;

public int UdpSlidingExpirationTimeSeconds { get; set; } = 5*60;

/// <summary>
/// WebApi服务
/// 默认929端口
/// </summary>
public int WebApiPort { get; set; } = 929;

/// <summary>
/// 转发远程地址 (可选项)知道转发的地址有利于提升性能
/// </summary>
public List<JTNEClientConfiguration> ForwardingRemoteAddress { get; set; }
}
}

+ 32
- 0
src/JTNE.DotNetty.Core/JTNE.DotNetty.Core.csproj View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.1</LangVersion>
<Copyright>Copyright 2018.</Copyright>
<Authors>SmallChi</Authors>
<PackageId>JTNE.DotNetty.Core</PackageId>
<Product>JTNE.DotNetty.Core</Product>
<Description>基于DotNetty实现的JTNEDotNetty的核心库</Description>
<PackageReleaseNotes>基于DotNetty实现的JTNEDotNetty的核心库</PackageReleaseNotes>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/SmallChi/JTNewEnergyDotNetty</RepositoryUrl>
<PackageProjectUrl>https://github.com/SmallChi/JTNewEnergyDotNetty</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/SmallChi/JTNewEnergyDotNetty/blob/master/LICENSE</PackageLicenseUrl>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNetty.Handlers" Version="0.6.0" />
<PackageReference Include="DotNetty.Transport.Libuv" Version="0.6.0" />
<PackageReference Include="DotNetty.Codecs" Version="0.6.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\JTNE.Protocol\src\JTNE.Protocol\JTNE.Protocol.csproj" />
</ItemGroup>
</Project>

+ 42
- 0
src/JTNE.DotNetty.Core/JTNECoreDotnettyExtensions.cs View File

@@ -0,0 +1,42 @@
using JTNE.DotNetty.Core.Codecs;
using JTNE.DotNetty.Core.Configurations;
using JTNE.DotNetty.Core.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Newtonsoft.Json;
using System;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("JTNE.DotNetty.Core.Test")]
[assembly: InternalsVisibleTo("JTNE.DotNetty.Tcp.Test")]
[assembly: InternalsVisibleTo("JTNE.DotNetty.Udp.Test")]
[assembly: InternalsVisibleTo("JTNE.DotNetty.WebApi.Test")]
namespace JTNE.DotNetty.Core
{
public static class JTNECoreDotnettyExtensions
{
static JTNECoreDotnettyExtensions()
{
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
{
Newtonsoft.Json.JsonSerializerSettings settings = new Newtonsoft.Json.JsonSerializerSettings();
//日期类型默认格式化处理
settings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
settings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
//空值处理
settings.NullValueHandling = NullValueHandling.Ignore;
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
return settings;
});
}

public static IServiceCollection AddJTNECore(this IServiceCollection serviceDescriptors, IConfiguration configuration, Newtonsoft.Json.JsonSerializerSettings settings=null)
{
serviceDescriptors.Configure<JTNEClientConfiguration>(configuration.GetSection("JTNEConfiguration"));
serviceDescriptors.AddSingleton<JTNETcpAtomicCounterService>();
serviceDescriptors.AddScoped<JTNETcpDecoder>();
return serviceDescriptors;
}
}
}

+ 49
- 0
src/JTNE.DotNetty.Core/Metadata/JTNEAtomicCounter.cs View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace JTNE.DotNetty.Core.Metadata
{
/// <summary>
///
/// <see cref="Grpc.Core.Internal"/>
/// </summary>
public class JTNEAtomicCounter
{
long counter = 0;

public JTNEAtomicCounter(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);
}
}
}
}

+ 51
- 0
src/JTNE.DotNetty.Core/Services/JTNETcpAtomicCounterService.cs View File

@@ -0,0 +1,51 @@
using JTNE.DotNetty.Core.Metadata;

namespace JTNE.DotNetty.Core.Services
{
/// <summary>
/// Tcp计数包服务
/// </summary>
public class JTNETcpAtomicCounterService
{
private readonly JTNEAtomicCounter MsgSuccessCounter = new JTNEAtomicCounter();

private readonly JTNEAtomicCounter MsgFailCounter = new JTNEAtomicCounter();

public JTNETcpAtomicCounterService()
{

}

public void Reset()
{
MsgSuccessCounter.Reset();
MsgFailCounter.Reset();
}

public long MsgSuccessIncrement()
{
return MsgSuccessCounter.Increment();
}

public long MsgSuccessCount
{
get
{
return MsgSuccessCounter.Count;
}
}

public long MsgFailIncrement()
{
return MsgFailCounter.Increment();
}

public long MsgFailCount
{
get
{
return MsgFailCounter.Count;
}
}
}
}

+ 93
- 0
src/JTNE.DotNetty.Tcp/Handlers/JTNETcpConnectionHandler.cs View File

@@ -0,0 +1,93 @@
using DotNetty.Handlers.Timeout;
using DotNetty.Transport.Channels;
using JTNE.DotNetty.Core;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace JTNE.DotNetty.Tcp.Handlers
{
/// <summary>
/// JTNE服务通道处理程序
/// </summary>
internal class JTNETcpConnectionHandler : ChannelHandlerAdapter
{
private readonly ILogger<JTNETcpConnectionHandler> logger;


public JTNETcpConnectionHandler(
ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<JTNETcpConnectionHandler>();
}

/// <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)
{
if(idleStateEvent.State== IdleState.ReaderIdle)
{
string channelId = context.Channel.Id.AsShortText();
logger.LogInformation($"{idleStateEvent.State.ToString()}>>>{channelId}");
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();
}
}
}


+ 61
- 0
src/JTNE.DotNetty.Tcp/Handlers/JTNETcpServerHandler.cs View File

@@ -0,0 +1,61 @@
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
using System;
using Microsoft.Extensions.Logging;
using JTNE.Protocol;
using JTNE.DotNetty.Core.Services;
using Newtonsoft.Json;

namespace JTNE.DotNetty.Tcp.Handlers
{
/// <summary>
/// JTNE服务端处理程序
/// </summary>
internal class JTNETcpServerHandler : SimpleChannelInboundHandler<byte[]>
{
private readonly ILogger<JTNETcpServerHandler> logger;
private readonly JTNETcpAtomicCounterService jTNETcpAtomicCounterService;

public JTNETcpServerHandler(
ILoggerFactory loggerFactory,
JTNETcpAtomicCounterService jTNETcpAtomicCounterService
)
{
logger = loggerFactory.CreateLogger<JTNETcpServerHandler>();
this.jTNETcpAtomicCounterService = jTNETcpAtomicCounterService;
}


protected override void ChannelRead0(IChannelHandlerContext ctx, byte[] msg)
{
try
{
JTNEPackage jtNePackage = JTNESerializer.Deserialize(msg);
jTNETcpAtomicCounterService.MsgSuccessIncrement();
if (logger.IsEnabled(LogLevel.Debug))
{
//logger.LogDebug(JsonConvert.SerializeObject(jtNePackage));
logger.LogDebug("accept package success count<<<" + jTNETcpAtomicCounterService.MsgSuccessCount.ToString());
}
}
catch (JTNE.Protocol.Exceptions.JTNEException ex)
{
jTNETcpAtomicCounterService.MsgFailIncrement();
if (logger.IsEnabled(LogLevel.Error))
{
logger.LogError("accept package fail count<<<" + jTNETcpAtomicCounterService.MsgFailCount.ToString());
logger.LogError(ex, "accept msg<<<" + ByteBufferUtil.HexDump(msg));
}
}
catch (Exception ex)
{
jTNETcpAtomicCounterService.MsgFailIncrement();
if (logger.IsEnabled(LogLevel.Error))
{
logger.LogError("accept package fail count<<<" + jTNETcpAtomicCounterService.MsgFailCount.ToString());
logger.LogError(ex, "accept msg<<<" + ByteBufferUtil.HexDump(msg));
}
}
}
}
}

+ 32
- 0
src/JTNE.DotNetty.Tcp/JTNE.DotNetty.Tcp.csproj View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.1</LangVersion>
<Copyright>Copyright 2018.</Copyright>
<Authors>SmallChi</Authors>
<PackageId>JTNE.DotNetty.Tcp</PackageId>
<Product>JTNE.DotNetty.Tcp</Product>
<Description>基于DotNetty实现的JTNEDotNetty的Tcp服务</Description>
<PackageReleaseNotes>基于DotNetty实现的JTNEDotNetty的Tcp服务</PackageReleaseNotes>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/SmallChi/JTNewEnergyDotNetty</RepositoryUrl>
<PackageProjectUrl>https://github.com/SmallChi/JTNewEnergyDotNetty</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/SmallChi/JTNewEnergyDotNetty/blob/master/LICENSE</PackageLicenseUrl>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.0</Version>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetty.Buffers" Version="0.6.0" />
<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="Microsoft.Extensions.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\JTNE.DotNetty.Core\JTNE.DotNetty.Core.csproj" />
</ItemGroup>
</Project>

+ 26
- 0
src/JTNE.DotNetty.Tcp/JTNETcpDotnettyExtensions.cs View File

@@ -0,0 +1,26 @@
using JTNE.DotNetty.Core.Codecs;
using JTNE.DotNetty.Tcp.Handlers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using System;
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("JTNE.DotNetty.Tcp.Test")]

namespace JTNE.DotNetty.Tcp
{
public static class JTNETcpDotnettyExtensions
{
public static IServiceCollection AddJTNETcpHost(this IServiceCollection serviceDescriptors)
{
serviceDescriptors.TryAddScoped<JTNETcpConnectionHandler>();
serviceDescriptors.TryAddScoped<JTNETcpDecoder>();
serviceDescriptors.TryAddScoped<JTNETcpServerHandler>();
serviceDescriptors.AddHostedService<JTNETcpServerHost>();
return serviceDescriptors;
}
}
}

+ 103
- 0
src/JTNE.DotNetty.Tcp/JTNETcpServerHost.cs View File

@@ -0,0 +1,103 @@
using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Handlers.Timeout;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Libuv;
using JTNE.DotNetty.Core.Codecs;
using JTNE.DotNetty.Core.Configurations;
using JTNE.DotNetty.Tcp.Handlers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace JTNE.DotNetty.Tcp
{
/// <summary>
/// JTNE Tcp网关服务
/// </summary>
internal class JTNETcpServerHost : IHostedService
{
private readonly IServiceProvider serviceProvider;
private readonly ILogger<JTNETcpServerHost> logger;
private DispatcherEventLoopGroup bossGroup;
private WorkerEventLoopGroup workerGroup;
private IChannel bootstrapChannel;
private IByteBufferAllocator serverBufferAllocator;
private readonly JTNEConfiguration configuration;

public JTNETcpServerHost(
IServiceProvider provider,
ILoggerFactory loggerFactory,
IOptions<JTNEConfiguration> jTNEConfigurationAccessor)
{
serviceProvider = provider;
configuration = jTNEConfigurationAccessor.Value;
logger = loggerFactory.CreateLogger<JTNETcpServerHost>();
}

public Task StartAsync(CancellationToken cancellationToken)
{
bossGroup = new DispatcherEventLoopGroup();
workerGroup = new WorkerEventLoopGroup(bossGroup, configuration.EventLoopCount);
serverBufferAllocator = new PooledByteBufferAllocator();
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)
.ChildOption(ChannelOption.Allocator, serverBufferAllocator)
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
using (var scope = serviceProvider.CreateScope())
{
channel.Pipeline.AddLast("systemIdleState", new IdleStateHandler(
configuration.ReaderIdleTimeSeconds,
configuration.WriterIdleTimeSeconds,
configuration.AllIdleTimeSeconds));
channel.Pipeline.AddLast("jtneTcpConnection", scope.ServiceProvider.GetRequiredService<JTNETcpConnectionHandler>());
//LengthFieldBasedFrameDecoder 定长解码器
//参数说明:
//maxFrameLength:解码的帧的最大长度
//lengthFieldOffset:长度字段的偏差(长度属性的起始位(偏移位),包中存放有整个大数据包长度的字节,这段字节的其实位置)
//lengthFieldLength:长度字段占的字节数(即存放整个大数据包长度的字节所占的长度)
//lengthAdjustmen:添加到长度字段的补偿值(长度调节值,在总长被定义为包含包头长度时,修正信息长度)。
//initialBytesToStrip:从解码帧中第一次去除的字节数(跳过的字节数,根据需要我们跳过lengthFieldLength个字节,以便接收端直接接受到不含“长度属性”的内容)
//failFast :为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常
//22 JTNEPackage数据体长度
//2 JTNEPackage数据体长度占两个字节
//1 JTNEPackage校验位
channel.Pipeline.AddLast("jtneTcpDecoder", new LengthFieldBasedFrameDecoder(int.MaxValue, 22, 2, 1, 0));
channel.Pipeline.AddLast("jtneTcpBuffer", scope.ServiceProvider.GetRequiredService<JTNETcpDecoder>());
channel.Pipeline.AddLast("jtneTcpServerHandler", scope.ServiceProvider.GetRequiredService<JTNETcpServerHandler>());
}
}));
logger.LogInformation($"JTNE TCP Server start at {IPAddress.Any}:{configuration.TcpPort}.");
return bootstrap.BindAsync(configuration.TcpPort)
.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);
}
}
}

+ 23
- 0
src/JTNE.DotNetty.Tests/JTNE.DotNetty.Hosting/JTNE.DotNetty.Hosting.csproj View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>7.1</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<ProjectReference Include="..\..\JTNE.DotNetty.Tcp\JTNE.DotNetty.Tcp.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>

+ 39
- 0
src/JTNE.DotNetty.Tests/JTNE.DotNetty.Hosting/Program.cs View File

@@ -0,0 +1,39 @@
using JTNE.DotNetty.Core;
using JTNE.DotNetty.Tcp;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace JTNE.DotNetty.Hosting
{
class Program
{
static async Task Main(string[] args)
{
//232301FE313233343536373839000000000000000001002A130116173738000131323334353637383939383736353433323130300304313233343435363739383730FD
//2323020131323334353637383900000000000000000100D001040507003A00001A0A00640063030602007B02030202010201004100370300EC00640203020042023605085800650308AE006F0C9600030102030D1B221A0A560D086502040100CB006605010031AD030012D1CB061115007B0709000832124211320607110000159D03000003E8000003E9000003EA03000007D0000007D1000007D20300000BB800000BB900000BBA0300000FA000000FA100000FA20802010002007B0037006F03006F00DE014D03000504D2004200DE0301BC022B029A0902010004010203040200040506070867
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<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddJTNECore(hostContext.Configuration)
.AddJTNETcpHost();
});
await serverHostBuilder.RunConsoleAsync();
}
}
}

+ 18
- 0
src/JTNE.DotNetty.Tests/JTNE.DotNetty.Hosting/appsettings.json View File

@@ -0,0 +1,18 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Trace"
}
},
"Console": {
"LogLevel": {
"Default": "Trace"
}
}
},
"JTNEConfiguration": {

}
}

+ 22
- 0
src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/JTNE.DotNetty.Tcp.Test.csproj View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\JTNE.DotNetty.Tcp\JTNE.DotNetty.Tcp.csproj" />
</ItemGroup>

</Project>

+ 36
- 0
src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/TestBase.cs View File

@@ -0,0 +1,36 @@
using JTNE.DotNetty.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;

namespace JTNE.DotNetty.Tcp.Test
{
public class TestBase
{
public static IServiceProvider ServiceProvider;

static TestBase()
{
var serverHostBuilder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory);
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddJTNECore(hostContext.Configuration)
.AddJTNETcpHost();
});
var build = serverHostBuilder.Build();
build.Start();
ServiceProvider = build.Services;
}
}
}

+ 14
- 0
src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/UnitTest1.cs View File

@@ -0,0 +1,14 @@
using System;
using Xunit;

namespace JTNE.DotNetty.Tcp.Test
{
public class UnitTest1
{
[Fact]
public void Test1()
{

}
}
}

+ 18
- 0
src/JTNE.DotNetty.Tests/JTNE.DotNetty.Tcp.Test/appsettings.json View File

@@ -0,0 +1,18 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Trace"
}
},
"Console": {
"LogLevel": {
"Default": "Trace"
}
}
},
"JTNEConfiguration": {

}
}

+ 58
- 0
src/JTNE.DotNetty.sln View File

@@ -0,0 +1,58 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.329
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{62F3C213-D06A-4AD2-9806-6380E3412D36}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JTNE.Protocol", "JTNE.Protocol\src\JTNE.Protocol\JTNE.Protocol.csproj", "{B807AE69-CF8E-4AF7-8446-3C55DBA36915}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JTNE.DotNetty.Core", "JTNE.DotNetty.Core\JTNE.DotNetty.Core.csproj", "{045A407E-8D6B-4EDE-87F9-AD3E2531E978}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JTNE.DotNetty.Tcp", "JTNE.DotNetty.Tcp\JTNE.DotNetty.Tcp.csproj", "{2AD8F740-5468-4BF7-B37F-D98F061B9939}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8241ED6E-2BF2-4378-AC97-466A1CA6032C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JTNE.DotNetty.Tcp.Test", "JTNE.DotNetty.Tests\JTNE.DotNetty.Tcp.Test\JTNE.DotNetty.Tcp.Test.csproj", "{298C5650-2DAC-40E2-9309-12A8651E0347}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JTNE.DotNetty.Hosting", "JTNE.DotNetty.Tests\JTNE.DotNetty.Hosting\JTNE.DotNetty.Hosting.csproj", "{5BA5FC40-8A66-439F-B5C6-209E754488D9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B807AE69-CF8E-4AF7-8446-3C55DBA36915}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B807AE69-CF8E-4AF7-8446-3C55DBA36915}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B807AE69-CF8E-4AF7-8446-3C55DBA36915}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B807AE69-CF8E-4AF7-8446-3C55DBA36915}.Release|Any CPU.Build.0 = Release|Any CPU
{045A407E-8D6B-4EDE-87F9-AD3E2531E978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{045A407E-8D6B-4EDE-87F9-AD3E2531E978}.Debug|Any CPU.Build.0 = Debug|Any CPU
{045A407E-8D6B-4EDE-87F9-AD3E2531E978}.Release|Any CPU.ActiveCfg = Release|Any CPU
{045A407E-8D6B-4EDE-87F9-AD3E2531E978}.Release|Any CPU.Build.0 = Release|Any CPU
{2AD8F740-5468-4BF7-B37F-D98F061B9939}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AD8F740-5468-4BF7-B37F-D98F061B9939}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AD8F740-5468-4BF7-B37F-D98F061B9939}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AD8F740-5468-4BF7-B37F-D98F061B9939}.Release|Any CPU.Build.0 = Release|Any CPU
{298C5650-2DAC-40E2-9309-12A8651E0347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{298C5650-2DAC-40E2-9309-12A8651E0347}.Debug|Any CPU.Build.0 = Debug|Any CPU
{298C5650-2DAC-40E2-9309-12A8651E0347}.Release|Any CPU.ActiveCfg = Release|Any CPU
{298C5650-2DAC-40E2-9309-12A8651E0347}.Release|Any CPU.Build.0 = Release|Any CPU
{5BA5FC40-8A66-439F-B5C6-209E754488D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BA5FC40-8A66-439F-B5C6-209E754488D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BA5FC40-8A66-439F-B5C6-209E754488D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BA5FC40-8A66-439F-B5C6-209E754488D9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B807AE69-CF8E-4AF7-8446-3C55DBA36915} = {62F3C213-D06A-4AD2-9806-6380E3412D36}
{298C5650-2DAC-40E2-9309-12A8651E0347} = {8241ED6E-2BF2-4378-AC97-466A1CA6032C}
{5BA5FC40-8A66-439F-B5C6-209E754488D9} = {8241ED6E-2BF2-4378-AC97-466A1CA6032C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BA9480D2-2C5C-4E82-9D6B-4D98C420E6A1}
EndGlobalSection
EndGlobal

+ 1
- 1
src/JTNE.Protocol

@@ -1 +1 @@
Subproject commit 342b3492c22132c8be8984a531ac96594e01247c
Subproject commit b799819135d795f979eac484683e294f8a94a780

Loading…
Cancel
Save