@@ -25,6 +25,13 @@ namespace JT808.DotNetty.Configurations | |||
public int WriterIdleTimeSeconds { get; set; } = 3600; | |||
public int AllIdleTimeSeconds { get; set; } = 3600; | |||
/// <summary> | |||
/// WebAPI服务 | |||
/// 默认828端口 | |||
/// </summary> | |||
public int WebAPIPort { get; set; } = 828; | |||
/// <summary> | |||
/// 会话报时 | |||
/// 默认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> | |||
<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.Transport.Libuv" Version="0.6.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.Hosting.Abstractions" 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<JT808Decoder>(); | |||
services.TryAddScoped<JT808ServerHandler>(); | |||
services.TryAddScoped<JT808WebAPIServerHandler>(); | |||
services.TryAddSingleton<IJT808SessionService, JT808SessionServiceDefaultImpl>(); | |||
services.TryAddSingleton<IJT808UnificationSendService, JT808UnificationSendServiceDefaultImpl>(); | |||
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 | |||
{ | |||
public class JT808ServerHost : IHostedService | |||
internal class JT808ServerHost : IHostedService | |||
{ | |||
public IServiceProvider Provider { get; } | |||
private readonly IServiceProvider serviceProvider; | |||
private readonly JT808Configuration configuration; | |||
private readonly ILogger<JT808ServerHost> logger; | |||
private DispatcherEventLoopGroup bossGroup; | |||
@@ -35,7 +35,7 @@ namespace JT808.DotNetty | |||
ILoggerFactory loggerFactory, | |||
IOptions<JT808Configuration> jT808ConfigurationAccessor) | |||
{ | |||
Provider = provider; | |||
serviceProvider = provider; | |||
configuration = jT808ConfigurationAccessor.Value; | |||
logger=loggerFactory.CreateLogger<JT808ServerHost>(); | |||
} | |||
@@ -59,7 +59,7 @@ namespace JT808.DotNetty | |||
.ChildHandler(new ActionChannelInitializer<IChannel>(channel => | |||
{ | |||
IChannelPipeline pipeline = channel.Pipeline; | |||
using(var scope= Provider.CreateScope()) | |||
using (var scope = serviceProvider.CreateScope()) | |||
{ | |||
channel.Pipeline.AddLast("systemIdleState", new IdleStateHandler( | |||
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>(); | |||
}); | |||
} | |||
} | |||
} |