@@ -25,6 +25,13 @@ namespace JT808.DotNetty.Configurations | |||||
public int WriterIdleTimeSeconds { get; set; } = 3600; | public int WriterIdleTimeSeconds { get; set; } = 3600; | ||||
public int AllIdleTimeSeconds { get; set; } = 3600; | public int AllIdleTimeSeconds { get; set; } = 3600; | ||||
/// <summary> | |||||
/// WebAPI服务 | |||||
/// 默认828端口 | |||||
/// </summary> | |||||
public int WebAPIPort { get; set; } = 828; | |||||
/// <summary> | /// <summary> | ||||
/// 会话报时 | /// 会话报时 | ||||
/// 默认5分钟 | /// 默认5分钟 | ||||
@@ -0,0 +1,15 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT808.DotNetty.Dtos | |||||
{ | |||||
public class JT808DefaultResultDto: JT808ResultDto<string> | |||||
{ | |||||
public JT808DefaultResultDto() | |||||
{ | |||||
Data = "Hello,JT808 WebAPI"; | |||||
Code = 200; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,119 @@ | |||||
using DotNetty.Buffers; | |||||
using DotNetty.Codecs.Http; | |||||
using DotNetty.Common; | |||||
using DotNetty.Common.Utilities; | |||||
using DotNetty.Transport.Channels; | |||||
using JT808.DotNetty.Dtos; | |||||
using JT808.DotNetty.Interfaces; | |||||
using Microsoft.Extensions.Logging; | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT808.DotNetty.Handlers | |||||
{ | |||||
/// <summary> | |||||
/// jt808 webapi服务 | |||||
/// 请求量不大,只支持JSON格式 | |||||
/// ref: dotnetty HttpServer | |||||
/// </summary> | |||||
internal class JT808WebAPIServerHandler : ChannelHandlerAdapter | |||||
{ | |||||
private static readonly ThreadLocalCache Cache = new ThreadLocalCache(); | |||||
sealed class ThreadLocalCache : FastThreadLocal<AsciiString> | |||||
{ | |||||
protected override AsciiString GetInitialValue() | |||||
{ | |||||
DateTime dateTime = DateTime.UtcNow; | |||||
return AsciiString.Cached($"{dateTime.DayOfWeek}, {dateTime:dd MMM yyyy HH:mm:ss z}"); | |||||
} | |||||
} | |||||
private static readonly AsciiString TypeJson = AsciiString.Cached("application/json"); | |||||
private static readonly AsciiString ServerName = AsciiString.Cached("JT808WebAPINetty"); | |||||
private static readonly AsciiString ContentTypeEntity = HttpHeaderNames.ContentType; | |||||
private static readonly AsciiString DateEntity = HttpHeaderNames.Date; | |||||
private static readonly AsciiString ContentLengthEntity = HttpHeaderNames.ContentLength; | |||||
private static readonly AsciiString ServerEntity = HttpHeaderNames.Server; | |||||
volatile ICharSequence date = Cache.Value; | |||||
private readonly ILogger<JT808WebAPIServerHandler> logger; | |||||
private readonly IJT808SessionService jT808SessionService; | |||||
private readonly IJT808UnificationSendService jT808UnificationSendService; | |||||
public JT808WebAPIServerHandler( | |||||
IJT808SessionService jT808SessionService, | |||||
IJT808UnificationSendService jT808UnificationSendService, | |||||
ILoggerFactory loggerFactory) | |||||
{ | |||||
this.jT808SessionService = jT808SessionService; | |||||
this.jT808UnificationSendService = jT808UnificationSendService; | |||||
logger = loggerFactory.CreateLogger<JT808WebAPIServerHandler>(); | |||||
} | |||||
public override void ChannelRead(IChannelHandlerContext ctx, object message) | |||||
{ | |||||
if (message is IHttpRequest request) | |||||
{ | |||||
try | |||||
{ | |||||
Process(ctx, request); | |||||
} | |||||
finally | |||||
{ | |||||
ReferenceCountUtil.Release(message); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
ctx.FireChannelRead(message); | |||||
} | |||||
} | |||||
private void Process(IChannelHandlerContext ctx, IHttpRequest request) | |||||
{ | |||||
string uri = request.Uri; | |||||
//switch (uri) | |||||
//{ | |||||
// //case "/json": | |||||
// // byte[] json = Encoding.UTF8.GetBytes(NewMessage().ToJsonFormat()); | |||||
// // this.WriteResponse(ctx, Unpooled.WrappedBuffer(json), TypeJson, JsonClheaderValue); | |||||
// // break; | |||||
// default: | |||||
// var response = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.NotFound, Unpooled.Empty, false); | |||||
// ctx.WriteAndFlushAsync(response); | |||||
// ctx.CloseAsync(); | |||||
// break; | |||||
//} | |||||
byte[] json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new JT808DefaultResultDto())); | |||||
this.WriteResponse(ctx, Unpooled.WrappedBuffer(json), TypeJson, json.Length); | |||||
} | |||||
private void WriteResponse(IChannelHandlerContext ctx, IByteBuffer buf, ICharSequence contentType, int contentLength) | |||||
{ | |||||
// Build the response object. | |||||
var response = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.OK, buf, false); | |||||
HttpHeaders headers = response.Headers; | |||||
headers.Set(ContentTypeEntity, contentType); | |||||
headers.Set(ServerEntity, ServerName); | |||||
headers.Set(DateEntity, this.date); | |||||
headers.Set(ContentLengthEntity, contentLength); | |||||
// Close the non-keep-alive connection after the write operation is done. | |||||
ctx.WriteAsync(response); | |||||
} | |||||
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) | |||||
{ | |||||
string channelId = context.Channel.Id.AsShortText(); | |||||
logger.LogError(exception, $"{channelId} {exception.Message}"); | |||||
context.CloseAsync(); | |||||
} | |||||
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush(); | |||||
} | |||||
} |
@@ -7,11 +7,10 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="DotNetty.Codecs" Version="0.6.0" /> | <PackageReference Include="DotNetty.Codecs" Version="0.6.0" /> | ||||
<PackageReference Include="DotNetty.Codecs.Http" Version="0.6.0" /> | |||||
<PackageReference Include="DotNetty.Handlers" Version="0.6.0" /> | <PackageReference Include="DotNetty.Handlers" Version="0.6.0" /> | ||||
<PackageReference Include="DotNetty.Transport.Libuv" Version="0.6.0" /> | <PackageReference Include="DotNetty.Transport.Libuv" Version="0.6.0" /> | ||||
<PackageReference Include="JT808" Version="1.1.0" /> | <PackageReference Include="JT808" Version="1.1.0" /> | ||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.3" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.1.3" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" /> | ||||
<PackageReference Include="Microsoft.Extensions.Hosting.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" Version="2.1.1" /> | ||||
@@ -24,24 +24,12 @@ namespace JT808.DotNetty | |||||
services.TryAddScoped<JT808ConnectionHandler>(); | services.TryAddScoped<JT808ConnectionHandler>(); | ||||
services.TryAddScoped<JT808Decoder>(); | services.TryAddScoped<JT808Decoder>(); | ||||
services.TryAddScoped<JT808ServerHandler>(); | services.TryAddScoped<JT808ServerHandler>(); | ||||
services.TryAddScoped<JT808WebAPIServerHandler>(); | |||||
services.TryAddSingleton<IJT808SessionService, JT808SessionServiceDefaultImpl>(); | services.TryAddSingleton<IJT808SessionService, JT808SessionServiceDefaultImpl>(); | ||||
services.TryAddSingleton<IJT808UnificationSendService, JT808UnificationSendServiceDefaultImpl>(); | services.TryAddSingleton<IJT808UnificationSendService, JT808UnificationSendServiceDefaultImpl>(); | ||||
services.AddHostedService<JT808ServerHost>(); | services.AddHostedService<JT808ServerHost>(); | ||||
services.AddHostedService<JT808WebAPIServerHost>(); | |||||
}); | }); | ||||
} | } | ||||
public static void UseJT808Host(this IServiceCollection serviceDescriptors, HostBuilderContext hostContext) | |||||
{ | |||||
serviceDescriptors.Configure<JT808Configuration>(hostContext.Configuration.GetSection("JT808Configuration")); | |||||
serviceDescriptors.TryAddSingleton<JT808SessionManager>(); | |||||
serviceDescriptors.TryAddSingleton<JT808MsgIdHandlerBase, JT808MsgIdDefaultHandler>(); | |||||
serviceDescriptors.TryAddSingleton<IJT808SourcePackageDispatcher, JT808SourcePackageDispatcherDefaultImpl>(); | |||||
serviceDescriptors.TryAddScoped<JT808ConnectionHandler>(); | |||||
serviceDescriptors.TryAddScoped<JT808Decoder>(); | |||||
serviceDescriptors.TryAddScoped<JT808ServerHandler>(); | |||||
serviceDescriptors.TryAddSingleton<IJT808SessionService, JT808SessionServiceDefaultImpl>(); | |||||
serviceDescriptors.TryAddSingleton<IJT808UnificationSendService, JT808UnificationSendServiceDefaultImpl>(); | |||||
serviceDescriptors.AddHostedService<JT808ServerHost>(); | |||||
} | |||||
} | } | ||||
} | } |
@@ -21,9 +21,9 @@ using System.Threading.Tasks; | |||||
namespace JT808.DotNetty | namespace JT808.DotNetty | ||||
{ | { | ||||
public class JT808ServerHost : IHostedService | |||||
internal class JT808ServerHost : IHostedService | |||||
{ | { | ||||
public IServiceProvider Provider { get; } | |||||
private readonly IServiceProvider serviceProvider; | |||||
private readonly JT808Configuration configuration; | private readonly JT808Configuration configuration; | ||||
private readonly ILogger<JT808ServerHost> logger; | private readonly ILogger<JT808ServerHost> logger; | ||||
private DispatcherEventLoopGroup bossGroup; | private DispatcherEventLoopGroup bossGroup; | ||||
@@ -35,7 +35,7 @@ namespace JT808.DotNetty | |||||
ILoggerFactory loggerFactory, | ILoggerFactory loggerFactory, | ||||
IOptions<JT808Configuration> jT808ConfigurationAccessor) | IOptions<JT808Configuration> jT808ConfigurationAccessor) | ||||
{ | { | ||||
Provider = provider; | |||||
serviceProvider = provider; | |||||
configuration = jT808ConfigurationAccessor.Value; | configuration = jT808ConfigurationAccessor.Value; | ||||
logger=loggerFactory.CreateLogger<JT808ServerHost>(); | logger=loggerFactory.CreateLogger<JT808ServerHost>(); | ||||
} | } | ||||
@@ -59,7 +59,7 @@ namespace JT808.DotNetty | |||||
.ChildHandler(new ActionChannelInitializer<IChannel>(channel => | .ChildHandler(new ActionChannelInitializer<IChannel>(channel => | ||||
{ | { | ||||
IChannelPipeline pipeline = channel.Pipeline; | IChannelPipeline pipeline = channel.Pipeline; | ||||
using(var scope= Provider.CreateScope()) | |||||
using (var scope = serviceProvider.CreateScope()) | |||||
{ | { | ||||
channel.Pipeline.AddLast("systemIdleState", new IdleStateHandler( | channel.Pipeline.AddLast("systemIdleState", new IdleStateHandler( | ||||
configuration.ReaderIdleTimeSeconds, | configuration.ReaderIdleTimeSeconds, | ||||
@@ -0,0 +1,82 @@ | |||||
using DotNetty.Codecs.Http; | |||||
using DotNetty.Transport.Bootstrapping; | |||||
using DotNetty.Transport.Channels; | |||||
using DotNetty.Transport.Libuv; | |||||
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 | |||||
{ | |||||
/// <summary> | |||||
/// 集成一个webapi服务 | |||||
/// </summary> | |||||
internal class JT808WebAPIServerHost : IHostedService | |||||
{ | |||||
private readonly IServiceProvider serviceProvider; | |||||
private readonly JT808Configuration configuration; | |||||
private readonly ILogger<JT808WebAPIServerHost> logger; | |||||
private DispatcherEventLoopGroup bossGroup; | |||||
private WorkerEventLoopGroup workerGroup; | |||||
private IChannel bootstrapChannel; | |||||
public JT808WebAPIServerHost( | |||||
IServiceProvider provider, | |||||
ILoggerFactory loggerFactory, | |||||
IOptions<JT808Configuration> jT808ConfigurationAccessor) | |||||
{ | |||||
serviceProvider = provider; | |||||
configuration = jT808ConfigurationAccessor.Value; | |||||
logger = loggerFactory.CreateLogger<JT808WebAPIServerHost>(); | |||||
} | |||||
public Task StartAsync(CancellationToken cancellationToken) | |||||
{ | |||||
bossGroup = new DispatcherEventLoopGroup(); | |||||
workerGroup = new WorkerEventLoopGroup(bossGroup, 1); | |||||
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, 8192) | |||||
.ChildHandler(new ActionChannelInitializer<IChannel>(channel => | |||||
{ | |||||
IChannelPipeline pipeline = channel.Pipeline; | |||||
using (var scope = serviceProvider.CreateScope()) | |||||
{ | |||||
pipeline.AddLast("encoder", new HttpResponseEncoder()); | |||||
pipeline.AddLast("decoder", new HttpRequestDecoder(4096, 8192, 8192, false)); | |||||
pipeline.AddLast("jt808webapihandler", scope.ServiceProvider.GetRequiredService<JT808WebAPIServerHandler>()); | |||||
} | |||||
})); | |||||
logger.LogInformation($"WebAPI Server start at {IPAddress.Any}:{configuration.WebAPIPort}."); | |||||
return bootstrap.BindAsync(configuration.WebAPIPort).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); | |||||
} | |||||
} | |||||
} |
@@ -1,41 +0,0 @@ | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.AspNetCore.Server.Kestrel.Core; | |||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Microsoft.Extensions.DependencyInjection.Abstractions; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using Microsoft.Extensions.Options; | |||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.AspNetCore.Hosting.Server; | |||||
namespace JT808.DotNetty | |||||
{ | |||||
public static class JT808WebHostBuilderKestrelExtensions | |||||
{ | |||||
/// <summary> | |||||
/// Specify Kestrel as the server to be used by the web host. | |||||
/// </summary> | |||||
/// <param name="hostBuilder"> | |||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. | |||||
/// </param> | |||||
/// <returns> | |||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. | |||||
/// </returns> | |||||
public static IHostBuilder UseKestrel(this IHostBuilder hostBuilder, Action<KestrelServerOptions> options) | |||||
{ | |||||
return hostBuilder.ConfigureServices((context,services) => | |||||
{ | |||||
services.Configure(options); | |||||
// Don't override an already-configured transport | |||||
services.TryAddSingleton<ITransportFactory, SocketTransportFactory>(); | |||||
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>(); | |||||
services.AddSingleton<IServer, KestrelServer>(); | |||||
}); | |||||
} | |||||
} | |||||
} |